K临近算法之KD树构造

在使用k近邻法进行分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决的方式进行预测。由于k近邻模型的特征空间一般是n维实数向量,所以距离的计算通常采用的是欧式距离。关键的是k值的选取,如果k值太小就意味着整体模型变得复杂,容易发生过拟合,即如果邻近的实例点恰巧是噪声,预测就会出错,极端的情况是k=1,称为最近邻算法,对于待预测点x,与x最近的点决定了x的类别。k值得增大意味着整体的模型变得简单,极端的情况是k=N,那么无论输入实例是什么,都简单地预测它属于训练集中最多的类,这样的模型过于简单。经验是,k值一般去一个比较小的值,通常采取交叉验证的方法来选取最优的k值。

实现k近邻法时,主要考虑的问题是如何对训练数据进行快速k近邻搜索,这点在特征空间的维数大以及训练数据容量大时尤其重要。k近邻法的最简单实现是线性扫描,这时要计算输入实例与每一个训练实例的距离,当训练集很大时,计算非常耗时,这种方法是不可行的。为了提高k近邻搜索的效率,可以考虑使用特殊的结构存储训练数据,以减少计算距离的次数。具体方法有很多,这里介绍kd树方法。

1、实例

先以一个简单直观的实例来介绍k-d树算法。假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点位于二维空间内(如图2中黑点所示)。k-d树算法就是要确定图2中这些分割空间的分割线(多维空间即为分割平面,一般为超平面)。下面就要通过一步步展示k-d树是如何确定这些分割线的。如下图:

k-d树算法可以分为两大部分,一部分是有关k-d树本身这种数据结构建立的算法,另一部分是在建立的k-d树上如何进行最邻近查找的算法。

2、构造kd树

kd树是一种对k维空间中的实例点进行存储以便对其进行快速搜索的树形数据结构。kd树是二叉树,表示对k维空间的一个划分。构造kd树相当于不断地用垂直于坐标轴的超平面将k维空间进行切分,构成一系列的k维超矩形区域。kd树的每一个节点对应于一个k维超矩形区域。k-d树是一个二叉树,每个节点表示一个空间范围。下表给出的是k-d树每个节点中主要包含的数据结构。

从上面对k-d树节点的数据类型的描述可以看出构建k-d树是一个逐级展开的递归过程。下面给出的是构建k-d树的伪码。

 算法:构建k-d树(createKDTree)
输入:数据点集Data-set和其所在的空间Range
输出:Kd,类型为k-d tree
1.If Data-set为空,则返回空的k-d tree
2.调用节点生成程序:(1)确定split域:对于所有描述子数据(特征矢量),统计它们在每个维上的数据方差。假设每条数据记录为64维,可计算64个方差。挑选出最大值,对应的维就是split域的值。数据方差大表明沿该坐标轴方向上的数据分散得比较开,在这个方向上进行数据分割有较好的分辨率;(2)确定Node-data域:数据点集Data-set按其第split域的值排序。位于正中间的那个数据点被选为Node-data。此时新的Data-set' = Data-set \ Node-data(除去其中Node-data这一点)。
3.dataleft = {d属于Data-set' && d[split] ≤ Node-data[split]}Left_Range = {Range && dataleft}dataright = {d属于Data-set' && d[split] > Node-data[split]}Right_Range = {Range && dataright}
4.left = 由(dataleft,Left_Range)建立的k-d tree,即递归调用createKDTree(dataleft,Left_Range)。并设置    left的parent域为Kd;right = 由(dataright,Right_Range)建立的k-d tree,即调用createKDTree(dataleft,Left_Range)。并设置        right的parent域为Kd。

