Dense Retrieval 双塔检索封面

摘要

稠密检索(dense retrieval)把查询和文档分别压缩为定长向量,再用点积或余弦相似度找最近邻。它真正值得复现的不是“调用一个 embedding API”,而是三个可检验环节:双塔如何产生向量,负样本如何塑造空间,离线索引与在线查询如何组成评测协议。本文以 DPR 和 Contriever 为两条主线,阅读官方实现,并提供一个无第三方依赖的最小实验。脚本只验证机制与日志,不声称复现论文规模的 BERT、Wikipedia、Natural Questions 或 BEIR 结果。

复现价值与目标:把向量接口还原成训练问题

词面检索会奖励共同词项;稠密检索希望把“红色星球”和“Mars”也拉近。代价是:相关性不再由可读的词频公式直接决定,而由训练对、负样本、池化、归一化和索引共同决定。因此,本次目标不是追逐一个排行榜数字,而是回答四个问题:

  1. 查询塔和文档塔输出的张量形状是什么?
  2. 一个 batch 为什么同时提供正样本和负样本?
  3. 为什么文档可以离线编码,而查询必须在线编码?
  4. Recall@k 上升时,是否只是训练集记忆、假负样本泄漏或评测口径改变?

核心思想:双塔、分数矩阵与对比损失

1. 两个编码器先分开读,再在向量空间相遇

给定查询 (q_i) 和文档 (p_j),双塔分别计算:

[ \mathbf{h}^{q}_i=f_q(q_i)\in\mathbb{R}^{d},\qquad \mathbf{h}^{p}_j=f_p(p_j)\in\mathbb{R}^{d} ]

其中 (f_q) 是查询编码器,(f_p) 是文档编码器,(d) 是向量维度。若一个 batch 有 (B) 个查询和 (B) 个正文档,则两侧张量形状都是 ([B,d])。它们之间没有逐 token 的 cross-attention;这正是文档向量能被预计算的原因。

相似度常取点积:

[ s_{ij}=\frac{(\mathbf{h}^{q}_i)^\top\mathbf{h}^{p}_j}{\tau} ]

因此一次矩阵乘法得到 ([B,B]) 的分数矩阵。温度 (τ>0) 控制 softmax 的尖锐程度;若向量先做 L2 归一化,点积等价于余弦相似度。是否归一化不能靠习惯猜测,必须与训练和索引配置一致。

2. 对角线是正样本,其余位置成为 batch 内负样本

若第 (i) 个查询的正文档也是第 (i) 行文档,损失为:

[ \mathcal{L}=-\frac{1}{B}\sum_{i=1}^{B} \log\frac{\exp(s_{ii})}{\sum_{j=1}^{B}\exp(s_{ij})} ]

分母中的其余 (B-1) 个文档就是 in-batch negatives(批内负样本)。batch 变大通常意味着更多对比项,但也更容易把未标注的相关文档错当作负样本。硬负样本则是“看起来很像却不是标注答案”的文档;它能增加训练信号,也可能放大标注缺口。

3. DPR 与 Contriever 改变的是监督来源,不是检索骨架

DPR(Karpukhin 等,EMNLP 2020)用问答监督训练查询编码器和 passage 编码器,论文实现以 BERT 表示和点积打分为核心,并组合随机或 BM25 困难负样本。论文摘要报告:在其开放域问答设置中,DPR 相比强 Lucene-BM25 的 top-20 passage retrieval accuracy 有 9–19 个百分点的绝对提升。这是论文报告,不是本文 toy 数据的结论。

Contriever(Izacard 等,TMLR 2022)关注无监督迁移:从同一文本构造两个扰动视图,以裁剪、删除等增强形成伪正对,再做对比预训练。官方模型默认对最后一层 token 表示做 mask 后平均池化;官方页面报告其无监督模型在 BEIR 的 Recall@100 上超过 BM25 的 15 个数据集中的 11 个。它没有证明稠密检索在任何领域都胜过 BM25;指标、数据集和迁移条件缺一不可。

数据与负样本合同:模型开始训练前先冻结证据

稠密检索最容易被忽略的输入不是文本,而是相关性关系。每个样本至少应保留 query_id、原始 query、positive_passage_ids、负样本 ID 与来源;passage 表必须有稳定的文档 ID、标题、正文和切分版本。一个查询可能有多个正确文档,所以不能把训练时选中的单个正样本误当成唯一真值。若 qrels 允许三个相关文档,预测命中另外两个也应算正确;否则所谓“困难负样本”可能只是漏标正样本。

