最近邻搜索 - 经典树型结构 M-Tree

ops/2024/12/12 5:33:49/

前言

如果你对这篇文章感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

最近邻搜索的目标是从 N N N 个对象中,快速找到距离查询点最近的对象。根据需求的不同,该任务又分为「精准查找」与「近似查找」,并且查找的目标也分为「找到前 K K K 个最近的对象」与「找到距查询点距离小于 r r r 的对象」。处理此类任务的关键在于组织已有对象的数据结构,大致分为以下三类:

  1. 树型结构:例如 Vantage-Point Tree (VP-Tree) [1]、M-Tree [2]、Cover Tree [3]、Faster Cover Trees [4] 等;
  2. 图型结构:例如 Hierarchical Navigable Small World (HNSW) [5]、Locallyadaptive Vector Quantization (LVQ) [6] 等;
  3. 哈希方法:例如 Locality Sensitive Hashing (LSH) 中的 SimHash、MinHash 等。

在当前时代下,如果你的对象就是向量,使用的距离度量也是常见度量(e.g., 欧式距离、余弦相似度等),可以直接调用一些成熟的向量数据库或最近邻检索算法库解决问题。

本文主要对一个经典的树型结构 M-Tree 进行介绍(已提出 20 多年,在数据库领域有大量应用),其特点是:

  1. 支持任意满足对称性 (Symmetry)、非负性 (Non-negativity)、三角不等式 (Triangle Inequality) 的距离度量;
  2. 支持整体数据结构的动态维护,即根据新对象的插入对整体结构做局部调整。

M-Tree 结构

M-Tree 中分为内部节点和叶节点,其中每个内部节点包含一个路由对象 (Routing Objects) O r O_r Or,并且动态维护该路由节点所覆盖的半径 r ( O r ) r(O_r) r(Or),即 O r O_r Or 对应子树中的所有点,距 O r O_r Or 的距离小于等于 r ( O r ) r(O_r) r(Or)。维护每个内部节点的覆盖半径,可用于后续近邻查搜过程中的快速剪枝,避免遍历整棵树。

每个内部节点还需维护其与父节点 P ( O r ) P(O_r) P(Or) 之间的距离(同样用于后续查搜时的快速剪枝),具体维护的信息如下所示:
在这里插入图片描述
与内部节点不同的是,每个叶节点无需维护其覆盖半径,并且该节点直接指向具体的数据点,具体维护的信息如下所示:
在这里插入图片描述

需要注意的是,所有的数据点都会出现在叶节点中,内部节点中的路由对象为一些 “关键” 数据点的拷贝。


M-Tree 查搜

近邻查搜通常分为两种,一种是 Range Queries,即对于待查询数据 Q Q Q,查找所有满足 d ( O j , Q ) ≤ r ( Q ) d(O_j,Q)\leq r(Q) d(Oj,Q)r(Q) 的数据 O j O_j Oj;另一种是 Nearest Neighbor Queries,即查找距查询数据 Q Q Q 最近的前 k k k 个数据点。

首先介绍 Range Queries,其查搜时有两种剪枝方式。假设当前查询节点为 O r O_r Or,其与查询点 Q Q Q 之间的距离为 d ( O r , Q ) d(O_r,Q) d(Or,Q),则 O r O_r Or 子树内任意点 O ′ O' O 满足(距离三角不等式):
d ( O ′ , Q ) ≥ d ( O r , Q ) − d ( O ′ , O r ) ≥ d ( O r , Q ) − r ( O r ) . d(O',Q)\geq d(O_r,Q)-d(O',O_r)\geq d(O_r,Q)-r(O_r). d(O,Q)d(Or,Q)d(O,Or)d(Or,Q)r(Or).

