网上介绍K-近邻算法的例子很多,其Python实现版本基本都是来自于机器学习的入门书籍《机器学习实战》,虽然K-近邻算法本身很简单,但很多初学者对其Python版本的源代码理解不够,所以本文将对其源代码进行分析。
什么是K-近邻算法?
简单的说,K-近邻算法采用不同特征值之间的距离方法进行分类。所以它是一个分类算法。
优点:无数据输入假定,对异常值不敏感
缺点:复杂度高
好了,直接先上代码,等会在分析:(这份代码来自《机器学习实战》)
def classify0(inx, dataset, lables, k): dataSetSize = dataset.shape[0] diffMat = tile(inx, (dataSetSize, 1)) - dataset sqDiffMat = diffMat**2 sqDistance = sqDiffMat.sum(axis=1) distances = sqDistance**0.5 sortedDistances = distances.argsort() classCount={} for i in range(k): label = lables[sortedDistances[i]] classCount[label] = classCount.get(label, 0) + 1 sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0]
该函数的原理是:
存在一个样本数据集合,也称为训练集,在样本集中每个数据都存在标签。在我们输入没有标签的新数据后,将新数据的每个特征与样本集中对应的特征进行比较,然后提取最相似(最近邻)的分类标签。一般我们只选样本数据集中前K 个最相似的数据。最后,出现次数最多的分类就是新数据的分类。
classify0函数的参数意义如下:
inx : 是输入没有标签的新数据,表示为一个向量。
dataset: 是样本集。表示为向量数组。
labels:对应样本集的标签。
k:即所选的前K。
用于产生数据样本的简单函数:
def create_dataset(): group = array([[1.0, 1.1], [1.0, 1.1], [0, 0], [0, 0.1]]) labels = ['A', 'A', 'B', 'B'] return group, labels
注意,array是numpy里面的。我们需要实现import进来。
from numpy import * import operator
我们在调用时,
group,labels = create_dataset() result = classify0([0,0], group, labels, 3) print result
显然,[0,0]特征向量肯定是属于B 的,上面也将打印B。
知道了这些,初学者应该对实际代码还是很陌生。不急,正文开始了!
源码分析
dataSetSize = dataset.shape[0]
shape是array的属性,它描述了一个数组的“形状”,也就是它的维度。比如,
In [2]: dataset = array([[1.0, 1.1], [1.0, 1.1], [0, 0], [0, 0.1]]) In [3]: print dataset.shape (4, 2)
所以,dataset.shape[0] 就是样本集的个数。
diffMat = tile(inx, (dataSetSize, 1)) - dataset
tile(A,rep)函数是基于数组A来构造数组的,具体怎么构造就看第二个参数了。其API介绍有点绕,但简单的用法相信几个例子就能明白。
我们看看tile(inx, (4, 1))的结果,
In [5]: tile(x, (4, 1)) Out[5]: array([[0, 0], [0, 0], [0, 0], [0, 0]])
你看,4扩展的是数组的个数(本来1个,现在4个),1扩展的是每个数组元素的个数(原来是2个,现在还是两个)。
为证实上面的结论,
In [6]: tile(x,(4,2)) Out[6]: array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
和,
In [7]: tile(x,(2,2)) Out[7]: array([[0, 0, 0, 0], [0, 0, 0, 0]])
关于,tile的具体用法,请自行查阅API DOC。
得到tile后,减去dataset。这类似一个矩阵的减法,结果仍是一个 4 * 2的数组。
In [8]: tile(x, (4, 1)) - dataset Out[8]: array([[-1. , -1.1], [-1. , -1.1], [ 0. , 0. ], [ 0. , -0.1]])
结合欧式距离的求法,后面的代码就清晰些,对上面结果平方运算,求和,开方。
我们看看求和的方法,
sqDiffMat.sum(axis=1)
其中,
In [14]: sqDiffmat Out[14]: array([[ 1. , 1.21], [ 1. , 1.21], [ 0. , 0. ], [ 0. , 0.01]])
求和的结果是对行求和,是一个N*1的数组。
如果要对列求和,
sqlDiffMat.sum(axis=0)
argsort()是对数组升序排序的。
classCount是一个字典,key是标签,value是该标签出现的次数。
这样,算法的一些具体代码细节就清楚了。