负样本来源决定模型学到什么边界。随机负样本很快会变得太容易;batch 内负样本便宜,却依赖 batch 的主题分布;BM25 负样本词面相似,适合逼迫模型区分实体与关系;由旧稠密模型挖出的近邻更接近当前决策边界,但会继承旧模型偏差。实验日志应分别记录四类负样本的数量、被判为假负的比例和损失贡献,不能只写一个笼统的 num_negatives

训练、验证和测试还要按信息来源切分。如果同一文章的相邻 passage 同时出现在训练与测试中,模型可能靠局部措辞而非可迁移语义命中。对 Contriever 式自监督训练,还应保存增强前文本、两次增强后的视图和随机种子;删除词、裁剪长度或采样窗口的改变,本质上改变了“什么应当保持不变”的学习假设。对 DPR 式问答监督,则要记录答案字符串如何映射到 passage,以及没有唯一证据时如何选正样本。

DPR / Contriever 双塔训练与检索流程

官方代码阅读路线:先跟数据流,不先追配置项

阅读 DPR 仓库可按四步走。第一,看 dpr/models/biencoder.py 中查询塔与上下文塔怎样返回表示;第二,看 BiEncoderNllLoss.calc 如何形成分数矩阵、做 log_softmax,再把正样本位置交给 NLL;第三,看 generate_dense_embeddings.py 如何分片生成文档向量;第四,看 dense_retriever.py 如何装载 FAISS 索引并输出 top-k。官方仓库已于 2023 年归档,所以复现时应固定 commit 和依赖,而不是假设最新 Transformers 一定兼容。

阅读 Contriever 仓库时,先看 src/contriever.pyforward 对 padding 位置清零,再按有效 token 数做平均池化。随后看 src/inbatch.py 的对比学习入口、generate_passage_embeddings.py 的分片保存,以及 passage_retrieval.py 的查询编码、索引加载、近邻搜索和 answer match。官方 README 还特别说明 Natural Questions 的答案预处理与 DPR 版本略有不同;若不记录数据版本,两个“Recall@k”可能并不可比。

最小实验:手写一遍比封装调用更有解释力

code/minimal_dense_retrieval.py 只用 Python 标准库,固定随机种子 57。它为查询塔和文档塔维护两套独立的 24 维词向量表,对句子做平均池化,手工计算 6×6 点积分数、softmax 交叉熵及梯度,再把 10 篇 passage 编成离线索引。运行:

python3 code/minimal_dense_retrieval.py --check-only

本次实际验证:查询向量形状为 ([6,24]),passage 索引为 ([10,24]),训练分数矩阵为 ([6,6]);240 步后损失从 1.7899 降到 0.0044,toy 集 Recall@1、Recall@3 和 MRR 都为 1.0,断言通过。这个结果只证明梯度、索引、qrels 和指标实现自洽。训练查询与测试查询相同、语料只有十篇,因此满分主要是记忆能力检查,不能外推为 DPR 或 Contriever 的泛化结果。

若要升级为论文级复现,应把词表平均替换成官方 checkpoint,严格复用 passage 切分、query/answer 预处理和负样本文件;生成文档 embedding 时记录模型哈希、维度、归一化与 dtype;最后才在 exact search 与 FAISS 近似索引之间比较质量—延迟差异。大模型下载、完整 Wikipedia 索引和 BEIR 评测本次均未运行,待人工核验

从 toy 到官方模型:分三段复现,不一次放大所有变量

第一段是协议复现:保留本文的小语料,用标准库或小型神经网络确认正样本下标、分数方向、Recall/MRR 与输出 ID 映射。此时目标是让故意交换 qrels、翻转分数或打乱 passage ID 时测试必然失败。若错误注入后指标仍不变,评测脚本比模型更值得怀疑。

第二段是模型复现:语料与 qrels 不变,只替换为官方 DPR 或 Contriever checkpoint。先用精确矩阵乘法,不启用近似索引;抽查同一段文本经过批处理与单条编码所得向量是否一致,padding 是否进入平均池化,模型是否处于 eval 模式。DPR 查询塔与文档塔不能交换;Contriever 的池化和文本规范化必须跟 checkpoint 配置一致。这样得到的是模型接口验证,仍不是论文 benchmark 复现。

第三段才是系统复现:换成论文数据版本,分片生成全量 passage embedding,构建 FAISS 索引,再跑官方评测。每个分片应记录 passage ID 范围、向量行数、维度、校验和与生成耗时;合并后检查 ID 唯一性和总数。先用少量 query 对照 exact top-k,再启用近似参数。只有数据、模型、exact search 都对齐后,ANN 的速度与召回损失才有解释意义。

