向量检索原理入门:从 ANN 到 HNSW
做 RAG、推荐系统时,百万级向量检索慢得像蜗牛?cosine 和 L2 该用哪个?面试官一问「召回率怎么算」就卡壳?这篇文章用最直观的方式,把向量检索的核心原理讲透,并标注每个考点的面试追问方向。
目录
- 为什么不能用暴力搜索?
- ANN:用近似换速度
- HNSW:分层导航的小世界图
- cosine vs L2:方向还是距离?
- 参数调优:召回率与延迟的权衡
- 面试考点速查表
1. 为什么不能用暴力搜索?
先看最朴素的做法:精确最近邻检索。给定一个查询向量,遍历库里所有向量,算一遍距离,取最近的 k 个。
python
import numpy as np
def brute_force(query, vectors, k=10):
"""暴力扫描:O(n) 线性遍历"""
dists = np.linalg.norm(vectors - query, axis=1)
idx = np.argsort(dists)[:k]
return idx, dists[idx]
问题在哪?
- 复杂度 O(n):100 万条 × 768 维,每次查询要算 100 万次距离,延迟轻松到几十毫秒甚至上百毫秒。
- 维度灾难(curse of dimensionality):维度越高,向量在高维空间越稀疏,所有点之间的距离趋同,精确检索的「精确」意义也在减弱。
🎯 面试考点 :面试官问「为什么不用暴力搜索」,别只答「太慢」,要说出 O(n) 线性扫描 + 维度灾难 两层原因。
2. ANN:用近似换速度
ANN(Approximate Nearest Neighbor,近似最近邻) 的核心思想:以可接受的精度损失,换取数量级的速度提升。
它不保证返回一定 是真正的最近邻,只保证返回足够接近的邻居。
核心指标:召回率 Recall@k
ini
Recall@k = ANN 返回的 k 个结果中,有多少比例是真正的 top-k 最近邻
例:精确检索返回 [A,B,C],ANN 返回 [A,C,D],则 recall@3 = 2/3。
🎯 面试考点 :ANN 的「近似」不是玄学,是有量化指标的。Recall@k 与延迟的权衡 是工程落地的核心,也是贯穿全文的主线。
3. HNSW:分层导航的小世界图
HNSW(Hierarchical Navigable Small World)是目前最主流的 ANN 算法。拆开看:
3.1 NSW:单层小世界图
NSW 本身是单层 的。它的「小世界」结构借鉴了现实社交网络的六度分隔思想:
- 每个节点只连接少数几个邻居
- 但通过少数几步就能到达任意节点
搜索时用贪心算法导航:从入口节点出发,每次走到「比当前更接近目标」的邻居,直到无法再改进。
3.2 HNSW:加一层「跳表」
HNSW 在 NSW 之上加了分层,借鉴**跳表(Skip List)**思想:
- 高层 :节点少、连接稀疏 → 负责快速跨越长距离
- 底层 :节点全、连接稠密 → 负责精细定位
搜索时从最高层入口开始,逐层贪心逼近、向下钻取,最后在底层做精细检索。
🎯 面试考点 :NSW 是单层的,HNSW 才是分层------这是最容易混淆的硬伤。面试官一听就知道你有没有吃透。
4. cosine vs L2:方向还是距离?
4.1 数学定义
公式(竖线在表格里会被误判为分隔符,所以单独放):
text
cosine:cos(θ) = (A·B) / (|A| × |B|)
L2:√(Σ(ai-bi)²)
| cosine 相似度 | L2 距离 | |
|---|---|---|
| 衡量 | 方向一致性 | 绝对位置差异 |
| 范围 | -1, 1,越大越相似 | [0, +∞),越小越近 |
| 对模长 | 不敏感 | 敏感 |
4.2 归一化后:两者等价(核心考点)
若 A、B 都是单位向量(|A|=|B|=1):
css
|A-B|² = |A|² + |B|² - 2A·B = 2 - 2cos(A,B)
所以:L2 距离越小 ⟺ cosine 相似度越大,两者排序完全一致。
但注意 :等价的前提是都归一化。不归一化时,一个向量模长很大但方向接近,cosine 高但 L2 远,排序可能完全不同。
🎯 面试考点 :这是本题最核心的考点。很多系统(如 Faiss)先归一化再用 L2,因为 L2 计算更快、效果等价。
4.3 场景怎么选?
判断标准不是「文本用 cosine、聚类用 L2」这种死记,而是问:我关心的是方向还是绝对位置?
- 文本 embedding:关心语义方向,且模长受文本长度影响 → 默认 cosine(或归一化后 L2)
- 聚类(K-means):基于质心欧氏距离 → 默认 L2;特征归一化后用 cosine 也等价
- 异常检测:偏离中心的距离更直观 → L2
5. 参数调优:召回率与延迟的权衡
HNSW 参数分构建期 和查询期两组:
5.1 构建期参数(决定「图长什么样」)
| 参数 | 含义 | 影响 |
|---|---|---|
| M | 每个节点最大邻居数 | 越大图越稠密、召回率越高,但内存和构建时间越大(典型 16~64) |
| M_max | 底层最大连接数(≈2M) | 控制底层稠密程度,防止退化 |
| ef_construction | 插入时搜索的候选集大小 | 越大插入找邻居越准、图质量越高,但构建越慢(典型 100~200) |
5.2 查询期参数(决定「怎么搜」)
| 参数 | 含义 | 影响 |
|---|---|---|
| ef_search | 查询时维护的候选集大小 | 越大召回率越高、延迟越高------召回率 vs 延迟的核心旋钮 |
5.3 实战:FAISS 代码
python
import faiss
d = 768 # 向量维度
M = 16
ef_construction = 100
ef_search = 50
# 构建索引
index = faiss.IndexHNSWFlat(d, M)
index.hnsw.efConstruction = ef_construction
index.add(vectors) # 构建期
# 查询
index.hnsw.efSearch = ef_search
D, I = index.search(query, k=10) # 查询期
⚠️ 踩坑提醒 :构建期参数(M、ef_construction)线上已固定,改它们要重建索引 。线上延迟超标,只能调 ef_search ;而且 ef_search 要先离线评测 recall-延迟曲线再定值,不是拍脑袋。
6. 面试考点速查表
| 考点 | 一句话答案 | 加分点 |
|---|---|---|
| 为什么用 ANN? | 用可接受的召回率损失换数量级速度提升 | 说出 O(n) 扫描 + 维度灾难 |
| Recall@k 是什么? | ANN 返回结果中真正 top-k 的比例 | 强调它是工程落地核心指标 |
| HNSW 原理? | 分层(跳表)+ 小世界图(贪心导航) | 强调 NSW 是单层、HNSW 才分层 |
| cosine vs L2? | cosine 看方向、L2 看距离 | 归一化后等价:|A-B|²=2-2cos |
| 怎么调参? | 构建期管图质量、查询期管权衡 | 强调 ef_search 靠实测、构建期不能混调 |
总结:核心框架一句话背下来
scss
向量检索 = 距离度量 + 索引算法 + 参数调优
距离度量:cosine 看方向 / L2 看距离 → 归一化后等价
索引算法:HNSW = 分层(跳表) + 小世界(贪心导航)
参数调优:构建期(M, ef_construction)管图质量
查询期(ef_search)管召回率 vs 延迟
核心指标:Recall@k 与延迟的权衡
💡 面试加分建议
- 主动串知识点:比如方案设计时提到「归一化后 L2 更快」,体现体系化理解。
- 提备选方案:IVFPQ 更省内存但召回率略低、暴力扫描延迟不可接受------说明取舍比只说「选 HNSW」更有说服力。
- 强调工程思维:先离线评测、再上线,这是面试官最看重的。
🛠️ 实战背书
这篇不是纯理论------我的 RAG 项目(FastAPI + ChromaDB)底层用的正是 hnswlib 的 HNSW,文中每个参数(M、ef_construction、ef_search)都在真实问答管线上验证过。面试时讲"我用过 + 知道为什么这么调",比只会背定义值钱得多。
如果这篇文章对你有帮助,欢迎点赞收藏~ 后续可以继续更新:IVFPQ 量化原理、向量数据库(Milvus/FAISS/Qdrant)选型实战、RAG 检索优化实战。 🚀