KNN算法之KD树(K-dimension Tree)实现 K近邻查询
KD树是一种分割k维数据空间的数据结构,主要应用于多维空间关键数据的搜索,如范围搜索和最近邻搜索。
KD树使用了分治的思想,对比二叉搜索树(BST),KD树解决的是多维空间内的最近点(K近点)问题。(思想与之前见过的最近点对问题很相似,将所有点分为两边,对于可能横跨划分线的点对再进一步讨论)
KD树用来优化KNN算法中的查询复杂度。
一、建树
建立KDtree,主要有两步操作:选择合适的分割维度,选择中值节点作为分割节点。
分割维度的选择遵循的原则是,选择范围最大的纬度,也即是方差最大的纬度作为分割维度,为什么方差最大的适合作为特征呢?
因为方差大,数据相对“分散”,选取该特征来对数据集进行分割,数据散得更“开”一些。
分割节点的选择原则是,将这一维度的数据进行排序,选择正中间的节点作为分割节点,确保节点左边的点的维度值小于节点的维度值,节点右边的点的维度值大于节点的维度值。
这两步步骤影响搜索效率,非常关键。
二、搜索K近点
需要的数据结构:最大堆(此处我对距离取负从而用最小堆实现的最大堆,因为python的heapq模块只有最小堆)、堆栈(用列表实现)
a.利用二叉搜索找到叶子节点并将搜索的结点路径压入堆栈stack中
b.通过保存在堆栈中的搜索路径回溯,直至堆栈中没有结点了
对于b步骤,需要区分叶子结点和非叶结点:
1、叶子结点:
叶子结点:计算与目标点的距离。若候选堆中不足K个点,将叶子结点加入候选堆中;如果K个点够了,判断是否比候选堆中距离最小的结点(因为距离取了相反数)还要大, 说明应当加入候选堆;
2、非叶结点:
对于非叶结点,处理步骤和叶子结点差不多,只是需要额外考虑以目标点为圆心,最大堆的堆顶元素为半径的超球体是否和划分当前空间的超平面相交,如果相交说明未访问的另一边的空间有可能包含比当前已有的K个近点更近的点,需要搜索另一边的空间;此外,当候选堆中没有K个点,那么不管有没有相交,都应当搜索未访问的另一边空间,因为候选堆的点不够K个。
步骤:计算与目标点的距离
1、若不足K个点,将结点加入候选堆中;
如果K个点够了,判断是否比候选堆中距离最小的结点(因为距离取了相反数)还要大。
2、判断候选堆中的最小距离是否小于Xi离当前超平面的距离(即是否需要判断未访问的另一边要不要搜索)当然如果不足K个点,虽然超平面不相交,依旧要搜索另一边,直到找到叶子结点,并且把路径加入回溯栈中。
三、预测
KNN通常用来分类或者回归问题,笔者已经封装好了两种预测的方法。
python代码实现:
1 import heapq
2 class KDNode(object):
3 def __init__(self,feature=None,father=None,left=None,right=None,split=None):
4 self.feature=feature # dimension index (按第几个维度的特征进行划分的)
5 self.father=father #并没有用到
6 self.left=left
7 self.right=right
8 self.split=split # X value and Y value (元组,包含特征X和真实值Y)
9
10 class KDTree(object):
11 def __init__(self):
12 self.root=KDNode()
13 pass
14
15 def _get_variance(self,X,row_indexes,feature_index):
16 # X (2D list): samples * dimension
17 # row_indexes (1D list): choose which row can be calculated
18 # feature_index (int): calculate which dimension
19 n = len(row_indexes)
20 sum1 = 0
21 sum2 = 0
22 for id in row_indexes:
23 sum1 = sum1 + X[id][feature_index]
24 sum2 = sum2 + X[id][feature_index]**2
25
26 return sum2/n - (sum1/n)**2
27
28 def _get_max_variance_feature(self,X,row_indexes):
29 mx_var = -1
30 dim_index = -1
31 for dim in range(len(X[0])):
32 dim_var = self._get_variance(X,row_indexes,dim)
33 if dim_var>mx_var:
34 mx_var=dim_var
35 dim_index=dim
36 # return max variance feature index (int)
37 return dim_index
38
39 def _get_median_index(self,X,row_indexes,feature_index):
40 median_index = len(row_indexes)//2
41 select_X = [(idx,X[idx][feature_index]) for idx in row_indexes]
42 sorted_X = select_X
43 sorted(sorted_X,key= lambda x:x[1])
44 #return median index in feature_index dimension (int)
45 return sorted_X[median_index][0]
46
47 def _split_feature(self,X,row_indexes,feature_index,median_index):
48 left_ids = []
49 right_ids = []
50 median_val = X[median_index][feature_index]
51 for id in row_indexes:
52 if id==median_index:
53 continue
54 val = X[id][feature_index]
55 if val < median_val:
56 left_ids.append(id)
57 else:
58 right_ids.append(id)
59 # return (left points index and right points index)(list,list)
60 # 把当前的样本按feature维度进行划分为两份
61 return left_ids, right_ids
62
63 def build_tree(self,X,Y):
64 row_indexes =[i for i in range(len(X))]
65 node =self.root
66 queue = [(node,row_indexes)]
67 # BFS创建KD树
68 while queue:
69 root,ids = queue.pop(0)
70 if len(ids)==1:
71 root.feature = 0 #如果是叶子结点,维度赋0
72 root.split = (X[ids[0]],Y[ids[0]])
73 continue
74 # 选取方差最大的特征维度划分,取样本的中位数作为median
75 feature_index = self._get_max_variance_feature(X,ids)
76 median_index = self._get_median_index(X,ids,feature_index)
77 left_ids,right_ids = self._split_feature(X,ids,feature_index,median_index)
78 root.feature = feature_index
79 root.split = (X[median_index],Y[median_index])
80 if left_ids:
81 root.left = KDNode()
82 root.left.father = root
83 queue.append((root.left,left_ids))
84 if right_ids:
85 root.right = KDNode()
86 root.right.father = root
87 queue.append((root.right,right_ids))
88
89 def _get_distance(self,Xi,node,p=2):
90 # p=2 default Euclidean distance
91 nx = node.split[0]
92 dist = 0
93 for i in range(len(Xi)):
94 dist=dist + (abs(Xi[i]-nx[i])**p)
95 dist = dist**(1/p)
96 return dist
97
98 def _get_hyperplane_distance(self,Xi,node):
99 xx = node.split[0]
100 dist = abs(Xi[node.feature] - xx[node.feature])
101 return dist
102
103 def _is_leaf(self,node):
104 if node.left is None and node.right is None:
105 return True
106 else:
107 return False
108
109 def get_nearest_neighbour(self,Xi,K=1):
110 search_paths = []
111 max_heap = [] #use min heap achieve max heap (因为python只有最小堆)
112 priority_num = 0 # remove same distance
113 heapq.heappush(max_heap,(float('-inf'),priority_num,None))
114 priority_num +=1
115 node = self.root
116 # 找到离Xi最近的叶子结点
117 while node is not None:
118 search_paths.append(node)
119 if Xi[node.feature] < node.split[0][node.feature]:
120 node = node.left
121 else:
122 node = node.right
123
124 while search_paths:
125 now = search_paths.pop()
126 # 叶子结点:计算与Xi的距离,若不足K个点,将叶子结点加入候选堆中;
127 # 如果K个点够了,判断是否比候选堆中距离最小的结点(因为距离取了相反数)还要大,
128 # 说明应当加入候选堆;
129 if self._is_leaf(now):
130 dist = self._get_distance(Xi,now)
131 dist = -dist
132 mini_dist = max_heap[0][0]
133 if len(max_heap) < K :
134 heapq.heappush(max_heap,(dist,priority_num,now))
135 priority_num+=1
136 elif dist > mini_dist:
137 _ = heapq.heappop(max_heap)
138 heapq.heappush(max_heap,(dist,priority_num,now))
139 priority_num+=1
140 # 非叶结点:计算与Xi的距离
141 # 1、若不足K个点,将结点加入候选堆中;
142 # 如果K个点够了,判断是否比候选堆中距离最小的结点(因为距离取了相反数)还要大,
143 # 2、判断候选堆中的最小距离是否小于Xi离当前超平面的距离(即是否需要判断另一边要不要搜索)
144 # 当然如果不足K个点,虽然超平面不相交,依旧要搜索另一边,
145 # 直到找到叶子结点,并且把路径加入回溯栈中
146 else :
147 dist = self._get_distance(Xi, now)
148 dist = -dist
149 mini_dist = max_heap[0][0]
150 if len(max_heap) < K:
151 heapq.heappush(max_heap, (dist, priority_num, now))
152 priority_num += 1
153 elif dist > mini_dist:
154 _ = heapq.heappop(max_heap)
155 heapq.heappush(max_heap, (dist, priority_num, now))
156 priority_num += 1
157
158 mini_dist = max_heap[0][0]
159 if len(max_heap)<K or -(self._get_hyperplane_distance(Xi,now)) > mini_dist:
160 # search another child tree
161 if Xi[now.feature] >= now.split[0][now.feature]:
162 child_node = now.left
163 else:
164 child_node = now.right
165 # record path until find child leaf node
166 while child_node is not None:
167 search_paths.append(child_node)
168 if Xi[child_node.feature] < child_node.split[0][child_node.feature]:
169 child_node = child_node.left
170 else:
171 child_node = child_node.right
172 return max_heap
173
174 def predict_classification(self,Xi,K=1):
175 # 多分类问题预测
176 y =self.get_nearest_neighbour(Xi,K)
177 mp = {}
178 for i in y:
179 if i[2].split[1] in mp:
180 mp[i[2].split[1]]+=1
181 else:
182 mp[i[2].split[1]]=1
183 pre_y = -1
184 max_cnt =-1
185 for k,v in mp.items():
186 if v>max_cnt:
187 max_cnt=v
188 pre_y=k
189 return pre_y
190
191 def predict_regression(self,Xi,K=1):
192 #回归问题预测
193 pre_y = self.get_nearest_neighbour(Xi,K)
194 return sum([i[2].split[1] for i in pre_y])/K
195
196
197 # t =KDTree()
198 # xx = [[3,3],[1,2],[5,6],[999,999],[5,5]]
199 # z = [1,0,1,1,1]
200 # t.build_tree(xx,z)
201 # y=t.predict_regression([4,5.5],K=5)
202 # print(y)
参考资料:
《统计学习方法》——李航著
https://blog.csdn.net/qq_32478489/article/details/82972391?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
https://zhuanlan.zhihu.com/p/45346117
https://www.cnblogs.com/xingzhensun/p/9693362.html
KNN算法之KD树(K-dimension Tree)实现 K近邻查询的更多相关文章
- KNN算法与Kd树
最近邻法和k-近邻法 下面图片中只有三种豆,有三个豆是未知的种类,如何判定他们的种类? 提供一种思路,即:未知的豆离哪种豆最近就认为未知豆和该豆是同一种类.由此,我们引出最近邻算法的定义:为了判定未知 ...
- KNN算法之KD树
KD树算法是先对数据集进行建模,然后搜索最近邻,最后一步是预测. KD树中的K指的是样本特征的维数. 一.KD树的建立 m个样本n维特征,计算n个特征的方差,取方差最大的第k维特征作为根节点.选择第k ...
- 【分类算法】K近邻(KNN) ——kd树(转载)
K近邻(KNN)的核心算法是kd树,转载如下几个链接: [量化课堂]一只兔子帮你理解 kNN [量化课堂]kd 树算法之思路篇 [量化课堂]kd 树算法之详细篇
- 从K近邻算法谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...
- 从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
转载自:http://blog.csdn.net/v_july_v/article/details/8203674/ 从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 前言 前两日,在微博上说: ...
- <转>从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/likika2012/article/details/39619687 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章待写:1.KD树:2.神经 ...
- kd树和knn算法的c语言实现
基于kd树的knn的实现原理可以参考文末的链接,都是一些好文章. 这里参考了别人的代码.用c语言写的包括kd树的构建与查找k近邻的程序. code: #include<stdio.h> # ...
- k临近法的实现:kd树
# coding:utf-8 import numpy as np import matplotlib.pyplot as plt T = [[2, 3], [5, 4], [9, 6], [4, 7 ...
- kd树 求k近邻 python 代码
之前两篇随笔介绍了kd树的原理,并用python实现了kd树的构建和搜索,具体可以参考 kd树的原理 python kd树 搜索 代码 kd树常与knn算法联系在一起,knn算法通常要搜索k近邻, ...
- k近邻法的C++实现:kd树
1.k近邻算法的思想 给定一个训练集,对于新的输入实例,在训练集中找到与该实例最近的k个实例,这k个实例中的多数属于某个类,就把该输入实例分为这个类. 因为要找到最近的k个实例,所以计算输入实例与训练 ...
随机推荐
- Docker 镜像库国内加速的几种方法
概述 在国内,拉取 Docker 镜像速度慢/时不时断线/无账号导致限流等,比较痛苦. 这里提供加速/优化的几种方法. 梳理一下,会碰到以下情况: 国内下载速度慢/时不时断线:是因为网络被限制了. 没 ...
- Kafka Stream 高级应用
9.1将Kafka 与其他数据源集成 对于第一个高级应用程序示例,假设你在金融服务公司工作.公司希望将其现有数据迁移到新技术实现的系统中,该计划包括使用 Kafka.数据迁移了一半,你被要求去更新公司 ...
- 基于Protege的知识建模实战
一.Protege简介.用途和特点 1.Protege简介 Protege是斯坦福大学医学院生物信息研究中心基于Java开发的本体编辑和本体开发工具,也是基于知识的编辑器,属于开放源代码软件.这个软件 ...
- Couchdb-权限绕过--命令执行--(CVE-2017-12635)&&(CVE-2017-12636)--H2database命令执行--(CVE-2022-23221)
Couchdb-权限绕过--命令执行--(CVE-2017-12635)&&(CVE-2017-12636)--H2database命令执行--(CVE-2022-23221) 环境概 ...
- Django框架——Django与Ajax、分页器
文章目录 1 Django与Ajax 一 什么是Ajax 优点: 二 基于jquery的Ajax实现 Ajax-->服务器-->Ajax执行流程图 三 案例 一 通过Ajax,实现前端输入 ...
- C++的模板类在HotSpot VM中的应用
模板是c++的一种特性,允许函数或者类通过泛型(generic types)的形式表现或者运行.模板可以使得函数或类在对应不同的类型(types)的时候正常工作,而无需为每一种类型分别写一份代码. 在 ...
- TIPTOP GP ERP二次开发规范说明
TIPTOP GP ERP二次开发规范说明 TIPTOP ERP共分三大区:topstd(标准区) + toppord(正式区) + toptest(测试区) 标准区 ...
- Java-全网最详细数据结构
数构&算法:数据结构 数据结构是计算机存储.组织数据的方式.数据结构是指相互之间存在一种或多种特定关系的数据元素的集合.通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率.数据结构往 ...
- 【动画进阶】神奇的背景,生化危机4日食 Loading 动画还原
最近,在 Steam 玩一款老游戏(生化危机 4 重置版),其中,每当游戏转场的过程中,都有这么一个有趣的 Loading 动画: 整个效果有点类似于日食效果,中间一圈黑色,向外散发着太阳般的光芒. ...
- Fox and Minimal path 题解
Fox and Minimal path 题目大意 构造一张无向图,使得从 \(1\) 到 \(2\) 的最短路数量为 \(k\). 思路分析 我们首先可以发现当 \(k = 2^t\) 时的构造方式 ...