RRF 倒数排名融合:不同检索器的结果怎样合并

在混合检索中,关键词检索可能返回 12.8 分,向量检索可能返回 0.83
分。这两个数来自不同评分体系,直接相加没有可靠的解释。

RRF(Reciprocal Rank
Fusion)改用排名位置进行融合:先看一篇文档在各路结果中排第几,再累加这些位置对应的贡献。

先统一排名的起点

本文排名从 1 开始,使用公式:

RRF(d) = Σᵢ 1 / (k + rankᵢ(d))

文档没有出现在某一路返回列表中时,该路贡献为 0。若代码下标从 0
开始,则写作
1 / (k + index + 1)。两种写法等价,但不能对已经从 1
开始的排名再加一次 1。公式与原始论文一致。RRF
原始论文

k 是平滑参数,常见取值为 60;它不是最终返回条数 top-k。k
越大,相邻名次之间的贡献差异越小。

完整算一遍

假设两路检索列表中,关注以下五篇文档。列表还可能包含其他文档,因此第二路出现第
6 名并不矛盾。

文档 检索器一排名 检索器二排名 k=60 时的总分
D1 1 4 1/61 + 1/64 = 0.032018
D2 2 1 1/62 + 1/61 = 0.032522
D3 3 6 1/63 + 1/66 = 0.031025
D4 未返回 2 0 + 1/62 = 0.016129
D5 4 未返回 1/64 + 0 = 0.015625

这五篇文档的顺序为 D2 > D1 > D3 > D4 > D5。D2
在两路中都靠前,因此总分最高。

用 Python 实现

from collections import defaultdict

def rrf(rankings, k=60):
    if k < 0:
        raise ValueError("k 不能为负数")
    scores = defaultdict(float)
    for ranking in rankings:
        seen = set()
        for rank, doc_id in enumerate(ranking, start=1):
            if doc_id in seen:
                raise ValueError("同一路结果不能包含重复文档 ID")
            seen.add(doc_id)
            scores[doc_id] += 1.0 / (k + rank)
    return sorted(scores.items(), key=lambda item: (-item[1], item[0]))

print(rrf([["A", "B", "C"], ["B", "D", "A"]]))

这里用文档 ID 合并结果。不同检索器必须对同一文档使用相同
ID,否则同一内容会被误认为多篇文档。并列分数则按 ID
排序,方便复现实验。

使用边界

RRF
不需要对原始分数进行归一化,但会丢掉分差信息:第一名仅领先第二名一点,与领先很多,在
RRF 中没有区别。

它也无法找回所有检索器都没召回的文档。每一路返回多少候选,会改变参与融合的集合;大量高度相似的检索器还可能重复强化同一种偏好。因此,增加检索路数不等于一定提高质量。

RRF
适合作为多路检索的初始融合基线。如果验证集表明不同检索路的重要程度明显不同,可以比较加权分数融合等方法。最终应依据检索质量和下游回答表现做选择。

自测:排名已经从 1 开始时,第 1 名在 k=60 下的贡献是多少?答案是
1/61。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