Approximate Nearest Neighbor Negative Contrastive Learning for Dense Text Retrieval
Lee Xiong, Chenyan Xiong, Ye Li, Kwok-Fung Tang, Jialin Liu, Paul Bennett, Junaid Ahmed, Arnold Overwijk
cs.IR, cs.CL, cs.LG
2020-07-02
微软证明batch内负例梯度近乎为零,改用异步更新的ANN索引采全局难负例,点积检索在TREC上接近BERT重排,在线延迟约百分之一。
第一段检索长期靠 BM25 这种词袋匹配,词汇对不上就找不到。稠密检索(Dense Retrieval)把问题和文档都编进同一向量空间,用近似最近邻(ANN)去搜,表示可学、能接预训练、检索也快。问题是:学出来的双塔经常打不过 BM25,文档检索上更明显。
卡点在负例。重排阶段的负例来自上一程检索,天然比较难;第一段检索要把相关文档从整个语料里分开,负例理论上是「剩下的全部」。当时主流做法是 batch 内随机负例,或从 BM25 顶部抽。论文从方差缩减的角度证明:batch 远小于语料、真正难的负例又极少,局部负例几乎抽不到有信息量的样本,损失趋近零,梯度范数跟着塌掉,训练收敛变慢。
ANCE(Approximate nearest neighbor Negative Contrastive Estimation)用正在训练的双塔,从全库 ANN 索引里取当前模型认为最像正例的文档当负例。这些负例按定义就是当前模型最难分的。
全库编码每个 batch 更新一次不现实,所以索引异步刷新:Trainer 继续用上一版索引抽负例,旁边的 Inferencer 拿最近的 checkpoint 重算全库向量,算完再换索引。实验里 Trainer 和 Inferencer 各占一半 GPU,每 1 万 step 刷一次索引。每个正例从 ANN 顶部 200 条里均匀抽 1 条负例。
模型本身很朴素:RoBERTa-base 的 Siamese 双塔、点积相似度、NLL 损失。长文档用 FirstP(前 512 token)和 MaxP(切最多 4 段再 max-pool,ANN 原生支持)。TREC 上先用 BM25 负例热身,OpenQA 上从 DPR 的 checkpoint 接着训。
TREC 2019 DL 上,ANCE 是唯一把 BERT-Siamese 稳稳抬过稀疏检索的负例策略。
| 方法 | MARCO Passage MRR@10 | TREC Passage NDCG@10 | TREC Document NDCG@10 |
| BM25 | 0.240 | 0.506 | 0.519 |
| DPR (BM25+随机负例) | 0.311 | 0.600 | 0.557 |
| ANCE FirstP | 0.330 | 0.648 | 0.615 |
| ANCE MaxP | n/a | n/a | 0.628 |
| BERT 重排 | n/a | 0.742 | 0.646 |
文档检索上 MaxP 的 0.628 已经贴近 BERT 重排的 0.646。OpenQA 的 NQ 上 Top-20 / Top-100 覆盖率 81.9 / 87.5,DPR 是 78.4 / 85.4;同一套阅读器换 ANCE 召回,NQ 准确率从 RAG-Token 的 44.1 到 46.0。商业搜索离线实验里,2.5 亿库 +18.4%,80 亿库约 +15%。
在线延迟:单条 query 取 100 篇,ANCE 约 11.6 ms,BM25+BERT 重排约 1.42 s,大约快两个数量级。训练侧的大头是每轮给全库重新编码,单次约 10 小时,靠异步刷新摊掉。
局部负例与测试时 top 难负例的重合是 0;ANCE 负例从 63% 起步,收敛到 100%。训练损失和各层梯度范数也按理论走:局部负例损失贴零、梯度塌掉,ANCE 负例一直很难、梯度大一个数量级。
这篇文章把稠密检索从「双塔打不过词袋」变成「点积检索可以接近交互式 BERT 级联」。负例分布必须跟测试时要分开的文档分布对齐,这个判断后来成了 DPR、RepLLaMA 一路硬负例挖掘的默认前提。工程上异步 ANN 刷新也成了大规模双塔训练的标准件。
如果只做重排、负例已经够难,ANCE 的增量有限。它解决的是第一段检索自己给自己造难负例。
TREC 2019 的标注池来自稀疏检索系统,稠密检索和 BM25 的 top 100 重合不到 25%,空洞率高,Recall 不太可信;MARCO 的点击标注更稳一些。文档任务依赖 BM25 热身,从零训 ANCE 的数字论文没给。异步间隔过大时训练会抖,附录只说 1:1 GPU 够用,没有更细的失败边界。FirstP 截断长文档是权宜之计。负例来自模型自己的 top 检索,确认偏误(false negative)没有单独处理。