作者 周知航

审核 张翔

ARGO曾经推出了ARGO:KNN算法介绍​zhuanlan.zhihu.com

介绍了KNN算法的原理,其核心是寻找待测样本在训练样本中的k个近邻,从而从k个近邻中获取数量最多的类别作为待测样本的类别。

KNN算法是机器学习中最简单的算法之一,但是工程实现上,如果训练样本过大,则传统的遍历全样本寻找k近邻的方式将导致性能的急剧下降。 因此,为了优化效率,不同的训练数据存储结构被纳入到实现方式之中。在sikit-learn中的KNN算法参数也提供了'kd_tree'之类的算法选择项。本文将从如下几个方面阐述kd树:

kd树的概念

从例子理解kd树的构建及查找

ball tree 和其他树类型简介

一 kd树的概念

kd(k-dimensional)树的概念自1975年提出,试图解决的是在k维空间为数据集建立索引的问题。依上文所述,已知样本空间如何快速查询得到其近邻?唯有以空间换时间,建立索引便是计算机世界的解决之道。但是索引建立的方式各有不同,kd树只是是其中一种。它的思想如同分治法,即:利用已有数据对k维空间进行切分。

我们先回顾一下二叉查找树,这也是在一维空间中的kd树的情形:

由图可知,二叉树在时间复杂度上是O(logN),远远优于全遍历算法。对于该树,可以在空间上理解:树的每个节点把对应父节点切成的空间再切分,从而形成各个不同的子空间。查找某点的所在位置时,就变成了查找点所在子空间。上图仅仅是一维,如果换到二维,或者是更高维度上,这棵树该怎么建立? 又该怎么理解所切成的子空间呢?

我们可以用一个在网络上较为普遍的例子来演示一下二维的kd树构建及查找方式。

二 从例子理解kd树的构建及查找

假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}。

依据上文,对于一维空间的切分的最终结果实际上切出的是线段,同理,二维空间中,对于该6个点集最后的切分结果的空间应该是子平面集合。这里,kd树最终目标就是构建一棵树,能够将二维平面切分得到的最终的子平面集,如下图所示:

切分后的空间示意图

找到切分这些平面的直线便是建立kd树的重点。我们将先介绍这种构建方式,然后再介绍基于kd树的最近邻查找的查询方法。

构建

将二维的平面想像成一块方型蛋糕,kd树构建就是面点师要将蛋糕切成上面示意图的模样。先将平面上的六个点在蛋糕上做好标记。面点师举起刀,他该如何下刀呢?这就涉及到kd树在切分时要注意两个要点 ,切分域和切分点的选择。

只要明确了这两个要点的实施规则,切分蛋糕就变得十分容易。因为对于子平面的切分,依然可以按照这些规则递归进行。

a 切分域的选择

切分(split)域,即切分的维度方向,在这个例子中,也就是在二维平面上,面点师必须决定,我这第一刀是平行于x轴横着切还是平行于y轴竖着切?作为面点师,最害怕的情况就是切出的子平面分布不均匀,存在大量过大或者过小的子平面。所以,借助统计学的手段,kd树的每轮中本次的切分域的选择将计算待切分空间内的点此轮未被切分维度上的方差,找方差最大的维度作为此次的切分域。 方差较大,表明在该维度上的点的分散度较高,按该维度切分分辨率比较高。

在这里,一轮是指的必须依照k个维度,对每个维度对平面进行一次切分。比如面点师如果第一刀选择了切分X轴,则第二刀必须选择Y轴。如果多余两个维度,则接下来依然按照剩下维度的方差值进行计算来选择切分域。直至遍历所有维度,一轮结束。

计算第一次切分时两个维度上的方差,得到

在X轴的方向方差较大,所以面点师选择首先切分X轴(也就是在二维平面中沿着Y轴方向)。

b 切分点的选择

