k近邻原理及其python实现
k-NN(k-nearest neighbor)
,从英语翻译过来就是k个最接近的邻居;我们现在只要有k和最接近这两个概念就行了。接下来笔者将详细介绍其原理,并用python实现k-NN。
kNN原理
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的过程可以概括为:
- 确定带标签的数据(训练集)和要进行分类的不带标签的数据(测试集)
- 计算不带标签的数据与带标签数据中每个数据的相似度
- 按照相似度从小到达排序,并选取最相似的k个数据
- 从k个数据中确定最哪个类别的数据最多,将其作为未知数据的标签
kNN的python实现
在以下代码中,我们取
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:ljt_IT@163.com
"""
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__()
才疏学浅,难免有错误和不当之处,欢迎交流批评指正!
同时有问题的话欢迎留言或邮箱联系(ljt_IT@163.com)。
Reference:
[1] 傻了吧嗒.K-近邻算法(史诗级干货长文).
https://blog.youkuaiyun.com/u010451580/article/details/51373081.20160.5.11
[2] 李航.《统计学习方法》[M].2012.3.北京:清华大学出版社,2019.5(重印):14-15.