导读:
文章比较长,笔者尝试使用简单的语言使读者知道笔者所要描述的问题。如果有人想看代码,请直接翻到文章末尾。代码可直接运行,笔者使用的python 3.6.5+pyCharm。
----------------------------------------------------------------------------------------------------------- 本篇文章主要有以下几个方面:(1)KNN的原理
(2)KNN需要解决的问题
(3)KNN的重构
(4)KNN关键算法
(5)错误处理
(6)总结
(7)其他
一,K近邻算法的原理:
K近邻算法,就是K个最近邻居的意思。核心思想用一句话概括就是“近朱者赤,近墨者黑”。
二,K近邻算法需要解决的问题:
(1),计算距离:即给定测试对象,计算它与训练集中每 个对象的距离
确切的说为多维数组间距离的计算
(2),寻找邻居:即确定距离最近的k个训练对象
对距离的集合进行排序,找出最近的k个对象
(3),确定分类:即根据这k个近邻归属的主要类别,来对测试对象进行分类。
确定分类时所需要解决的问题:
(a),要有适当的训练数据集;
(b),距离函数的选取;
(c),k取值的决定;
(d),最终分类决策时确定测试样本类别的方法如何选择。
三,最近邻算法的优缺点:
优点:
模型简单,计算简单,准确率高,容易实现,易于操作。
避免了类不平衡问题。
缺点:
预测速度慢,不能处理很多特征的数据集。
四,对KNN算法的重构
(1)下载数据集:
下载数据集,这里直接使用已有的鸢尾花数据集,它返回到是一个Bunch对象。它包含在scikit-learn的datasets模块中。通过调用load_iris函数来加载数据
(2)切分数据集为训练集和测试集:
一般来说,训练集占75%,测试集占25%
定义函数:splitDateSets(),默认训练集的比例为0.75
返回值:X_train,Y_train,y_test和y_test,他们都是NumPy数组如书上15所说,
注意:在对数据进行拆分之前,先利用伪随机数生成器将数据集打乱。给伪随机数设立随机种子random.seed(0),使得每次切割的数据集一致。
(3)KNN算法的实现:
分两部分,KNN训练和KNN模型精度的计算,即包括用训练数据构建模型的算法,也包括对新数据点进行预测的算法。它还包括算法从训练集中提取的信息。【构建模型只需要保存训练数据即可。想要对新数据点做出预测,算法需要在训练数据集中找到最近的数据点,就是它的最近邻】
五,关键算法
(一)欧式距离的说明:
欧几里得距离或欧几里得度量是欧几里得空间中两点间“普通”(即直线)距离。使用这个距离,欧氏空间成为度量空间。相关联的范数称为欧几里得范数。较早的文献称之为毕达哥拉斯度量。
(二)关键代码
#每一个测试数据都与训练集中的数据计算距离
for i in range(f):
for j in range(m):
dist = la.norm(X_train[j] - test[i])
distance.append(dist)
# 从小到大排序,每个数的索引位置,argsort()函数返回的是数组值从小到大的索引值
index_sort = np.argsort(distance)
# 获得距离样本点最近的K个点的标记值y
near_y = [y_train[i] for i in index_sort[:k]]
# 统计邻近K个点标记值的数量
count_y = Counter(near_y)
# 返回标记值最多的那个标记.most_common(),返回一个列表,包含counter中n个最大数目的元素
y_predict = count_y.most_common(1)[0][0]
pdt.append(y_predict)
六,错误分析及结果
在重构KNN算法计算欧式距离的方法中,错误如下:
很明显,是数组角标越界,但是分析后没有发现,为每一步添加print()方法查看数据变化。
(排序输出及后面均为错误数据)
(distance数据错误)
利用excle发现distance在第112的数据的地方后均为错误:即一次循环后均为错误数据,发现自己对norm理解有些错误,经过改正后,distance输出正确,但是后面的数据依然没有正确输入。继续分析逻辑,整理代码。发现变量名重复了。定义了两个k值,后面在K近邻中的k和for语句中的k值重复,换为其他变量,问题解决。
模型精度结果:
使用其他数据预测:
使用教材【Python机器学习基础教程】18页的数据[5,2.9,1,0.2],进行验证,得到结果如下:
七,总结
重构KNN,不仅需要对KNN的思路非常清楚,而且需要对其中的数据计算过程比较清楚,尤其在计算欧式距离的过程中,涉及到的多维矩阵的计算,尽管使用简单的函数就能够实现,但是如果要调用NumPy文档中的数学方法,需要对线性代数中矩阵的知识有所了解,比如说,三角矩阵,否则不能很好的理解多维数组欧式距离的计算。
在重构这个算法时候,主要有以下问题/麻烦及认识:
(1) 第一次,万事开头难,任何事情的第一次都是比较麻烦的。重构算法的难,难就在于使用python话的代码叙述流程,相比起其他语言python确实是有有时的,简单易读,但是从其他编程语言将这种思维转变过来,得稍微锻炼一下;
(2) Python基础不够扎实,虽然也可以一边查,一边写,但这样对于这个作业来说太耗费时间,且极易让人产生挫败感。这样显得就比较困难。
(3) 编写Python代码的经验还不够,代码出错只能简单的分析错误。不能借用编译器很好的分析,虽然可以借用其他方法,但是,不可避免的走了许多弯路。
(4) 对于split()函数,课本及其他书籍中均调用sciket-learn中的函数时使用过一个种子数,使得它能够每次生成相同的序列。而使用permutation()每次的执行结果都是不同的。这里应当改进下,可以使用Python中的集合或者列表及推导式,集合的内部实现保证了元素不重复,并做了大量的优化。
八,说明
在本次重构KNN算法的过程中,即是对机器学习的学习,同时也很考验Python基础能力。在编写该算法中,参考了大量文档和书籍,部分如下:
(1) Python机器学习基础教程 Andreas C Muller,Sarah Guido
(2) 大话Python机器学习 张居营
(3)计算机视觉学习笔记(2.1)-KNN算法中距离矩阵的计算
(4)机器学习之k-近邻(kNN)算法与Python实现
(5)索引与切片
(6)欧几里得度量
(7)python collections模块详解
(8)np.linalg.norm(求范数)
(9)Python-Counter-计数函数
(10)Numpy中文文档
(11)【Python】Numpy 中的 shuffle VS permutation
(12)numpy中argsort函数用法
九,代码
from sklearn.datasets import load_iris
import numpy as np
import numpy.linalg as la
from collections import Counter
import random
#加载数据集
def load_datasets():
iris_dataset = load_iris()
return iris_dataset
# 生成不重复随机数列的函数,number为个数
def RandomNumbers(number, start=0, end=0):
data = []
n = 0
random.seed(0)
while True:
element = random.randint(start, end)
if element not in data:
data.append(element)
n += 1
if n == number:
break
return data
# 分割数据集,默认为75%的训练集,25%的测试集合
def splitDataSets(dataset, rate=0.75):
# 分多少数据
trainSize = int(rate * len(dataset.data))
# 打乱数组[0,len(dataset.data))的排序后返回
index = RandomNumbers(len(dataset.data), 0, len(dataset.data) - 1)
# index = np.random.permutation(len(dataset.data))
# 分割数据集(X表示数据,y表示标签),以返回的index为下标
X_train = dataset.data[index[:trainSize]]
y_train = dataset.target[index[:trainSize]]
X_test = dataset.data[index[trainSize:]]
y_test = dataset.target[index[trainSize:]]
return X_train, y_train, X_test, y_test
# 预测函数
def predict(X_train, y_train, test, k=6):
pdt = []
distance = []
# 计算距离
m, n = X_train.shape
f, g = test.shape
# 每一个测试数据都与训练集中的数据计算距离
for i in range(f):
for j in range(m):
dist = la.norm(X_train[j] - test[i])
distance.append(dist)
# 从小到大排序,每个数的索引位置,argsort()函数返回的是数组值从小到大的索引值
index_sort = np.argsort(distance)
# 获得距离样本点最近的K个点的标记值y
near_y = [y_train[i] for i in index_sort[:k]]
# 统计邻近K个点标记值的数量
count_y = Counter(near_y)
# 返回标记值最多的那个标记.most_common(),返回一个列表,包含counter中n个最大数目的元素
y_predict = count_y.most_common(1)[0][0]
pdt.append(y_predict)
distance.clear()
return pdt
# 评估KNN模型
def evaluationKNN(Y_pre, Y_test):
# cnt = np.sum(preY == testY)
# acc = np.divide(cnt, len(testX))
# print("the accurate of knn:", round(acc, 4))
print("Test set score:{:.2f}".format(np.mean(Y_pre == Y_test)))
# 主函数
def main():
# 调用函数加载和切分数据集
X_train, y_train, X_test, y_test = splitDataSets(load_datasets())
# 打印出KNN对测试集的预测
print(predict(X_train, y_train, X_test))
# 打印出测试集的标签
print("{}".format(y_test))
# 对模型精度进行计算
evaluationKNN(predict(X_train, y_train, X_test), y_test)
# 对其他数据的预测test = [5, 2.9, 1, 0.2]
test = np.array([[5, 2.9, 1, 0.2]])
print("该数据的预测结果为:{}".format(predict(X_train, y_train, test)))
if __name__ == '__main__':
main()