切分点的选择策略比较简单,是将带切分平面上的所有数据点按照切分域的维度进行大小排序,选择正中间的点作为切分点。如果正中间点是偶数个数,则任取中间左边或者右边的值作为切分点。所以,对上述X轴的点位进行排序,(5 , 4),(7 , 2)是X轴方向的中间点。这里选择(7, 2)作为第一次的切分点。

根据已经选择的切分点和切分域,面点师终于畅快的下了第一刀,如下所示。

第一次切分后的结果

依照切分点进行切分后,当前切分点将从子平面的数据集中排除。

c 本轮第二次切分及第二轮切分

本轮并未就此结束,在X轴方向进行切分后将继续对Y方向进行切分。由于面点师第一刀把方形平面切成了两个子平面,对Y轴的切分自然需要对两个子平面各切一刀。这里再次强调,对于二维情况,Y是最后本轮最后维度,不需要再计算方差得到切分域,但是对于多维情况,此次计算是排除第一次切分域的维度后剩下维度在子平面中计算分列域下选择的方差最大的维度。

选择对Y方向上切分的两个子平面的切分点,分别选择(5,4)(也可以是(2,3))(9, 6)(也可以是8,1)。按照切分点和切分域下刀,如下图所示的两条蓝色线条,则是面点师的下刀痕迹。

已切好的平面

在第二轮开始后,由于右上角的平面数据点数为0,不用再切分。而剩下的平面中数据点数都为1。因为此时方差相同,计算方差选择切分域没有意义,使用对X轴进行切分,如上上图的红线所示。

以上只是面点师作为kd树构建时的指导思想,在计算机中该算法究竟该如何表示?接下来看看完成对上述分割时建立的kd树的数据结构。

d 树形数据结构表示

如图所示,这是对应切分方法应该建立的树。第一个根节点(7, 2)是第一次的切分点,切分的是X方向。也就是树的左子树的X轴方向都比7小,右子树X方向点都比7大。再看下一级(5,4),(9,6)点切分的是Y轴方向,也就是对于(5,4)为根的子树,左子树Y轴方向都比4小,右子树都比4大。以此类推。

值得注意的是,在kd树的实现过程中,切分域的选择并不一定采用方差选择方式,而可能只是简单的以 d mod n 维度的方式,来决定该层树的切分域(d代表树节点的深度,n代表维度数量,求模来确定切分域)。这样实现简单,效果也一般不会太差。

总结一下kd树的算法流程如下

在构建完kd树之后,就是kd树的应用了。如何使用kd树找到目标点的最近邻,kd树是如何帮助实现效率的提升的呢,这时候就得观察一下kd树是如何搜索最近邻的了。

查找

回到面点师切好的糕点平面图,用目标数据在kd树中寻找最近邻时,最核心的两个部分是:

1 寻找近似点-寻找最近邻的叶子节点作为目标数据的近似最近点。

2 回溯-以目标数据和最近邻的近似点的距离沿树根部进行回溯和迭代。

以两个例子来描述这一过程。例子来源:

例子1 搜索点(2.1,3.1)

计算近似最近点--很显然,通过查找寻找kd树,很容易定位到该点将落在点(2,3)所在的叶子节点上。 此时,将(2,3)作为该点的近似最近点。计算出到该近似点的距离为0.141。

为了理解方便,将(2,3)到(2.1,3.1)的距离为直径,以(2,3)为圆心作圆。

回溯--在根据目标节点寻找近似最近点时,其路径是(7,2)->(5,4)->(2,3)。 先计算该点与近似最近点的父节点(5,4)的距离,看看是否该距离是否小于0.141,(5,4)点的距离大于0.141,说明被(5,4) 切分的另外一个子平面与(2,3)点为圆心的圆没有交集。但不能排除跟(7,2)所切开的右子平面没有交集,所以再往上回溯(7,2)点,直至确认跟右子平面没有交集,回溯结束,确认最近邻点位(2,3)。

例子2 搜索点(2, 4.5)

重复上述搜索过程。

计算近似最近点--其落在叶子节点(4,7)点上。近似距离为3.20。

