k k k近邻的实现—— k d kd kd树
k k k近邻的主要工作量是:找到到样本最近的 k k k个点。最简单的无疑是线性扫描,但数据量太大时非常耗时不可行,因此提出了 k d kd kd树。
k d kd kd树
构造 k d kd kd树
- 特点
- 一种对 k k k维空间中的实例点进行存储以便进行快速检索的树形结构
- 是二叉树
- 构造 k d kd kd树相当于不断用垂直于坐标轴的超平面将 k k k维空间划分
- 树的每个结点对应于一个 k k k维超矩阵区域
- 构造方法
输入维 k k k维空间数据集 T = { x 1 , x 2 , ⋯   , x N } T = \left\{ {{x_1},{x_2}, \cdots ,{x_N}} \right\} T={x1,x2,⋯,xN},其中 x i = ( x i ( 1 ) , x i ( 2 ) , ⋯   , x i ( k ) ) T {x_i} = {\left( {x_i^{\left( 1 \right)},x_i^{\left( 2 \right)}, \cdots ,x_i^{\left( k \right)}} \right)^T} xi=(xi(1),xi(2),⋯,xi(k))T- 构造根节点,根节点对应于包含 k k k维空间所有实例点的超矩阵区域
- 选择 x ( 1 ) {x^{\left( 1 \right)}} x(1)为坐标轴,以 T T T中所有实例的 x ( 1 ) {x^{\left( 1 \right)}} x(1)坐标的中位数为切分点,通过切分点并垂直于 x ( 1 ) {x^{\left( 1 \right)}} x(1)坐标轴的超平面将根节点的矩形区域分为两个子区域。
- 由根节点生成深度为 1 1 1的左、右子节点,左子节点对应坐标 x ( 1 ) {x^{\left( 1 \right)}} x(1)小于切分点的子区域,右子节点则是大于的。
- 重复:对深度为 j j j的结点,选择 x ( l ) {x^{\left( l \right)}} x(l)为切分的坐标轴, l = j (   m o d   k ) + 1 l = j\left( {\bmod k} \right) + 1 l=j(modk)+1。
- 直到两个区域都没有实例为止,形成 k d kd kd树的区域划分。
注:通过中位数分割得到的 k d kd kd树是平衡的,但平衡 k d kd kd树的搜索效率未必是最优的。
k d kd kd树的搜索方法
这里我们以最近邻为例,介绍搜索算法。
- 输入:构造好的 k d kd kd树,目标点 x x x
- 输出: x x x的最近邻
- 搜索
- 在
k
d
kd
kd树中找到包含目标点
x
x
x的叶节点。
从根节点出发,若目标点 x x x当前维德坐标小于切分点则向左移动,否则向右,直到叶子节点为止。 - 从此结点为“当前最近点”,向上回退,并执行
- 如果该节点该结点比当前最近点离目标更近,则以此结点作为“当前最近点”
- 当前最近点必然存在于该节点的一个子节点对应的区域,检查另一子节点的区域是否与(以目标点为球心,目标点到当前最近点距离为半径的超球体)相交,若相交则移动到另一个子节点,继续递归,不相交则向上回退。
- 回退到根节点结束。
k d kd kd树搜索的平均计算法复杂度为 O ( log N ) O\left( {\log N} \right) O(logN), N N N为训练实例数。
k d kd kd树更适合训练实例远大于空间维数时的 k k k近邻搜索。
- 在
k
d
kd
kd树中找到包含目标点
x
x
x的叶节点。
简单示例
如图所示的
k
d
kd
kd树,求S的最近邻(这里考虑欧式距离)。
其实将其转化为树的形式为:
整个搜索过程对比两个图,应该是:
- 从根节点开始,向下找到包含 S S S的结点 D D D,此时最近点也为 D D D
- 从 D D D向上搜索, B B B显然比 D D D离 S S S远
- 搜索 B B B的另一个子节点, F F F并不与此时C的圆相交,显然也不是
- 向上回退到 A A A, A A A不能成为最近点
- 搜索 A A A的另外一边,发现与圆相交,说明在另一边存在最近点
- 在另一边搜索会找到点 E E E,也就是真正的最近点