Elasticsearch 不愿免费的秘密武器---RRF(TorchV 版)
TorchV官网直达链接
随着 AI 搜索技术 的飞速发展,以 RAG(检索增强生成) 为代表的检索技术日益成熟。从基础的 SimpleRAG 到进阶的 AdvanceRAG,乃至灵活的 ModularRAG,优化策略层出不穷。在这股浪潮中,我们不难发现混合检索 、多路召回 、问题扩展等技术已成为业界的普遍共识。
然而,这些看似花样繁多的技术,却有一个共同的"痛点":它们都需要进行多次召回,并妥善处理多次召回结果的融合。
以混合检索为例:
想象一下,你的 AI 知识库系统同时运行着强大的向量检索(理解语义)和精准的关键词检索(匹配字词)。理想情况下,它们应该珠联璧合,召回最相关的结果。但现实是,如何让这两个"语言不通"的伙伴达成共识,排出最终的最优列表? 这,正是多路召回(Multi-Retrieval Path)策略面临的核心挑战:如何平衡多种检索器召回结果之间的差异?
在 TorchV 团队的实践中,我们发现了一种被广泛低估但极为有效的算法------倒数排名融合(Reciprocal Rank Fusion, RRF)。
一、 检索分数,一个棘手的问题
在混合检索场景下,不同检索器所生成的相关性分数往往是**"不可通约"**的。接下来,我们以 Elasticsearch 的实现为例,深入探讨这个问题。
例如,基于余弦相似度的向量检索分数通常介于0到1之间,且其分布可能较为集中;

如图中多个位次的得分均为0.8左右
而基于 BM25 算法的关键词检索分数则可能是一个范围极大的正数,其量级与分布特性与向量相似度截然不同。

如图中第一位次的数据得分17.95
这种内在的分数尺度差异,使得融合成为一大挑战。
在早期的 ES 检索中,我们会使用boost参数用于混合两个子查询的权重。Boost 值是一个相对权重 ,它增加了对应查询部分在相关性评分中的影响力,但该分值并不是绝对的 :
新分数 ≈ 旧分数 * boost
在 BM25 分数极大的情况下,即使设置了 boost
,依然难以限制关键词搜索的分数大幅超过 向量检索。更何况,BM25 召回结果既不存在固定区间,也非线性分布,这就导致难以找到一个合适的 boost
值来混合两者,达到理想的权重平衡。
因此简单地采用线性归一化或加权平均等方法进行融合,极易导致某一检索器的分数权重被不恰当地放大或缩小,从而无法实现有效的融合。
二、RRF:化"分"为"秩"的巧妙之道
因此我们引入了 RRF 进行优化, RRF 的分数计算方式为

别担心,让我们一步步来理解这个公式。首先,假设只有一个检索器,此时检索结果如下:

这里我们假设平滑因子 k=60。
ini
DOC1 的分数 = 1/ (平滑因子+所排位次) = 1 /61 = 0.0164DOC2,DOC3的分数也以此类推。
现在我们引入第二个检索器

我们分别对某一个 DOC 在 A、B 两个检索器中的得分求和:
css
DOC1 的分数 = 1/ (平滑因子+ A 所排位次)+ 1/ (平滑因子+ B所排位次)= 1/61 + 1/63 = 0.0323DOC2 的分数 = 1/62 + 1/61 = 0.0325
那么最终的排序就会是 DOC2 -> DOC1 -> DOC4 -> DOC3
最后我们在此基础上为不同的检索器加上权重(Weight)

此时的计算结果
css
DOC1 的分数 = 0.9 * (1/ (平滑因子+ A所排位次))+ 0.1 * (1/ (平滑因子+ B所排位次))= 0.9 * 1/61 + 0.1 * 1/63 = 0.0163DOC2 的分数 = 0.0162
此时就由于 A 检索器的权重较高,最终排序 DOC1 -> DOC2 -> DOC3 -> DOC4
此时也能总结出这个公式(只是比数学表示略逊一筹)

RRF 的精妙之处在于其对原始分数的规避。 它不依赖于检索器输出的具体相关性分数 ,而是专注于文档在每个检索结果列表中的相对"排名"。通过将排名转换为倒数形式并进行加权求和,RRF 能够有效地融合来自不同检索器的结果,即使这些检索器的评分机制存在显著差异,也能基于统一的排名维度公平地衡量文档的综合相关性,从而避免了复杂且易出错的分数归一化问题。
但在实践的过程中,我们也留意到 ES 对于 RRF 具有许可证限制,一般的开源社区版无法使用 RRF 进行重排序:

ES 内置 RRF 的许可证限制
在 TorchV 当前版本的召回链路中,我们通过程序内归一化的实现手段巧妙地规避了这个问题。但持续关注行业前沿动向时我们也欣喜地发现,在 Elasticsearch 8.16+ 的较高版本中,新增了**检索器(retrievers)**功能,并在其中支持了 RRF 的使用。这无疑为开发者们在未来的检索中提供了更多的选择,值得期待!

三、RRF 的更多"隐藏"用途
RRF 虽然设计之初主要用于混合多个检索器的结果,但在 RAG 场景中,它仍有大量可施展拳脚的地方。例如在问题分解的场景下:
用户查询:"公司贷款政策和申请流程是什么?"
分解为:
- 子问题 1:"公司贷款政策有哪些?"
- 子问题 2:"公司贷款申请流程是什么?"
对多个子问题并行检索的结果,我们就可以使用 RRF 进行融合,从而更全面地回答用户的复杂查询。
四、实际应用效果评估
在我们的测试评估中,集成 RRF 算法的召回链路带来了显著的效果提升。
目前 TorchV 的极高召回率中,RRF 就贡献了约 10% 的提升,这份战绩不可谓不大。
当然,尽管 RRF 看起来是一把"能斩断乱麻的快刀",但也存在其本身的局限性,其只关注于位次排序的特点也注定其无法对召回结果本身的分数给予考虑。这使得微调和优化变得颇具挑战性。
在检索结果融合上,我们也看到了 Linear、MinMax、Sigmod 等等更多的方式,其各有优劣,在不同的组件中发挥作用。
有更多需求的朋友也可以进一步探索分享!