ANN,IVF,HNSW

哈喽大家好!

上回我们聊到了向量数据库是 LLM 的"外挂硬盘",而相似性搜索是这个硬盘的"检索魔法"。

今天这篇是硬核实战篇!我们不整那些虚头巴脑的概念,直接把工业界最主流、最核心的三个算法:K-Means 聚类、IVF 倒排索引、以及大名鼎鼎的 HNSW,彻底拆解清楚!

别被这些英文缩写吓跑,其实它们的原理,和你平时在超市找商品、或者在城市里找路的逻辑是一模一样的。


📌 还记得我们的专利案例吗?

在上一篇里,我们设定了一个贯穿全系列的案例

场景:你在科技公司搜索研发部实习,要从 1000 万条专利 中检索相似专利。

查询"一种基于深度学习的图像去噪方法"

目标:找到最相关的 3 条专利(真实答案是:卷积神经网络降噪、深度学习图像增强、自编码器去噪)

今天我们就用这个案例,一步步演示 K-Means、IVF、HNSW 到底是怎么工作的!


一、 核心思想:ANN(近似最近邻)

在讲具体算法前,我们要先达成一个共识:ANN (Approximate Nearest Neighbor)

翻译成人话就是:“差不多得了,要的是速度”

在海量数据(比如 1 亿条)面前,要找到“绝对”最近的那个向量(暴力搜索),计算机得算断气。ANN 的目标是:允许你稍微有一点点误差(比如本来是第 1 名,你找出了第 2 名),但速度要快几百倍,内存要省几倍。

接下来的所有算法,都是为了实现这个“又快又准”的平衡。


二、 基础中的基础:K-Means 聚类

在讲高级的 IVF 之前,必须先懂 K-Means,因为它是很多索引算法的“地基”。

1. 这是在干嘛?

想象操场上站了 1000 个学生(向量),乱七八糟的。教官吹哨子:“所有人分成 4 个班,每班选个班长!”
K-Means 就是这个自动分班、选班长的过程。

2. 算法怎么动?(迭代过程)

这是一个“动态调整”的过程:

  1. 随机点将:电脑先在人堆里随便指 4 个人当“临时班长”(质心 Centroids)。
  2. 各自归队:剩下 996 个人,看自己离哪个临时班长最近,就站到哪个班的队伍里。
  3. 篡位(重新计算):队伍站好后,大家发现临时班长其实站得偏了。于是算出这个队伍真正的“正中心”位置,那个位置变成“新班长”。
  4. 循环:新班长上任 -> 大家根据新班长调整队伍 -> 再算新中心……直到班长的位置不再变动,队伍稳定了。

我画个图你就能看懂这个循环:

1
2
3
4
5
6
7
8
9
10
graph TD
Start((开始)) --> Random[1. 随机选 K 个中心点]
Random --> Assign[2. 所有点找最近的中心点归队]
Assign --> Recalculate[3. 计算每队的新中心坐标]
Recalculate --> Check{中心点位置变了吗?}
Check -- 变了很多 --> Assign
Check -- 没变或微调 --> End((收敛完成))

style Start fill:#f9f
style End fill:#bfb

3. 有啥 Bug?

现实中,数据是密密麻麻连在一起的。如果查询点刚好在两个班级的交界处(边界相邻),它的"真爱"可能在隔壁班,但因为它离本班班长稍微近了 0.01 毫米,它就被锁死在本班里找,结果就错过了隔壁班最近的那个邻居。

这个问题怎么解决?看下面 IVF 的操作。

🔬 专利案例演示:K-Means 聚类过程

让我们看看 1000 万条专利是怎么被"分班"的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🏫 K-Means 分班过程(K=1000,即分成 1000 个班)

第 1 轮:随机选了 1000 个"临时班长"
- 班长 #127:某个机械专利
- 班长 #456:某个化学专利
- 班长 #789:某个图像处理专利 ← 我们的查询可能会落在这附近!
...

第 2 轮:所有专利找最近的班长归队
- "卷积神经网络降噪" → 归入 #789 班
- "深度学习图像增强" → 归入 #789 班
- "自编码器去噪" → 归入 #791 班 ← 注意!它去了隔壁班!
...

第 3 轮:重新计算每个班的中心位置,更新班长
...

第 47 轮:班长位置稳定,聚类完成!

关键发现:我们的三个"真爱"专利,有 2 个在 #789 班,1 个在 #791 班!

这就是为什么后面 IVF 要用 nprobe 参数——如果只搜 1 个班,就会漏掉那个在隔壁班的"自编码器去噪"!


三、 中流砥柱:IVF (倒排文件索引)

IVF (Inverted File Index) 是目前数据库里用得超级多的一种技术。它的灵感直接来源于 K-Means。

1. 什么是“倒排”?

  • 在搜索引擎里,倒排是:单词 -> 包含这个词的文章列表
  • 在向量数据库里,倒排是:班长(中心点) -> 属于这个班的所有向量列表

2. Voronoi 单元(泰森多边形)[了解]

