自己动手写K邻近分类算法(KNN)
1 什么是KNN(k-nearest neighbor)?
在样本空间中,找到训练样本中与新实例距离最近的K个已知样本,使用这k个样本中数量较多的类别作为新实例的类别。这就是K邻近,找到距离最近的K个点,然后共同投票。
2 KNN算法
输入:实例的特征向量、实例的类别
输出:新实例所属的类别 x x
步骤:
- 根据距离度量,找到与最邻近的
k
k
个点,涵盖这个个点的邻域记为
Nk(x)
N
k
(
x
)
- 在 Nk(x) N k ( x ) 中根据分类决策的规则决定 x x 的类别
其中,距离度量可以使用 Lp L p 距离:其中 p≥1 p ≥ 1
Lp(xi,xj)=(∑l=1n|x(l)i−x(l)j|p)1p L p ( x i , x j ) = ( ∑ l = 1 n | x i ( l ) − x j ( l ) | p ) 1 p
当 p=2 p = 2 时,这个公式为欧氏距离, p=1 p = 1 时为马哈顿距离, p=\infin p = \infin 时,表示的各坐标的最大值。 实际当中,最常用的是欧氏距离。
K值的选择
使用太小的K值,会产生过拟合,尤其在噪声节点较多,那么预测结果会很差,但是如果K值设置的太大的话,不相关的节点也会对结果产生影响,所以K值得选择很重要。
实际中通常使用交叉验证的方法,进行K值的选择。
3 代码实现
3.1 对数据线性扫描(简单,粗暴)
from math import sqrt class KNN: def __init__(self,k): self.k = k def fit(self,trainX,trainY): self.trainX=trainX self.trainY=trainY def distance(self,x,y): # 距离度量 res = 0.0 for i in range(len(x)): res += abs(x[i]**2-y[i]**2) res = sqrt(res) return res def findKN(self,dist): # 找到距离最近的K个节点 def fun(a): return a[1] indexed_dist = [ [v,k] for v,k in enumerate(dist)] new_dist = sorted(indexed_dist,key=fun) res = [] # 如果训练数据数量小于k的处理 if len(new_dist) > self.k: for i in range(self.k): res.append(new_dist[i][0]) else: for i in new_dist: res.append(i[0]) return res def predicate(self,testX): label = [] for test in testX: dist = [] # 计算当前节点到所有节点的距离 for k,v in enumerate(self.trainX): dist.append(self.distance(v,test)) # 找到最近的K个节点 kn = self.findKN(dist) nnl = [] for i in kn: nnl.append(self.trainY[i]) nnl.sort() res = [ 1 ,nnl[0]] # 找到出现次数最多的类别 for i in range(1,len(nnl)): if nnl[i] == res[1]: res[0]+=1 else: res[0] -= 1 if res[0] <= 0: res[0] = 0 res[1] = nnl[i] label.append(res[1]) return label if __name__ == '__main__': knn = KNN(2) # 训练数据 trainX = [[1,2],[4,3],[2,2],[1,1]] trainY = [1,2,3,1] knn.fit(trainX,trainY) # 测试数据 testX = [[2,3],[1,0]] res = knn.predicate(testX) print(res)
3.2 使用kd树进行搜索
下回分解
4 总结
K邻近同样是一个简单的算法,没有显式的学习过程,就是看着哪种情况最多就用哪个结果,其思想也很简单,但是在求K近邻时按照传统的线性方法,计算量会过于巨大。以后会介绍使用kd树搜索,来简化这一过程。
初学乍练,请多多指正!