回溯--对于该点的回溯过程就会比刚才复杂一些。其父节点(5,4)与目标点的距离为3.04,小于3.20。 这就意味着(4,7)点作为最近近似点的假设不成立。以(5,4)作为最近近似点。以3.04做圆,圆是跟(5,4)所切分的上下两个平面相交的,所以需要检查(5,4)的另外一个子树的叶子节点(2,3)是否可以作为近似最近点。发现(2,3)的距离为1.5小于3.04,更新(2,3)为近似最近点。最后回溯至(7,2),确认跟(7,2)切分的右子平面无关。 回溯结束,(2,3)为其最近点。

以上两例说明了为什么kd树能够减少查询次数。但是,例子2中,目标数据点的位置有可能也会要求查询另外一颗子树,加多查询次数,降低查询性能。在维度较高时,这种性能降低十分明显,所以一般kd树要求数据维度在20维以内。通常原则是,如果维度是k, 数据点数是N, 需要满足N >> 2k。

为了应对这一问题,不但出现了对于kd树本身算法的改进如BBF算法,也出现了ball tree一类的针对解决性能下降的适应高维的查找型数据结构。

三 ball tree 和其他树类型介绍

在kd tree 中,我们看到一个导致性能下降的最核心因素是因为kd树的平面是一个个的方形,求最近邻时使用的是圆形。方形平面跟圆形相交的可能性是极高的,如果方形的交汇点多的话,圆形和几个平面相交的可能性就变得更大。这样,凡是相交的平面,都需要进行检查,大大的降低运行效率。

为了解决这一个问题,ball tree抛弃了kd树画的方形,而建立球形,去掉棱角。简而言之,就是使用超球面而不是超矩形划分区域。

它的构造方式是:所有的数据先构成一个最大的球体。从球中选择一个离球的中心最远的点,然后选择第二个点离第一个点最远。将圆中所有离这两个点最近的观测点都赋给这两个簇的中心,而后计算每一个簇的中心点和包含所有其所属观测点的最小半径。不断递归得到最终结果如上图C所示。在上图B中所示数据结构中A,B,C , D, E非叶子节点存储了球的中心点和半径。

它的查找方式是一样先寻找叶子节点,并计算叶子节点和目标点的距离,此值确定目标最小距离上限。根据构造方式可知,可以根据目标点计算出其叶子节点。此时,唯一需要验证的是其兄弟节点的距离,如果小于上限就更新最近目标点。

也能够看出ball tree的构建比kd tree复杂的多,也耗时的多。但是为了节省查询时间这种耗时也是值得的。

对于高维度索引树,还有VP树和MVP树,这里就不再多做解释了。

四 总结

本文重点讲述了kd树的构建和查询,也同时介绍了kd树的改进数据结构ball树。kd树是KNN算法的重要基础,KNN是最简单的机器学习算法之一,而kd树的复杂估计也是读者没有想到的,所谓细节之处皆是魔鬼,此话诚然。

参考文献

好文推荐

kd树就给大家介绍到这了,欢迎大家多多提建议并关注我们的公众号。

微信文章列表索引:往期文章​mp.weixin.qq.com

