重排与混合检索

核心观点:召回是广撒网,重排是精挑细选

检索系统的两阶段架构(召回 → 重排)是当前工业界处理大规模文档检索的标准范式。召回阶段的核心目标是高召回率(Recall),尽可能不遗漏相关文档;重排阶段的核心目标是高精度(Precision),将最相关的文档排在最前。两阶段分工协作,在搜索质量与系统效率之间取得平衡。


一、为什么召回之后还需要重排

1.1 召回阶段的局限

召回阶段通常采用「候选生成(Candidate Generation)」策略,其典型实现包括:

  • 稀疏检索(Sparse Retrieval):如 BM25、TF-IDF,基于词项匹配
  • 稠密检索(Dense Retrieval):如双编码器(Bi-Encoder)向量相似度

无论哪种方法,召回阶段都面临以下约束:

  1. 向量空间的表达能力有限:Bi-Encoder 将查询和文档映射到统一向量空间,但「语义相似」不等于「任务相关」。例如,查询「苹果公司财报」与「水果营养价值」在语义空间中可能距离不近,但在向量空间中若共现模式相似,可能被错误召回。

  2. 单一相似度指标的局限:向量检索依赖余弦相似度或内积,这些指标难以捕捉查询与文档之间的复杂交互关系

  3. 计算效率的约束:召回阶段需要在毫秒级时间内从千万或亿级文档库中返回候选集,无法使用过于复杂的模型。

1.2 重排阶段的价值

重排(Rerank)阶段在召回候选集之上进行精细化排序,其核心价值在于:

  • 交互式特征提取:Cross-Encoder 能同时编码查询与文档,捕捉两者的交互特征
  • 多信号融合:可整合相关性分数、文本匹配度、用户行为信号等多维度信息
  • Precision 提升:将最相关的少量文档(如 top-20)精准地排在最前

从信息检索理论看,Precision@K 对用户体验至关重要——用户通常只关注第一页结果。即使 Recall@1000 达到 99%,若 top-20 中充斥着不相关文档,用户的感知质量依然很差。


二、混合检索的互补关系

2.1 稀疏检索 + 稠密检索

维度稀疏检索(BM25)稠密检索(Bi-Encoder)
核心机制词项频率与逆文档频率语义向量相似度
优势精确关键词匹配、对专有名词友好语义泛化能力、跨语言能力
劣势无法处理同义词和语义漂移对专有实体和精确匹配较弱
适用场景专有名词检索、精确查询语义相似但表述不同的情况

互补逻辑

  • BM25 擅长精确词项匹配,Query 中若有「深度学习」「Transformer」等关键术语,BM25 能精准命中
  • Bi-Encoder 擅长捕捉语义相似,当 Query 与 Doc 表述方式不同但含义相近时(如「如何训练神经网络」vs「神经网络训练方法」),向量检索更为有效

混合检索的典型做法:

Query → [BM25召回 top-100] ∪ [向量召回 top-100] → 合并去重 → 重排 → Top-K

2.2 规则过滤 + 模型检索

工业系统中,规则过滤通常作为模型检索的前置保障层:

  1. 业务规则过滤:年龄限制、地域限制、内容合规性检查等
  2. 质量规则过滤:文档质量分数阈值、最低相关度阈值
  3. 去重规则:语义去重(Near-Duplicate Detection)、时间衰减去重

这种「先规则后模型」的架构符合直觉:规则层承担简单明确的布尔逻辑,模型层负责复杂的软判断。


三、重排模型详解

3.1 Bi-Encoder vs Cross-Encoder

这是检索系统中两种核心编码范式:

Bi-Encoder(双编码器)

Query → Encoder_Q → vector_Q
Doc   → Encoder_D → vector_D
Score = cosine_similarity(vector_Q, vector_D)

特点

  • 查询和文档独立编码,编码结果可预先计算并索引
  • 查询延迟低:只需计算一次向量,即可与所有文档向量做相似度运算
  • 交互能力弱:无法捕捉查询与文档之间的细粒度交互

适用场景:召回阶段的大规模候选生成

Cross-Encoder(交叉编码器)

[Query; Doc] → Encoder → Score

特点

  • 查询和文档拼接后联合编码,能充分捕捉交互特征
  • 准确性高:显著优于 Bi-Encoder 的排序质量
  • 计算开销大:每个 (Query, Doc) 对都需要独立编码,无法复用中间结果

适用场景:重排阶段的小规模精细排序

3.2 排序学习的三种范式

排序学习(Learning to Rank, LTR)有三种主流方法:

Pointwise(点对点)

每个文档单独评估,预测其与查询的相关性分数。

目标函数

其中 为模型预测分数, 为真实标签(0/1 或 0-4 的相关性等级), 为交叉熵或均方误差损失。

优点:简单直观,可直接复用分类模型 缺点:忽略了文档之间的相对顺序关系

Pairwise(成对排序)

对每个查询-文档对,模型判断哪个文档更相关。

目标函数

优点:直接优化相对顺序,符合排序本质 缺点:计算复杂度 ,且不直接优化 NDCG 等评价指标

Listwise(列表排序)

