附05:RRF 融合推导 + Rerank 三层原理 —— 精准度的质变
本节目标:理解 RRF 为什么比直接加权好、掌握公式推导与 Java 实现、理解 Rerank 三层方案及 Bi-Encoder vs Cross-Encoder 核心区别。
第5课你实现了 Hybrid Retrieval。这里把两个关键原理讲透。
1. RRF:为什么需要融合?
在第5课的 multiSearch() 中我们做了多路召回:
code
向量检索结果:[A, B, C, D, E] // 按向量相似度排序
关键词检索: [B, F, A, G, H] // 按 BM25 评分排序
核心问题:两个结果集得分尺度完全不同——向量是 cos 距离 0-1,BM25 是 0-30+。直接加权融合是错的。
RRF 公式
对每个文档 d,在每路检索结果中获取它的排名 rank_i(d):
code
score_rrf(d) = Σ 1 / (k + rank_i(d))
i∈检索路
k 通常取 60(微软论文推荐值)。
精髓:只看排名,不看原始分数。巧妙规避尺度问题。
算法直觉
Code
文档 X 在向量检索排第1:贡献 1/(60+1) = 0.0164
文档 X 在关键词检索排第3:贡献 1/(60+3) = 0.0159
RRF 总分:0.0323
文档 Y 只在向量检索出现,排第1:贡献 0.0164
RRF 总分:0.0164
X > Y → 多路命中的文档优先(强烈的"投票"机制)
Java 实现
java
public List<ChunkVO> rrfFusion(
Map<String, List<ChunkVO>> resultsByMethod,
int k, int topK) {
Map<Long, Double> scores = new HashMap<>();
Map<Long, ChunkVO> chunkMap = new HashMap<>();
for (List<ChunkVO> results : resultsByMethod.values()) {
for (int rank = 0; rank < results.size(); rank++) {
ChunkVO chunk = results.get(rank);
double rrfScore = 1.0 / (k + rank + 1);
scores.merge(chunk.getId(), rrfScore, Double::sum);
chunkMap.putIfAbsent(chunk.getId(), chunk);
}
}
return scores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topK)
.map(e -> {
ChunkVO c = chunkMap.get(e.getKey());
c.setSimilarity(e.getValue());
return c;
})
.collect(Collectors.toList());
}
进阶融合方法对比
| 方法 | 优势 | 劣势 | 推荐度 |
|---|---|---|---|
| 简单加权 | 直观 | 尺度问题,需调参 | 推荐 |
| RRF | 零调参,鲁棒 | 忽略实际分数差异 | 推荐 |
| CombSUM | 利用分数 | 必须归一化 | 推荐 |
| 学习排序 (LTR) | 精度最高 | 需要标注数据 | 推荐(生产) |
2. Rerank:为什么需要重排?
向量检索的本质缺陷
code
向量相似度 ≠ 真实相关性
例子:
Query: "治疗失眠的方剂"
向量Top1: "失眠让人痛苦" (相似度0.92,但无价值)
向量Top5: "酸枣仁汤治虚劳虚烦不得眠" (相似度0.78,才是正解)
原因:Embedding 是双塔模型(Query 和 Doc 分别编码),无法捕捉细粒度交互。训练目标是"语义相似",不是"问答匹配"。
Bi-Encoder vs Cross-Encoder(必背)
Code
双塔 (Bi-Encoder) —— 检索用:
Query → Encoder → Vec_Q
→ cosine_similarity
Doc → Encoder → Vec_D
快(Doc 可预计算)
Q 和 D 无交互
交叉编码 (Cross-Encoder) —— 重排用:
[Query] [SEP] [Doc] → BERT → 单个相关性分数
精度高(细粒度交互)
慢(每对 Q-D 都要算一次)
工业架构:Bi-Encoder 召回 100 → Cross-Encoder 重排 10。快 + 准 = 完美组合。
3. Rerank 三种实现层次
Level 1:规则打分(第5课方案)
java
private double computeRelevanceScore(String[] queryTerms, ChunkVO chunk) {
double score = 0.0;
// ① 关键词命中加分
for (String term : queryTerms) {
if (text.contains(term)) score += 1.0;
}
// ② 向量相似度加权
if (chunk.getSimilarity() != null) {
score += chunk.getSimilarity() * 2.0;
}
// ③ 长度合理性加分(太短/太长都扣分)
int len = chunk.getOriginalText().length();
if (len > 50 && len < 500) score += 0.5;
return score;
}
优势:零延迟、零成本。问题:规则有限,无法处理同义词。
Level 2:Cross-Encoder 模型(工业标准)
推荐模型:BAAI/bge-reranker-large(中文最强)、bge-reranker-v2-m3(多语言)、Cohere Rerank(API 服务)。
python
from sentence_transformers import CrossEncoder
model = CrossEncoder('BAAI/bge-reranker-large')
scores = model.predict([(query, doc1), (query, doc2), ...])
Level 3:LLM-as-Reranker(最贵最准)
Prompt: "对以下文档按与查询的相关性排序,返回 JSON 格式..."。适用场景:候选少(<20)且要求极高精度时。
升级路径
code
现状(规则打分)
↓
引入 Cross-Encoder(BGE-Reranker)—— 精度跃升
↓
Pre-rerank + Post-rerank 两阶段
↓
RRF 多路融合 + Cross-Encoder 精排
核心要点
- RRF 只看排名不看分数——k=60 是经验最优,零调参
- Bi-Encoder 快但粗,Cross-Encoder 慢但精——召回用前者,重排用后者
- Rerank 是 RAG 从 60 分到 90 分的关键一跳
下一节附06,我们深入 Prompt 工程:从"能跑"到"高质量输出"的七大技巧。