KNN原理介绍与python实现

1. 引言

    K近邻法(K-Nearest Neighbor,以下简称KNN)是由Cover和Hart于1968年提出来的基本分类和回归方法,KNN的基本思想是对于每一个样本,计算与其最邻近的K个样本点,然后基于某种分类规则的的方式将这K个邻近点的类别作为该样本的预测类别,因此,KNN并不具有显式的学习过程。

2. KNN原理介绍

2.1 KNN算法原理介绍

    对于一个给定的训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) } T=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right\} T={(x1,y1),(x2,y2),,(xN,yN)},其中, x i ∈ X ⊆ R n x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n} xiXRn为实例的特征向量, y i ∈ Y = { c 1 , c 2 , ⋯   , c K } y_{i} \in \mathcal{Y}=\left\{c_{1}, c_{2}, \cdots, c_{K}\right\} yiY={c1,c2,,cK}为实例的类别, i = 1 , 2 , ⋯   , N i=1,2, \cdots, N i=1,2,,N。则对于一个实例向量 x x x,KNN算法将根据指定的距离度量方式,从训练集 T T T中找出与 x x x最邻近的K个点,记涵盖这K个点的 x x x的领域为 N k ( x ) N_{k}(x) Nk(x)

    接着,根据分类决策规则,一般采用多数表决的形式,计算 N k ( x ) N_{k}(x) Nk(x)中的多数类,然后将多数类的类别作为实例 x x x的预测类别 y y y,其计算公式如下:
y = arg ⁡ max ⁡ c j ∑ x j ∈ N k ( x ) I ( y i = c j ) , i = 1 , 2 , ⋯   , N ; j = 1 , 2 , ⋯   , K y=\arg \max _{c_{j}} \sum_{x_{j} \in N_{k}(x)} I\left(y_{i}=c_{j}\right), \quad i=1,2, \cdots, N ; \quad j=1,2, \cdots, K y=argcjmaxxjNk(x)I(yi=cj),i=1,2,,N;j=1,2,,K
其中 I I I为指示函数,即当 y i = c j y_{i}=c_{j} yi=cj时, I = 1 I=1 I=1,否则, I = 0 I=0 I=0

    当K取值为1时,则此时K邻近法也称为最近邻法,此时,对于每一个实例 x x x,其预测类别将直接采用与其最邻近的点的类别。

    对于KNN模型,主要有三个基本要素——距离度量、K值和分类规则。对于距离度量,常用的有欧式距离、曼哈顿距离等,用更一般的 L p L_{p} Lp距离表示如下:
