k近邻算法与kd树的创建和搜索

hit2015spring

欢迎关注我的博客:http://blog.csdn.NET/hit2015spring

k近邻算法

是一种常用的监督学习的方法:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于k个邻居的信息来进行预测。通常:

分类任务中用投票,选择k个样本中出现最多的类别标记作为预测结果
回归任务中:平均法
还可以基于距离的远近进行加权平均或者加权投票,距离越近权重越大

没有显示的训练过程,训练时间开销为0,是一种懒惰学习

分类的结果与k的选择距离计算方式的选择有关系

k近邻分类器示意图

$L_p距离间的关系$

上图可以看出二维空间中,距离的计算方式不同,包含的区域也是不相同的。故会影响结果

k值得选择也会对结果产生影响,k值小的话,相当于用较小的邻域中的训练实例进行预测,学习的近似误差会减小,只有与输入实例较近的训练实例才会对预测结果起作用,但是估计误差会增大,意味着会产生过拟合。

kd树的构建

实现k近邻方法时,面临一个计算速度的挑战问题,当特征空间的维数非常大以及训练数据非常大的时候,我们优化这个搜索的方法就显得尤为重要了

一般方法:最简单的是实现一个线性扫描,就是计算输入的实例与每一个训练实例之间的距离,训练集很大的时候,计算会耗时很长,于是就出现了kd树的这种方法。

构造kd树的数学描述比较难懂:

输入的是 k 维空间数据集T={x1,x2,,xN},注意这里的k维指的是特征的维度是k维,即 xi=(x(1)i,x(2)i,,x(k)i),i=1,2,,N

(1)开始:构造根结点,根结点对应于包含 T k维空间的超矩形区域.选择 x(1) 为坐标轴,以 T 中所有的实例的x(1)坐标的中位数为切分点,将根结点对应的超矩形区域划分为两个子区域。切分由通过切分点并与坐标轴 x(1) 垂直的超平面实现。

由根结点生成深度为1的左右两个子结点:左结点对应坐标 x(1) 小于切分点的子区域,右结点对应于坐标 x(1) 大于切分点的子区域,落在切分超平面的点保存在根结点

(2)重复:对深度为j的结点,选择 x(l) 为切分的坐标轴,以该结点的区域内部所有的实例的 x(l) 坐标的中位数作为切分点,将该结点对应的矩形区域切分为两个子区域。切分由通过切分点并与坐标轴 x(l) 垂直的超平面实现。
该结点生成左右两个结点,左结点对应坐标 x(l) 小于切分点的子区域,右子结点对应坐标 x(l) 大于切分点的子区域。落在切分超平面上的实例点保存在该结点。

(3)直到两个子区域没有实例存在时停止。

直接通过一个例子来理解:

这里写图片描述

这里写图片描述

简单的理解就是:每次选择一个维度的特征,把该实例点划分为两个部分,一个大于中位数,一个小于中位数,中位数就保留在这个结点,其他的放到下一层中,继续上面的过程。

当然了这里面的关键是怎么选取要切分那个维度呢?

最简单的方法就是轮着来,即如果这次选择了在第i维上进行数据划分,那下一次就在第j(j≠i)维上进行划分,例如:j = (i mod k) + 1。想象一下我们切豆腐时,先是竖着切一刀,切成两半后,再横着来一刀,就得到了很小的方块豆腐。

可是“轮着来”的方法是否可以很好地解决问题呢?再次想象一下,我们现在要切 的是一根木条,按照“轮着来”的方法先是竖着切一刀,木条一分为二,干净利落,接下来就是再横着切一刀,这个时候就有点考验刀法了,如果木条的直径(横截 面)较大,还可以下手,如果直径较小,就没法往下切了。因此,如果K维数据的分布像上面的豆腐一样,“轮着来”的切分方法是可以奏效,但是如果K维度上数 据的分布像木条一样,“轮着来”就不好用了。因此,还需要想想其他的切法。

如果一个K维数据集合的分布像木条一样,那就是说明这K维数据在木条较长方向 代表的维度上,这些数据的分布散得比较开,数学上来说,就是这些数据在该维度上的方差(invariance)比较大,换句话说,正因为这些数据在该维度 上分散的比较开,我们就更容易在这个维度上将它们划分开,因此,这就引出了我们选择维度的另一种方法:最大方差法(max invarince),即每次我们选择维度进行划分时,都选择具有最大方差维度。

最后再给一个例子看看:

这里写图片描述

运用kd树查找

构建好了kd树之后就是利用kd树来进行最近邻查找了:

(1)将查询数据Q从根结点开始,按照Q与各个结点比较的结果向下访问kd树,直到查找到叶子结点。

就是把Q的k个特征,在每一个结点上面的维度k与对应的分割的值m(那个中位数)这样向下走,就到了一个叶子结点,把Q与结点上面的数据进行计算距离,记做当前的最近邻点P_point和最小距离P_dist。

(2)进行回溯操作,这个操作是为了找到离Q点更加近的最近邻点。即判断没有被访问的分支里面的点是否还具有更近的点。

如果Q与其父结点下的未被访问过的分支之间的距离小于Dcur,则认为该分支中存在离P更近的数据,进入该结点,进行(1)步骤一样的查找过程,如果找到更近的数据点,则更新为当前的“最近邻点”Pcur,并更新Dcur

怎样判断未被访问的树的分支里面是否有离Q更近的点?

从几何空间上来看,就是判断以Q为中心center和以Dcur为半径Radius的超球面(Hypersphere)与树分支Branch代表的超矩形(Hyperrectangle)之间是否相交。

在实现中,在构造树的过程中,记录下每个子树所在的分割维度k和分割值m,(k, m),Q与子树的距离则为|Q(k) - m|。即这个距离和当前存的最小距离相比较,如果有比这个距离小,那就进去搜索,如果没有那就往上面回溯。

就是理解为超球体和超平面是否相交

这里写图片描述

以上就是Kd-tree的构造过程和基于Kd-Tree的最近邻查找过程。

下面用一个简单的例子来演示基于Kd-Tree的最近邻查找的过程。

数据点集合:(2,3), (4,7), (5,4), (9,6), (8,1), (7,2) 。

已建好的Kd-Tree:

这里写图片描述

假设我们的k-d tree就是上面通过样本集{(2,3), (5,4), (9,6), (4,7), (8,1), (7,2)}创建的。
我们来查找点(2.1,3.1),在(7,2)点测试到达(5,4),在(5,4)点测试到达(2,3),然后search_path中的结点为<(7,2), (5,4), (2,3)>,从search_path中取出(2,3)作为当前最佳结点nearest, dist为0.141;
然后回溯至(5,4),以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆,并不和超平面y=4相交,如下图,所以不必跳到结点(5,4)的右子空间去搜索,因为右子空间中不可能有更近样本点了。
于是在回溯至(7,2),同理,以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆并不和超平面x=7相交,所以也不用跳到结点(7,2)的右子空间去搜索。
至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2.1,3.1)的最近邻点,最近距离为0.141。

参考文章
参考文章

福利答谢大家!

感谢您阅读本篇文章,对此特别发放一个无门槛的现金红包,打开支付宝扫码领取!
这里写图片描述

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页