跳到主要内容

相似检索:距离、混合搜索(即语义与关键词两路并用,详见第 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(无关)怎么读
欧氏距离 L24.627.316.34越低越相似
点积(dot product,两串数对应相乘再相加)12.27−0.770.95越高越相似,可为负
余弦距离 cosine0.450.970.95越低越相似

三个度量给出了同一个判断:A 和 B 是一伙的(意思都是「毯子暖和」), C 离它们远远的。这就是语义搜索的全部底层: 把「意思」变成「几何位置」, 「找相似的句子」变成「找近邻的点」。余弦距离那组数字还顺手演示了一个细节: 0.45 只算中等相似——毕竟两条评论措辞完全不同;作者的总结很妙: 「从数学上看,『Taylor Swift』不是一条暖和毯子的语义等价物」7

三个度量怎么选?书里的用法差异:欧氏算的是直线距离(适合关心「绝对位置」的场景); 点积严格说不是距离而是投影量级,一个向量在另一个方向上伸得越远分越高; 余弦只看方向不看长短,对「文本长度不同」最不敏感,是文本检索里最常用的8。 此外还有 Manhattan、Jaccard、Hamming 等一串备选,书里点名列了9

3. 语义搜索的死角:关键词必须回来

3.1 dense(语义)搜索什么时候失灵

第 04 章的向量是稠密向量(dense,每位都有值),搜它就是 dense search。它擅长「意思相近」, 但有两种输入它会抓瞎10:

  1. 训练域之外的内容。 模型只在它见过的语义世界里认路;
  2. 指称性文本。 产品货号、代码、订单号、人名——这些字符串没有「意思」可言, 嵌入出来毫无区分度。查这类东西,老老实实用字符串匹配。

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. 可带走的

  1. 向量搜索 ⊃ 相似算法 ⊃ 距离度量——三层,别混。
  2. 三条毯子评论的距离表(4.62/12.27/0.45)是「语义=几何」最直观的证明。
  3. 产品货号、代码、人名查语义搜索会抓瞎——指称性文本走关键词。
  4. BM25 是 1972 年 TF-IDF 的孙子,但今天仍是关键词检索的王
  5. RRF 用名次不用分数,免归一化;代价是丢掉分数的置信信息。
  6. k-NN 仍是最准的;小数据(<几万到百万级)直接用它,撑不住再换 ANN。
  7. HNSW = 飞机→火车→步行的分层跳转,配六度分隔的直觉。

7. 原文地图

主题原书章原文位置
R=检索专章Similarity Searching with Vectorstext/31-fm-similarity-searching-with-vectors.txt:14(搜「the R or retrieval part」)
三层包含关系Similarity Searching with Vectorstext/31-fm-similarity-searching-with-vectors.txt:64(搜「A similarity algorithm can use different distance metrics」)
向量空间与别名Similarity Searching with Vectorstext/31-fm-similarity-searching-with-vectors.txt:83(搜「latent space」)
2D 投影错觉Similarity Searching with Vectorstext/31-fm-similarity-searching-with-vectors.txt:90(搜「large dots」) · text/31-fm-similarity-searching-with-vectors.txt:93(搜「mathematical certainty」)
三条测试句Semantic search exampletext/32-fm-semantic-search-example.txt:56(搜「cozy temperature」)
384 维输出Semantic search exampletext/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 SwiftCosine distancetext/35-fm-cosine-distance.txt:25(搜「0.4523802399635315」) · text/35-fm-cosine-distance.txt:32(搜「not the semantic equivalent」)
其他度量清单Cosine distancetext/35-fm-cosine-distance.txt:35(搜「Jaccard」)
dense 搜索的失灵场景Dense searchtext/36-fm-dense-search.txt:7(搜「serial numbers」)
sparse 与词袋Sparse searchtext/37-fm-sparse-search.txt:4(搜「mostly zeros」) · text/37-fm-sparse-search.txt:7(搜「bag of words」)
BM25 基于 TF-IDFSparse searchtext/37-fm-sparse-search.txt:10(搜「Best Matching 25」)
hybrid 定义Hybrid searchtext/38-fm-hybrid-search.txt:4(搜「dense and sparse」)
RRF 只用名次Hybrid searchtext/38-fm-hybrid-search.txt:22(搜「Reciprocal Rank Fusion」) · text/38-fm-hybrid-search.txt:182(搜「1.0 / (i + 1)」)
RRF 丢分数信息的提醒Hybrid searchtext/38-fm-hybrid-search.txt:186(搜「really close score」)
EnsembleRetriever 一行版Hybrid searchtext/38-fm-hybrid-search.txt:150(搜「EnsembleRetriever」)
k-NN 暴力定义k-NNtext/39-fm-k-nn.txt:4(搜「brute force」)
复杂度线性、翻倍翻倍k-NNtext/39-fm-k-nn.txt:4(搜「O(n * d)」所在段,搜「if your data doubles」)
作者 2.5-3 万条实战与 2-6%k-NNtext/39-fm-k-nn.txt:7(搜「25,000 to 30,000」)
ANN 权衡与亚线性ANNtext/40-fm-ann.txt:4(搜「sacrificing some accuracy」) · text/40-fm-ann.txt:16(搜「sublinear」)
四类索引技术ANNtext/40-fm-ann.txt:53(搜「LSH」) · text/40-fm-ann.txt:76(搜「PQ」)
HNSW 飞机火车比喻ANNtext/40-fm-ann.txt:94(搜「plane to the nearest」)
六度分隔灵感ANNtext/40-fm-ann.txt:101(搜「six degrees of separation」)
FAISS/pgvector 提供多种索引ANNtext/40-fm-ann.txt:43(搜「FAISS」)
pgvector 支持精确+近似 k-NNpgvectortext/41-fm-pgvector.txt:4(搜「exact k-NN」)
ANNOY 树森林Approximate Nearest Neighbors Oh Yeahtext/46-fm-approximate-nearest-neighbors-oh-yeah.txt:4(搜「forest of trees」)
服务选型清单Similarity Searching with Vectorstext/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」)

