Fusion-RAG

多路分数不可比,直接加权会被某一通道主导,或同一段重复占满 Top-k。根因是跨通道未校准。本方法用 RRF 或校准融合。它不增加新信号,前路都空则融合也空。

本文属于 RAG 工程框架中的「2 索引与召回」环节,聚焦「Fusion(融合召回)」方法。

定位

维度 内容
角色 多路结果的名次合并
输入 → 输出 多份 rank_list → 去重后的融合列表
默认组合 Elasticsearch RRF;ranx / PyTerrier
何时不用 只有一路检索;前路 Recall 已经是零

核心机制

$$
\mathrm{RRF}(d)=\sum_i \frac{1}{k+\mathrm{rank}_i(d)}
$$

未出现在该路的 $\mathrm{rank}_i$ 视为 $\infty$(该项为 0)。

实现路径与心智:吃入多路已排序列表,按排名倒数加权(RRF)或先校准再加,去重后截断。底层心智:可比的是名次不是原始分;融合不能变出任何一路都没召回的文档。前路都空,融合也空。

优缺点

  • 优点:稳健、计算轻。
  • 缺点:不创造新召回。

契约与走通样例

输入

1
{"lists": {"bm25": ["C1", "C9", "C3"], "dense": ["C3", "C1", "C5"]}}

中间量

$k=60$:C1 得 $1/61+1/62\approx0.0325$,C3 得 $1/63+1/61\approx0.0323$,C9 仅 $1/62$。C1 第一。

输出

1
[{"doc_id": "C1", "rrf": 0.0325}, {"doc_id": "C3", "rrf": 0.0323}]

社区实现

Elasticsearch RRF;离线评测用 ranx。风险:各路 top_k 差一个数量级时,短名单通道被饿死。

工程落地

最小可运行示例

复制为 .py 后直接运行(仅标准库)。生产可用 Elasticsearch rank_window_size 的 RRF 或 ranx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""多路排名按名次倒数融合;未出现在该路的文档该项为 0。"""
from __future__ import annotations


def reciprocal_rank_fusion(rank_lists: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
"""输入多份已排序 doc_id 列表;输出 (doc_id, rrf) 降序。"""
scores: dict[str, float] = {}
for lst in rank_lists:
for rank, doc_id in enumerate(lst, start=1):
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(scores.items(), key=lambda x: -x[1])


if __name__ == "__main__":
fused = reciprocal_rank_fusion(
[
["C1", "C9", "C3"], # bm25
["C3", "C1", "C5"], # dense
],
k=60,
)
print([(d, round(s, 4)) for d, s in fused[:2]])
# 期望 C1≈0.0325, C3≈0.0323

参数

参数 起点 影响
RRF $k$ 60 标准平滑
每路长度 同量级 否则一路缺席

失效—信号—螺丝

  • 加原始分:一路主导。螺丝:改 RRF。
  • 未去重:同一 chunk 占两席。螺丝:按 id 合并。
  • 前路空:融合也空。螺丝:回退查召回,不要调 $k$。

规模(100 篇生物学 PDF)

融合本身毫秒级。成本在各通道前序检索。

-------------本文结束感谢您的阅读-------------