5附课9 分钟

附05:RRF 融合推导 + Rerank 三层原理

理解 RRF 为什么比直接加权好、掌握公式推导与 Java 实现、理解 Rerank 三层方案及 Bi-Encoder vs Cross-Encoder 核心区别。

RRFRerankCross-EncoderBi-Encoder检索
进度保存在本机浏览器;验收通过后再点更稳妥

附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 精排

核心要点

  1. RRF 只看排名不看分数——k=60 是经验最优,零调参
  2. Bi-Encoder 快但粗,Cross-Encoder 慢但精——召回用前者,重排用后者
  3. Rerank 是 RAG 从 60 分到 90 分的关键一跳

下一节附06,我们深入 Prompt 工程:从"能跑"到"高质量输出"的七大技巧。