直接优化整个候选列表的排序质量,最接近评价指标。

目标函数

以 LambdaMART 为代表的 listwise 方法直接优化 NDCG:

优点:直接优化排序评价指标 缺点:训练复杂度高,需要更精细的超参数调优

3.3 重排为什么能提升 Precision

重排提升 Precision 的核心机制在于交互特征的充分利用。Cross-Encoder 在重排阶段能访问完整的查询-文档上下文,从而:

  1. 精确匹配信号:识别查询中的专有名词是否在文档中精确出现
  2. 依赖关系捕捉:如查询「刘德华的妻子」需要文档同时包含「刘德华」和「妻子」这两个实体及其关系
  3. 多信号联合判断:将语义相似度、词汇重叠度、结构相似度等多种信号联合建模

从数学上看,重排阶段优化的是条件排序质量——在给定召回候选集 的情况下,学习一个排序函数 使得:

因为 是召回阶段输出的高质量候选集合,其 Precision@K 的理论上限远高于全量文档库,重排模型只需在好的候选中挑最优,而非大海捞针。


四、负例采样与 Hard Negatives

4.1 为什么需要负例

检索模型的训练本质上是一个对比学习问题:让相关文档的分数高于不相关文档。因此负例采样的质量直接影响模型性能。

随机负例(Random Negatives): 从文档库中随机采样作为负例。优点是简单,缺点是过于简单——模型容易学到「简单区分即可」的浅层特征。

4.2 Hard Negatives(难负例)

Hard Negatives 是指与正例较为相似但实际不相关的文档,是提升模型判别能力的关键。

来源

  • 召回阶段的高相似度但非正例的文档:如 Bi-Encoder 召回的 top-100 中未被标注为正例的文档
  • 同类别不同实体的文档:如查询「iPhone 15」时,「iPhone 14」就是 hard negative

训练策略

一种经典做法是两阶段对比学习

  1. 第一阶段:使用随机负例训练基础模型
  2. 第二阶段:用第一阶段模型从正例附近采样 hard negatives,重新训练

In-Batch Negatives

一种高效的负例利用方式是批内负例。在一个训练批次中,每个查询的正例对其他查询来说都是负例:

对于 batch 中的 query_i 和 doc_i:
- 正例:doc_i
- 负例:batch 中所有其他 doc_j (j ≠ i)

这种方法的优点是无额外计算成本,缺点是负例质量依赖于 batch 构成。

4.3 语义相似 ≠ 任务相关

这是检索系统中最重要的认知偏差之一。举例说明:

查询文档语义相似度任务相关性
「华为手机」「华为公司简介」中(偏离核心需求)
「深度学习入门」「深度学习进阶」低(同一主题但非包含关系)
「北京旅游攻略」「上海旅游攻略」低(地域不匹配)

语义相似度衡量的是语言层面的关联性,而任务相关性需要结合用户意图、查询上下文、业务目标等多维度判断。Cross-Encoder 之所以有效,正是因为它能在重排阶段融合更多信号来弥补语义相似度与真实相关性的差距。


五、工程流程:两阶段检索系统搭建

5.1 系统架构概览

用户查询
    ↓
┌─────────────────────────────────────────┐
│            第一阶段:召回                │
│  ┌─────────────┐    ┌─────────────┐    │
│  │   BM25      │    │  Bi-Encoder │    │
│  │  稀疏召回   │    │   向量召回   │    │
│  └─────────────┘    └─────────────┘    │
│         ↓                  ↓            │
│        Top-100          Top-100         │
│              ↓    ↓                     │
│         合并去重(可能包含规则过滤)      │
│              ↓                          │
│           Top-200(召回候选集)          │
└─────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────┐
│            第二阶段:重排                │
│  ┌─────────────────────────────────┐    │
│  │        Cross-Encoder            │    │
│  │   对 Top-200 逐个打分排序        │    │
│  └─────────────────────────────────┘    │
│              ↓                          │
│           Top-20 → 返回用户              │
└─────────────────────────────────────────┘

5.2 召回候选集大小选择

召回候选集大小是系统设计中的关键超参数,需要在效果与效率之间权衡:

召回量Precision@20(典型值)延迟(典型值)适用场景
50中高延迟敏感场景
100平衡场景
200很高中高效果优先
500+极高召回质量优先

决策依据

  • 重排模型能力:Cross-Encoder 越强,可承受的召回候选集越大
  • 正例分布:如果 top-1 文档在 top-50 召回中的覆盖率很高,可减小召回量
  • 系统延迟预算:若延迟要求 < 50ms,召回量通常限制在 100 以内

一个实用的原则是:召回候选集应覆盖 95%+ 的期望正例。可以用公式估算:

其中 是查询 的全部相关文档集合, 是召回候选集。

5.3 重排模型训练流程

数据准备

  1. 标注数据构建

    • 点击数据作为弱标注(点击 > 未点击)
    • 人工标注作为强标注(相关 / 不相关 / 极度相关)
    • 合成负例:随机负例 + Hard Negatives
  2. 训练/验证/测试集划分

    • 按查询划分,避免数据泄露
    • 确保验证集分布与测试集一致

模型训练

