更新、更全的《机器学习》的更新网站,更有python、go、数据结构与算法、爬虫、人工智能教学等着你:https://www.cnblogs.com/nickchen121/p/11686958.html

kd树

k近邻算法中讲到它有一个较为致命的缺点就是每个实例到未来新数据之间都需要计算一次距离,如果实例数趋于无穷,那么计算量是很庞大的。但是我们要知道的是计算距离是为了找到距离目标点最近的\(k\)个实例,那么是不是有另外一种更好的方法,能够更快速找到这\(k\)个最近的实例呢?由此kd树被发明了出来。

kd树(k-dimensional tree)简单而言就是\(k\)个特征维度的二叉树,要注意这里的\(k\)值和k近邻算法中的\(k\)值不同。

一、kd树学习目标

  1. kd树的构造与搜索
  2. kd树流程
  3. kd树优缺点

二、kd树引入

# kd树引入图例
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc') # 测试点
plt.scatter(5, 6, marker='s', c='k', s=50)
plt.text(5, 5, s='《未知类型的电影》(5,6)', fontproperties=font, ha='center') # 生成动作片
np.random.seed(1)
action_x1 = np.random.randint(1, 7, 15)
action_x2 = np.random.randint(8, 17, 15)
plt.scatter(action_x1, action_x2, marker='o', s=30, c='r', label='动作片') # 生成爱情片
romance_x1 = np.random.randint(8, 17, 15)
romance_x2 = np.random.randint(1, 7, 15)
plt.scatter(romance_x1, romance_x2, marker='x', s=30, c='g', label='爱情片') # 测试文本标记
plt.text(4, 11, s='A', ha='center')
plt.text(5, 7, s='B', ha='center')
plt.text(1, 14, s='C', ha='center') plt.xlim(0, 18)
plt.ylim(0, 18)
plt.xlabel('接吻镜头(次)', fontproperties=font)
plt.ylabel('打斗场景(次)', fontproperties=font)
plt.title('kd树引入图例', fontproperties=font, fontsize=20)
plt.legend(prop=font)
plt.show()

这里将继续沿用k近邻算法的例子。

如上图所示,假设现有一部《未知类型的电影》,k近邻的思想是计算出数据集中每一部电影到《未知类型的电影》的距离,之后找到\(k\)个近邻对《未知类型的电影》分类。但是可以发现如电影C这样偏僻位置的电影明显是可以不用计算它们与《未知类型的电影》的距离的。

那现在假设有没有这么一种可能?我们不再计算每部电影到《未知类型的电影》的距离,而是直接得到《未知类型的电影》最近的\(k\)部电影?首先这种假设应该不可能,其次真的有,那还要其他机器学习算法作甚?那么再想想能不能通过某种方法找到离《未知类型的电影》较近的电影呢?我们假设有这种方法,并且这种方法被称作kd树,我们看看它是如何实现的。

  1. 比如kd树找到电影A即离《未知类型的电影》较近的某部电影,然后把距离大于电影A到《未知类型的电影》之间的电影排除掉,或者是继续找找看有没有哪部电影到《未知类型的电影》的距离更近?
  2. 如果可以找到电影A,则也可以kd树找到电影B
  3. 递归使用kd树搜索直到找到最近的某几部电影,停止搜索

上述整个过程其实就是kd树实现的一个过程,接下来将从理论层面抽象的讲解kd树。

三、kd树详解

3.1 构造kd树

kd树主要是不断地划分\(k\)维空间。

在一个二维空间平面中,kd树则是不断地使用垂直于\(x\)轴或\(y\)轴的直线把平面划分成一系列较小的矩形。在\(k\)维空间中,kd树则是不断地用垂直于坐标轴的超平面将\(k\)维空间切分成一系列的\(k\)维超矩形区域。

假设有\(m\)个实例\(n\)维特征的数据集

\[T=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}
\]

