Python中的增量最近邻算法
有没有人知道在Python中有没有可以逐步更新的最近邻算法?我找到的那些,比如这个,似乎都是一次性处理的。有没有可能实现一个可以逐步更新的最近邻算法呢?
3 个回答
确实有这样的东西。Scipy Cookbook 网站上有一个完整的 kNN 算法 的实现,可以逐步更新。
为了帮助那些对这个话题感兴趣但不太了解相关术语的人,可能需要一些背景知识。
kNN 引擎主要依赖两种数据表示方式:一种是存储在多维数组中的所有数据点之间的距离(称为 距离矩阵),另一种是 kd-tree,它将数据点本身存储在一个多维的二叉树中。
基于 kd-tree 的 KNN 算法只需要进行两个操作:首先,从数据集中创建树(这类似于其他机器学习算法中的 训练 步骤),然后搜索这棵树以找到“最近邻居”(这类似于 测试 步骤)。
在 KNN 算法中,如果是基于 kd-tree 的在线或增量训练,意味着要往已经建好的 kd-tree 中 插入节点。
回到 SciPy Cookbook 中的 kd-Tree 实现:负责节点插入的具体代码出现在注释“插入节点到 kd-tree”之后(实际上,所有在这个注释之后的代码都是与节点插入相关的)。
最后,SciPy 库的空间模块(scipy.spatial 模块)中有一个叫做 KDTree 的 kd-tree 实现(scipy.spatial.KDTree),但我认为它不支持节点插入,至少在文档中没有这样的功能(我没有查看源代码)。
这内容虽然来得有点晚,但为了后人留个记录:
其实有一种方法可以把像KD树这样的批处理算法变成增量算法,这个方法叫做 静态到动态转换。
要生成一个KD树的增量版本,你需要存储一组树,而不是仅仅一棵树。当你的最近邻结构中有 N 个元素时,你的结构会为 N 的二进制表示中每个“1”位存储一棵树。而且,如果树 T_i 对应于 N 的第 i 位,那么树 T_i 就包含 2^i 个元素。
举个例子,如果你的结构中有 11 个元素,那么 N = 11,二进制表示为 1011,因此你会有三棵树 - T_3、T_1 和 T_0,分别包含 8 个元素、2 个元素和 1 个元素。
现在,我们要往结构中插入一个元素 e。插入后,我们会有 12 个元素,二进制表示为 1100。对比新旧的二进制字符串,我们发现 T_3 没有变化,新增了一棵树 T_2,它有 4 个元素,而树 T_1 和 T_0 被删除了。我们通过批量插入 e 以及所有在 T_2 “下面”的元素(也就是 T_1 和 T_0)来构建新的树 T_2。
通过这种方式,我们从一个静态的基础结构创建了一个增量的点查询结构。不过,将静态结构“增量化”时会有一个渐进的减速,表现为额外的 log(N) 因子:
- 在结构中插入 N 个元素的时间复杂度是: O(N log(N) log(n))
- 对包含 N 个元素的结构进行最近邻查询的时间复杂度是: O(log(n) log(n))
我觉得逐步构建KD树或KNN树的问题在于,正如你在评论中提到的,树最终会变得不平衡,而简单的树旋转无法解决平衡问题并保持一致性。至少,重新平衡的任务并不简单,肯定不想在每次插入时都去做这个。通常,人们会选择用批量的方法来构建树,插入一堆新点,让树在一定程度上变得不平衡,然后再进行重新平衡。
一个非常相似的做法是,先为M个点批量构建数据结构,使用它处理M'个点,然后再用M+M'个点批量重建数据结构。因为重新平衡并不是我们熟悉的树的快速算法,所以重建并不一定慢,某些情况下甚至可能更快(这取决于你逐步算法中点的进入顺序)。
话虽如此,如果你选择重建的方法,你写的代码量、调试的难度,以及别人理解你代码的难易程度都会显著减少。如果这样做,你可以使用批量方法,并保持一个外部列表,记录那些还没有插入到树中的点。可以用一种简单粗暴的方法来确保这些点中没有哪个比树中的点更近。
下面有一些关于Python实现和讨论的链接,但我没有找到明确声称是增量的实现。祝你好运。
http://www.scipy.org/Cookbook/KDTree
http://cgi.di.uoa.gr/~compgeom/pycgalvisual/kdppython.shtml
http://sites.google.com/site/mikescoderama/Home/kd-tree-knn
http://en.wikipedia.org/wiki/Kd-tree
注意:我这里的评论适用于高维空间。如果你在处理2D或3D,以上说的可能不太合适。(如果你在处理非常高维的空间,建议使用简单粗暴的方法或近似最近邻算法。)