kNN(k-Nearest Neighbours)原理详解
1 kNN简介
kNN(k- Nearest Neighbor)法即k最邻近法,最初由 Cover和Hart于1968年提出,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一,它的适用面很广,并且在样本量足够大的情况下准确度很高,多年来得到了很多的关注和研究。
它的工作原理是:存在一个样本数据集合,也称作为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
2 图解kNN基本原理
假设有三种兔子,第一种兔子叫作绿兔(Green),它们的平均身高是 50厘米,平均体重 5公斤。选取100个样本,分别测量它们的身高和体重,画在坐标图上,用绿色方块表示。
第二种兔子叫蓝兔(blue),它们体型比较小,平均身高是 30厘米,平均体重是 4公斤。同样,选取100个样本,分别测量它们的身高和体重,并将它们画在同一个坐标图上,用蓝色三角表示。
最后一种兔子叫黄兔(yellow),它们的平均身高45厘米,但体重较轻,平均只有2.5公斤。100只黄兔的数据用黄色圆圈表示。
在这些数据中,(身高,体重)的二元组叫做特征(features),兔子的品种则是分类标签(class label)。我们想解决的问题是,给定一个未知分类的新样本的所有特征,通过已知数据来判断它的类别。
现在假设有一只兔子R,想要确定它属于绿兔、蓝兔和黄兔中的哪一类,应该怎么做呢?按照最普通的直觉,应该在已知数据里找出几个和我们想探究的兔子最相似的几个点,然后看看那些兔子都是什么个情况;如果它们当中大多数都属于某一类别,那么兔子R大概率也就是那个类别了。
为了确定兔子R属于哪一类,首先测量出其身长为 40 厘米,体重 2.7 公斤,为了直观展示,将其画在上述同一坐标系中,用红色五角星表示。
现在预设一个整数k,寻找距离兔子R最近的k个数据样本进行分析。kNN 算法如何对这次观测进行分类要取决于k的大小。直觉告诉我们兔子R像是一只黄兔,因为除了最近的蓝色三角外,附近其他都是黄色圆圈。的确,如果设 k = 15,算法会判断这只兔子是一只黄兔。但是如果设 k = 1,那么由于距离最近的是蓝色三角,会判断兔子R是一只蓝兔。
如果按照15NN和1NN的方法对这个二维空间上的每一个点进行分类,会形成以下的分割:
在两组分类中,1NN 的分类边界明显更“崎岖”,但是对历史样本没有误判;而 15NN 的分类边界更平滑,但是对历史样本有发生误判的现象。选择k的大小取决于对偏差和方差之间的权衡。
1)k近邻算法中k的选取
如果我们选取较小的k值,那么就会意味着我们的整体模型会变得复杂,容易发生过拟合!假设我们选取k=1这个极端情况,并且有训练数据和待分类点如下图:
上图中有俩类,一个是黑色的圆点,一个是蓝色的长方形,现在我们的待分类点是红色的五边形。根据我们的k近邻算法步骤来决定待分类点应该归为哪一类。我们由图中可以得到,五边形离黑色的圆点最近,k又等于1,因此,我们最终判定待分类点是黑色的圆点。
由这个处理过程我们很容易能够感觉出问题了,如果k太小了,比如等于1,那么模型就太复杂了,我们很容易学习到噪声,也就非常容易判定为噪声类别,而在上图,如果,k大一点,k等于8,把长方形都包括进来,我们很容易得到我们正确的分类应该是蓝色的长方形!如下图:
所谓的过拟合就是在训练集上准确率非常高,而在测试集上准确率低,经过上例,我们可以得到k太小会导致过拟合,很容易将一些噪声(如上图离五边形很近的黑色圆点)学习到模型中,而忽略了数据真实的分布!
如果我们选取较大的k值,就相当于用较大邻域中的训练数据进行预测,这时与输入实例较远的(不相似)训练实例也会对预测起作用,使预测发生错误,k值的增大意味着整体模型变得简单。
我们想,如果k=N(N为训练样本的个数),那么无论输入实例是什么,都将简单地预测它属于在训练实例中最多的类。这时,模型是不是非常简单,这相当于你压根就没有训练模型呀!直接拿训练数据统计了一下各个数据的类别,找最大的而已!这好像下图所示:
我们统计了黑色圆形是8个,长方形个数是7个,那么,如果k=N,我就得出结论了,红色五边形是属于黑色圆形的。这个时候,模型过于简单,完全忽略训练数据实例中的大量有用信息,是不可取的。
综上分析,k值既不能过大,也不能过小,在我举的这个例子中,我们k值的选择,在下图红色圆边界之间这个范围是最好的,如下图:
注:这里只是为了更好让大家理解,真实例子中不可能只有两维特征,但是原理是一样的,我们就是想找到较好的k值大小。那么我们一般怎么选取呢?常用的方法是从k=1开始,使用检验集估计分类器的误差率。重复该过程,每次K增值1,允许增加一个近邻。选取产生最小误差率的K。一般k的取值不超过20,上限是
n
n
n的开方,随着数据集的增大,K的值也要增大。(李航博士书上讲到,我们一般选取一个较小的数值,通常采取交叉验证法来选取最优的k值。也就是说,选取k值很重要的关键是实验调参,类似于神经网络选取多少层这种,通过调整超参数来得到一个较好的结果)。
2)距离函数
我们在上面的例子中把一个很重要的概念隐藏了起来,选择一个数量k 还只是小问题,更重要的是距离的计算方法。毕竟,当我们说“最近的k个点”时,这个“近”是怎么衡量的?
在数学中,一个空间上距离的严格定义如下:
设
M
M
M为一个空间,
M
M
M上的一个距离函数
d
:
M
×
M
→
R
d : M × M → \mathbb{R}
d:M×M→R,满足:
∙
∙
∙
d
(
x
,
y
)
≥
0
d(x,y)≥0
d(x,y)≥0
∀
x
,
y
∈
M
∀x,y∈M
∀x,y∈M
∙
∙
∙
d
(
x
,
y
)
=
0
⟺
x
=
y
d(x,y)=0 ⟺ x=y
d(x,y)=0⟺x=y
∙
∙
∙
d
(
x
,
y
)
=
d
(
y
,
x
)
∀
x
,
y
∈
M
d(x,y)=d(y,x) ∀x,y∈M
d(x,y)=d(y,x) ∀x,y∈M
∙
∙
∙
d
(
x
,
z
)
≤
d
(
x
,
y
)
+
d
(
y
,
z
)
∀
x
,
y
,
z
∈
M
d(x,z)≤d(x,y)+d(y,z) ∀x,y,z∈M
d(x,z)≤d(x,y)+d(y,z) ∀x,y,z∈M
两个点
x
,
y
x,y
x,y 之间的距离就是
d
(
x
,
y
)
d(x,y)
d(x,y)。
我们一般最常用的距离函数是欧氏距离,也称作
L
2
L_{2}
L2距离。如果
x
=
(
x
1
,
x
2
,
…
,
x
n
)
x=(x_{1},x_{2},…,x_{n})
x=(x1,x2,…,xn)和
y
=
(
y
1
,
y
2
,
…
,
y
n
)
y=(y_{1},y_{2},…,y_{n})
y=(y1,y2,…,yn)是
n
n
n维欧式空间
R
n
\mathbb{R}^{n}
Rn上的两个点,那它们之间的
L
2
L_{2}
L2距离是:
d
2
(
x
,
y
)
=
∑
i
=
1
n
(
x
i
−
y
i
)
2
(1)
d_{2}(x,y)=\sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^{2}} \tag{1}
d2(x,y)=i=1∑n(xi−yi)2(1)
L
2
L_{2}
L2是更普遍的
L
p
L_{p}
Lp距离在
p
=
2
p = 2
p=2 时的特例。
L
p
L_{p}
Lp距离的函数
d
p
d_{p}
dp定义如下:对于
1
≤
p
<
∞
1\leq p< \infty
1≤p<∞,有:
d
p
(
x
,
y
)
=
(
∑
i
=
1
n
∣
x
i
−
y
i
∣
p
)
1
p
(2)
d_{p}(x,y)=\left(\sum_{i=1}^{n}|x_{i}-y_{i}|^{p}\right)^{\frac{1}{p}} \tag{2}
dp(x,y)=(i=1∑n∣xi−yi∣p)p1(2)
还有
L
∞
L_{\infty }
L∞距离:
d
∞
(
x
,
y
)
=
m
a
x
i
=
1
,
2...
,
n
∣
x
i
−
y
i
∣
(3)
d_{\infty }(x,y)=\underset{i=1,2...,n}{max}|x_{i}-y_{i}| \tag{3}
d∞(x,y)=i=1,2...,nmax∣xi−yi∣(3)
在实际应用中,距离函数的选择应该根据数据的特性和分析的需要而定,本篇就不进行更深入的探讨,一般情况下使用最常用的
L
2
L_{2}
L2函数即可。
3)归一化处理
【注意】使用 kNN 时需要根据特征数据的取值区间来调整坐标轴的比例,这个做法叫作标准化或者归一化。为什么要这么做呢?拿上面的例子来说,一只兔子的身长(cm)数值平均是它的体重(kg)的 10倍左右,如果我们在这组数值上直接使用 L 2 L_{2} L2距离函数的话就会导致横轴的距离比重明显放大,分类结果也不合理,如下图所示:
如果把坐标轴成其他的单位,比如毫米和吨,并用相应的新数值来计算距离,又会得到完全不同的分类标准。甚至,在极端情况下,如果身高用纳米并且体重用吨计量,那么相比之下身高的数值会奇高无比,以至于两点之间的距离是完全由身高决定的,体重则没有任何权重。为了解决这个问题,我们应该在计算距离时把所有坐标轴进行归一化。
在之前的例子中,由于横轴数值大约是竖轴的 10 倍左右,所以我们将横轴(身高)的数值压缩 10倍,即计算距离时使用:
d
(
(
x
1
,
x
2
)
,
(
y
1
,
y
2
)
)
=
(
x
1
10
−
y
1
10
)
2
+
(
x
2
−
y
2
)
2
(4)
d((x_{1},x_{2}),(y_{1},y_{2}))=\sqrt{(\frac{x_{1}}{10}-\frac{y_{1}}{10})^{2}+(x_{2}-y_{2})^{2}} \tag{4}
d((x1,x2),(y1,y2))=(10x1−10y1)2+(x2−y2)2(4)
就可以得出合理的 kNN 分类。
一般来说,假设进行 kNN 分类使用的样本的特征是
{
(
x
i
1
,
x
i
2
,
.
.
.
,
x
i
n
)
}
i
=
1
m
\left \{ \left ( x_{i1},x_{i2},...,x_{in} \right ) \right \}_{i=1}^{m}
{(xi1,xi2,...,xin)}i=1m,取每一轴上的最大值减最小值:
M
j
=
m
a
x
i
=
1
,
2
,
.
.
.
,
m
(
x
i
j
)
−
m
i
n
i
=
1
,
2
,
.
.
.
,
m
(
x
i
j
)
(5)
M_{j}=\underset{i=1,2,...,m}{max}\left ( x_{ij} \right )-\underset{i=1,2,...,m}{min}\left ( x_{ij} \right ) \tag{5}
Mj=i=1,2,...,mmax(xij)−i=1,2,...,mmin(xij)(5)
并且,在计算距离时将每一个坐标轴除以相应的 M_{j}以进行归一化,即:
d
(
(
y
1
,
.
.
.
,
y
n
)
,
(
z
1
,
.
.
.
,
z
n
)
)
=
∑
j
=
1
n
(
y
j
M
j
−
z
j
M
j
)
2
(6)
d((y_{1},...,y_{n}),(z_{1},...,z_{n}))=\sqrt{\sum_{j=1}^{n}\left ( \frac{y_{j}}{M_{j}}-\frac{z_{j}}{M_{j}} \right )^{2}} \tag{6}
d((y1,...,yn),(z1,...,zn))=j=1∑n(Mjyj−Mjzj)2(6)
便可以规避坐标轴比例失衡的问题。
4)概率kNN
上面的kNN算法返回的是对一组特征的绝对分类,告诉我们这只兔子被判断为哪一个类别。可有时我们并不想知道一个确切地分类,而想知道它属于某个分类的概率是多大。比如我们发现一只身长 37厘米,体重 4.84公斤的小兔兔,在下图五角星的位置。
这只兔子的特征数据在绿兔和蓝兔的分界处,机器不论判断它属于哪个类别都很有可能是错的。这时,类似“它有一半可能性是绿兔,一半可能性是蓝兔”的反馈会更有意义。
为了这个目的,我们同样找出距离问题特征最近的
k
k
k个样本,但与其寻找数量最多的分类,我们统计其中每个类别的分别有多少个,再除以
k
k
k得到一个属于每一个类别概率值。比如在上面的图里,距离五角星最近的 15个样本中,有 8只绿兔和 7 只蓝兔,由此判断:它有 53% 的可能性是绿兔,47% 的可能性是蓝兔,0%的可能性是黄兔。
在整个二维空间中的每一个点上进行概率 kNN 算法,可以得到每个特征点是属于某个类别的概率热力图,图中颜色越深代表概率越大。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/14a2c6540bb39679e699865901b9c17e.png)
相比于绝对的分类,这些概率的计算会给我们更有效的表述以及更多的应用空间。
3 kNN算法总结
根据上述对kNN算法原理的解析,可以总结出其实现主要包含以下几个步骤:
对于一组训练样本以及一个测试样本,
(1)计算已知类别数据集中的点与当前点之间的距离;
(2)按照距离递增次序排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类。
kNN算法作为一种较简单的算法,它的不足之处在于:
(1)没有明显的训练过程,它是“懒惰学习”的典型代表,它在训练阶段所做的仅仅是将样本保存起来,如果训练集很大,必须使用大量的存储空间,训练时间开销为零;
(2)必须对数据集中每个数据计算距离值,实际中可能非常耗时。
由于上述的不足,为了提高kNN搜索的速度,可以利用特殊的数据存储形式来减少计算距离的次数。kd树就是一种以二叉树的形式存储数据的方法。kd树就是对k维空间的一个划分。构造kd树相当于不断用垂直于坐标轴的超平面将k维空间切分,构成一系列k维超矩阵区域。kd树的每一个节点对应一个超矩阵区域。后续文章讲详细介绍kd树的原理及算法实现。
【参考资料】
https://www.joinquant.com/view/community/detail/a98b7021e7391c62f6369207242700b2
https://zhuanlan.zhihu.com/p/25994179