其中\(x_i\)是实例的特征向量即\(({x_i}^{(1)},{x_i}^{(2)},\cdots,{x_i}^{(n)})\),\(y_i\)是实例的类别,数据集有\(\{c_1,c_2,\cdots,c_j\}\)共\(j\)个类别。

  1. 构造k维空间的超矩形区域。如果是有两个特征\(x_1\)和\(x_2\)的二维空间,则是由\((x1_{min},x1_{max},x2_{min},x2_{max})\)构成的矩形区域
  2. 选择特征\(x^{(1)}\)为坐标轴,以\(T\)中所有实例的\(x^{(1)}\)坐标的中位数作为切分点,将根结点对应的超矩形区域切分成左右两个子区域。将\(x^{(1)}\)对应值小于切分点的实例划入左子区域;将\(x^{(1)}\)对应值大于切分点的实例划入右子区域;将切分点保留在根结点
  3. 重复2步骤,即选择\(x^{(l)}, \quad (l=1,2,\cdots,n)\)为切分的坐标轴,以该结点中还剩下的\(k\)个实例的\(x^{(l)}\)坐标的中位数作为切分点,将该结点对应的超矩形区域切分成左右两个子区域。将\(x^{(l)}\)对应值小于切分点的实例划入左子区域;将\(x^{(l)}\)对应值大于切分点的实例划入右子区域;将切分点保留在该结点
  4. 切割后的子区域没有实例时,即无法划分时结束

构造kd树时需要注意左子结点对应坐标\(x^{(l)}\)小与切分点的\(x^{(l)}\)坐标,右子结点对应坐标\(x^{(l)}\)大于切分点的\(x^{(l)}\)坐标。

3.1.1 示例

假设有一个二维空间\((x_1,x_2)\)的数据集

\[T = \{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\}
\]

首先\(x\)轴上的数据有\((2、4、5、7、8、9)\),中位数为\(7\)(注:如果以\(5\)划分也行,但是本文都是以较大的作为切分点,但是不能以\({\frac{5+7}{2}}\)划分,总之划分点符合kd树的构造要求即可),则以\(x=7\)划分一次矩形区域;左子区域\(y\)轴上的数据有\((3、4、7)\),中位数为\(4\),则以\(y=4\)划分一次数据,接着以\(x\)轴上的数据划分,而\(x\)轴数据只剩下\((2,4)\),直接以\(x=4\)划分左子区域的右子区域,以\(x=2\)划分左子区域的左子区域;右子区域\(y\)轴上数据有\((1、6)\),由于划分点需要大于左子结点的,因此右子结点为\((9,6)\),即以\(y=6\)划分左子区域,接着只剩下一个点\((8,1)\),又以\(x\)轴划分,则以\(x=8\)划分。

通过以上划分,即可得以下特征空间划分图。

# kd树的构造示特征空间划分图例
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc') plt.scatter(2, 3, c='r', s=30)
plt.text(2, 3.5, s='$(2,3)$', ha='center')
plt.scatter(5, 4, c='r', s=30)
plt.text(5, 4.5, s='$(5,4)$', ha='center')
plt.scatter(9, 6, c='r', s=30)
plt.text(9, 6.5, s='$(9,6)$', ha='center')
plt.scatter(4, 7, c='r', s=30)
plt.text(4, 7.5, s='$(4,7)$', ha='center')
plt.scatter(8, 1, c='r', s=30)
plt.text(8, 1.5, s='$(8,1)$', ha='center')
plt.scatter(7, 2, c='r', s=30)
plt.text(7, 2.5, s='$(7,2)$', ha='center') plt.hlines(4, 0, 7, linestyle='-', color='k')
plt.hlines(6, 7, 10, linestyle='-', color='k') plt.vlines(2, 0, 4, linestyle='-', color='k')
plt.vlines(4, 4, 10, linestyle='-', color='k')
plt.vlines(7, 0, 10, linestyle='-', color='k')
plt.vlines(8, 0, 6, linestyle='-', color='k') plt.xlim(0, 10)
plt.ylim(0, 10)
plt.title('kd树构造-示例图例1', fontproperties=font, fontsize=20)
plt.show()

同超矩形区域划分一样可以构造如下图所示的kd二叉树

首先以\(x\)轴作为划分轴,\(x\)轴上的数据有\((2、4、5、7、8、9)\),中位数为\(7\),根结点为\((7,2)\),左子结点\((2,3)、(4,7)、(5,4)\),右子结点\((8,1)、(9,6)\);之后以\(y\)轴作为划分轴,左子结点\(y\)轴有数据\((3、4、7)\),则左子结点的中位数为\(4\),则左子结点\((5,4)\),左子结点的左孙子结点\((2,3)\),左子结点的右孙子结点\((4,7)\);右子结点\(y\)轴有数据\((1、6)\),则有右子结点\((9,6)\),右子结点的左孙子结点\((8,1)\)。

