k-近邻(K-Nearest Neighbor)算法也称为最近邻或k-最近邻算法,是一种用于分类的机器学习算法,属于监督学习。该方法也是一种最为简单的分类方法,具有精度高、对异常值不敏感等优点。
算法原理
KNN在某种程度上与距离判别法有着很多相似的地方。在距离判别法中,我们是判断未知类别的数据点距离样本中各类数据中心的距离,离哪一类的中心最近,就认为该组数据属于哪一类;对于KNN,它是计算未知类别的数据点与样本所有点的距离,然后由近到远排序,选取最近的k个点,这k个点中属于哪一类的最多,就认为该组数据属于哪一类。
关于各组数据点之间距离的度量,与距离判别法采用的方法是相同的,并且有着非常多的距离度量的方法,详见兔兔的《相似性度量(距离度量)》方法系列文章,尤其是其中的样本距离度量方法。在本文中,为了讲解方便,就采用欧式距离进行度量。数据X(i)与X(j)之间欧式距离的公式为:
p表示指标(变量)数。
所以,对于KNN,其算法流程为:
(1)对于未知类别的一组数据X(i),计算它到样本中各组数据的距离(样本含有n组数据,并且每组数据都对应一种类别,样本的指标与未知数据的指标一致)。
(2)将样本数据根据距离由小到大排列。
(3)选取最近的k组数据。
(4)统计k组数据中属于哪一类的最多,该未知数据类别即为k组数据中含有个数最多的那一类。
算法实现
兔兔在这里以 dry bean dataset数据集为例,该数据集有13611组数据,16个变量,7个类别。
import numpy as np
import pandas as pd
class KNN:
def __init__(self,x,dataset,label,k=10):
self.x=x #待分类的一组数据,为行向量
self.dataset=dataset #数据集
self.label=label #数据集中各组数据对应类别
self.k=k #选取最近点的个数
self.n,self.p=dataset.shape #n为样本数,p为指标数
def distance(self,x,y):
'''数据之间的欧式距离'''
return np.sqrt(np.sum((x-y)**2))
def main(self):
'''KNN主程序'''
dis=[];label=[]
for i in range(self.n):
dis.append(self.distance(self.x,self.dataset[i]))
label.append(self.label[i])
a=pd.Series(label,dis)
b=a.sort_index() #按照距离排序
b=b.values
count={}
for i in range(self.k):
if b[i] not in count:
count[b[i]]=1
else:
count[b[i]]+=1
num=max(count.values()) #选取个数最多的一类
for key,value in count.items():
if value==num:
return key
if __name__=='__main__':
df=pd.DataFrame(pd.read_csv('Dry_Bean_Dataset.csv'))
dataset=np.array(df.loc[0:10000,'Area':'ShapeFactor4'])
y=np.array(df.loc[0:10000,'Class'])
knn=KNN(x=np.array(df.loc[100,'Area':'ShapeFactor4']),dataset=dataset,label=y,k=100)
result=knn.main()
print(df.loc[100,'Class'])
print(result)
在实现算法的过程中,关键在于如何根据距离进行排序,同时各个距离值与对应的类别是要对应的。兔兔在这里采用pandas中的Series进行处理,这样根据距离排序的同时对应的类别也随之变化,当然,也可以采用DataFrame,或者采取字典等方法来实现。统计各类别的个数时这里采用字典的方法,在这里其实也可以采用pandas中groupby函数来统计。
为了初步观察该模型效果,兔兔采用样本中第100条相同的数据来进行测试,结果result为SEKER,事实上它也的确是SEKER。
若进一步测试效果,则可以采用下面的方法。
def test(traindata,testdata):
'''测试准确率'''
count=0
n=len(testdata[0])
for t in range(n):
knn=KNN(x=testdata[0][t],dataset=traindata[0],label=traindata[1])
if testdata[1][t] is knn.main():
count+=1
else:
continue
return count/n
if __name__=='__main__':
df=pd.DataFrame(pd.read_csv('Dry_Bean_Dataset.csv'))
df=np.array(df)
np.random.shuffle(df) #随机打乱数据
df=pd.DataFrame(df)
traindata=[np.array(df.loc[0:10000,0:15]),np.array(df.loc[0:10000,16])] #选取前10000组数据作为训练集
testdata=[np.array(df.loc[10001:11000,0:15]),np.array(df.loc[10001:11000,16])] #选取1000组数据作为测试集
test=test(traindata=traindata,testdata=testdata)
print(test)
由于计算量较大,程序可能会运行较长时间才能显示结果,具体的运行时间可以在程序中写入time函数来统计。兔兔的最终运行结果为0.727,准确率并不是很高,但也还算可以。
对于KNN ,其应用十分广泛,不仅可以用于普通的数据的分类,理论上也可以用于图像、文本、音频等分类。例如手写数字黑白图像,把各个像素点转成0或1,图像的m×n维矩阵转换成mn×1维的向量,采用海明距离等序列距离度量方法计算向量之间的距离(即图片之间的距离),然后用KNN计算未知图片属于哪一类。但是对于更为复杂的问题,KNN效果有时并不很好。
总结
KNN 算法作为一种最为简单的机器学习算法,具有很多优点,并且算法简便,无需过多的公式推导,关键在于算法的实现。但是缺点也很明显:计算复杂度高、空间复杂度高等。所以在使用KNN 时,需要根据实际问题进行分析,如果效果不好或者不适合使用KNN,就需要选择其它的算法了。