以上述举的实例来看,过程如下:

    由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。(1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向;(2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7;(3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如下图所示。x <= 7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。


如算法所述,k-d树的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是左右子空间的’根’节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,如下图所示。最后生成的k-d树如下图所示。

3、搜索KD树

在k-d树中进行数据的查找也是特征匹配的重要环节,其目的是检索在k-d树中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。

星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行’回溯’操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中先从(7,2)点开始进行二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为小于(7,2)和(5,4),大于(2,3),首先以(2,3)作为当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如下图所示。发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。

再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。

一个复杂点了例子如查找点为(2,4.5)。同样先进行二叉查找,先从(7,2)查找到(5,4)节点,在进行查找时是由y = 4为分割超平面的,由于查找点为y值为4.5,因此进入右子空间查找到(4,7),形成搜索路径<(7,2),(5,4),(4,7)>,取(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。然后回溯到(5,4),计算其与查找点之间的距离为3.041。以(2,4.5)为圆心,以3.041为半径作圆,如下图左所示。可见该圆和y = 4超平面交割,所以需要进入(5,4)左子空间进行查找。此时需将(2,3)节点加入搜索路径中得<(7,2),(2,3)>。回溯至(2,3)叶子节点,(2,3)距离(2,4.5)比(5,4)要近,所以最近邻点更新为(2,3),最近距离更新为1.5。回溯至(7,2),以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如下图右所示。至此,搜索路径回溯完。返回最近邻点(2,3),最近距离1.5。

查询伪代码如下:

算法:k-d树最邻近查找
输入:Kd,    //k-d tree类型target  //查询数据点
输出:nearest, //最邻近数据点dist      //最邻近数据点和查询点间的距离
1. If Kd为NULL,则设dist为infinite并返回
2. //进行二叉查找,生成搜索路径
Kd_point = &Kd;                   //Kd-point中保存k-d tree根节点地址
nearest = Kd_point -> Node-data;  //初始化最近邻点
while(Kd_point)push(Kd_point)到search_path中; //search_path是一个堆栈结构,存储着搜索路径节点指针
/*** If Dist(nearest,target) > Dist(Kd_point -> Node-data,target)nearest  = Kd_point -> Node-data;    //更新最近邻点Max_dist = Dist(Kd_point,target);  //更新最近邻点与查询点间的距离  ***/s = Kd_point -> split;                       //确定待分割的方向If target[s] <= Kd_point -> Node-data[s]     //进行二叉查找Kd_point = Kd_point -> left;elseKd_point = Kd_point ->right;
nearest = search_path中最后一个叶子节点; //注意:二叉搜索时不比计算选择搜索路径中的最邻近点,这部分已被注释
Max_dist = Dist(nearest,target);    //直接取最后叶子节点作为回溯前的初始最近邻点3. //回溯查找
while(search_path != NULL)back_point = 从search_path取出一个节点指针;   //从search_path堆栈弹栈s = back_point -> split;                   //确定分割方向If Dist(target[s],back_point -> Node-data[s]) < Max_dist   //判断还需进入的子空间If target[s] <= back_point -> Node-data[s]Kd_point = back_point -> right;  //如果target位于左子空间,就应进入右子空间elseKd_point = back_point -> left;    //如果target位于右子空间,就应进入左子空间将Kd_point压入search_path堆栈;If Dist(nearest,target) > Dist(Kd_Point -> Node-data,target)nearest  = Kd_point -> Node-data;                 //更新最近邻点Min_dist = Dist(Kd_point -> Node-data,target);  //更新最近邻点与查询点间的距离

当维数较大时,直接利用k-d树快速检索的性能急剧下降。假设数据集的维数为D,一般来说要求数据的规模N满足条件:N远大于2的D次方,才能达到高效的搜索。

原文链接:https://blog.csdn.net/qll125596718/article/details/8426458

knn之KD树深度构建原理相关推荐

  1. 用python手写KNN算法+kd树及其BBF优化(原理与实现)(下篇)

    用python手写KNN算法+kd树及其BBF优化(原理与实现)(下篇) 接上一篇用python手写KNN算法+kd树及其BBF优化(原理与实现)(上篇) 我们使用training2和test2两个数 ...

  2. 【KNN以及KD树】

    文章目录 前言 一.学习类型 二.机器学习算法使用步骤 1.收集数据 2准备输入数据 3分析输入数据 4训练算法 5测试算法 6使用算法 三.KNN 1KNN简介 2KD树简介 总结 前言 提示:这里 ...

  3. python机器学习案例系列教程——K最近邻算法(KNN)、kd树

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 K最近邻简介 K最近邻属于一种估值或分类算法,他的解释很容易. 我们假设一个人的优秀成为设定为1.2.3.4.5.6.7.8.9.10 ...

  4. 浅谈KNN算法原理及python程序简单实现、KD树、球树

    最近比较空闲,打算利用这一段时间理一下机器学习的一些常见的算法.第一个是KNN算法: KNN 1.原理: KNN,K-NearestNeighbor---K最近邻 K最近邻,就是K个最近的邻居的意思, ...

  5. KNN算法与Kd树(转载+代码详细解释)

    最近邻法和k-近邻法 下面图片中只有三种豆,有三个豆是未知的种类,如何判定他们的种类? 提供一种思路,即:未知的豆离哪种豆最近就认为未知豆和该豆是同一种类.由此,我们引出最近邻算法的定义:为了判定未知 ...

  6. 【算法】FLANN中kd树构建和查询的简明分析

    flann源码参考:flann: https://github.com/flann-lib/flannsudo apt install libflann-dev 目录 K-最近邻搜索(K-Neares ...

  7. 【特征匹配】SIFT原理之KD树+BBF算法解析

    转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/47606159 继上一篇中已经介绍了SIFT原理与C源码剖析,最后得到了一系列特 ...

  8. KNN—Kd树—BBF优化

    一.KD树基本解释 1.1.基础概念 为了优化KNN的计算,使用KD树解决通过距离函数在高维矢量之间进行相似性检索的问题,快速而准确地找到查询点的近邻. 索引结构中相似性查询: 范围查询:给定查询点和 ...

  9. KD树——k=1时就是BST,里面的数学原理还是有不明白的地方,为啥方差划分?...

    Kd-Tree,即K-dimensional tree,是一棵二叉树,树中存储的是一些K维数据.在一个K维数据集合上构建一棵Kd-Tree代表了对该K维数据集合构成的K维空间的一个划分,即树中的每个结 ...

最新文章

  1. lumen mysql 事务_简单几部搞定laravel/lumen跨库操作
  2. (转载)IP,主机名,域名
  3. open-capacity-platform 项目启动
  4. python3安装步骤
  5. mysql中如何去除重复数据_MySQL中如何删除重复数据只保留一条
  6. 2018蓝桥杯省赛---java---A---7(三体攻击)
  7. Springboot2 Quartz实现JAVA定时任务的动态配置
  8. 轨迹相似性度量方法总结
  9. Shell编程之变量
  10. recycleview 自动循环滚动_划重点 | 不能不知的滚动轴承知识—轴承分类(续)
  11. 黑暗森林:知识图谱的前世今生
  12. 关于FLEX中找不到目标对象或通道未定义错误
  13. Thymeleaf模板引擎详细介绍
  14. windows11(win10大部分通用)系统C盘清理 | 深度优化
  15. busybox内置tftpd服务器配置
  16. 电子商务类网站CDN加速方案
  17. 共享充电宝的终局:金钱游戏
  18. es6中的静态属性和方法
  19. 阿里云公共DNS安全传输服务介绍(DoH/DoT)
  20. 浅谈偏光镜使用与选购[机器视觉系列]

热门文章

  1. 【飞行器】基于matlab四旋翼飞行器三维动态仿真【含Matlab源码 269期】
  2. 电力系统中的定值区是什么意思
  3. uniapp引入uview文件查找失败:‘uview-ui‘ at main.js:
  4. MXC极域,被严重低估的未来物联网
  5. vue 倒计时 插件_Vue学习笔记-倒计时插件
  6. hive获取当前时间(类似sql的now()函数)
  7. 使用addr2line分析crash问题
  8. Qt编写GIF录屏工具(开源)
  9. Word论文中关于章、节、图、表、公式自动编号及引用
  10. linux复制jar文件,linux如何将界面上的一个JAR文件拷贝到ROOT下啊