# kd树的构造kd二叉树形成图例
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc') plt.scatter(20, 15, c='white', edgecolor='k', s=2000)
plt.text(20, 14.5, s='$(7,2)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(18.5, 13.5), xy=(13.5, 11.5),
arrowprops=dict(arrowstyle="-", color='b')) plt.scatter(12, 10, c='white', edgecolor='k', s=2000)
plt.text(12, 9.5, s='$(5,4)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(12, 8), xy=(9, 5),
arrowprops=dict(arrowstyle="-", color='b')) plt.scatter(8, 3, c='white', edgecolor='k', s=2000)
plt.text(8, 2.5, s='$(2,3)$', ha='center',
fontsize=15, color='k', fontweight='bold') plt.scatter(16, 3, c='white', edgecolor='k', s=2000)
plt.text(16, 2.5, s='$(4,7)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(12, 8), xy=(16, 5),
arrowprops=dict(arrowstyle="-", color='b')) plt.scatter(28, 10, c='white', edgecolor='k', s=2000)
plt.text(28, 9.5, s='$(9,6)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(21.5, 13.5), xy=(26, 11),
arrowprops=dict(arrowstyle="-", color='b')) plt.scatter(24, 3, c='white', edgecolor='k', s=2000)
plt.text(24, 2.5, s='$(8,1)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(27, 8), xy=(24.5, 5),
arrowprops=dict(arrowstyle="-", color='b')) plt.hlines(15, 24, 38, linestyle='--', color='r')
plt.text(39, 14.5, ha='center', color='r', s='X', fontsize=15)
plt.hlines(10, 32, 38, linestyle='--', color='b')
plt.text(39, 9.5, ha='center', color='b', s='Y', fontsize=15)
plt.hlines(3, 28, 38, linestyle='--', color='r')
plt.text(39, 2.5, ha='center', color='r', s='X', fontsize=15) plt.xlim(-1, 40)
plt.ylim(-1, 20)
plt.title('kd树构造-示例图例2', fontproperties=font, fontsize=20)
plt.show()

3.2 kd树搜索

从根结点出发,递归的向下访问kd树,如果目标点\(x\)的\(x^{(l)}\)维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点,直到子结点为叶子结点为止,并假设该叶子结点为目标点的当前最近点。

递归的从该叶子结点向上回退,对每个结点上的实例都进行如下操作:

  1. 如果该实例到目标点的距离更近,则以该实例点为当前最近点
  2. 检查当前最近点父结点的另一子结点对应的区域是否有更近的点,即另一子结点的超矩形区域是否与以目标点为球心、以目标点到当前最近点的半径形成的超球体是否相交
    1. 如果相交,则另一子结点对应的超矩形区域内存在离目标点更近的点,移到另一子结点继续递归搜索
    2. 如果不相交,则向上回退
  3. 当回退到根结点时结束,目标点的最近邻点为当前最近点

上述kd树搜索的过程可以看出,由于很多实例点所在的超矩形区域和超球体不相交,压根不需要计算距离,打打节省了计算时间。

但是值得注意的是kd树的平均计算复杂度为\(O(\log{N})\),当特征维数接近训练集实例数时,kd树的搜索效率则会迅速下降,几乎接近线性扫描。

3.2.1 示例

