1 引入原因
- K近邻算法需要在整个数据集中搜索和测试数据x最近的k个点,如果一一计算,然后再排序,开销过大
- 引入KD树的作用就是对KNN搜索和排序的耗时进行改进
2 KD树
2.1 主体思路
- 以空间换时间,利用训练样本集中的样本点,沿各维度依次对k维空间进行划分,建立二叉树
- 利用分治思想提高算法搜索效率
- 二分查找的算法复杂度是O(logN),KD树的搜索效率与之接近(取决于所构造kd-tree是否接近平衡树)
- 上图为为训练样本对空间的划分以及对应的kd树
- 绿色实心五角星为测试样本,通过kd-tree的搜索算法,快速找到与其最近邻的3个训练样本点(空心五角星标注的点)
2.2 KD树的建立
2.2.1 以一个例子引入
- 比如我有6个点:(2,3),(4,7),(5,4),(7,2),(8,1),(9,6)
- 1) 数据有两个维度,分别计算x,y方向上数据的方差
- x方向上的方差最大
- ——>先沿着X轴方向进行split
- 注:这一步也可以不要,因为KD树适用的问题大多是维度小于20的,所以按照维度顺序一个一个来也没有问题
- 2)根据x轴方向的值2,5,9,4,8,7排序选出中位数为7
- x≤7的和x >7的被分开了
- x≤7的和x >7的被分开了
- 3) 被分开的左半区和右半区分别选出y轴方向的中位数(偶数选小的那个)
- 4)左上方三个点再根据x轴分一刀(其他三个区域已经各只剩一个点了)
- 最终得到的KD树
2.2.2 伪代码
def kd_tree_construct:
input:
x: 训练样本集
dim: 当前节点的分割维度(子节点的分割维度=(dim+1)%样本的维度)
output:
node: 构造好的kd tree的根节点
if 只有一个数据点:
创建一个叶子结点node包含这一单一的点
node.point = x[0]
node.son1 = None
node.son2 = None
return node
else:
记dim维度上的中位点为x(对x中的数据按dim维排序,取中位点,偶数个则取较小的那个)
记xl为左集合(dim维小于p点的所有点)
记xr为右集合(dim维大于p点的所有点)
创建带有两个孩子的node:
node.point = p
node.son1 = fit_kd_tree(xl)
node.son2 = fit_kd_tree(xr)
return node
2.3 KD树上的最近邻查找
2.3.1 伪代码
def kd_tree_search:
global:
Q, 缓存k个最近邻点(初始时包含一个无穷远点)
q, 与Q对应,保存Q中各点与测试点的距离
input:
k, 寻找k个最近邻
t, 测试点
node, 当前节点(一开始时根节点)
dim, 当前节点的分割维度(子节点的分割维度=(dim+1)%数据点的维度)
output:
无
if distance(t, node.point) < max(q):
将node.point添加到Q,并同步更新q
若Q内超过k个近邻点,则移出与测试点距离最远的那个点,并同步更新q
if t[dim]-max(q) < node.point[dim]:
kd_tree_search(k,t,node.son1)
if t[dim]+max(q) > node.point[dim]:
kd_tree_search(k,t,node.son2)
2.3.1 以一个例子开始
2.3.1.1 例子1
搜索(2.1,3.1)
记k=1
- 第1步:将(7,2)加入Q中,maxq=5.02,更新Q
- 2.1-5.02≤7
- 搜索左儿子
- 第2步:将(5.4)加入Q中,maxq=3.04,更新Q
- 3.1-3.04≤4
- 搜索下儿子
- 第3步:将(2,3)加入Q中,maxq=0.1414,更新Q
- 已经是叶子节点了,结束
- 3.1-3.04≥4
- 搜索上儿子
- 第4步:将(4,7)加入Q中,maxq=4.338>0.1414,不更新Q,仍为0.1414
- 已经是叶子节点了,结束
- 3.1-3.04≤4
- 2.1-5.02≥7
- 搜索右儿子
- 第5步,将(9,6)加入Q中,maxq=7.484>0.1414,不更新Q,仍为0.1414
- 3.1+7.484>6
- 搜索上儿子
- 没有上儿子,结束
- 2.1-5.02≤7
- 算法结束,最近的点是(2,3),q=0.1414
2.3.1.2 例子2 回溯时改变最近邻点
假设我们要查询的点是2,4.5
同样记k=1
- 第1步:将(7,2)加入Q中,maxq=5.59,更新Q
- 2-5.59≤7
- 搜索左儿子
- 第2步:将(5.4)加入Q中,maxq=3.04,更新Q
- 4.5-3.04≤4
- 搜索下儿子
- 第3步:将(2,3)加入Q中,maxq=1.5,更新Q
- 4.5+3.04≥4
- 搜索上儿子
- 第4步:将(4,7)加入Q中,maxq=3.20>1.5,不更新Q,仍为1.5
- 4.5-3.04≤4
- 2+5.59 >7
- 搜索右儿子
- 第5步,将(9,6)加入Q中,maxq=7.16>1.5,不更新Q,仍为1.5
- 4.5+7.16>6
- 搜索上儿子
- 没有上儿子,结束
- 4.5+7.16>6
- 2-5.59≤7
- 算法结束,最近的点是(2,3),距离为1.5
参考内容:KNN的核心算法kd-tree和ball-tree - 简书 (jianshu.com)
k-d tree算法 - J_Outsider - 博客园 (cnblogs.com)