相似检索:距离、混合搜索(即语义与关键词两路并用,详见第 3 节)与索引
这一章讲三件事: 两个向量怎么算出「像不像」的分数(拿真实的数算一遍); 为什么纯语义搜索会漏,关键词搜索必须回来(混合检索);数据规模变大时,搜索算法怎么换档。 原书开宗明义:这一章讲的就是 RAG 里的那个 R1。
1. 先分清三个被混用的词
向量搜索(vector search)、相似算法(similarity algorithm)、距离度量(distance metric)—— 这三个词经常被混着用,其实是三层包含关系2:向量搜索是整个检索机制; 它内部用某种相似算法(如 k-NN、ANN);算法算相似时用某种距离度量(如余弦——即两根向量方向的夹角大小、欧氏——即直线距离)。 分不清这三层,后面读任何检索文档 都会晕。
还要收一个词:向量空间(vector space)——所有向量住的那个高维空间,也叫 embedding space 或 latent space。搜索就发生在这个空间里:语义相近的文本,向量在空间里挨得近3。
一个反直觉的几何事实: 把 1536 维的向量投影到二维图上看,有些「没被选中」的点看起来 比检索结果更靠近查询点——但那只是投影的错觉。检索结果是按全部维度算出来的, 在完整空间里它们确实更近。原书用一张图专门讲这个骗局4。
2. 主走查:两条毯子评论,算三种距离
原书给了一组可以亲手复现的实验。三条句子5:
A. This blanket has such a cozy temperature for me!(这条毯子温度真舒服)
B. I am so much warmer and snug using this spread!(这条毯子让我更暖和)
C. Taylor Swift was 34 years old in 2024.(一条无关评论)
用 sentence-transformers 的小模型 paraphrase-MiniLM-L6-v2 把它们嵌入,
输出形状是 (3, 384)——3 条向量,每条 384 维。现在两两算距离(下面的数全部是书里的真实运行结果)6:
| 度量 | A↔B(同义) | A↔C(无关) | B↔C(无关) | 怎么读 |
|---|---|---|---|---|
| 欧氏距离 L2 | 4.62 | 7.31 | 6.34 | 越低越相似 |
| 点积(dot product,两串数对应相乘再相加) | 12.27 | −0.77 | 0.95 | 越高越相似,可为负 |
| 余弦距离 cosine | 0.45 | 0.97 | 0.95 | 越低越相似 |
三个度量给出了同一个判断:A 和 B 是一伙的(意思都是「毯子暖和」), C 离它们远远的。这就是语义搜索的全部底层: 把「意思」变成「几何位置」, 「找相似的句子」变成「找近邻的点」。余弦距离那组数字还顺手演示了一个细节: 0.45 只算中等相似——毕竟两条评论措辞完全不同;作者的总结很妙: 「从数学上看,『Taylor Swift』不是一条暖和毯子的语义等价物」7。
三个度量怎么选?书里的用法差异:欧氏算的是直线距离(适合关心「绝对位置」的场景); 点积严格说不是距离而是投影量级,一个向量在另一个方向上伸得越远分越高; 余弦只看方向不看长短,对「文本长度不同」最不敏感,是文本检索里最常用的8。 此外还有 Manhattan、Jaccard、Hamming 等一串备选,书里点名列了9。
3. 语义搜索的死角:关键词必须回来
3.1 dense(语义)搜索什么时候失灵
第 04 章的向量是稠密向量(dense,每位都有值),搜它就是 dense search。它擅长「意思相近」, 但有两种输入它会抓瞎10:
- 训练域之外的内容。 模型只在它见过的语义世界里认路;
- 指称性文本。 产品货号、代码、订单号、人名——这些字符串没有「意思」可言, 嵌入出来毫无区分度。查这类东西,老老实实用字符串匹配。
3.2 sparse(关键词)搜索回来了
稀疏向量(sparse vector)是按词表(即这份语料里出现过的全部单词的清单)逐词计数得到的向量——词表几万个词,一句话只用了几十个, 所以向量里几乎全是零,「稀疏」由此得名。bag-of-words(词袋)是最朴素的实现:数词,谁匹配多谁赢11。
这一代的关键词之王是 BM25:它在 TF-IDF 的基础上加了两件事——出现太频繁的词降权, 稀有词加分。听着耳熟?它就是第 04 章那个 1972 年算法的直系后代, 直到今天仍是「最常用的算法之一」12。
3.3 hybrid:两个都要,RRF 融合
语义管「换个说法也能命中」,关键词管「专名代码一个不漏」——混合检索(hybrid search) 两路都跑,把结果融合排序13。
融合时有个真实的工程难题:语义搜索给的是距离分,BM25 给的是相关度分, 两种分数不在一个尺度上,没法直接加。解法是 RRF(Reciprocal Rank Fusion,倒数排名融合): 不比分数,只比名次——每个文档的得分是它在各路结果里名次的倒数(第 1 名得 1.0,第 2 名得 0.5……), 两路相加。因为只用名次,不需要把异质分数归一化——归一化即把不同尺度的分数换算到同一把尺子上——这是它最聪明的地方14。
原书的代码实验室把数据源换成了 Google 的环境报告 PDF,同时跑 dense(k=10)与 BM25(k=10),
再手写了一个模仿 RRF 的融合函数;同时点明:LangChain 里 EnsembleRetriever 一行就能干同样的事15。
4. 规模换档:k-NN 到 ANN
4.1 k-NN:暴力但最准
k-NN(k 近邻)的做法最朴素:查询向量和库里每一个向量算距离,排序,取前 k 个16。 复杂度(算力开销随规模增长 的速度)是 O(n·d)——数据翻倍,查询时间翻倍;数据到了百万、十亿级,物理上就不可行了17。
但作者的实战经验值得整段记住:k-NN 仍然是最准的。他做过 2.5 万到 3 万条向量、256 维的项目, k-NN 的检索评测指标比近似方案高 2%–6%——「足以抵消计算成本的小幅增加」18。
4.2 ANN:牺牲一点准,换回大量快
ANN(近似最近邻)是一族算法的统称:用索引结构把搜索空间缩小到「可能是近邻」的一小撮候选, 只对候选算距离。代价是可能漏掉真正的最近邻,换来的是查询时间的增长慢于数据的增长(即比线性——数据与耗时一比一同步涨——增长得更慢)——数据翻倍,等待不再翻倍19。
ANN 靠四类索引技术把范围缩下来20:
| 技术 | 一句话原理 |
|---|---|
| LSH | 用哈希函数把相似向量扔进同一个桶,查桶不查全库 |
| KD-tree / Ball tree | 按维度/超球递归(即一层层套用同一招)切分空间,剪掉无关分支 |
| PQ(乘积量化) | 把向量切成小段压缩,用压缩后的近似距离算 |
| HNSW | 多层「小世界」图,从随机入口一路跳向目标 |
HNSW 单独说,因为它是当下主流向量库的标配。 它的 NSW 部分:挑出一批「位置好」的节点当枢纽, 搜索从随机入口出发,每一步跳向当前节点的邻居中离查询最近的那个,大块数据被整段跳过。 H(层级)部分的比喻,原书给得很形象:像出门远行——先坐飞机到最近的机场,再换火车到小镇, 最后在小范围内步行找目的地——每降一层,候选范围缩小一圈21。
这个设计还有个人文出处:灵感来自人类社交网络的「六度分隔」——任何两个人平均只要六层朋友关系就能连上22。
4.3 换档判据
作者的实操判据:先上 k-NN,等到「处理时间的增长让人无法忍受」再换 ANN。 一百万条 1536 维向量在像样的开发环境里,k-NN 依然能扛——很多小项目用着 ANN, 其实换成 k-NN 检索质量会更好23。
5. 边界与局限
- 三种距离度量没有万能赢家;余弦虽常用,但「文本长度该不该影响相似度」取决于具体应用。
- RRF 只看名次不看分数,会丢信息:语义搜索里「第一名遥遥领先」和「第一名险胜」在它眼里没区别——原书提醒了这一点24。
- 本章的服务选型列表(pgvector、Elasticsearch、FAISS、Vertex AI Vector Search、Azure AI Search、ANNOY、Pinecone、Weaviate、Chroma)是 2025 年的快照,各家的索引算法与版本更替频繁,选型前要重查25。
6. 可带走的
- 向量搜索 ⊃ 相似算法 ⊃ 距离度量——三层,别混。
- 三条毯子评论的距离表(4.62/12.27/0.45)是「语义=几何」最直观的证明。
- 产品货号、代码、人名查语义搜索会抓瞎——指称性文本走关键词。
- BM25 是 1972 年 TF-IDF 的孙子,但今天仍是关键词检索的王。
- RRF 用名次不用分数,免归一化;代价是丢掉分数的置信信息。
- k-NN 仍是最准的;小数据(<几万到百万级)直接用它,撑不住再换 ANN。
- HNSW = 飞机→火车→步行的分层跳转,配六度分隔的直觉。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| R=检索专章 | Similarity Searching with Vectors | text/31-fm-similarity-searching-with-vectors.txt:14(搜「the R or retrieval part」) |
| 三层包含关系 | Similarity Searching with Vectors | text/31-fm-similarity-searching-with-vectors.txt:64(搜「A similarity algorithm can use different distance metrics」) |
| 向量空间与别名 | Similarity Searching with Vectors | text/31-fm-similarity-searching-with-vectors.txt:83(搜「latent space」) |
| 2D 投影错觉 | Similarity Searching with Vectors | text/31-fm-similarity-searching-with-vectors.txt:90(搜「large dots」) · text/31-fm-similarity-searching-with-vectors.txt:93(搜「mathematical certainty」) |
| 三条测试句 | Semantic search example | text/32-fm-semantic-search-example.txt:56(搜「cozy temperature」) |
| 384 维输出 | Semantic search example | text/32-fm-semantic-search-example.txt:69(搜「384」) |
| 欧氏距离结果 | Euclidean distance (L2) | text/33-fm-euclidean-distance-l2.txt:26(搜「4.6202903」) |
| 点积结果 | Dot product (also called inner product) | text/34-fm-dot-product-also-called-inner-product.txt:17(搜「12.270497」) |
| 余弦距离结果与 Taylor Swift | Cosine distance | text/35-fm-cosine-distance.txt:25(搜「0.4523802399635315」) · text/35-fm-cosine-distance.txt:32(搜「not the semantic equivalent」) |
| 其他度量清单 | Cosine distance | text/35-fm-cosine-distance.txt:35(搜「Jaccard」) |
| dense 搜索的失灵场景 | Dense search | text/36-fm-dense-search.txt:7(搜「serial numbers」) |
| sparse 与词袋 | Sparse search | text/37-fm-sparse-search.txt:4(搜「mostly zeros」) · text/37-fm-sparse-search.txt:7(搜「bag of words」) |
| BM25 基于 TF-IDF | Sparse search | text/37-fm-sparse-search.txt:10(搜「Best Matching 25」) |
| hybrid 定义 | Hybrid search | text/38-fm-hybrid-search.txt:4(搜「dense and sparse」) |
| RRF 只用名次 | Hybrid search | text/38-fm-hybrid-search.txt:22(搜「Reciprocal Rank Fusion」) · text/38-fm-hybrid-search.txt:182(搜「1.0 / (i + 1)」) |
| RRF 丢分数信息的提醒 | Hybrid search | text/38-fm-hybrid-search.txt:186(搜「really close score」) |
| EnsembleRetriever 一行版 | Hybrid search | text/38-fm-hybrid-search.txt:150(搜「EnsembleRetriever」) |
| k-NN 暴力定义 | k-NN | text/39-fm-k-nn.txt:4(搜「brute force」) |
| 复杂度线性、翻倍翻倍 | k-NN | text/39-fm-k-nn.txt:4(搜「O(n * d)」所在段,搜「if your data doubles」) |
| 作者 2.5-3 万条实战与 2-6% | k-NN | text/39-fm-k-nn.txt:7(搜「25,000 to 30,000」) |
| ANN 权衡与亚线性 | ANN | text/40-fm-ann.txt:4(搜「sacrificing some accuracy」) · text/40-fm-ann.txt:16(搜「sublinear」) |
| 四类索引技术 | ANN | text/40-fm-ann.txt:53(搜「LSH」) · text/40-fm-ann.txt:76(搜「PQ」) |
| HNSW 飞机火车比喻 | ANN | text/40-fm-ann.txt:94(搜「plane to the nearest」) |
| 六度分隔灵感 | ANN | text/40-fm-ann.txt:101(搜「six degrees of separation」) |
| FAISS/pgvector 提供多种索引 | ANN | text/40-fm-ann.txt:43(搜「FAISS」) |
| pgvector 支持精确+近似 k-NN | pgvector | text/41-fm-pgvector.txt:4(搜「exact k-NN」) |
| ANNOY 树森林 | Approximate Nearest Neighbors Oh Yeah | text/46-fm-approximate-nearest-neighbors-oh-yeah.txt:4(搜「forest of trees」) |
| 服务选型清单 | Similarity Searching with Vectors | text/42-fm-elasticsearch.txt:4(搜「Elasticsearch」) · text/44-fm-google-vertex-ai-vector-search.txt:4(搜「Vertex AI」) · text/49-fm-chroma.txt:4(搜「Chroma」) |