利用例题3.2构造的kd树求点 x = ( 3 , 4.5 ) T x=(3,4.5)^\mathrm{T} x=(3,4.5)T的最近邻点。
第一步:在kd树中找到包含目标点x的叶节点
从根节点(7,2)开始,
x
=
(
3
,
4.5
)
T
x = (3,4.5)^\mathrm{T}
x=(3,4.5)T的
x
(
1
)
<
7
x^{(1)}< 7
x(1)<7进入左节点(5,4),而
x
(
2
)
>
4
x^{(2)} > 4
x(2)>4进入右节点(4,7)。目标节点x xx在叶节点(4,7)对应的区域中。
第二步:递归向上退,更新“当前最近点”
比较
①
d
d
d(当前节点,目标节点)与
d
d
d(当前最近点,目标节点)
②
d
m
i
n
d_{min}
dmin(当前节点的兄弟节点,目标节点)与
d
d
d(当前最近点,目标节点)
迭代次数 | 当前节点 | 兄弟节点中最近的节点 | 当前最近点 | 是否更新 |
---|---|---|---|---|
0 | (4,7) | - | (4,7) | - |
1 | (4,7) | (2,3) | (4,7) | 是 |
2 | (5,4) | (5,4) | (2,3) | 否 |
3 | (7,2) | - | (2,3) | - |
第三步:退回到根节点时,“当前最近点”即为最近邻点
代码
import numpy as np
class kdNode():
def __init__(self, x, y):
self.value = x
self.dimension = y #切分的x维度
self.left = None
self.right = None
def loadData():
T = np.array([[2,3], [5,4], [9,6], [4,7], [8,1], [7,2]])
return T
def buildTree(T, Depth):
if len(T) == 0:
return None
k = T.shape[1]
T = T[T[:, int(Depth % k)].argsort()]
mid = T.shape[0] // 2
root = kdNode(T[mid], Depth%k)
root.left = buildTree(T[:mid], Depth+1)
root.right = buildTree(T[mid+1:], Depth+1)
return root
def levelTravel(root):
queue = [root]
level = 0
while len(queue):
temp = []
print("第%d层结点:"%level)
level += 1
for i in range(len(queue)):
print(queue[i].value)
if queue[i].left != None:
temp.append(queue[i].left)
if queue[i].right != None:
temp.append(queue[i].right)
queue = temp
def find_closest(root, x, min_dis, closest_point):
#step1:在kd树中找出包含目标点x的叶节点
if root == None:
return
#更新最短距离和最近点
cur_distance = (sum((root.value[:] - x)**2))**0.5
if min_dis[0] < 0 or cur_distance < min_dis:
min_dis[0] = cur_distance
for i in range(len(root.value)):
closest_point[i] = root.value[i]
# 递归向下访问kd树
if x[root.dimension] <= root.value[root.dimension]:
find_closest(root.left, x, min_dis, closest_point)
else:
find_closest(root.right, x, min_dis, closest_point)
#计算测试点和分割超平面的距离,如果相交则进入叶节点的右子树进行遍历查找最近点
distance = abs(x[root.dimension] - root.value[root.dimension])
if distance > min_dis[0]:
return
else:
if x[root.dimension] <= root.value[root.dimension]:
find_closest(root.right, x, min_dis, closest_point)
else:
find_closest(root.left, x, min_dis, closest_point)
return
if __name__ == "__main__":
T = loadData()
root = buildTree(T, 0)
levelTravel(root)
test_x = [3, 4.5]
closet_point = np.copy(root.value)
min_dis = np.array([-1.0])
find_closest(root, test_x, min_dis, closet_point)
print("测试的点", test_x)
print("最近的点",closet_point)
print("最短距离%f" % min_dis[0])
题目:参照算法3.3,写出输出为x的k近邻的算法。
算法3.3——用kd树求最近邻
输入:已构造的kd树;目标点x;
输出:x的最近邻.
(1)在kd树中找出包含目标点x的叶结点:从根结点出发,递归地向下访问
kd树.若目标点x当前维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点.直到子结点为叶结点为止.
(2)以此叶结点为“当前最近点”。
(3)递归地向上回退,在每个结点进行以下操作:
(a)如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”。
(b)当前最近点一定存在于该结点一个子结点对应的区域.检查该子结点的父结点的另一子结点对应的区域是否有更近的点.具体地,检查另一子结点对应的区域是否与以目标点为球心、以目标点与“当前最近点”间的距离为半径的超球体相交.
如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点.接着,递归地进行最近邻搜索;
如果不相交,向上回退.
(4)当回退到根结点时,搜索结束.最后的“当前最近点”即为x的最近邻点
# 构建kd树,搜索待预测点所属区域
from collections import namedtuple
import numpy as np
# 建立节点类
class Node(namedtuple("Node", "location left_child right_child")):
def __repr__(self):
return str(tuple(self))
# kd tree类
class KdTree():
def __init__(self, k=1):
self.k = k
self.kdtree = None
# 构建kd tree
def _fit(self, X, depth=0):
try:
k = self.k
except IndexError as e:
return None
# 这里可以展开,通过方差选择axis
axis = depth % k
X = X[X[:, axis].argsort()]
median = X.shape[0] // 2
try:
X[median]
except IndexError:
return None
return Node(
location=X[median],
left_child=self._fit(X[:median], depth + 1),
right_child=self._fit(X[median + 1:], depth + 1)
)
def _search(self, point, tree=None, depth=0, best=None):
if tree is None:
return best
k = self.k
# 更新 branch
if point[0][depth % k] < tree.location[depth % k]:
next_branch = tree.left_child
else:
next_branch = tree.right_child
if not next_branch is None:
best = next_branch.location
return self._search(point, tree=next_branch, depth=depth + 1, best=best)
def fit(self, X):
self.kdtree = self._fit(X)
return self.kdtree
def predict(self, X):
res = self._search(X, self.kdtree)
return res