接下来将拿上一节构造的kd树举例kd树搜索的整个过程,假设目标点为\((2,4.5)\)。

  1. 搜索目标点对应的叶子结点

    1. 从根结点\((7,2)\)开始查找,由于\((7,2)\)是由\(x=7\)划分的,并且目标点的\(x\)值为\(2\),由于\(2<7\)则到左子\((5,4)\)结点
    2. 由于\((5,4)\)是由\(y=4\)分隔超平面的,并且目标点的\(y\)值为\(4.5\),由于\(4.5>4\)则到右子\((4,7)\)叶子结点
    3. 假设\((4,7)\)叶子结点为当前最近结点
    4. 目标掉搜索到叶子结点的路径为\((7,2)\rightarrow(5,4)\rightarrow(4,7)\)
  2. 搜索目标点对应的当前最近点
    1. 当前最近结点\((4,7)\)到目标点的距离为\(3.2\),而当前最近结点的父节点\((5,4)\)到目标点的距离为\(3.0\),则当前最近结点更新为\((5,4)\)
    2. 以下图蓝色五角星目标点\((2,4.5)\)为圆心,以当前最近结点\((5,4)\)到目标点的距离\(3.0\)为半径做一个超球体,即下图的红色圆圈
    3. 可以发现红色圆圈和\(y=4\)超平面相交,因此进入\((5,4)\)的左子结点查找,即找到\((2,3)\)
    4. 计算\((2,3)\)到目标点的距离为\(1.5\),则当前最近结点更新为\((2,3)\)
    5. 以下图蓝色五角星目标点\((2,4.5)\)为圆心,以当前最近结点\((2,3)\)到目标点的距离\(1.5\)为半径做一个超球体,即下图的绿色圆圈
    6. 由于结点\((2,3)\)为叶子结点,开始沿父结点回溯,父结点\((5,4)\)已经考虑过,继续往上回溯到\((7,2)\)
    7. 绿色圆圈和\(x=7\)没有相交,又由于\((7,2)\)为根节点,搜索结束
    8. 通过上述搜索目标点最近点为\((2,3)\),最近距离为\(1.5\)

值得注意的是上述都是以最近邻展开讨论kd树的,如果想得到\(k\)个近邻,只需要最后保留\(k\)个最近邻的结点上的实例点即可,之后依据多数表决法即可获得目标点的类别;如果是回归问题,取\(k\)个近邻点的标记值的平均数或中位数即可。

# kd树的构造示特征空间划分图例
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc') plt.scatter(2, 3, c='r', s=30)
plt.text(2, 3.5, s='$(2,3)$', ha='center')
plt.scatter(5, 4, c='r', s=30)
plt.text(5, 4.5, s='$(5,4)$', ha='center')
plt.scatter(9, 6, c='r', s=30)
plt.text(9, 6.5, s='$(9,6)$', ha='center')
plt.scatter(4, 7, c='r', s=30)
plt.text(4, 7.5, s='$(4,7)$', ha='center')
plt.scatter(8, 1, c='r', s=30)
plt.text(8, 1.5, s='$(8,1)$', ha='center')
plt.scatter(7, 2, c='r', s=30)
plt.text(7, 2.5, s='$(7,2)$', ha='center') plt.hlines(4, 0, 7, linestyle='-', color='k')
plt.hlines(6, 7, 10, linestyle='-', color='k') plt.vlines(2, 0, 4, linestyle='-', color='k')
plt.vlines(4, 4, 10, linestyle='-', color='k')
plt.vlines(7, 0, 10, linestyle='-', color='k')
plt.vlines(8, 0, 6, linestyle='-', color='k') plt.scatter(2, 4.5, s=30410, c='white', edgecolor='r')
plt.scatter(2, 4.5, s=15000, c='white', edgecolor='g')
plt.scatter(2, 4.5, s=50, c='b', marker='*')
plt.text(2, 5, s='目标点$(2,4.5)$', ha='center',fontproperties=font)
plt.scatter(2, 3, c='r', s=30)
plt.scatter(4, 7, c='r', s=30) plt.xlim(0, 10)
plt.ylim(0, 10)
plt.title('kd树搜索-示例图例', fontproperties=font, fontsize=20)
plt.show()

四、kd树流程

4.1 输入

有\(m\)个实例\(n\)维特征的数据集

\[T=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}
\]

其中\(x_i\)是实例的特征向量即\(({x_i}^{(1)},{x_i}^{(2)},\cdots,{x_i}^{(n)})\)。

4.2 输出

距离目标点最近的\(k\)个实例及\(k\)个实例到目标点的距离。