L p ( x i , x j ) = ( ∑ i = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p L_{p}\left(x_{i}, x_{j}\right)=\left(\sum_{i=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right|^{p}\right)^{\frac{1}{p}} Lp(xi,xj)=(i=1nxi(l)xj(l)p)p1
其中, x i = ( x i ( 1 ) , x i ( 2 ) , ⋯   , x i ( n ) ) T x_{i}=\left(x_{i}^{(1)}, x_{i}^{(2)}, \cdots, x_{i}^{(n)}\right)^{\mathrm{T}} xi=(xi(1),xi(2),,xi(n))T x j = ( x j ( 1 ) , x j ( 2 ) , ⋯   , x j ( n ) ) T x_{j}=\left(x_{j}^{(1)}, x_{j}^{(2)}, \cdots, x_{j}^{(n)}\right)^{\mathrm{T}} xj=(xj(1),xj(2),,xj(n))T分别表示 n n n维向量空间中的两个点,当 p = 2 p=2 p=2时,则相当于欧式距离:
L 2 ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 L_{2}\left(x_{i}, x_{j}\right)=\left(\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right|^{2}\right)^{\frac{1}{2}} L2(xi,xj)=(l=1nxi(l)xj(l)2)21
p = 1 p=1 p=1时,则相当于曼哈顿距离:
L 1 ( x i , x j ) = ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ L_{1}\left(x_{i}, x_{j}\right)=\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right| L1(xi,xj)=l=1nxi(l)xj(l)
p = ∞ p=\infty p=时,则相当于各个坐标距离的最大值:
L ∞ ( x i , x j ) = max ⁡ l ∣ x i ( l ) − x j ( l ) ∣ L_{\infty}\left(x_{i}, x_{j}\right)=\max _{l}\left|x_{i}^{(l)}-x_{j}^{(l)}\right| L(xi,xj)=lmaxxi(l)xj(l)
当采用不同的距离度量方式时,此时对于每个实例 x x x得到的K个近邻点可能是不一样的。

    对于K值的选择,当K值选的比较小时,则此时模型容易受到噪声点的影响,估计误差会比较大,当K值选的比较大时,则容易受到不相似点的影响,近似误差比较大,因此,K值的选择过大或过小对结果的影响都比较大,一般来讲,会通过交叉验证法来选择最优的K值。

    对于分类规则的选择,一般采用的是多数表决规则。

2.2 KNN算法的实现——kd树

    根据KNN算法的原理我们可以知道,KNN是没有显示的学习过程,因此,对于每一个实例,都要对所有的训练集 T T T进行遍历,当训练集非常多,维度非常大时,此时KNN算法将非常耗费计算资源和计算时间,因此,在实际情况中,我们会采用一些更加高效的方法来实现KNN算法,其中,kd树是最常用的一种,下面具体介绍kd树的构造和搜索过程。

    kd树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构,这里的k指的是向量空间的维度,而不是KNN中的K值。kd树二叉树,对于k维空间训练集 T = { x 1 , x 2 , ⋯   , x N } T=\left\{x_{1}, x_{2}, \cdots, x_{N}\right\} T={x1,x2,,xN},其中 x i = ( x i ( 1 ) , x i ( 2 ) , ⋯   , x i ( k ) ) T , i = 1 , 2 , ⋯   , N x_{i}=\left(x_{i}^{(1)}, x_{i}^{(2)}, \cdots, x_{i}^{(k)}\right)^{\mathrm{T}}, \quad i=1,2, \cdots, N xi=(xi(1),xi(2),,xi(k))T,i=1,2,,N,kd树的构建步骤如下:

  1. 首先,构造根结点,根结点即为包含整个训练集 T T T的k维超矩形区域。
  2. 接着,按顺序从k个坐标轴中选择一个坐标轴 x ( j ) x^{(j)} x(j)作为切分坐标轴,并计算训练集所有实例的 x ( j ) x^{(j)} x(j)坐标中位数作为切分点,将通过切分点并与 x ( j ) x^{(j)} x(j)坐标轴垂直的超平面作为切分超平面,将父结点对应的超矩形区域切分为两个子区域,此时,左子结点表示对应坐标 x ( j ) x^{(j)} x(j)小于切分点的子区域,右子结点表示对应坐标 x ( j ) x^{(j)} x(j)大于切分点的子区域,而落在切分超平面上的实例点则保存在父结点。
  3. 重复2的过程,直到两个子区域没有实例存在时为止,此时,对应的结点作为kd树的叶结点,最终形成kd树的区域划分。

    当kd树勾结完成后,此时对于k维向量空间,会被划分为很多个小的超矩形区域,每个小的超矩形区域即为kd树的叶结点,对于一个新的实例 x x x,此时,先找到他所在的叶结点,然后从该叶结点出发,依次回退到父结点,不断查找与它最邻近的结点,直到回退到根结点为止,这样一来,搜索就相当于被限制在局部区域上,从而使得效率大大提高。具体的搜索步骤如下(以最近邻为例):

  1. 在kd树中找出包含目标点 x x x的叶结点:从根结点出发,递归地向下访问kd树。若目标点 x x x当前维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点,直到子结点为叶结点为止。

  2. 以此叶结点为“当前最近点”。

  3. 递归地向上回退,在每个结点进行以下操作:
    a. 如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”
    b. 当前最近点一定存在于该结点一个子结点对应的区域。检查该子结点的父结点的另一子结点对应的区域是否有更近的点,具体地,检查另一子结点对应的区域是否与以目标点为球心、以目标点与“当前最近点”间的距离为半径的超球体相交。如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点,接着,递归地进行最近邻搜索; 如果不相交,向上回退。

  4. 当回退到根结点时,搜索结束。最后的“当前最近点”即为 x x x的最近邻点。

3. KNN的python实现

    python中sklearn已经有KNN模型的API接口,因此,本文不再重复造轮子,有关代码的实现可以参见本人的github地址:

  • github项目地址:https://github.com/lch123456/machine-learning

    未来,该项目将陆续增加机器学习中的其他模型,包括一些sklearn上没有的模型,如果涉及到一些类似的任务,比如分类任务,可以同时对比多个模型的效果。

4. 总结

    最后,对KNN模型的优缺点总结如下:

  • KNN算法原理相对比较简单,也比较容易理解
  • 由于KNN并没有显式的学习过程,因此,当数据量比较大时,相比其他模型,其计算资源和时间的消耗将比较大。
  • 由于涉及到距离的计算,当样本的分布密度不均衡时,K值的确定会相对比较麻烦。

你可能感兴趣的:(机器学习)