你在很多技术文章里会看到“Voronoi”这个词,其实就是划分势力范围
空间中每一个点,都有一个对应的“最近中心点”。把所有离中心点 A 最近的区域画个圈,就是 A 的地盘(Voronoi 单元)。这就把无限的空间切成了有限的小块。

3. 检索过程(怎么找?)

这招叫:擒贼先擒王

假设来了一个查询向量 Query(比如用户问“哪里有可爱的猫”):

  1. 找班长(粗筛):先别管那是几亿个小兵,先拿 Query 去跟那几千个“班长”比。算算离哪个班长最近。
  2. 搜小兵(细筛):找到最近的班长后,通过倒排表拿到这个班的花名册,只在这个班里挨个算距离。

这样,计算量瞬间从“1 亿次”变成了“几千次(找班长)+ 几百次(班里找)”,速度起飞!

4. 解决边界问题的绝招:nprobe

为了防止“真爱在隔壁班”的情况,我们不光搜最近的那 1 个班,我们搜最近的 n 个班。
这就是参数 nprobe 的含义。

  • nprobe = 1:只搜最近的 1 个笼子,快但可能漏。
  • nprobe = 10:搜最近的 10 个笼子,稍微慢点,但基本不会漏。

🔬 专利案例演示:IVF 检索全过程

好,现在让我们完整走一遍 IVF 检索流程!

Step 1:查询来了

1
2
3
4
5
用户输入:"一种基于深度学习的图像去噪方法"

Embedding 模型处理

得到 768 维查询向量 Query

Step 2:找班长(粗筛)

1
2
3
4
5
6
Query1000 个班长(中心点)比距离:

班长 #789(图像处理类):距离 0.08 ← 最近!
班长 #791(计算机视觉类):距离 0.11 ← 第二近
班长 #456(化学类):距离 0.89 ← 太远了,不管
...

Step 3:搜小兵(细筛)

假设 nprobe = 1(只搜最近的 1 个班):

1
2
3
4
5
6
7
8
9
10
📊 IVF 搜索结果(nprobe=1)

只搜 #789 班,班里有 8000 条专利
逐个计算距离...

✅ 第 1 名:基于卷积神经网络的图像降噪系统(距离 0.12)
✅ 第 2 名:深度学习图像增强方法(距离 0.15)
❌ 第 3 名:某个图像分割专利(距离 0.35)