Footnotes

  1. 出处:「Similarity Searching with Vectors」第 14 段(text/31-fm-similarity-searching-with-vectors.txt:14,搜「the R or retrieval part」)。

  2. 出处:「Similarity Searching with Vectors」第 64 段(text/31-fm-similarity-searching-with-vectors.txt:64,搜「A similarity algorithm can use different distance metrics」)。

  3. 出处:「Similarity Searching with Vectors」第 83 段(text/31-fm-similarity-searching-with-vectors.txt:83,搜「latent space」)。

  4. 出处:「Similarity Searching with Vectors」第 90-93 段(text/31-fm-similarity-searching-with-vectors.txt:90,搜「large dots」;text/31-fm-similarity-searching-with-vectors.txt:93,搜「mathematical certainty」)。

  5. 出处:「Semantic search example」第 56 段(text/32-fm-semantic-search-example.txt:56,搜「cozy temperature」)。

  6. 出处:「Semantic search example」第 69 段(text/32-fm-semantic-search-example.txt:69,搜「384」);欧氏结果 text/33-fm-euclidean-distance-l2.txt:26(搜「4.6202903」);点积结果 text/34-fm-dot-product-also-called-inner-product.txt:17(搜「12.270497」);余弦结果 text/35-fm-cosine-distance.txt:25(搜「0.4523802399635315」)。

  7. 出处:「Cosine distance」第 32 段(text/35-fm-cosine-distance.txt:32,搜「not the semantic equivalent」)。

  8. 出处:「Dot product (also called inner product)」第 4 段(text/34-fm-dot-product-also-called-inner-product.txt:4,搜「magnitude of the projection」);余弦只看方向见「Cosine distance」第 4 段(text/35-fm-cosine-distance.txt:4,搜「difference in directionality」)。

  9. 出处:「Cosine distance」第 35 段(text/35-fm-cosine-distance.txt:35,搜「Jaccard」)。

  10. 出处:「Dense search」第 7 段(text/36-fm-dense-search.txt:7,搜「serial numbers」)。

  11. 出处:「Sparse search」第 4 段(text/37-fm-sparse-search.txt:4,搜「mostly zeros」)与第 7 段(text/37-fm-sparse-search.txt:7,搜「bag of words」)。

  12. 出处:「Sparse search」第 10 段(text/37-fm-sparse-search.txt:10,搜「Best Matching 25」)。

  13. 出处:「Hybrid search」第 4 段(text/38-fm-hybrid-search.txt:4,搜「dense and sparse」)。

  14. 出处:「Hybrid search」第 22 段(text/38-fm-hybrid-search.txt:22,搜「Reciprocal Rank Fusion」)与第 182 段(text/38-fm-hybrid-search.txt:182,搜「1.0 / (i + 1)」);「不用归一化」的说明在第 184 段(同文件,搜「does not require these scores」)。

  15. 出处:「Hybrid search」第 150 段(text/38-fm-hybrid-search.txt:150,搜「EnsembleRetriever」);PDF 换源在第 76 段(text/38-fm-hybrid-search.txt:76,搜「google-2023-environmental-report」)。

  16. 出处:「k-NN」第 4 段(text/39-fm-k-nn.txt:4,搜「brute force」)。

  17. 出处:「k-NN」第 4 段(text/39-fm-k-nn.txt:4,搜「if your data doubles」)。原文给出 O(n * d) 复杂度并说明百万、十亿级数据不可行。

  18. 出处:「k-NN」第 7 段(text/39-fm-k-nn.txt:7,搜「25,000 to 30,000」)。

  19. 出处:「ANN」第 4 段(text/40-fm-ann.txt:4,搜「sacrificing some accuracy」)与第 16 段(text/40-fm-ann.txt:16,搜「sublinear」)。

  20. 出处:「ANN」第 53 段(text/40-fm-ann.txt:53,搜「LSH」)、第 61 段(text/40-fm-ann.txt:61,搜「KD-trees」)、第 76 段(text/40-fm-ann.txt:76,搜「PQ」)、第 84 段(text/40-fm-ann.txt:84,搜「HNSW」)。

  21. 出处:「ANN」第 94 段(text/40-fm-ann.txt:94,搜「plane to the nearest」)。

  22. 出处:「ANN」第 101 段(text/40-fm-ann.txt:101,搜「six degrees of separation」)。原文还追溯到 1929 年 Frigyes Karinthy 的短篇小说。

  23. 出处:「Key RAG Components in LangChain」第 477-480 段(text/72-fm-key-rag-components-in-langchain.txt:477,搜「still better than anything」;text/72-fm-key-rag-components-in-langchain.txt:480,搜「1 million data points」)。

  24. 出处:「Hybrid search」第 186 段(text/38-fm-hybrid-search.txt:186,搜「really close score」)。

  25. 出处:「Elasticsearch」第 4 段(text/42-fm-elasticsearch.txt:4,搜「Elasticsearch」);「Google Vertex AI Vector Search」第 4 段(text/44-fm-google-vertex-ai-vector-search.txt:4,搜「fully managed」);「Chroma」第 4 段(text/49-fm-chroma.txt:4,搜「embedded vector database」)。