以 Cross-Encoder 为例:

# 伪代码:Cross-Encoder 训练
for epoch in range(num_epochs):
    for batch in dataloader:
        query, doc, label = batch
        # 拼接输入:[CLS] query [SEP] doc [SEP]
        score = cross_encoder(query, doc)  # 输出相关度分数
        loss = BCE_loss(score, label)
        loss.backward()
        optimizer.step()

训练技巧

  • 使用对比学习预训练Bi-Encoder,再用标注数据微调Cross-Encoder
  • 交替使用随机负例和hard negatives进行课程学习(Curriculum Learning)
  • 标签平滑(Label Smoothing)防止过拟合

评估指标

指标计算方式侧重
MRR@KMean Reciprocal Rank, 的均值top-1 质量
NDCG@KNormalized Discounted Cumulative Gain综合排序质量
Precision@Ktop-K 中相关文档比例精确度
Recall@K相关文档中被召回的比例召回能力(重排阶段通常不关注)

5.4 延迟与吞吐控制

重排阶段是延迟的主要瓶颈。以下是典型的优化策略:

延迟优化

  1. 模型轻量化

    • 使用蒸馏(Distillation)压缩 Cross-Encoder
    • 参数量从 110M(BERT-base)→ 22M(DistilBERT)或更小
    • 延迟可从 20ms/样本 降至 3-5ms/样本
  2. 批处理(Batching)

    • 积攒多个查询的重排请求,合并为一个批次
    • 吞吐提升 3-5x,但会增加 P99 延迟
  3. Early Exiting

    • 对低置信度样本使用浅层网络输出
    • 减少 20-30% 的计算量

吞吐优化

  1. 异步重排:召回与重排解耦,召回完成后立即进行重排请求
  2. 模型并行:多 GPU 分布式推理
  3. 缓存:对高频查询的重排结果进行缓存

5.5 缓存与分层过滤

缓存策略

缓存层级缓存内容命中率(典型值)适用场景
Query 缓存完整查询的重排结果10-30%高频 query(如热播剧、热搜事件)
语义缓存相似的语义 query 结果20-40%查询意图集中场景
Document 缓存单文档的特征向量文档更新频率低

分层过滤架构

Query → 缓存查询 → 命中? → [返回缓存结果]
                        ↓ 否
                    规则过滤 → 通过? → [返回空/兜底结果]
                                     ↓ 是
                                 两阶段检索 → 返回结果

层级化设计确保了:

  • 高频 query 的毫秒级响应
  • 规则过滤提前拦截无效请求,节省重排资源
  • 兜底策略保证系统在极端情况下的可用性

六、系统设计建议与注意事项

6.1 混合检索系统设计建议

  1. 不要让召回成为瓶颈: 召回阶段决定了重排阶段的天花板。如果 top-200 召回候选集中根本不包含正确答案,再强的重排模型也无能为力。建议定期评估召回阶段的 Recall@200。

  2. BM25 和向量的融合权重需要调优: 简单的加权融合 中, 的选择至关重要。可以用 A/B 测试或强化学习的方法动态调整。

  3. 监控召回质量和重排质量的分位数: 关注 Recall@500(召回)和 NDCG@10(重排)的长期趋势,而非仅看平均值。

  4. 文档新鲜度的影响: 对于新闻搜索等场景,向量索引的更新频率需要与文档更新频率匹配。BM25 对新文档友好,而向量索引可能需要定期重训练。

6.2 重排模型训练注意事项

  1. 负例质量 >> 负例数量: 100 个精心挑选的 hard negatives 远比 10000 个随机负例有效。建议用「召回模型的高分但非正例」作为 hard negatives 的主要来源。

  2. 避免标签噪声: 点击数据作为训练标签时,点击 ≠ 相关。需要通过 Session 分析、停留时长等信号对点击数据进行去噪。

  3. 跨领域迁移需谨慎: 在一个领域训练的重排模型直接迁移到另一个领域通常效果下降明显。领域适配(如领域微调)是必要的。

  4. 线上线下一致性(Train-Serve Gap): 训练时使用的负例分布应与线上召回候选集分布一致。如果训练时用随机负例,线上用召回候选集作负例,会导致严重的分布偏移。

  5. 延迟与质量的帕累托最优: 在延迟约束下,不要追求单个 query 的极致质量,而应追求全局的帕累托最优。某些情况下,轻量级模型的快速排序优于重量级模型的慢速精确排序。


七、总结

两阶段检索系统(召回 → 重排)是当前大规模信息检索系统的主流架构,其核心设计哲学是分工与协作

  • 召回阶段负责高召回,用 BM25 和 Bi-Encoder 两种互补的召回方式尽可能覆盖所有潜在相关文档
  • 重排阶段负责高精度,用 Cross-Encoder 精细化排序,将最相关的少量文档排在最前

混合检索融合了稀疏检索的精确匹配能力和稠密检索的语义泛化能力,是当前工程系统的主流选择。而重排模型的训练核心在于负例质量、线上线下分布一致性,以及在延迟约束下追求最优的排序质量。

理解「召回是广撒网,重排是精挑细选」这一核心思想,是设计高效检索系统的关键。