k-NN(k-nearest neighbor),从英语翻译过来就是k个最接近的邻居;我们现在只要有k和最接近这两个概念就行了。接下来笔者将详细介绍其原理,并用python实现k-NN。
k近邻法由Cover和Hart P在1967年提出的一种分类和回归的方法[1]。
其原理为:给定一组带标签的数据,为了确定不带标签数据的类别(标签),我们从带标签数据中选取k个与不带标签的最相似的数据;然后从k个最相似带标签的数据中决定未知数据的标签,未知数据的标签为k个中标签数目最多的那个。
哇,好难懂的原理,来,我们看个例子:
暂时还没没想出比这个[1]更好的例子,引一下:
我们可以用k近邻算法来分类一个电影是爱情片还是动作片:
电影名称 | 打斗镜头 | 接吻镜头 | 电影类型 |
---|---|---|---|
电影1 | 1 | 101 | 爱情片 |
电影2 | 5 | 89 | 爱情片 |
电影3 | 108 | 5 | 动作片 |
电影4 | 115 | 8 | 动作片 |
如果有一个电影有101个接吻镜头,1个打斗镜头,我们就可以认为该片为爱情片;如果有5个接吻镜头,108个打斗镜头,当然我们会认为该电影为动作片。假如现在有一个电影有8个打斗镜头,89个接吻镜头,因为接吻镜头占了绝大多数,我们依然可以将该电影判定为爱情片。
kNN是如何如何实现这一判断过程呢?
kNN是计算相似性来进行判断的,在计算问题中相似性可以看作是距离,距离可以是任何已知的衡量距离的方式,比如说曼哈顿曼哈顿距离或欧式距离。
我们可以用 L p L_p Lp距离[2]:
对于特征空间 X X X的 n n n维实数向量 x i , x j ∈ X {x_i},{x_j} \in X xi,xj∈X, x i = ( x i ( 1 ) , x i ( 2 ) , . . , x i ( n ) ) {x_i} = ({x_i}^{(1)},{x_i}^{(2)},..,{x_i}^{(n)}) xi=(xi(1),xi(2),..,xi(n)), x j = ( x j ( 1 ) , x j ( 2 ) , . . , x j ( n ) ) {x_j} = ({x_j}^{(1)},{x_j}^{(2)},..,{x_j}^{(n)}) xj=(xj(1),xj(2),..,xj(n))的 L p L_p Lp距离定义为:
L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p {L_p}({x_i},{x_j}) = {(\sum\limits_{l = 1}^n {|{x_i}^{(l)} - {x_j}^{(l)}{|^p}} )^{\frac{1}{p}}} Lp(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣p)p1
p ≥ 1 p \ge 1 p≥1,当 p = 1 p=1 p=1时为曼哈顿距离, p = 2 p=2 p=2时为欧式距离。
现在我们在 L p L_p Lp距离中取 p = 2 p=2 p=2,拿我们刚刚的例子来做计算的话:
我们要判断的电影与电影1到4的距离分别为:
( 8 , 89 ) → ( 1 , 101 ) : 13.89 ( 8 , 89 ) → ( 5 , 89 ) : 3 ( 8 , 89 ) → ( 108 , 5 ) : 130.60 ( 8 , 89 ) → ( 115 , 8 ) : 134.20 \begin{array}{l} (8,89) \to (1,101):13.89\\ (8,89) \to (5,89):3\\ (8,89) \to (108,5):130.60\\ (8,89) \to (115,8):134.20 \end{array} (8,89)→(1,101):13.89(8,89)→(5,89):3(8,89)→(108,5):130.60(8,89)→(115,8):134.20
从以上距离选最小的一个,即: ( 5 , 89 ) (5,89) (5,89)电影2,为爱情片,因此,我们可以判定 ( 8 , 89 ) (8,89) (8,89)为爱情片,ok,搞定。
嗯?等等,k近邻,k近邻,k呢?
怎么回事呢,其实我们刚刚只拿了一个最近的点来判断,这种情况下k=1,我们可以将其称为最近邻,而不是k近邻。
现在我们来用k近邻来判断一下,我们取k=3:
首先我们将距离从小到大排序(相似度由大到小,距离越小越相似),排序前k(3)位为: ( 5 , 89 ) , ( 1 , 101 ) , ( 108 , 5 ) (5,89),(1,101),(108,5) (5,89),(1,101),(108,5),其中前两个为爱情片,第3个为动作片;爱情片的数量最多,占 2 / 3 2/3 2/3,因此我们可以将 ( 8 , 89 ) (8,89) (8,89)划分为爱情片。至此,我们完成了kNN的整个过程。
结合以上,整个kNN的过程可以概括为:
在以下代码中,我们取 p = 2 p=2 p=2,即用欧式距离;并取k=3,程序结果预测到 ( 8 , 89 ) (8,89) (8,89)为爱情片。
此外,笔者在鸢尾花数据集上对其进行了测试,测试结果显示能够达到96%的准确率。
代码如下:
# -*- coding:UTF-8 -*-
"""
created on Thursday,19:33,2020-01-02
@author:Jeaten
e_mail:[email protected]
"""
import math
def judge(actual,prediction):### 判断实际值是否与预测值相等
return actual==prediction
def distance(point1,point2,p):### 计算两点的Lp距离
'''
this function is used to calculate the distance of two points
:param point1: first point
:param point2: second point
:param p: value p, is used to distinguish all kinds of distances
:return: the distance under 'p'
'''
assert point1.__len__()==point2.__len__()
dis=0
for i in range(point1.__len__()):
dis+=math.pow(abs(point1[i]-point2[i]),p)
return dis**(1/p)
def generate_data():### 生成数据
'''
this function is used to generate the data you need
:return: the feature and label
'''
height_weight=[(1,101),(5,89),(108,5),(115,8)]
label=[1,1,-1,-1]
return height_weight,label
def knn(actual,feature,label,k):### knn算法
'''
the knn(k-nearest neighbor) algorithm
:param actual: the actual value you want to know which category it belongs to
:param feature: the feature of all data
:param label: the label of all data
:param k: the value of k
:return: the category the actual value most likely belongs to
'''
assert feature.__len__()==label.__len__()
temp=[]
for i in range(feature.__len__()):
temp.append(distance(actual,feature[i],p))
temp=sorted(enumerate(temp),key=lambda x:x[1])
res=[label[i[0]] for i in temp]
return max(res,key=res[:k].count)
def __test__():
#### 测试其在鸢尾花数据集上的效果
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
load_data = load_iris()
x = load_data.data
y = load_data.target
x_train, x_test, y_train, y_test = train_test_split( x, y, test_size=0.2 )
correct = 0
for i in range( x_test.__len__() ):
prediction = knn( x_test[i], x_train, y_train, k )
if judge( y_test[i], prediction ):
correct += 1
print( correct, x_test.__len__(), correct / x_test.__len__() )
if __name__ == '__main__':
p=2
k=3
feature,label=generate_data()
dic={1:"爱情片",-1:"动作片"}### 字典结构,用于显示结果
act=(8,89)
print("该片为:",dic[knn(act,feature,label,k)])
# __test__()
才疏学浅,难免有错误和不当之处,欢迎交流批评指正!
同时有问题的话欢迎留言或邮箱联系([email protected])。
[1] 傻了吧嗒.K-近邻算法(史诗级干货长文).
https://blog.csdn.net/u010451580/article/details/51373081.20160.5.11
[2] 李航.《统计学习方法》[M].2012.3.北京:清华大学出版社,2019.5(重印):14-15.