之前做机器学习相关实验的时候就使用过K近邻(kNN)算法,但是那时只是停留在简单的了解阶段,并没有对该算法深层次的原理进行研究,最近我通过学习《统计学习方法》一书,对kNN算法有了深入的理解,下面我将结合自己的学习总结一下kNN相关的内容:
一、kNN算法的简介
kNN算法是一种基本的分类与回归方法,其基本原理是:给定一个训练数据集,对新输入实例,在训练数据集中找到与该实例最近邻的K个实例,这k个实例的多数属于某个类别,就把该输入实例分为这个类别。
k-NN算法的三要素是:距离度量、k值的选择、分类规则。常用的距离度量是欧式距离以及更一般的
Lp
距离。k值小时,k近邻模型更复杂;k值大时,k近邻模型更简单。k值的选择反映了对近似误差与评估误差之间的权衡,通常由交叉验证方法来选择最优的k。常用的分类决策规则是多数表决,对应于经验风险最小化。
在k-NN算法中,当训练集、距离度量、k值以及分类规则确定后,其结果是唯一确定的。
二、kNN算法的实现:kd树
kd树是一中便于对k维空间中的数据进行快速搜索的数据结构(注意,k不是k个邻居的意思)。kd树是一种二叉树,表示对k维空间的一个划分,其每个节点对应于k维空间划分中的一个超矩形区域。利用kd树可以省去对绝大部分数据的搜索,从而减少搜索的计算量。
1.kd树的构造
构造kd树的过程我自己总结了一个口诀就是:“选择中位数,一横一竖”
构造平衡kd树算法
输入:k维空间数据集
T={x1,x2,...,xN}
,其中
xi=(x(1)i,x(2)i,...,x(k)i)
,
i=1,2...,N;
输出kd树。
(1)分别基于输入的数据集,计算k维数据中方差最大的一维(也可以依次分割每一维度),为了便于介绍,本算法以依次分割每一维度进行的,所以选择
x(1)
为分割坐标轴;
(2)选择
x(1)
为分割坐标轴,以T中所有实例的
x(1)
坐标的中位数为切割点,将根节点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴
x(1)
垂直的超平面实现。
由根节点生成深度为1的左、右子节点:左子节点对应坐标
x(1)
小于切分点的子区域,右子节点对应于坐标
x(1)
大于切分点的子区域。
(3)重复步骤(2),对于深度为j的节点,选择
x(l)
为切分点的坐标轴,
l=j(modk)+1
,以该节点的区域切中所有实例的
x(l)
的中位数为切分点,将该节点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴
x(l)
垂直的超矩形实现。
(4)停止条件:直到两个子区域内没有实例存在时停止。
举例:
假设有6个二维数据点
{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}
,数据点位于二维空间内,如图1:
a)依次分割每一维度,因此先分割x轴;
b)根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以以(7,2)为分割点。这样,该节点的分割超平面就是通过(7,2)并垂直于x轴的直线x = 7;
c)确定左子空间(x <= 7的部分)和右子空间(x > 7的部分)进行上面两步递归操作直到空间中只包含一个数据点。最后生成的kd树对应的空间划分为图1,kd树如图2。
图1 kd树对应的空间划分
图2 kd树示例
2.kd树的最近邻搜索
给定一个目标点,搜索其最近邻,首先找到包含目标点的叶节点,然后从该叶节点出发,依次退回到其父节点,不断查找是否存在比当前最近点更近的点,直到退回到根节点时终止,获得目标点的最近邻点。如果按照流程可描述如下:
1. 从根节点出发,若目标点x当前维的坐标小于切分点的坐标,则移动到左子节点,反之则移动到右子节点,直到移动到最后一层叶节点。
2. 以此叶结点为“当前最近点”
3. 递归的向上回退,在每个节点进行如下的操作:
a.如果该节点保存的实例点距离比当前最近点更小,则该点作为新的“当前最近点”
b.检查“当前最近点”的父节点的另一子节点对应的区域是否存在更近的点,如果存在,则移动到该点,接着,递归地进行最近邻搜索。如果不存在,则继续向上回退
4. 当回到根节点时,搜索结束,获得最近邻点
以上kd树的最近邻搜索分析,即k=1时的情况。当k>1时,在搜索时“当前最近点”中保存的点个数<=k的即可。
相比于线性扫描,kd树搜索的平均计算复杂度为O(logN).但是当样本空间维数接近样本数时,它的效率会迅速降低,并接近线性扫描的速度。因此kd树搜索适合用在训练实例数远大于样本空间维数的情况