4.3 流程

  1. 构造kd树
  2. 搜索离目标点最近的\(k\)个实例点
  3. 分类问题依据多数表决法即可获得目标点的类别;如果是回归问题,取

    02-17 kd树的更多相关文章

    1. 利用KD树进行异常检测

      软件安全课程的一次实验,整理之后发出来共享. 什么是KD树 要说KD树,我们得先说一下什么是KNN算法. KNN是k-NearestNeighbor的简称,原理很简单:当你有一堆已经标注好的数据时,你 ...

    2. 2016 ICPC青岛站---k题 Finding Hotels(K-D树)

      题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=5992 Problem Description There are N hotels all over ...

    3. kd树和knn算法的c语言实现

      基于kd树的knn的实现原理可以参考文末的链接,都是一些好文章. 这里参考了别人的代码.用c语言写的包括kd树的构建与查找k近邻的程序. code: #include<stdio.h> # ...

    4. PCL点云库:Kd树

      Kd树按空间划分生成叶子节点,各个叶子节点里存放点数据,其可以按半径搜索或邻区搜索.PCL中的Kd tree的基础数据结构使用了FLANN以便可以快速的进行邻区搜索.FLANN is a librar ...

    5. KNN算法与Kd树

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

    6. k临近法的实现:kd树

      # coding:utf-8 import numpy as np import matplotlib.pyplot as plt T = [[2, 3], [5, 4], [9, 6], [4, 7 ...

    7. 从K近邻算法谈到KD树、SIFT+BBF算法

      转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...

    8. bzoj 3489: A simple rmq problem k-d树思想大暴力

      3489: A simple rmq problem Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 551  Solved: 170[Submit][ ...

    9. k近邻法的C++实现:kd树

      1.k近邻算法的思想 给定一个训练集,对于新的输入实例,在训练集中找到与该实例最近的k个实例,这k个实例中的多数属于某个类,就把该输入实例分为这个类. 因为要找到最近的k个实例,所以计算输入实例与训练 ...

    随机推荐

    1. centos 6.8 下没有yum命令解决方法(报错: -bash: yum: command not found)

      1.去 http://mirrors.163.com/centos/6/os/x86_64/Packages/ 地址下载4个rpm安装包:python-iniparse-0.3.1-2.1.el6.n ...

    2. http响应

      1.http响应 2.响应行常见状态码 200 :请求成功. 302 :请求重定向. 当访问网址A时,由于网址A服务器端的拦截器或者其他后端代码处理的原因,会被重定向到网址B. 304 :请求资源没有 ...

    3. 同步FIFO design and IP level verification

      一.前言 应聘IC前端相关岗位时,FIFO是最常考也是最基本的题目.FIFO经常用于数据缓存.位宽转换.异步时钟域处理.随着芯片规模的快速增长,灵活的system verilog成为设计/验证人员的基 ...

    4. Pytorch的基础数据类型

      引言 本篇介绍Pytorch的基础数据类型,判断方式以及常用向量 基础数据类型 torch.Tensor是一种包含单一数据类型元素的多维矩阵. 目前在1.2版本中有9种类型. 同python相比,py ...

    5. thymeleaf自定义标签方言处理

      项目背景:springboot+thymeleaf thymeleaf两种方式处理自定义标签:AbstractAttributeTagProcessor 和 AbstractElementTagPro ...

    6. thinkphp将上传的临时文件移动到指定目录

      thinkphp将上传的临时文件移动到指定目录 新建common.php文件 <?phpuse think\facade\Env; /** 移动上传的临时文件 * * @img_dir stri ...

    7. 【linux】【jenkins】自动化运维四 整合gitlab、docker发布java项目

      jenkins发布java项目 过程参考发布vue项目.https://www.cnblogs.com/jxd283465/p/11543431.html 大同小异. vue建立的是Freestyle ...

    8. 转:int整数除以int整数一定得到的是int整数(易忽视)

      版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u014053368/article/de ...

    9. Hive之行转列与列转行

      行转列 原始数据: 需求: 把星座和血型一样的人归类到一起.结果如下: 射手座,A 大海|凤姐 白羊座,A 孙悟空|猪八戒 白羊座,B 宋宋 实现: vi person_info.txt 孙悟空 白羊 ...

    10. 导出 mysql 数据到 redis

      决定你要导入到 redis 的数据类型 假设我的表 t_user 的结构为 列名 注释 类型 name 名称 varchar idcard 身份证号 varchar phone 手机号 varchar ...