机器学习算法(二十五):KD树详解及KD树最近邻算法

本文详细介绍了K-D树(KD树)的概念,包括其构建、插入、删除和最近邻搜索算法。K-D树是一种用于多维空间关键数据搜索的数据结构,特别适用于范围查询和最近邻搜索。文章通过实例展示了KD树的构建过程,解释了最近邻搜索的回溯机制,并分析了其在高维数据上的性能瓶颈。此外,还探讨了BBF算法作为KD树的改进,以及球树、VP树和MVP树等高维空间索引结构,以应对高维数据的挑战。

目录

1 KD树

1.1 什么是KD树

1.2 KD树的构建

1.3 KD树的插入

1.4 KD树的删除

1.5 KD树的最近邻搜索算法

1.5.1 举例:查询点(2.1,3.1)

1.5.2 举例:查询点(2,4.5)

2 kd树近邻搜索算法的改进:BBF算法

3 球树、M树、VP树、MVP树

3.1 球树

3.2 VP树与MVP树简介


        特征点匹配和数据库查、图像检索本质上是同一个问题,都可以归结为一个通过距离函数在高维矢量之间进行相似性检索的问题,如何快速而准确地找到查询点的近邻,不少人提出了很多高维空间索引结构和近似查询的算法。

        一般说来,索引结构中相似性查询有两种基本的方式:

  • 一种是范围查询,范围查询时给定查询点和查询距离阈值,从数据集中查找所有与查询点距离小于阈值的数据
  • 另一种是K近邻查询,就是给定查询点及正整数K,从数据集中找到距离查询点最近的K个数据,当K=1时,它就是最近邻查询。

    同样,针对特征点匹配也有两种方法:

  1. 最容易的办法就是线性扫描,也就是我们常说的穷举搜索,依次计算样本集E中每个样本到输入实例点的距离,然后抽取出计算出来的最小距离的点即为最近邻点。此种办法简单直白,但当样本集或训练集很大时,它的缺点就立马暴露出来了,举个例子,在物体识别的问题中,可能有数千个甚至数万个SIFT特征点,而去计算这成千上万的特征点与输入实例点的距离,明显是不足取的。
  2. 另外一种,就是构建数据索引,因为实际数据一般都会呈现簇状的聚类形态,因此我们想到建立数据索引,然后再进行快速匹配。索引树是一种树结构索引方法,其基本思想是对搜索空间进行层次划分。根据划分的空间是否有混叠可以分为Clipping和Overlapping两种。前者划分空间没有重叠,其代表就是k-d树;后者划分空间相互有交叠,其代表为R树。

        1975年,来自斯坦福大学的Jon Louis Bentley在ACM杂志上发表的一篇论文:Multidimensional Binary Search Trees Used for Associative Searching 中正式提出和阐述的了如下图形式的把空间划分为多个部分的k-d树。                                     

1 KD树

1.1 什么是KD树

   Kd-树是K-dimension tree的缩写,是对数据点在k维空间(如二维(x,y),三维(x,y,z),k维(x1,y,z..))中划分的一种数据结构,主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。本质上说,Kd-树就是一种平衡二叉树。

    首先必须搞清楚的是,k-d树是一种空间划分树,说白了,就是把整个空间划分为特定的几个部分,然后在特定空间的部分内进行相关搜索操作。想像一个三维(多维有点为难你的想象力了)空间,kd树按照一定的划分规则把这个三维空间划分了多个空间,如下图所示: 