kd树的根节点_KD树简介相关推荐

  1. kd树的根节点_kd树总结

    Kd-Tree,即K-dimensional tree,是一种高维索引树形数据结构,常用于在大规模的高维数据空间进行最近邻查找(Nearest Neighbor)和近似最近邻查找(Approximat ...

  2. 数据结构-树:根节点、子节点、叶子节点是什么?

    前言:这个属于数据结构:树. 下面给个例子图解释(根节点.子节点.叶子节点). 上图数字 1.3.7是叶子节点:(因为他们下面没有分叉出子节点,所以称为:叶子节点)[度为0] 数字2.8是子节点: ( ...

  3. 树的根节点个数(0或者1)

    树是结点的集合,它的根结点数目是? 答:0个(空树)或1个 其他参考: 来源1 我做到一个题目是: (87)树是结点的集合,它的根结点数目是© A.有且只有1 B.1或多于1 C.0或1 D.至少2 ...

  4. 我眼中的Linux设备树(五 根节点)

    五 根节点 一个最简单的设备树必须包含根节点,cpus节点,memory节点.根节点的名字及全路径都是"/",至少需要包含model和compatible两个属性.model属性我 ...

  5. java 获取树的子节点,工作总结---获取树的子节点及树的复制

    一:树节点的结构 class KprDimension{ private String id; //树的当前节点id private String name; //树节点名称 private Stri ...

  6. java 递归获取树的父节点_Java 树父节点递归获取树子节点

    package nodes4j; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * ...

  7. b树与b+树的区别_一篇文章理清B树、B-树、B+树、B*树索引之间的区别与联系

    概述 相信对于B树.B-树.B+树.B*树索引这几个大家都很容易混淆,下面单独对这几个索引做下分类总结. B树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结 ...

  8. 2020-11-05 关于前端‘古董‘dojo的树结构如何默认展开根节点

    在工作中对公司的老平台进行维护中遇到了统一平台选择区域树样式统一的问题,自己在网上搜索了好多但也没有找到合适的方法,直到找公司前辈请教后解决了这个问题.这里就把这个比较久远的前端问题和大家分享一下: ...

  9. HDU - 1890 Robotic Sort(Splay-区间翻转+删除根节点)

    题目链接:点击查看 题目大意:给出一个序列,初始时是乱序的,要求根据规则排序,规则是,遍历每一个点 i ,使得区间 [ i , a[ i ] ] 内的数翻转,a[ i ] 表示的是数列中第 i 大的数 ...

  10. 从Linus Torvalds一封发飙的电邮开始谈设备树究竟是棵什么树?

    [导读] 新版的U-Boot以及内核都引入了设备树,那么这究竟是棵什么样的树呢?长啥样?有啥用?为啥弄个这样的树?本文基于对设备树标准的理解,来学习整理一下相关的要点,供大家参考. Linux为啥要设 ...

最新文章

  1. MySQL数据库查询中的特殊命令
  2. Openstack贡献者须知 2 — 社区工作运作 代码贡献流程
  3. session_onend,你到底什么时候才能激发?
  4. java是什么_Java是什么?
  5. C#中要使一个类支持FOREACH遍历,实现过程怎样? [转]
  6. npm安装报错Error: EPERM: operation not permitted解决方案
  7. 拖拽的方式使用qbuttongroup_【无机纳米材料科研制图——Photoshop 0402】PS使用选框工具修改图片/图层...
  8. 重读读书笔记的重要性
  9. 前端-requests-flask对应关系 form
  10. 【普通の随笔】3.26
  11. test.php变成夏总,test.php
  12. 为什么我们要使用图嵌入?
  13. 我的世界java8u101_jre8下载 Java SE Runtime Environment 8 9.0u101 多语言安装版 下载-脚本之家...
  14. 【原型设计】第一节:Axure RP9 下载安装、汉化和激活使用的方法教程
  15. 雅虎团队经验:网站页面性能优化的 34条
  16. 计算机execl必背知识点,【分享】Excel必备基础知识(1)
  17. 20190818 思维导图简易教程
  18. win10家庭中文版计算机,Win10家庭版和专业版有哪些区别?教你区分Win10家庭版和专业版...
  19. php 模拟登陆微信,微信公众平台模拟登陆有关问题
  20. PhalAPI学习笔记 ——— 第二章接口服务请求

热门文章

  1. FPGA零基础学习:IP CORE 之 ROM设计
  2. 计算机学报论文字数要求,常见EI学报综述类文章分析
  3. Msm8960(APQ8064)平台的MSM-AOSP-kitkat编译适配(1):基础知识
  4. 一块带给无数人年少欢乐的CPU
  5. 获取TrustedInstaller权限
  6. 如鹏java学习进程 模拟小球弹跳
  7. texlive 2022安装与使用
  8. android 电视安装apk文件损坏,安装电视软件时提示解析包出现问题怎么破?
  9. c语言 二维数组指针
  10. xml转json(dom4j + fastjson)