因此如果 O r O_r Or 满足 d ( O r , Q ) > r ( Q ) + r ( O r ) d(O_r,Q)>r(Q)+r(O_r) d(Or,Q)>r(Q)+r(Or),即 d ( O ′ , Q ) > r ( Q ) d(O',Q)>r(Q) d(O,Q)>r(Q),则无需再遍历 O r O_r Or 子树内的节点,此为第一种剪枝方式。

此外,假设 O r O_r Or 的父节点为 O p O_p Op,其距 Q Q Q 的距离为 d ( O p , Q ) d(O_p,Q) d(Op,Q),则 O r O_r Or 子树内任意点 O ′ O' O 满足(距离三角不等式):
d ( O ′ , Q ) ≥ d ( O r , Q ) − r ( O r ) ≥ ∣ d ( O r , Q p ) − d ( O p , Q ) ∣ − r ( O r ) . d(O',Q)\geq d(O_r,Q)-r(O_r)\geq |d(O_r,Q_p)-d(O_p,Q)|-r(O_r). d(O,Q)d(Or,Q)r(Or)d(Or,Qp)d(Op,Q)r(Or).

因此如果 O r O_r Or 满足 ∣ d ( O r , Q p ) − d ( O p , Q ) ∣ > r ( Q ) + r ( O r ) |d(O_r,Q_p)-d(O_p,Q)|>r(Q)+r(O_r) d(Or,Qp)d(Op,Q)>r(Q)+r(Or),则可以直接将整颗 O r O_r Or 子树剪枝,并且无需计算 d ( O r , Q ) d(O_r,Q) d(Or,Q),此为第二种剪枝。

以下为 M-Tree 执行 Range Queries 的具体算法:

在这里插入图片描述

接下来是 Nearest Neighbor Queries,其剪枝思路与 Range Queries 类似,只是将查询范围 r ( Q ) r(Q) r(Q) 换成了 d k d_k dk,即当前距 Q Q Q k k k 小的距离。另外 d m i n ( T ( O r ) ) = max ⁡ { d ( O r , Q ) − r ( O r ) , 0 } , d m a x ( T ( O r ) ) = d ( O r , Q ) + r ( O r ) d_{min}(T(O_r))=\max\{d(O_r,Q)-r(O_r),0\},d_{max}(T(O_r))=d(O_r,Q)+r(O_r) dmin(T(Or))=max{d(Or,Q)r(Or),0},dmax(T(Or))=d(Or,Q)+r(Or) 分别代表 O r O_r Or 子树中距 Q Q Q 可能的最小和最大距离,执行 Nearest Neighbor Queries 的具体算法如下所示:

在这里插入图片描述


M-Tree 构建

M-Tree 的构建为依次插入新的数据点,其中每个叶节点可以存储多个数据点,如果叶节点满了,则会进行节点分裂,进而动态地维护整颗树结构。

M-Tree 数据插入

在每一层插入新数据点时,首先选择不会使内部节点覆盖半径变大的节点,如果不存在这样的节点,则选择使覆盖半径变大幅度最小的节点,具体算法如下所示:

在这里插入图片描述

M-Tree 节点分裂

如果最后插入的叶节点满了,则需要进行分裂。具体分裂过程为:

  1. 从当前节点 N 的所有数据点 + 新数据点 O n O_n On 中选出两个代表节点 O p 1 O_{p_1} Op1 O p 2 O_{p_2} Op2,对应 Promote 操作;
  2. 将这些数据点集合 N \mathcal{N} N 分为距 O p 1 O_{p_1} Op1 更近的 N 1 \mathcal{N}_1 N1 和距 O p 2 O_{p_2} Op2 更近的 N 2 \mathcal{N}_2 N2,对于 Partition 操作;
  3. 将 N 中的路由节点替换为 O p 1 O_{p_1} Op1,并判断 N 的上层节点是否满了,如果没满则插入 O p 2 O_{p_2} Op2,否则继续向上进行分裂。

整体算法如下所示:

在这里插入图片描述

节点分裂之后,仍需维护新增节点的覆盖半径,具体如下:
r ( O p 1 ) = max ⁡ { d ( O r , O p 1 ) + r ( O r ) ∣ O r ∈ N 1 } . r\left(O_{p_1}\right)=\max \left\{d\left(O_r, O_{p_1}\right)+r\left(O_r\right) \mid O_r \in \mathcal{N}_1\right\}. r(Op1)=max{d(Or,Op1)+r(Or)OrN1}.

节点分裂策略

两个代表点的选择有多种策略,具体可以查看原文,此处介绍几种代表性策略:

  1. m_RAD: 计算 N \mathcal{N} N 中所有节点两两之间距离,选择最小化 r ( O p 1 ) + r ( O p 2 ) r(O_{p_1})+r(O_{p_2}) r(Op1)+r(Op2) 的代表点;
  2. mM_RAD: 计算 N \mathcal{N} N 中所有节点两两之间距离,选择最小化 max ⁡ { r ( O p 1 ) , r ( O p 2 ) } \max\{r(O_{p_1}),r(O_{p_2})\} max{r(Op1),r(Op2)} 的代表点;
  3. m_LB_DIST: 将 O p 1 O_{p_1} Op1 设置为原节点 O p O_p Op,选择距离 O p O_p Op 最远的点作为 O p 2 O_{p_2} Op2.

代码实现

  • 原作者代码实现 - C++
  • Github 其他开源版本 - Python
  • Github 其他开源版本 - C++ / Java / Python

参考资料

  1. VLDB 1994 - Content-based image indexing.
  2. VLDB 1997 - M-tree: An Efficient Access Method for Similarity Search in Metric Spaces.
  3. ICML 2006 - Cover Trees for Nearest Neighbor.
  4. ICML 2015 - Faster Cover Trees
  5. TPAMI 2020 - Hierarchical Navigable Small World
  6. VLDB 2023 - Similarity search in the blink of an eye with compressed indices

http://www.ppmy.cn/ops/141171.html

相关文章

MAVEN--Maven的生命周期,pom.xml详解,Maven的高级特性(模块化、聚合、依赖管理)

目录 (一)Maven的生命周期 1.Maven的三套生命周期 2.Maven常用命令 (二)pom.xml详解 (三)Maven的高级特性(模块化、聚合、依赖管理) 1.Maven的依赖范围 2.版本维护 3.依赖传…

无人机飞手考证后从事吊运植保创业技术详解

无人机飞手考证后从事吊运植保创业的技术详解如下: 一、无人机飞手考证流程 1. 报名与资格审核: 选择正规培训机构,提交身份证明、学历证明等相关材料。 通过初步审核,确保学员满足年龄(年满16周岁)、身…

CGM:卡与应用内存管理

内存资源管理 1. 管理概述 1.1 可选功能说明 1.2 管理主体责任 2. 资源分配规则 2.1 加载时分配 2.1.1 最小内存要求 2.1.2 资源可用性检查 2.2安装时分配 2.2.1 内存配额设定 2.2.2 预留内存处理 3. 资源使用管理 3.1 应用运行时分配 3.1.1 数据存储分配 3.1.2 资源耗尽处理 3…

QT 中 QDateTime::currentDateTime() 输出格式备查

基础 QDateTime::currentDateTime() //当前的日期和时间。 QDateTime::toString() //以特定的格式输出时间,格式 yyyy: 年份(4位数) MM: 月份(两位数,07表示七月) dd: 日期(两位数&#xff0c…

2024小迪安全基础入门第十一课

目录 一、算法识别加解密-MD5&AES&DES&RSA #算法加密-概念&分类&类型 二、 解密条件-有源码找逻辑&无源码JS逆向 #加密解密-识别特征&解密条件 #密码存储 一、算法识别加解密-MD5&AES&DES&RSA 安全测试中: 密文-有源…

ARM学习(36)静态扫描规则学习以及工具使用

笔者来学习了解一下静态扫描以及其规则,并且亲身是实践一下对arm 架构的代码进行扫描。 1、静态扫描认识 静态扫描:对代码源文件按照一定的规则进行扫描,来发现一些潜在的问题或者风险,因为不涉及代码运行,所以其一般…

网管平台(基础篇):路由器的介绍与管理

路由器简介 路由器(Router)是一种计算机网络设备,它的主要作用是将数据通过打包,并按照一定的路径选择算法,将网络传送至目的地。路由器能够连接两个或更多个网络,并根据信道的情况自动选择和设定路由&…

Kafka集群创建

上次集群忘了写文档,这次集群创建zk和kafka放在了一起,版本和生产一致,所以使用低版本 2.8.6 一、准备配置 1.1、配置env $ cat /etc/profile.d/kafka.sh # Java Environment export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export P…