1.2 KD树的构建

        

        再举一个简单直观的实例来介绍k-d树构建算法。假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点位于二维空间内,如下图所示。为了能有效的找到最近邻,k-d树采用分而治之的思想,即将整个空间划分为几个小部分,首先,粗黑线将空间一分为二,然后在两个子空间中,细黑实直线又将整个空间划分为四部分,最后虚黑直线将这四部分进一步划分。        

        6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}构建kd树的具体步骤为:

  1. 确定:split域=x。具体是:6个数据点在x,y维度上的数据方差分别为39,28.63,所以在x轴上方差更大,故split域值为x;
  2. 确定:Node-data = (7,2)。具体是:根据x维上的值将数据排序,6个数据的中值(所谓中值,即中间大小的值)为7,所以Node-data域位数据点(7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于:split=x轴的直线x=7;
  3. 确定:左子空间和右子空间。具体是:分割超平面x=7将整个空间分为两部分:x<=7的部分为左子空间,包含3个节点={(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点={(9,6),(8,1)};

        如上算法所述,kd树的构建是一个递归过程,我们对左子空间和右子空间内的数据重复根节点的过程就可以得到一级子节点(5,4)(9,6),同时将空间和数据集进一步细分,如此往复直到空间中只包含一个数据点。

    与此同时,经过对上面所示的空间划分之后,我们可以看出,点(7,2)可以为根结点,从根结点出发的两条红粗斜线指向的(5,4)和(9,6)则为根结点的左右子结点,而(2,3),(4,7)则为(5,4)的左右孩子(通过两条细红斜线相连),最后,(8,1)为(9,6)的左孩子(通过细红斜线相连)。如此,便形成了下面这样一棵k-d树:

         k-d树的数据结构:

        

/** a node in a k-d tree */
struct kd_node
{
	int ki;                      /**< partition key index *///关键点直方图方差最大向量系列位置
	double kv;                   /**< partition key value *///直方图方差最大向量系列中最中间模值
	int leaf;                    /**< 1 if node is a leaf, 0 otherwise */
	struct feature* features;    /**< features at this node */
	int n;                       /**< number of features */
	struct kd_node* kd_left;     /**< left child */
	struct kd_node* kd_right;    /**< right child */
};

       也就是说,如之前所述,kd树中,kd代表k-dimension,每个节点即为一个k维的点。每个非叶节点可以想象为一个分割超平面,用垂直于坐标轴的超平面将空间分为两个部分,这样递归的从根节点不停的划分,直到没有实例为止。经典的构造k-d tree的规则如下:

  1. 随着树的深度增加,循环的选取坐标轴,作为分割超平面的法向量。对于3-d tree来说,根节点选取x轴,根节点的孩子选取y轴,根节点的孙子选取z轴,根节点的曾孙子选取x轴,这样循环下去。
  2. 每次均为所有对应实例的中位数的实例作为切分点,切分点作为父节点,左右两侧为划分的作为左右两子树。
     

### KD的概念 KD(K-d Tree),即K维,是一种用于组织点在k维空间中的状数据结构,主要用于多维空间中的快速搜索操作,如最近邻搜索和范围搜索。每个节点代表一个k维空间中的点,并且每个节点将空间划分为两个子空间,使得的左子中的所有点位于该节点的分割超平面的一侧,而右子中的点位于另一侧。这种分割方式确保了每个节点的子节点所在的区域是父节点区域的一个子集。 ### KD的原理 构建KD的过程是从给定的数据集中选择一个维度,并根据该维度上的中位数来分割数据集。这样做的目的是为了保持的平衡。接着,递归地对分割后的两个子集进行同样的操作,直到所有的数据都被插入到中。选择维度的方式可以是循环选择不同的维度,也可以基于某种启发式方法来决定最优分割维度。 在查询时,KD允许快速定位到接近查询点的数据点。当执行最近邻搜索时,从根节点开始,根据查询点在当前节点维度上的值决定进入左子还是右子。一旦到达叶子节点,就回溯并检查其他分支是否可能包含更近的邻居。这个过程涉及到计算查询点到超平面的距离,并判断是否有必要进入另一个分支进行搜索。 ### KD的实现 实现KD的关键在于如何有效地构建以及如何高效地执行搜索操作。以下是构建KD的基本步骤: 1. 选择一个维度作为分割维度。 2. 找到该维度上的中位数点。 3. 将中位数点作为当前节点,并将剩余的点分为两组,一组位于中位数点的一侧,另一组位于另一侧。 4. 对左右两组分别递归执行上述步骤。 以下是一个简单的Python伪代码示例,展示如何构建一个KD: ```python class Node: def __init__(self, point, left=None, right=None): self.point = point self.left = left self.right = right def build_kd_tree(points, depth=0): if not points: return None # 选择维度 k = len(points[0]) axis = depth % k # 按照当前维度排序并找到中位数 points.sort(key=lambda x: x[axis]) median = len(points) // 2 # 构建节点 return Node( point=points[median], left=build_kd_tree(points[:median], depth + 1), right=build_kd_tree(points[median + 1:], depth + 1) ) ``` 对于搜索算法,特别是最近邻搜索,实现起来更为复杂,因为需要考虑回溯过程以确保找到全局最近的邻居。 ###
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值