RM3 — 让搜索引擎自己看一眼结果再重搜一次
是什么
RM3(Relevance Model 3)是一套让搜索引擎跑两次、第二次比第一次更准的方法。日常类比:你用淘宝搜”那个红色保温杯”,搜出来 10 件商品,你瞄一眼标题发现都在用”焖烧""保冷""不锈钢”这几个词——你下次搜索如果也带上这几个词,结果会更对路。RM3 就是让机器自动做这件事。
技术上叫伪相关反馈(pseudo-relevance feedback,PRF):
- “相关反馈” = 用相关文档反过来改查询
- “伪” = 用户没真标,机器假装第一次检索的 top-10 就是相关的
提出于 2001 年,到今天仍然是 Anserini / Pyserini 这些 IR 工具包做查询扩展的默认开关。
为什么重要
不理解 RM3,下面这些事都说不清:
- 为什么 BM25 单独跑很弱、加个 RM3 就能在 TREC 数据集上多 5-15% MAP——几乎免费午餐
- 为什么 2020 年之后的稠密检索(dense retrieval)论文里几乎都有一行
BM25+RM3当强 baseline - 为什么搜索查询从 3 个词变成 13 个词,反而能搜得更准(看似违反直觉)
- 查询扩展、伪相关反馈、relevance model 这些术语指的几乎是同一件事——RM3 是它们的代表
核心要点
RM3 的过程可以拆成 三步:
-
第一次检索:用原查询 Q 跑一遍 BM25,取 top-k 篇文档(典型 k=10)。
-
估”相关词分布”:把 top-k 当成相关文档的样本,对每个候选词 w 估出概率 P(w|R)。直觉是”在 top-10 里出现得越多、出现的那篇文档越像原查询,这个词就越能代表用户真正想要的主题”。这一步算出的分布叫 RM1。
-
插值(关键的一步):新查询 = λ × 原查询 + (1−λ) × RM1。λ 典型取 0.5。这一步把”原查询”和”扩展词”按权重混合,就是把 RM1 变成 RM3 的那个”3”。
把新查询拿去第二次检索,结果就是最终答案。
为什么叫 RM3 不叫 RM1:原文里 RM1 是只用扩展词分布做新查询,效果不稳;后人发现把原查询和 RM1 按权重混合最稳,就叫这个变体 RM3。RM2 是另一个估计公式的变体,工业上几乎没人用。
实践案例
案例 1:一次最小的 RM3 跑通
用户搜 "AI 监管":
- 第一次跑 BM25:top-10 篇里含 6 篇讲欧盟 AI Act、3 篇讲中国生成式 AI 办法、1 篇讲伦理。
- 统计候选词权重:
AI Act / 高风险 / 透明度 / 合规 / 算法备案 / GPAI在 top-10 里频繁出现且来自得分高的文档 → RM1 给它们高概率。 - 插值:新查询 ≈
0.5 × ("AI" + "监管") + 0.5 × ("AI Act":0.18, "高风险":0.12, "透明度":0.10, ...)。
第二次检索就比第一次准——因为机器把”监管的具体抓手”补进了查询。
案例 2:Pyserini 一行命令跑 RM3
python -m pyserini.search.lucene \ --index msmarco-v1-passage \ --topics dl19-passage \ --output run.dl19.bm25-rm3.txt \ --bm25 --rm3默认参数:fb_terms=10, fb_docs=10, original_query_weight=0.5。
fb_docs=10↔ 上面的 k=10fb_terms=10↔ 从 RM1 里挑权重最高的 10 个词进新查询original_query_weight=0.5↔ 上面的 λ=0.5
调这三个旋钮就是 90% 的 RM3 调参。
案例 3:失败模式——主题漂移
用户搜 "jaguar"(想买美洲豹品牌车):
- 第一次 BM25 top-10 里 7 篇讲动物、3 篇讲车
- RM1 里
claws / habitat / amazon / spotted概率高 - 插值后新查询变成”美洲豹品牌车 + 爪子 + 栖息地”
- 第二次检索更偏动物,比第一次还差
这就是 query drift。短歧义查询是 RM3 的天敌。Pyserini 默认开 RM3 也有少数 query 反而下降。
案例 4:和 Rocchio 反馈的对比
Rocchio(1971)做的事和 RM3 几乎一样——都是”借相关文档改查询”——但表达方式不同:
- Rocchio 在向量空间里相加:
新查询向量 = α × 原查询 + β × 相关均值 − γ × 不相关均值 - RM3 在概率空间里混合两个语言模型分布
理论上 RM3 更干净(统一的概率框架),工程效果两者打平。Rocchio 在 TF-IDF 时代用得多,RM3 在 BM25 + 语言模型时代成了默认选择。
踩过的坑
-
停用词必须先过滤:不过滤的话
the / of / a占满扩展词槽,等于没扩展。所有工业实现都先过停用词表。 -
k 太大也不行:k 从 10 调到 50 通常变差,因为低分文档不相关、引入噪声。RM3 不是越多越好。
-
λ=0 会主题漂移:把权重全给扩展词、丢掉原查询,就把”用户到底搜什么”给丢了。论文和实践都建议 λ ≥ 0.5,给原查询一半以上的权重。
-
第二次检索成本翻倍:原查询 3 个词,扩展后 13 个词,倒排访问的代价随查询长度近线性增长。延迟敏感的场景(搜索框实时下拉)一般不能用 RM3。
-
长自然语言查询收益小:RAG 里的 user question 已经包含丰富语境,再扩展常常没收益甚至反伤。RM3 是给短关键词查询设计的。
适用 vs 不适用场景
适用:
- 传统稀疏检索(BM25 / QL / TF-IDF)的免费增强——加上去几乎稳赚
- 做 IR 实验需要一个强 baseline 时——
BM25+RM3就是默认强 baseline - 用户写短关键词的搜索系统(电商搜索、企业搜索、TREC 任务)
不适用:
- 实时性要求 <50ms(双倍检索吃不消)
- 已经用稠密检索 + cross-encoder rerank 的现代 stack(神经扩展更优)
- 查询本身已经是长自然语言(RAG 里 user question 通常 20+ 词)
- 短歧义查询(jaguar / apple / python)容易主题漂移
历史小故事(可跳过)
- 1971 年:Rocchio 提出在向量空间里把”相关向量”加进查询向量——经典相关反馈算法,但要用户真标。
- 1990s:Ponte 和 Croft 提出查询似然模型(query likelihood),把检索建成”哪篇文档最可能产生这个查询”。
- 2001 年:Lavrenko 和 Croft(同一个 Croft)想:既然文档可以是语言模型,那”相关性”本身能不能也建成一个语言模型?Relevance Model 论文出炉。RM1 / RM2 两种估计法都在原文里,RM3 这个名字是后人加的,特指”RM1 + 原查询线性插值”。
- 2017 年:Anserini 项目把 RM3 做成 Lucene 上的开箱即用功能,让”BM25+RM3”成为 IR 论文标配 baseline。
- 2020 年起:稠密检索(DPR / ANCE / ColBERT)论文几乎都跑 BM25+RM3 做对比;很多任务上 BM25+RM3 仍然能逼近甚至超过弱稠密模型。
学到什么
- 跑两次检索比跑一次准——这是 RM3 最反直觉但最强的洞见
- “伪”相关反馈 = 不需要用户真标,把 top-k 当成相关样本就行;用户连点击都不用
- 查询扩展不是越多越好——k 和 λ 都有甜区,超过就主题漂移
- 理论 → 工具 → 默认 baseline 走了 16 年(2001 → 2017 Anserini)
- 新方法要打 RM3 baseline 这件事告诉你:一个 25 年前的非神经方法仍然能让 2026 年的论文出汗
延伸阅读
- 论文 PDF:Lavrenko-Croft 2001 SIGIR(10 页,密度高,前 3 页够理解 RM1/RM3)
- Pyserini 文档:BM25 + RM3 教程(一行命令复现 TREC baseline)
- 综述:Anh & Moffat 2005(同时代 IR 优化思路对比)
- anserini-2017 —— Lucene 上把 RM3 做成开箱即用的工具包
- bm25 —— RM3 第一次检索默认用的打分函数
- ance-2020 —— 稠密检索代表作,论文里跑了 BM25+RM3 当 baseline
关联
- anserini-2017 —— 把 RM3 做成 Lucene 默认开关的工具
- colbert-2020 —— 稠密检索代表作,对比对象之一是 BM25+RM3
- ance-2020 —— 同上,神经检索 vs 稀疏 + RM3
- anh-moffat-2005 —— 同时代 IR 性能优化方向,与 RM3 互补
反向链接
- anh-moffat-2005 —— Anh-Moffat 2005 — 让倒排表压到接近熵下限还能 SIMD 解码
- anserini-2017 —— Anserini — 把工业搜索引擎 Lucene 改造成学术 IR 实验台
- colbert-2020 —— ColBERT — 让 BERT 检索既准又能扛大规模