这三段每次只替换一层,因此失败能被定位:协议段失败是实现或 qrels 问题,模型段失败是预处理或 checkpoint 问题,系统段失败才主要考虑分片、索引和资源配置。直接从 toy 跳到百万文档,会把所有错误都伪装成“模型效果不好”。

评测协议:先冻结 qrels,再谈模型优劣

问题 指标 必须固定的口径
前 k 个结果是否覆盖相关文档 Recall@k 每个查询的相关文档集合、去重规则
第一个相关文档排得多靠前 MRR@k 截断深度、无命中时记零
多级相关性排序是否合理 nDCG@k relevance grade 与折扣公式
索引是否值得部署 QPS、P50/P95 延迟、内存 硬件、batch、精度、索引参数

开放域问答常用 answer string 是否出现在 passage 中来判命中,但字符串规则会受大小写、别名、分词和规范化影响。纯检索数据则使用 qrels(query relevance labels,查询—文档相关性标注)。二者不能混成同一个“accuracy”。至少同时报告 BM25、exact dense search 和近似 dense search;若只在一个域内微调再测试,不能据此宣称零样本泛化。BEIR 的价值正是跨任务、跨领域检查这种迁移,但平均分仍应配合逐数据集结果阅读。

失败分析:从症状回到可证伪原因

损失下降但 Recall 不升。 先查正样本下标是否对应分数矩阵对角线,再查训练与检索是否使用同一 tokenizer、池化和归一化。toy 集若训练满分而留出集崩溃,是过拟合证据,不是索引 bug。

语义近、事实错的文档排在前面。 这通常暴露负样本不够难。加入 BM25 或旧模型挖出的 hard negatives,同时审计它们是否其实也是相关文档;否则模型会被迫分开两个都正确的答案。

exact search 正常,FAISS 结果下降。 固定同一批向量逐 query 对比 top-k ID,检查距离类型、归一化、向量顺序和文档 ID 映射,再调 nprobe、图搜索深度或量化配置。近似索引的误差不能算到编码器头上。

换域后 BM25 反超。 先按查询类型、文档长度和专有名词比例分桶。稠密模型可能学到训练域的语义捷径,而词面信号对编号、实体名和术语更稳。合理基线是报告稀疏、稠密与 hybrid,而不是删掉“不够先进”的 BM25。

结果无法复现。 保存 query、正样本 ID、负样本来源、模型与数据哈希、每个 checkpoint 的 Recall 曲线,以及检索输出而非只存聚合分数。索引必须能追溯到生成它的模型;模型更新但索引未重建,会产生最隐蔽的版本错配。

后续科研问题

  1. 在固定训练预算下,增加 batch 内负样本与提高 hard-negative 质量,哪一个更有效?应同时测假负率。
  2. Contriever 式自监督增强在医学缩写、代码标识符或多语言文本上,会不会删除真正决定相关性的 token?
  3. 向量归一化、温度和 embedding 维度如何共同改变难例的局部邻域,而不只是平均 Recall?
  4. 当语料持续更新时,旧 passage embedding 的漂移如何被检测?能否用小规模 exact-search 哨兵集触发重建索引?
  5. 稠密检索的错误究竟来自表示、标注还是 ANN 索引?需要把 encoder error 与 search error 分开报告。

这些问题都可以从本文脚本扩展:增加留出查询、构造一词之差的假负样本、记录每步相似度矩阵,再替换为真实 checkpoint。研究价值来自控制变量和失败证据,而不是把模型名字换得更大。

总结

双塔检索的关键约束很简单:查询和文档先独立编码,训练用对比损失塑造空间,推理时文档离线建索引、查询在线找近邻。DPR 说明有监督问答对与困难负样本能训练强检索器;Contriever 说明无监督对比预训练可以改善跨域起点。但任何“稠密优于稀疏”的结论都依赖数据、指标和版本。最小复现应先让分数矩阵、qrels、索引和日志可检查,再逐步放大模型与语料。

参考资料

检索日期:2026-08-29。

  1. Karpukhin et al., 2020, Dense Passage Retrieval for Open-Domain Question Answering(EMNLP / ACL Anthology)
  2. Meta AI Research, DPR 官方代码仓库(已归档)
  3. Izacard et al., 2022, Unsupervised Dense Information Retrieval with Contrastive Learning(TMLR / OpenReview PDF)
  4. Meta AI Research, Contriever 官方代码仓库(已归档)
  5. Thakur et al., 2021, BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models(NeurIPS Datasets and Benchmarks)
  6. Meta AI Research, FAISS Getting Started