Ch25: RRF 融合与复合 Rerank——源码与调参
Part 8: WeKnora 源码走读 前置:Ch24 三路检索 后续:Ch26 知识图谱 PMI 源码
结论先行:RRF 解决 分数不可比;复合 Rerank 解决 排对了但还不够细。大师要能改 k、能解释三信号权重、能用 Ch20 证明改动有效。
一、RRF 源码心智模型
\[\text{RRF}(d) = \sum_{r \in \text{routes}} \frac{1}{k + \text{rank}_r(d)}\]$k=60$ 时:排名第 1 得 $1/61$,排名靠后衰减;未召回的路不参与。
func RRF(fused map[string]float64, chunkID string, rank int, k int) {
if rank <= 0 { return }
fused[chunkID] += 1.0 / float64(k+rank)
}
func Fuse(allRoutes [][]RetrievalHit, k int) []ScoredChunk {
scores := map[string]float64{}
for _, route := range allRoutes {
for i, hit := range route {
RRF(scores, hit.ChunkID, i+1, k)
}
}
return sortByScore(scores)
}
调参直觉:
| k | 效果 |
|---|---|
| 小 (20) | 更信 top 排名,后排几乎无贡献 |
| 大 (60) | 默认,平衡 |
| 更大 | 弱路排名靠后也有 voice |
二、复合 Rerank 公式(源码对齐)
\[final = 0.6 \cdot s_{model} + 0.3 \cdot s_{base} + 0.1 \cdot s_{source}\]| 信号 | 来源 | 防什么失败 |
|---|---|---|
| $s_{model}$ | Cross-Encoder | 语义相关度 |
| $s_{base}$ | RRF 分归一化 | Rerank 模型域外术语失误 |
| $s_{source}$ | 摄入元数据 | 论坛帖压过官方 doc |
读 rerank/ 包时找 CompositeReranker.Score——确认权重是否可配置(feature 机会)。
三、常见 bug 模式
| 现象 | 可能根因 |
|---|---|
| 精确词查不到 | BM25 未进 RRF / 分词丢词 |
| 相关但排后 | Rerank 模型分低且 base 权重不够 |
| 旧文档排前 | 缺时效维度;source 未区分版本 |
| 三路结果重复计分 | 同 chunk 多路 rank 应 累加 RRF(正确)vs doc_id 去重策略错误 |
四、消融实验(大师必做)
改 k 或 Rerank 权重后,跑 Ch20 的 10 条 smoke:
# 伪 workflow
for config in baseline k40 k80 w0.5; do
WEKNORA_RRF_K=... python eval_weknora_ragas.py
done
PR 描述必须带 Context Recall / Precision 变化表——评审认数字。
五、与加权融合(Dify)代码级对比
Dify 混合检索:score = w1*bm25 + w2*cosine —— 需归一化分数尺度。
RRF:只用 rank —— 对分数分布鲁棒,但丢失「第一名领先第二名多少」的信息。
WeKnora 用 RRF + Rerank 二级精排 = rank 融合 + 语义精排 分工。
章末 · 大师自检
- 手算两文档在两路排名下的 RRF 分(k=60)
- 解释为何 RRF 不用原始 cosine 分数
- 说出复合 Rerank 三信号各防哪种错
- 设计一个消融实验验证
k=40vsk=60