· liyu · algorithm · 8 min read · — 次浏览
RRF 倒数排名融合:混合检索里的"排名仲裁者"
向量语义召回与 BM25 关键词召回的分数天然不可比,混合检索需要一个仲裁者。本文以混合检索为例讲解 RRF(Reciprocal Rank Fusion)算法的公式与 k 常数的数学直觉,并横向对比分数级融合、名次级融合与学习排序三类融合策略的取舍。
一、问题起点:两路召回,分数不可比
上一篇写向量数据库时,混合检索反复出现。它的标准形态是两路并行召回:
- 向量检索:Query 与文档都编码为 Embedding,按余弦相似度取 Top-K,负责语义泛化;
- BM25 关键词检索:基于词频的稀疏检索,负责专有名词、型号、代码标识的精确命中。
两路各返回一份排好序的列表之后,问题来了:怎么合并?
余弦相似度落在 [-1, 1],BM25 分数是无上界的正数,量纲与分布完全不同。直接把分数相加,等于让两个不同单位的量比赛——数值大的那一路会垄断排序。加权求和理论上可行,但权重怎么定?每换一个语料库、换一个 Embedding 模型,就要重新调一遍。
RRF(Reciprocal Rank Fusion,倒数排名融合)的思路是釜底抽薪:不比分数,只比名次。
二、算法本体:一行公式
score(d) = Σᵢ 1 / (k + rankᵢ(d))rankᵢ(d):文档 d 在第 i 路列表中的名次(第 1 名记 1);k:平滑常数,业界惯例取 60;- 文档未出现在某路列表中,该路贡献 0 分。
算法出自 Cormack 等人 2009 年的论文,实验中它击败了 Condorcet 融合与按名次训练的融合方法,此后成了信息检索领域的经典基线。
完整算一遍
取 k = 60,两路各返回 Top-3:
| 文档 | 向量检索名次 | BM25 名次 | RRF 得分 |
|---|---|---|---|
| A | 1 | — | 1/61 ≈ 0.0164 |
| B | 3 | 1 | 1/63 + 1/61 ≈ 0.0323 |
| C | 2 | 4 | 1/62 + 1/64 ≈ 0.0318 |
最终排序 B > C > A:A 是向量检索的头名,但 BM25 一票没投;B 两路都靠前,融合后登顶。“两路都点头”的文档被优先,这正是混合检索想要的仲裁逻辑。
k = 60 在平滑什么
1/(k + r) 是一条平缓递减的曲线,k 决定了”名次差距”被压缩到什么程度。极端对比:k = 0 时,第 1 名的得分是第 10 名的 10 倍,单路召回的头名几乎一票定胜负;k = 60 时,两者只差约 15%,每一路的意见都被拉到接近”一人一票”。
60 这个数字没有玄学——论文实验中它在多个数据集上稳定表现良好,后来就成了社区默认值,ES、Milvus 的文档里也都沿用 60。
三、融合策略对比:RRF 在光谱的哪个位置
“两路结果怎么合”这个问题,按用分数还是用名次、要不要训练,可以排成一条光谱:
| 策略 | 代表方法 | 优点 | 代价 |
|---|---|---|---|
| 分数级融合 | 加权和、加权平均 | 保留置信度强弱信息,效果上限高 | 必须先归一化(min-max / z-score),分数分布一变就失真,权重需逐场景调参 |
| 名次级融合 | Borda 计数、RRF | 免归一化、免调参,对异常分布鲁棒 | 丢弃分数强度:第 1 名比第 2 名好多少,这个信息没了 |
| 学习排序(LTR) | LambdaMART、交叉编码器重排 | 效果上限最高 | 需要标注数据训练,推理成本高,通常作为召回后的重排层 |
名次级融合内部也有一处分野。Borda 计数是最朴素的方案:直接把各路名次相加。它与 RRF 的差别在衰减曲线的形状——Borda 是线性衰减,第 1 名与第 2 名的差距,恒等于第 9 名与第 10 名的差距;RRF 用倒数曲线,头部名次之间区分更平滑,中后部的差异则被进一步压平。
这条倒数曲线的工程价值在于抗噪:当某一路召回偶发”自信的错误”(把不相关文档顶到很前),1/(k + r) 的平缓性会让这个异常名次对全局排序的破坏被显著稀释;换成线性名次相加或未归一化的分数相加,同样异常的杀伤力要大得多。论文的实验结论正是:这条简单的倒数曲线在多路融合场景里比精心设计的对手更稳。
实践中三类策略常常是分工而非互斥:RRF 负责便宜地把多路召回合并成一份候选列表,LTR(如交叉编码器重排)再对候选做精排——一个管仲裁,一个管精修。这也是目前主流 RAG 流水线的标准两层结构。
四、工程落地速览
- Elasticsearch / OpenSearch:RRF 是混合查询的官方一等公民,一个参数开启;
- Milvus:混合检索内置
RRFRanker,同场的WeightedRanker即分数级路线,正好对应光谱的两端; - pgvector:没有内置函数,但手写 SQL 反而最能看清算法本色:
-- 两路各自 ROW_NUMBER 排名,再按严格 RRF(k = 60)融合
SELECT id,
1.0 / (60 + v_rank) + 1.0 / (60 + t_rank) AS rrf_score
FROM (
SELECT id,
ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS v_rank,
ROW_NUMBER() OVER (ORDER BY ts_rank(to_tsvector('simple', content), $2) DESC) AS t_rank
FROM docs
) t
ORDER BY rrf_score DESC
LIMIT 10;五、小结
RRF 用一行公式回答了混合检索最棘手的问题——异构分数如何公平合议:放弃分数、只用名次,用倒数曲线加常数 60,把多路召回变成一场低方差、免调参的投票。它不是效果上限最高的融合策略,却是性价比最高的默认选项——ES、OpenSearch、Milvus 纷纷把它做成内置能力,就是最好的背书。
当你下次在 RAG 系统里同时启用向量库和 BM25 时,中间那个不起眼的融合器,做的就是这件事。
回看感悟累计回看 — 次
加载中…