召回率:67%(漏了"自编码器去噪",因为它在 #791 班!)

假设 nprobe = 3(搜最近的 3 个班):

1
2
3
4
5
6
7
8
9
10
📊 IVF 搜索结果(nprobe=3

搜 #789、#791、#788 三个班,共约 25000 条专利
逐个计算距离...

✅ 第 1 名:基于卷积神经网络的图像降噪系统(距离 0.12
✅ 第 2 名:深度学习图像增强方法(距离 0.15
✅ 第 3 名:图像去噪的自编码器实现(距离 0.18) ← 从 #791 班捞回来了!

召回率:100%

结论nprobe 就是你的"保险系数"。调大一点,多花点时间,但不容易漏人!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph TD
Q[用户查询 Query] -->|1. 对比所有中心点| Centroids{计算离谁最近}
Centroids -->|最近| C1["班级 A (中心点)"]
Centroids -->|第二近| C2["班级 B (中心点)"]
Centroids -->|第三近| C3["班级 C (中心点)"]

subgraph Range ["nprobe=3 的搜索范围"]
C1 --> Scan1["扫描 A 班所有向量"]
C2 --> Scan2["扫描 B 班所有向量"]
C3 --> Scan3["扫描 C 班所有向量"]
end

Scan1 & Scan2 & Scan3 --> Result[汇总排序,返回 Top K]

style Q fill:#f9f,stroke:#333
style Result fill:#bfb,stroke:#333

四、 HNSW (分层图索引)

IVF 虽然好,但它有个瓶颈:如果维数特别高,找班长也是要时间的。而且数据分布不均匀时,有的班特别挤,有的班没人。

于是,HNSW (Hierarchical Navigable Small World) 横空出世。它是目前性能最强的算法之一。

1. NSW:小世界理论

你肯定听过“六度人脉”:你通过不超过 6 个人,就能认识美国总统。
在图论里,这意味着:虽然节点很多,但任意两点之间的路径其实很短。

NSW (Navigable Small World) 的朴素思想是:贪婪搜索

  • 我要找目标点 T。
  • 我先随便从一个入口点 E 进去。
  • 看看 E 的朋友里,谁离 T 最近?假如是 A。
  • 那就跳到 A。再看 A 的朋友里谁离 T 最近?
  • 一步步逼近,直到找不到更近的朋友为止。

2. HNSW:给 NSW 修高架桥

NSW 的问题是:如果我不认识那个能“跨越半个地球”的朋友,我就得像走迷宫一样一步步挪,也就是“步长太短”。
HNSW 的改进核心是:分层 (Hierarchical)

你可以把它想象成交通系统

  • 第 0 层 (最底层):包含所有数据,大家都是邻居,密密麻麻(社区街道)。
  • 第 1 层:抽出一部分节点,建个稀疏点的网(城市主干道)。
  • 第 2 层 (顶层):再抽出一部分,建个超稀疏的网(高速公路/航线)。

3. 检索过程:跳伞战术

搜索过程就像坐飞机 -> 转高铁 -> 打车 -> 步行

  1. 从顶层出发:视野开阔,一步能跨很远。快速锁定大概区域(比如从北京定位到上海)。
  2. 降落一层:在那个大概区域里,利用更密的网,稍微精细一点找(定位到徐汇区)。
  3. 降落到底层:在最密集的网里,进行最后的精确搜索(找到那家咖啡馆)。

🔬 专利案例演示:HNSW 检索全过程

让我们跟着查询向量,走一遍 HNSW 的"跳伞之旅"!

第 2 层(高速公路层):只有 500 个节点

1
2
3
4
5
6
7
8
9
10
🛫 从入口点出发

当前位置:入口点(某个通用技术专利)
目标:找到"深度学习图像去噪"相关专利

1 跳:入口点 → "人工智能总类"节点(距离目标 0.6
2 跳:"人工智能总类""计算机视觉大类"节点(距离目标 0.3
3 跳:"计算机视觉大类""图像处理枢纽"节点(距离目标 0.15

找不到更近的邻居了,准备降落!

第 1 层(城市主干道层):有 5000 个节点

1
2
3
4
5
6
🚄 降落到第 1 层,从"图像处理枢纽"继续

4 跳:"图像处理枢纽""深度学习图像类"(距离目标 0.09
5 跳:"深度学习图像类""图像增强/去噪类"(距离目标 0.05

找不到更近的了,继续降落!

第 0 层(社区街道层):全部 1000 万个节点

1
2
3
4
5
6
7
🚶 降落到底层,精细搜索

6 跳:"图像增强/去噪类" → "卷积神经网络降噪"(距离 0.12)✅ 找到第 1 个!
7 跳:检查邻居 → "深度学习图像增强"(距离 0.15)✅ 找到第 2 个!
8 跳:检查邻居 → "自编码器去噪"(距离 0.18)✅ 找到第 3 个!

搜索完成!总共只跳了 8 步!

对比一下

  • 暴力搜索:比较 1000 万次
  • HNSW:只比较了约 200 次(每跳检查几十个邻居)

这就是 HNSW 的魔力:通过分层 + 贪婪跳跃,把 O(n) 的复杂度降到了 O(log n)!

我画了个分层图帮大家理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
graph TD
subgraph Layer2 [第 2 层:高速公路]
Entry(入口点) -->|长距离跳跃| NodeA(枢纽节点 A)
end

subgraph Layer1 [第 1 层:城市主干道]
NodeA -.->|降落| NodeA_L1
NodeA_L1 -->|中距离接近| NodeB(区域节点 B)
end

subgraph Layer0 [第 0 层:所有数据]
NodeB -.->|降落| NodeB_L0
NodeB_L0 -->|短距离逼近| NodeC(邻居 1)
NodeB_L0 -->|短距离逼近| NodeD(邻居 2)
NodeC <-->|最终确认| Target(目标结果!)
end

style Entry fill:#f96
style Target fill:#bfb

4. HNSW 的优缺点 (Trade-off)

天下没有免费的午餐,HNSW 这么强,代价是什么?

  • 优点非常快!召回率极高! 它是目前最接近完美的搜索算法。

  • 缺点吃内存(空间换时间)

  • 除了存向量本身,还得存它在每一层的“朋友关系”(邻接表)。数据量越大,这些关系占的内存越多。

  • 建索引慢:每插入一个新数据,都要计算它在每一层跟谁连,写入速度不如 IVF。


五、 终极 PK

看完这三大金刚,咱们最后做一个大总结,帮你决定怎么选:

算法 原理一句话 核心优势 核心劣势 适用场景
Flat 暴力硬算,绝不偷懒 100% 准确 慢到令人发指 数据极少(<1万条)做验证时用
IVF 划分地盘,擒贼先擒王 内存占用低,速度均衡 精度受 nprobe 影响,需要预训练 数据量大(亿级),内存预算有限,追求高性价比
HNSW 修建分层高速公路 最快,最准 内存占用大,写入稍慢 追求极致检索性能,内存管够,实时性要求高

小白选型指南:

  • 默认首选:如果不差内存,无脑上 HNSW。现在的向量数据库(Milvus, Chroma, Weaviate)很多默认都推这个。
  • 海量省钱:如果数据量几千万上亿,想省点内存,用 IVF_PQ(IVF倒排 + PQ量化压缩)。

好啦!这次咱们把**“怎么聚类分班”“怎么倒排查表”“怎么分层跳跃”**这三个最硬核的逻辑都拆干净了。

希望这篇“人话版”教程能让你下次看到这些技术名词时,脑海里直接浮现出“找班长”和“修高速”的画面,那是真的懂了!

我是 Richard,咱们下期见!👋