K近邻 kd树 搜索算法 python实现

K近邻 k维kd树搜索算法 python实现

在KNN算法中,当样本数据量非常大时,快速地搜索k个近邻点就成为一个难题。kd树搜索算法就是为了解决这个问题。本篇博客主要介绍多维数据kd树搜索算法的Python实现。

python数据结构之二叉树

本篇博客不对二叉树进行详细介绍,只提及一些比较重要的概念。

  • 二叉树抽象数据类型的定义:
ADT BinTree:
	BinTree(self, data, left, right)	#构造函数,创建一个新的二叉树
	is_empty(self)							#判断self是否为一个空二叉树
	num_nodes(self)						#求二叉树的结点个数
	data(self)									#获取根结点存储的数据
	left(self)									#获得二叉树的左子树
	right(self)									#获得二叉树的右子树
	set_left(self, btree)					#用btree取代原来的左子树
	set_right(self,btree)					#用btree取代原来的右子树
	traversal(self)							#遍历二叉树中各节点数据的迭代器
	forall(self, op)							#对二叉树中的每个结点的数据执行操作
  • 遍历二叉树的两种方法:
    • 深度优先遍历:先根序遍历,中根序遍历,后根序遍历。
    • 广度优先遍历。

kd树算法介绍

kd树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。kd树是二叉树,表示对k维空间的一个划分。构造kd树相当于不断用垂直于坐标轴的超平面将k维空间切分,构成一系列的k维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。

构造平衡kd树

输入:k维空间数据集 T = { x 1 , x 2 , . . . , x N } T=\{ x_{1},x_{2},...,x_{N}\} ,N表示样本个数。
其中 x i = ( x i 1 , x i 2 , . . . , x i k ) T , i = 1 , 2 , . . . , N x_{i} = (x_{i}^{1},x_{i}^{2},...,x_{i}^{k})^{T},i=1,2,...,N
输出:kd树

  1. 开始:构造根结点,根节点对应于包含T的k维空间的超矩形区域。选择第一维为坐标轴,以T中所有样本数据的第一维坐标的中位数为切分点,将根结点对应的超矩形区域切分成为两个子区域。切分由通过切分点并与第一维坐标轴垂直的超平面实现。由根结点生成深度为1的左、右子结点:左子结点对应第一维坐标小于切分点的子区域,右子结点对应第一位坐标大于切分点的子区域。将落在切分超平面上的实例点保存在根结点。
  2. 重复:对深度为j的结点,选择第i维为切分的坐标轴, i = ( j m o d k ) + 1 i=(j mod k)+1 。以该结点的区域中所有实例的第i维坐标的中位数为切分点,将该结点对应的超矩形区域切分为两个子区域。切分由通过切分点并与第i维坐标轴垂直的超平面实现。由该结点生成深度为i+1的左右子结点。
  3. 直到两个子区域没有实例存在时停止,从而形成kd树的区域划分。

用kd树的最近邻搜索

输入:已构造的kd树;目标点x;
输出:x的最邻近

  1. 在kd树中找出包含目标点的x的叶结点:从根结点出发,递归地向下访问kd树。若目标点x当前维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点。直到子结点为叶结点为止。
  2. 以此叶结点为“当前最近点”。
  3. 递归地向上回退,在每个结点进行以下操作:(a)如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”。(b)当前最近点一定存在于该节点的一个子结点对应的区域。检查该子结点的父结点的另一子结点对应的区域是否有更近的点。具体地,检查另一子结点对应的区域是否与以目标点为球心、以目标点与“当前最近点的距离为半径的超球体相交。如果相交,可能在另一个子结点对应的区域内存在距离目标点更近的点,移动到另一个子结点。接着,递归地进行最近邻搜索;如果不相交,向上回退。
  4. 当回退到根结点是,搜索结束,最后的“当前最近点”即为x的最近邻点。

如果实例点是随机分布的,kd树更适用于训练样本数远大于空间维数时的k近邻搜索。当空间维数接近训练实例数时。它的效率会迅速下降,几乎接近线性扫描。

kd树算法伪代码

建立kd树

初始维度设为dim = 0
def creat(points,dim):
	if points is not None:
    	1.找到所有样本点(points)在第dim维度下的坐标的中位点,记为point[media]
        2.确定过该中位点且垂直于第dim维坐标轴的一个超平面。(只是一个概念无需体现在代码中)
        3.该超平面将空间分为两个子空间。该中位点则作为当前kdtree的根结点						root.data=point[media]
        4.在第dim维度下,小于该中位点的所有样本点被划分到左子树,记为lefts点集
        5.大于该中位点的所有样本点被划分到右子树,记为rights点集.
    	递归执行:root.left = creat(lefts,(dim+1)% m)
    	递归执行:root.right = creat(rights,(dim+1)% m)
    	return root
    else:
    	return None

搜索最近邻点

def research_nn(point):
	1.遍历:将point点的坐标对应维度与kdtree的结点进行比较,小于结点对应维度的值往左子树走,反之往右子树走,等于的话就停止,或者知道kdtree的叶子结点再停止。将经过的所有节点坐标依次压入栈stack。以最后停下来的点(stack.pop)作为最近零点,并计算距离dist_min
	2.回溯:
	def back(point, dist):
		leaf = stack.pop
		计算leaf与point的距离,记为dist
		if dist <= dist_min:
			dist_min=dist
			return
		else:
			计算point到leaf的父结点所在的超平面的距离,记为dist
			if dist_min<dist:   #说明没有交点
				return
			else:
				stack。append(leaf的兄弟结点)
				back(point, dist)

kd树算法python实现

使用开源的KDTree库函数,详情见https://mp.csdn.net/mdeditor/87977160#

参考文献

1.李航,统计学习方法,清华大学出版社。
2.裘宗燕,数据结构与算法python语言描述,机械工业出版社。

猜你喜欢

转载自blog.csdn.net/qq7835144/article/details/87918771