k-d树 & kNN & ANN 等相关理解

花了两天时间就看了个k-d树 罪过罪过~

不过还是大约总结以下吧

起初看k-d树是在看李航的《统计学习方法》中提到的,kNN是个很简单的算法,但k-d树总的来说,稍微有点绕,感觉可能是看到的资料表述的不太清晰,也有可能是本来k-d树就不容易表述。


k-d树从根本上来说就是一个多维空间下的二叉查找树,区别于二叉查找树只有1维,k-d树可以有n维,故而k-d树在分叉的时候,需要固定一维,构成一个超平面进行划分,在选具体哪一维的时候,简单的做法是从第一维开始轮循,可能更优的做法是采用最大方差维进行划分,因为从根本上来说,我们需要把搜索空间划分的更均匀,这个时候总的搜索时间会下降,也更方便剪枝,考虑一种特殊情况,假设我们始终只使用1个维进行空间划分,那么整个搜索空间会被划分成长条状的超矩形,那么在搜索的时候,在kNN k = 1的情况下,超球体可能会横跨诸多搜索空间,也意味着我们需要遍历很多的搜索分支。


k-d树的建树方法比较简单,固定一维,然后左小右大,当然根结点中存储的是中位数,左小右大的基准也是该点,直到最终被点划分的子区域都是空白为止,此处应有图,不过很遗憾,懒得粘别人的博客了。


重点述说一下k-d树的查找最近邻以及k近邻的搜索方式,其实从搜索的角度来讲,不论是最近邻还是k近邻,从O(n) 到 log(n),节省掉的时间永远是剪枝剪掉的部分,而k-d树最核心的也应该是剪枝,不知道为什么,好多书,资料上都没有指出这一点,害的我有点怀疑自己,浪费时间,k-d树剪掉的枝就是假设已经找到一个当前最近点,那么如果当前最近点和目标点的距离小于目标点和某一块超矩形的距离,那么在这一块超矩形中都不去搜索,即减去这一分支。


简单述说一下最近邻和k近邻的搜索方法,这一块详细的算法书中都有,自己来写个很完善的伪代码可能也写不好...


在最近邻搜索中,先和二叉搜索树一样,找到树中离目标点最近的叶子节点,取该叶子节点为当前最近点,然后回溯,找到父节点,判断父节点与目标节点之间的距离,小则更新,大则不变,同时判断目标点与另一分支是否相交,此时只需判断目标点与父节点所在的那条划分超平面的距离是否大于当前最近点于目标点距离,若大于当前距离,则直接回溯至父节点的上一节点,即对该分支进行剪枝;若小于当前距离,则对该分支查找当前最近点,递归上述过程。搜索结束是查找至根节点。


k近邻于最近邻不同的地方是剪枝的条件不同,在最近邻中,若当前距离小于目标点到超矩形距离,则对超矩形进行剪枝,而在k近邻中,需要满足的条件有两个:(1)已经凑够最近的k个点。即在结果集中,和查找最近邻相似过程相似,找到当前最近点,回溯,将父节点加入结果集(需满足条件小于已知距离,结果集不足k可认为距离为正无穷),判断与另一分支是否相交,此处不同的地方是,若未凑足k个数字,仍需进行回溯,因为从整个搜索空间来讲,当前搜索空间仍与目标节点相较于其他节点近。(2)在已凑满的情况下,需满足当前距离小于结果集中的最大距离。满足以上两个条件即进入该分支。


整个描述感觉条理不是完全清晰,但关键的地方已经讲出,以后会不会更新2.0版本就随缘了,因为感觉这个东西做2.0版本不是特别必要~~


k-d树通过如上方法减小了搜索的时间复杂度,在ANN中,通过随机建森林,然后通过hash的方式,减小了搜索的时间复杂度,从理论的角度来讲,ann只会在一个树中进行搜索,当目标点在划分的边缘的时候会出现严重的不准确,但从实际的角度来讲,当总量为几百万,而我们一般取kNN,k为几十的时候,影响并没有那么大。


python scikit-learn提供了knn的相关实现方法,需要用的时候再仔细探究k-d树,ann等的实现对于效率、效果的影响吧。

以上

猜你喜欢

转载自blog.csdn.net/sunfei05/article/details/80499200