召回与精排
五层读懂一个词。这次拆的是:召回与精排(Recall & Rerank)--两阶段检索的工程架构,工业级搜索系统的标配漏斗。
L1 · 一句话点破
召回从百万级筛到 top-1000(保 recall,速度快),精排从 top-1000 重排到 top-10(求 precision,精度高)。两阶段配合,把"快但粗"和"慢但准"的优点结合起来。
L2 · 通俗类比
想象超市选苹果:
- 召回阶段:在仓库十万颗苹果中快速扫一遍,挑出可能新鲜的 1000 颗。要求快,宁可多挑不可漏。这步用粗筛标准(颜色、大小)。
- 精排阶段:把这 1000 颗逐个仔细检查(闻香、看把儿、测糖度),挑出最好的 10 颗摆上货架。要求准,可以慢。
为什么不直接精排十万颗?因为精排慢,逐个仔细检查要几小时。为什么召回后不只看 top-10?因为粗筛标准不够准,真正最好的苹果可能排到 top-500,被截断就漏了。
工程取舍:召回保 recall(不漏好货),精排求 precision(前排都是好货)。两阶段配合是工业检索系统的标配。
实际系统可能更多阶段:召回 -> 粗排 -> 精排 -> 重排(多样性 / 业务),形成多级漏斗。
L3 · 正经定义
召回 (Recall) 阶段:从大规模文档库(百万~十亿)快速筛选出候选集(千级),目标是高 recall(相关文档不漏)。
精排 (Rerank) 阶段:对候选集(千级)用复杂模型重排,输出 top-k(十级),目标是高 precision(top-k 都是相关的)。
典型架构:
全库 (10^9)
↓ 召回(双塔 + ANN + BM25)
候选集 (10^3)
↓ 精排(Cross-encoder)
top-k (10)
↓ 重排(多样性 / 业务规则)
最终展示 (10)召回阶段特性:
- 高 recall(> 95% 相关文档在候选集中)
- 低延迟(< 100ms)
- 可并行多路召回(双塔、BM25、ColBERT 等)
- 模型轻量(双塔、IVF-PQ)
精排阶段特性:
- 高 precision(top-k 都相关)
- 中等延迟(100~500ms)
- 模型重量级(Cross-encoder、LLM-reranker)
- 可用更多特征(文档元数据、用户行为、上下文)
多路召回融合:
def multi_recall(query, top_k_per_channel=500):
# 多路召回
dense_results = bi_encoder_retrieve(query, top_k=500) # 双塔
sparse_results = bm25_retrieve(query, top_k=500) # BM25
colbert_results = colbert_retrieve(query, top_k=500) # ColBERT
# 融合(RRF 或加权)
fused = rrf_fuse([dense_results, sparse_results, colbert_results])
return fused[:1000] # 精排候选精排重排:
def rerank(query, candidates, top_k=10):
pairs = [(query, c.text) for c in candidates]
scores = cross_encoder.score(pairs) # 批量打分
reranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
return reranked[:top_k]评估指标分阶段:
- 召回阶段:recall@1000(候选集中相关文档比例)
- 精排阶段:recall@10、MRR@10、NDCG@10(top-k 中相关文档排序质量)
L4 · 原理深挖
4.1 为什么要两阶段
单阶段方案的局限:
- 全用 Cross-encoder:精度高但每对前向,百万级不可行
- 全用双塔:可扩展但精度不足,top-1 可能不够准
两阶段的优势:
- 召回用快模型,从百万筛到千级
- 精排用重模型,在千级上求精度
- 总延迟 = 召回延迟 + 精排延迟 ≈ 50ms + 200ms = 250ms,可接受
信息论视角:召回是"宽网捞鱼",让真正相关的候选进入下一步;精排是"细筛分鱼",在候选中精确排序。两者目标不同,模型设计也不同。
4.2 召回阶段的多路融合
现代系统几乎都用多路召回(hybrid retrieval):
典型多路组合:
- 双塔(Dense):语义召回,强项是同义、近义
- BM25(Sparse):字面召回,强项是精确匹配、专有名词
- ColBERT(Late interaction):精度高于双塔,速度低于双塔,可作为补充召回
- 用户行为召回:基于点击日志、协同过滤
- 业务规则召回:如最新发布、热门、订阅等
融合方法:
- RRF (Reciprocal Rank Fusion):$\text{score}(d) = \sum_i \frac{1}{k + \text{rank}_i(d)}$,无需归一化,鲁棒
- 加权融合:$\text{score}(d) = \sum_i w_i \cdot \text{normalize}(s_i(d))$,需调权重
- 学习融合(LTR):训练排序模型融合多路分数
详见后续 hybrid-search、rrf、weighted-fusion 词条。
4.3 精排阶段的特征工程
Cross-encoder 主要用文本特征,但精排可加更多特征:
文本特征:
- query-doc 文本匹配分数(Cross-encoder)
- BM25 分数(保留作特征)
- 双塔相似度分数(保留作特征)
文档特征:
- 文档长度、发布时间、质量分
- 文档历史点击率、收藏率
- 文档作者权重、来源权威度
查询特征:
- query 长度、意图分类
- 用户历史查询、用户画像
- 上下文(如会话历史)
交叉特征:
- query 与 doc 的主题匹配度
- 用户与 doc 的历史交互
- 个性化偏好
精排模型:
- Cross-encoder:纯文本 rerank
- GBDT (LightGBM / XGBoost):特征工程强,可解释
- DNN:深度融合文本和特征
- LTR (Learning to Rank):LambdaMART 等经典方法
4.4 多阶段排序的漏斗
工业级系统常是多阶段,每阶段过滤比例约 10:1:
1. 召回(百万 -> 千)
- 多路并行:双塔 + BM25 + ColBERT
- 融合:RRF 或加权
- 目标:recall > 95%
2. 粗排(千 -> 百)
- 轻量 Cross-encoder 或 GBDT
- 用更多特征
- 目标:保留 top-100 中至少 90% 真正相关
3. 精排(百 -> 十)
- 重 Cross-encoder 或 DNN
- 全特征
- 目标:top-10 中至少 8 个相关
4. 重排(十 -> 展示)
- 多样性(MMR)
- 业务规则(去重、过滤、加权)
- 个性化为什么多阶段:
- 单次精排 top-1000 延迟太大(Cross-encoder 每对 50ms,千对 = 50s 不可行)
- 粗排用轻量模型先把千筛到百,再上重模型
- 每阶段过滤比例小(10:1)保证 recall 不掉太多
4.5 召回与精排的训练数据差异
召回训练数据:
- 大规模点击日志(千万~亿级)
- 弱监督(点击视为正样本,但点击 ≠ 相关)
- 难负样本挖掘(BM25 top-k 非相关)
- 偏好覆盖广(多领域、多查询类型)
精排训练数据:
- 人工标注(千~万级,质量高)
- 多级标签(高度相关 / 相关 / 一般 / 不相关)
- 偏好精度(细分相关度)
- 难负样本(与正样本相似的负样本)
关键差异:召回数据多但噪声大,精排数据少但精确。两阶段配合,召回用大数据泛化,精排用小数据精调。
4.6 召回阶段的 recall 上限决定整体上限
铁律:如果召回阶段漏掉相关文档(recall@1000 = 80%),精排再准也无法恢复(最高 recall@10 = 80%)。
工程含义:
- 召回阶段宁可多召回(top_k = 1000 而非 100),保证 recall
- 多路召回互补(BM25 + Dense + ColBERT),各路召回率叠加
- 召回质量评估比精排更重要(recall@1000 是关键指标)
实践:定期监控召回阶段的 recall@1000,掉到 90% 以下就要调参或加路。
4.7 精排的延迟优化
精排 top-1000 延迟大,优化手段:
优化 1:粗排预筛。粗排(轻量模型)先把 1000 筛到 100,再上 Cross-encoder。粗排可用 GBDT、轻量 BERT。
优化 2:批量化。top-100 一次前向,比逐对快 10 倍+。
优化 3:量化。FP16 / INT8 量化,速度 2~4 倍。
优化 4:缓存。同 query 候选分数缓存。
优化 5:异步预计算。可预知 query 时提前算(如热门 query 缓存结果)。
优化 6:模型蒸馏。把重 Cross-encoder 蒸馏到轻量 student,部署时用 student。
4.8 重排阶段:多样性与业务
精排后还需重排,原因:
多样性:top-10 都是相关,但内容雷同(如 10 篇都讲同一观点),用户体验差。用 MMR (Maximal Marginal Relevance) 等算法保证多样性:
$$ \text{MMR}(d) = \arg\max_{d \in C} [\lambda \cdot \text{sim}(q, d) - (1 - \lambda) \cdot \max_{d' \in S} \text{sim}(d, d')] $$
$S$ 是已选集,$\lambda$ 控制相关性与多样性的权衡。
业务规则:
- 去重(同作者、同来源限流)
- 过滤(敏感内容、过期内容)
- 加权(精品内容、付费内容加权)
- 个性化(用户偏好)
位置偏见:top 位置有展示优势,重排时考虑位置偏差(如用 ELO 评分)。
L5 · 沿革与坑
5.1 历史脉络
- 1990s:搜索引擎早期就是 BM25 单阶段排序
- 2000s:Yahoo、Google 引入 LTR(Learning to Rank),多特征精排
- 2010s:深度学习兴起,DSSM 等双塔召回
- 2016:YouTube 双塔推荐召回,工业级两阶段架构
- 2019:BERT-based reranker 兴起,精排精度大幅提升
- 2020:DPR 论文确立两阶段架构(双塔召回 + Cross-encoder 精排)
- 2021+:多路召回融合(BM25 + Dense)成为标配
- 2023+:RAG 系统采用同样架构(向量召回 + Cross-encoder 精排)
5.2 工程常见坑
坑 1:召回 top_k 太小。
召回 top_k = 50,若真正相关文档在第 51~100 位,精排再准也救不回。建议 top_k = 500~1000,给精排足够候选。
坑 2:精排候选规模太大。
精排 top_k = 1000 时 Cross-encoder 延迟太大(> 1 秒)。要么加粗排预筛,要么减小 top_k 到 100~200。
坑 3:召回和精排训练数据不一致。
召回用点击日志(弱监督),精排用人工标注(强监督)。两模型见的"相关"定义可能不一致,精排时召回的候选可能与精排判断冲突。训练时对齐两阶段的相关性定义。
坑 4:忘了监控召回 recall。
召回 recall 悄悄掉到 80%,精排再努力也救不回。监控 recall@1000,掉到 90% 以下立即调参。
坑 5:多路召回分数没归一化。
Dense 分数范围 [0, 1],BM25 分数范围 [0, 50+],直接加权 BM25 主导。要用 RRF(无需归一化)或归一化后加权。
坑 6:精排用召回阶段特征。
召回用文本向量,精排还用文本向量,信息冗余。精排要加新特征(点击率、用户行为、文档质量)提升价值。
坑 7:粗排召回率不够。
粗排把 1000 筛到 100 时漏掉真正相关文档,精排救不回。粗排 recall@100 要 > 95%。
坑 8:精排模型未量化。
FP32 推理慢,FP16 / INT8 量化速度快 2~4 倍,精度损失 < 1%。生产必量化。
坑 9:重排过度。
重排加太多业务规则(去重、过滤、加权),把精排结果搞乱。重排要克制,主要做多样性和必要业务规则。
坑 10:评估只看精排指标。
只看 recall@10、NDCG@10,不看 recall@1000,发现不了召回阶段问题。两阶段指标都要监控。
5.3 召回与精排的模型选择
召回模型:
| 模型 | 速度 | 精度 | 适用规模 |
|---|---|---|---|
| BM25 | 极快 | 中(字面) | 任意 |
| 双塔 (DPR/BGE) | 快 | 中(语义) | 大规模 |
| ColBERT | 中 | 高 | 中规模 |
精排模型:
| 模型 | 速度 | 精度 | 适用 |
|---|---|---|---|
| Cross-encoder | 慢 | 高 | 文本精排 |
| GBDT | 快 | 中 | 特征精排 |
| DNN | 中 | 高 | 多特征融合 |
| LTR (LambdaMART) | 中 | 中 | 经典排序 |
典型组合:
- 通用:BM25 + 双塔召回 -> Cross-encoder 精排
- 高精度:BM25 + 双塔 + ColBERT 召回 -> Cross-encoder 精排
- 工业级:多路召回 -> GBDT 粗排 -> Cross-encoder 精排 -> MMR 重排
- RAG 简化:BM25 + 双塔召回 -> 直接用 LLM 精排
5.4 RAG 中的召回与精排
RAG 系统的检索就是召回与精排的简化版:
知识库 (10^6 chunks)
↓ 召回(BM25 + Dense 混合)
候选 chunks (10^2)
↓ 精排(Cross-encoder 或 LLM)
top-k chunks (5~10)
↓ 喂给 LLM 生成回答RAG 简化版的特点:
- 召回阶段常用 BM25 + Dense 双路
- 精排可选(小规模知识库可省略)
- top-k 通常 5~10(受 LLM context 限制)
- 重排用 LLM 自身(生成时排序)
RAG 精排的取舍:
- 不精排:省延迟,但 top-k 可能有不相关 chunk,LLM 生成受影响
- Cross-encoder 精排:精度提升,但加 200ms 延迟
- LLM 精排:让 LLM 自己评估 chunk 相关性,精度高但成本高
速记卡
| 阶段 | 目标 | 模型 | 延迟 | 规模 |
|---|---|---|---|---|
| 召回 | recall 高 | BM25 / 双塔 / ColBERT | < 100ms | 百万 -> 千 |
| 粗排 | 中等过滤 | GBDT / 轻量 BERT | < 50ms | 千 -> 百 |
| 精排 | precision 高 | Cross-encoder / DNN | 100~500ms | 百 -> 十 |
| 重排 | 多样性 / 业务 | MMR / 规则 | < 10ms | 十 -> 展示 |
关键指标:
- 召回:recall@1000(候选集含相关文档比例)
- 精排:recall@10 / MRR@10 / NDCG@10
- 端到端:recall@10 / MRR@10
两阶段铁律:
- 召回 recall 决定上限(精排救不回召回漏掉的)
- 召回宁多勿少(top_k = 1000 而非 100)
- 精排求 precision(top-k 都是相关的)
- 多路召回互补(BM25 + Dense + ColBERT)
一句话记忆:召回保 recall(百万筛千,快但粗),精排求 precision(千筛十,慢但准)。两阶段漏斗配合,是工业检索系统的标配。RAG 系统简化为 BM25 + Dense 召回 + 可选 Cross-encoder 精排。