想象你是一座拥有百万册藏书的大图书馆的管理员。过去,读者想找书,必须说出书名里的某个词,目录系统就只会去卡片箱里找“字面相同”的词汇。有一天,一位读者红着眼眶问你:“我想找一本关于失去朋友后如何慢慢恢复的书。”老系统搜遍书名和关键词,返回“查无此书”。你忽然意识到:读者描述的是意思,而老系统只认识字眼。于是你引入了一套新系统——它给每本书都安上了一个“语义坐标”,就像城市有经纬度一样。读者描述需求后,系统把这段描述也变成坐标,再在地图上测量距离,把离得最近的几本书推荐出来。这套新方法,就是 Dense Retrieval(稠密检索):用 embedding 向量表达语义,用向量距离实现匹配检索。接下来,我们会像搭建这座语义图书馆一样,从检索根基、核心原理、模型选型、索引构建、相似度计算、效果评估到实战优化,完整走一遍。
一句话价值:掌握稠密检索,你就能让 RAG 系统从“查得到字”升级为“找得懂意”,这是决定检索增强应用召回质量与落地效果的核心技能。
目录
可视化演示
动态演示Dense Retrieva的核心机制,通过采集/分析/决策/反馈四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Dense Retrieva:稠密检索 稠密检索与稀疏检索的核心区别 Embedding模型选择与向 核心机制可视化 Dense Retrieva整体架构与关键组件
检索根基:从稀疏到稠密
图书馆的老系统就像一本按关键词排列的目录册。每本书只有在它的书名、作者、标签里出现了某个词,才会被那个词“钩住”。读者说“想找一本讲如何走出低谷的书”,目录册里根本没有“走出低谷”这四个字,于是它只能两手一摊。这种依赖词形重合的检索方式,就是稀疏检索。它的向量像一张巨大的点名单,绝大多数位置是 0,只有出现过的词才点亮。问题在于:字面不同,不代表语义不同。
稠密检索换了一种思路。它不再给每个词单独留一个位置,而是用神经网络把整段文字压缩成一个足够短的稠密向量。这个向量的每个维度几乎都不为 0,每本书都在“语义地图”上拥有了一个独一无二的坐标。读者描述查询时,系统把查询也投射到同一张地图上,接下来只需要计算两个坐标之间的距离。
从“查字面”到“找含义”,这是检索思路的一次根本升级。老系统像逐字核对,新系统像拿着地图定位;前者看到的是词,后者看到的是语义。
- 稠密向量
\mathbf{d}_{dense}表示文档或查询最终得到的稠密向量,也就是语义坐标。 - 编码器
f_{\theta}是一个带参数 θ 的神经网络模型,负责把文字“翻译”成向量坐标。 - 输入内容
整段文字表示输入不只是单个关键词,而是完整句子、段落甚至整篇文档。 - 向量空间
\in\mathbb{R}^{D}表示输出落在 D 维实数空间,D 常见取值是 256、384、768、1024 等。
核心原理:双塔编码器
明白了要给文字配坐标,接下来要解决:坐标从哪来?图书馆里不能靠人手工量距离,需要一台稳定的“定位仪”。在稠密检索里,这台定位仪就是 embedding 模型。为了让查询和文档可以公平比较,我们通常训练两个编码器:一个叫查询塔,负责把读者的问题变成向量;另一个叫文档塔,负责把候选内容变成向量。两个塔共享同一个语义地图,这样查询才能和文档直接比远近。
这种结构像一个“双塔”建筑:左边塔解读读者的问题,右边塔解读图书馆的藏书,两座塔的输出都汇入同一个坐标广场。训练时,模型会学习让语义相关的查询和文档向量靠得更近,让不相关的对彼此推开。推理时,我们只做一件事:把查询向量和文档向量送入相似度函数,得到匹配分数。
有了双塔模型,系统就可以预先给全馆图书算好坐标并存入数据库。读者来查询时,只需现场计算查询向量,再与库里的向量比较。坐标一旦统一尺度,“远”与“近”就是语义相关与否。
- 匹配分数
\text{score}(q,d)是查询 q 与文档 d 的语义匹配分数,越高代表越相关。 - 查询编码器
\mathbf{E}_Q(q)将查询 q 编码成查询向量。 - 文档编码器
\mathbf{E}_D(d)将文档 d 编码成文档向量。 - 相似度函数
\mathrm{sim}(\cdot,\cdot)是相似度计算函数,负责比较两个向量的方向与距离。
模型选型:选对Embedding
定位仪也有不同型号。有的适合全球导航,有的对某个城市的小巷特别熟悉。Embedding 模型同样如此:通用模型在海量多语言数据上训练,覆盖面广,适合中英文混合或者通用知识库;中文场景模型则针对中文语境做了优化,对成语、口语、中文长句的理解通常更好;如果你的知识库有强烈的领域色彩,比如法律、医疗、金融,还需要在领域数据上做微调。
选择模型时,向量维度是一个容易被忽视的决策点。维度越高,通常能表达更丰富的语义,但存储、内存和计算成本也越高。想象你给一百万本书各写一份“性格报告”:报告越厚,信息越细,但书架也会被撑爆。
模型选型不是越贵越好,而是要匹配业务场景。就像导航仪不必支持所有星球的坐标一样,先看语料语言,再看专业领域,最后用检索效果验证,才不会被“更大模型”带偏。
- 文档数量
N是语料中文档或段落的总数。 - 向量维度
D是每条 embedding 向量的维度,决定坐标的精细程度。 - 单值字节数
4是 float32 类型每个数值占用的字节数。 - 总量关系三者相乘得到向量部分的存储量,维度翻倍存储也翻倍。
索引构建:给向量建导航
如果你只有几十本书,逐本量距离没问题。但图书馆有百万本书,读者站在门口,系统不可能把每本书都拉出来量一遍。这时就需要给语义地图搭建导航系统。最朴素的方法是暴力搜索:逐一计算查询向量与所有文档向量的距离,精确但太慢。工程上更常用的是 ANN 近似最近邻索引,它在“召回速度”和“召回精度”之间做取舍。
HNSW 是目前非常流行的图式索引。它把文档向量组织成多层图:上层连接稀疏的“高速入口”,下层连接细密的“街区小路”。查询时从顶层快速下探,像开车从高架进入支路,只需要访问一小部分节点就能找到近邻。IVF 则更像给地图划分行政区,先把向量聚类成若干桶,查询时只搜最近的几个桶。
索引决定了检索系统能否撑住线上流量。选好 HNSW 的候选数、IVF 的桶数,就像给导航仪设置“优先高速还是优先距离”,需要在线下评测中反复校准。
- 检索耗时
t_{\text{search}}表示一次查询平均消耗的时间。 - 对数复杂度
O(\log N)表示当文档数 N 变得很大时,耗时只按对数比例增长。 - 近似意义
\approx表示这是近似复杂度,实际还受图结构、参数和数据分布影响。 - 索引方法
\text{HNSW}是层次化可导航小世界图的缩写,一种主流 ANN 索引。
相似度计算:度量语义距离
现在坐标有了、导航也有了,最后一步是回答:两个点在语义上到底有多近?这很像描述两座城市之间的关系:可以说直线距离,也可以说沿某条路开车的距离。向量空间里最常用的距离叫余弦相似度。它不看向量的长短,只看方向是否一致。如果把查询向量和文档向量都画成从原点出发的箭头,余弦相似度就是看这两支箭的夹角。
还有一种常用方法是点积。点积既看方向,也看模长。如果模型训练时已经对向量做了归一化,点积和余弦相似度在数学上等价。因此,工程上有一条朴素经验:训练时用哪种度量,上线检索就用哪种度量。
相似度计算看似简单,却直接影响排序。就像两座城市直线距离再近,也可能隔着山;余弦相似度高,也不代表业务上一定相关。因此,度量的选择要跟随模型的训练方式,而不是随意更换。
- 点积
\mathbf{q}\cdot\mathbf{d}是两个向量逐维度相乘再求和。 - 向量长度
\|\mathbf{q}\|是查询向量的模长,代表这个向量的“总长度”。 - 归一化分母分母把两个向量长度约去,使得结果只保留方向夹角信息。
- 余弦值
\cos(\mathbf{q},\mathbf{d})的取值范围是 -1 到 1,越大越相似。
效果评估:召回质量体检
有了新系统,怎么知道它比老系统好?图书馆需要定期做“读者满意调查”。评测稠密检索,通常需要构建一个评测集:给定一组查询,人工标注出哪些文档是真正相关的。然后让检索系统返回前 K 条结果,看看相关文档被捞回来多少。
最常用的指标是 Recall@K,它衡量“所有相关文档里,有多少出现在前 K 个结果中”。此外还有 MRR,它看第一个相关结果排在第几位;NDCG 则更进一步,给排在靠前位置的相关结果更高奖励。这些指标就像检查导航仪的三种方式:是否找到了目的地、是否第一次就指向正确、以及整体路线是否合理。
评测集是检索优化的方向盘。没有它,你可能只是感觉“结果看起来不错”;有了它,才能量化每一次调参究竟提升了多少。它也是后续混合检索、精排优化的决策依据。
- 相关文档
\text{相关文档}是评测集中人工标注为相关的文档集合。 - 前K个结果
\text{前K个结果}是检索系统返回的 Top-K 集合。 - 交集
\cap表示两者重合的文档。 - 召回率
\text{Recall@K}表示在 Top-K 内命中的相关文档占全部相关文档的比例。
实战优化:混合检索与精排
语义地图很强大,但它也有盲区。比如读者精确报出“ISBN 编号 978-7-201-12345”,语义检索可能会把它当作一串普通文本,反而不如老式目录系统精确。更聪明的做法是让两套系统合作:稀疏检索负责精确字面匹配,稠密检索负责语义泛化,再把两路分数融合。
同时,检索流程通常分为粗排和精排两段。向量索引先快速召回几百条候选,再用更重但更准的模型精排。就像导航先给出几条大路线,再由熟悉当地的人挑选最佳小路。为了让知识库保持新鲜,索引还需要支持增量更新:新书入库时,不能把所有旧坐标全部重算一遍。
实战中的稠密检索很少单兵作战。真正能落地的方案,往往是一套组合拳:语义召回、字面兜底、精排修正、增量更新。这样,图书馆既能理解读者模糊的感受,也能精确找到那本指定编号的书。
- 稠密分数
s_{\text{dense}}来自稠密检索的相似度分数。 - 稀疏分数
s_{\text{sparse}}来自稀疏检索(如 BM25)的匹配分数。 - 混合权重
\lambda是介于 0 到 1 的超参数,控制两路结果的占比。 - 融合结果
s_{\text{final}}是用于最终排序的融合分数。
AI 应用场景
RAG 企业知识库问答
阶段:检索召回
解决的问题:传统关键词检索无法理解员工的同义表达,例如员工问“发票丢了怎么办”,知识库文档标题是“票据遗失处理流程”,字面不重合导致检索失败。稠密检索通过语义向量召回相关文档,让 RAG 系统拿到真正有用的上下文。
具体案例:某企业上线员工助手,接入内部规章制度数据库。此前用 BM25 搜索“出差住宿超标了怎么说明”,返回结果七零八落;改造为稠密检索后,系统准确召回“差旅费报销附则”和“超标住宿审批说明”,再交给大模型生成回答。
电商客服语义匹配
阶段:相似问题召回
解决的问题:客服机器人需要从历史工单中找到相似问题,但用户表达口语化严重,如“东西没送到”和“包裹物流停滞”意思相近,关键词却完全不同。稠密检索能把这种语义相似的关系捕捉到。
具体案例:电商平台将百万条历史工单向量化,每当用户进线咨询,先用稠密检索召回相似工单,再结合大模型生成回复建议,显著提升一次性解决率。
法律案例语义检索
阶段:案件相关法条召回
解决的问题:法律场景中,当事人描述案情时不会使用精确的法言法语,如“朋友借我钱不还”与“民间借贷纠纷”需要语义桥接。领域微调后的 embedding 模型可以让案情描述与法条、判例在向量空间中靠近。
具体案例:法律科技公司构建判例检索系统,用法学语料对 embedding 模型做领域微调,再结合混合检索,使案情描述能够召回相关判例和法条,辅助律师快速完成类案检索。
Python 演示
import numpy as np class SimpleDenseRetriever: def __init__(self, dim=32, vocab_size=200, seed=0): np.random.seed(seed) self.dim = dim self.vocab_size = vocab_size self.embeddings = np.random.randn(vocab_size, dim).astype(np.float32) self.doc_vecs = {} self.doc_tokens = {} def tokenize(self, text): return list(text) def _normalize(self, vec): norm = np.linalg.norm(vec) if norm == 0: return vec return vec / norm def embed(self, tokens): vec = np.zeros(self.dim, dtype=np.float32) for token in tokens: idx = abs(hash(token)) % self.vocab_size vec += self.embeddings[idx] return self._normalize(vec) def add_document(self, doc_id, tokens): self.doc_tokens[doc_id] = tokens self.doc_vecs[doc_id] = self.embed(tokens) def search(self, query_tokens, top_k=3): qvec = self.embed(query_tokens) scores = [] for doc_id, dvec in self.doc_vecs.items(): score = float(np.dot(qvec, dvec)) scores.append((doc_id, score)) scores.sort(key=lambda x: x[1], reverse=True) return scores[:top_k] if __name__ == "__main__": corpus = { "doc1": "公司在月底统一报销差旅费用", "doc2": "发票丢失后需要提交遗失说明", "doc3": "出差住宿标准按城市等级调整", "doc4": "报销流程在OA系统发起申请" } retriever = SimpleDenseRetriever() for doc_id, text in corpus.items(): retriever.add_document(doc_id, retriever.tokenize(text)) query = "出差住宿发票怎么报销" results = retriever.search(retriever.tokenize(query)) print("Top results:") for doc_id, score in results: print(f"{doc_id}: {retriever.doc_tokens[doc_id]} score={score:.3f}")
self.embeddings 替换为预训练 embedding 模型产出的向量。self.embeddings = np.random.randn(vocab_size, dim).astype(np.float32)模拟一张“词向量表”,每个词都有一个初始坐标。vec = np.zeros(self.dim, dtype=np.float32)每条文档或查询先准备一个全零的稠密向量。vec += self.embeddings[idx]把每个词对应的向量坐标累加进整段文本的向量。return self._normalize(vec)归一化后,后续点积结果就等于余弦相似度。self.doc_vecs[doc_id] = self.embed(tokens)入库时提前算好文档向量,检索时不用重复计算。qvec = self.embed(query_tokens)查询也要用同一台“定位仪”映射到同一语义空间。score = float(np.dot(qvec, dvec))用点积计算查询与文档的相似度分数。scores.sort(key=lambda x: x[1], reverse=True)按分数从高到低排序,返回 Top-K。
常见误区
误区既然稠密检索能理解语义,那它肯定全面碾压 BM25,上线后直接把老方案替换掉就行。
正解稠密检索虽擅长语义泛化,但对精确匹配、产品型号、人名、ID 等场景反而容易失灵。比如查询“iPhone 15 Pro 256G 黑色”,语义模型可能只记住“手机 黑色”,把具体型号忘掉。更稳妥的做法是混合检索,让稀疏检索兜底精确字面。
为何常见:因为 Demo 里语义检索的效果看起来非常惊艳,容易让人忽略它面对精确查询时的短板。
误区维度越大,信息越丰富,检索效果一定越好,所以直接选 1024 维甚至更高。
正解维度提高会带来存储和计算成本的大幅上涨,而且当训练数据不足时,高维向量反而容易过拟合,产生“维数灾难”。选维度要结合语料规模、硬件资源和评测指标综合判断。
为何常见:人们天然认为“更多信息=更好”,却忽略了成本和边际收益递减。
误区点积和余弦相似度都是算相似度,线上用哪个都一样,选实现方便的那个就好。
正解如果模型训练时对向量做了归一化,两者等价;如果没有归一化,点积会受向量模长影响,导致高频文档更容易被排到前面。上线检索的度量必须和模型训练时的度量保持一致。
为何常见:初学时只看到公式长得像,没注意到模长归一化这个隐藏前提。
误区向量索引是一次性工作,把文档向量全部写入 HNSW 就可以了,后续新增文档时再说。
正解线上知识库每天都会新增和删除内容。如果不做增量更新,索引会逐渐过期,检索到已删除的内容或漏掉最新文档。生产系统需要设计定时增量构建策略。
为何常见:离线实验只关注单次效果,容易忽略线上数据的持续变化。
面试题
Q1. 稠密检索与稀疏检索的核心区别是什么?
- A.稠密检索只搜中文内容
- B.稠密检索依赖向量语义匹配← 正确答案
- C.稀疏检索使用神经网络
- D.稠密检索不需要建索引
答案:B。稀疏检索依赖关键词字面重合,稠密检索用 embedding 向量表达语义后计算距离,因此核心区别是是否依赖向量语义匹配。A、C、D 都不符合事实。
Q2. 稠密向量通常由什么模型产生?
- A.词频统计模型
- B.倒排索引模型
- C.神经网络编码器← 正确答案
- D.决策树模型
答案:C。稠密向量由 embedding 模型或神经网络编码器将文本映射到低维稠密空间而得到。词频统计和倒排索引都只产生离散的稀疏表示,决策树不适合直接生成向量。
Q3. 双塔编码器中的“双塔”指什么?
- A.查询塔和文档塔← 正确答案
- B.标题塔和正文塔
- C.训练塔和推理塔
- D.中文塔和英文塔
答案:A。双塔模型通常包含查询编码器和文档编码器,分别处理查询与文档,再把两边向量映射到同一语义空间比较相似度。
Q4. HNSW 索引的主要作用是什么?
- A.提升向量维度
- B.训练 embedding 模型
- C.做近似最近邻搜索← 正确答案
- D.替换相似度计算
答案:C。HNSW 是一种图式 ANN 索引,用多层图结构加速近邻查找。它不提升维度、不训练模型、不替换相似度函数,只是把“全库暴力搜索”变成近似搜索。
Q5. 余弦相似度的定义是什么?
- A.向量内积除以模长乘积← 正确答案
- B.向量各维度平方和
- C.两个向量逐项相减
- D.向量模长的算术平均
答案:A。余弦相似度关注方向夹角,数学形式是向量点积除以两个模长的乘积。B 是模长平方,C 是差向量,D 是长度平均。
Q6. Recall@K 衡量的是什么?
- A.前 K 个结果中相关文档占比← 正确答案
- B.全部文档的平均得分
- C.精排模型训练准确率
- D.单次检索响应耗时
答案:A。Recall@K 的分母是评测集中所有相关文档数,分子是前 K 个结果中命中的相关文档数,衡量召回覆盖能力。
Q7. 模型训练时用点积计算相似度,检索时最好怎么做?
- A.改用欧氏距离
- B.改用余弦相似度
- C.保持点积保持一致← 正确答案
- D.完全放弃相似度
答案:C。检索度量要与训练度量一致。如果训练时用了点积,线上最好继续用点积,避免因度量不一致导致排序行为偏离训练目标。
Q8. 纯稠密检索在专有名词查询上可能退化的原因是?
- A.向量维度必然不够
- B.语义模型对精确字面不敏感← 正确答案
- C.索引结构不稳定
- D.查询语句长度太长
答案:B。embedding 模型优先学习语义关联,对“型号、编号、人名”等精确 token 不够敏感。因此纯稠密检索可能混淆相似字面,需要混合检索补足精确匹配能力。
Q9. 以下哪些属于常见的向量索引方案?
- A.暴力搜索← 正确答案
- B.HNSW 图索引← 正确答案
- C.IVF 倒排索引← 正确答案
- D.B+ 树索引
答案:ABC。暴力搜索是精确基线,HNSW 和 IVF 是主流 ANN 近似索引。B+ 树主要用于关系型数据库的数值/字符串索引,不是向量索引方案。
Q10. 下列哪些指标可以反映稠密检索的排序质量?
- A.Recall@K← 正确答案
- B.MRR← 正确答案
- C.NDCG@K← 正确答案
- D.索引构建时间
答案:ABC。Recall@K 看召回覆盖,MRR 关注第一个相关结果的位置,NDCG@K 评估排序质量。索引构建时间属于性能指标,不反映检索结果的相关性排序。