什么是重排序(Rerank)?为什么检索之后还要重排序?
结论:重排序(Rerank)是"召回之后、生成之前"的第二阶段精排,用**交叉编码器(Cross-Encoder)**把"查询 + 候选片段" 拼在一起过一遍模型,逐对给出相关性分数。向量召回用的是双塔(Bi-Encoder),两边独立编码、只靠点积交互, 又快又粗;重排模型慢但看得见词与词的对应关系,所以能把"沾边"的候选压下去、"真正回答"的顶上来。
1. 双塔与交叉编码器的结构差异
双塔(Bi-Encoder)—— 召回用
query ─▶ [编码器] ─▶ q_vec ─┐
├─▶ 点积/余弦 ─▶ 分数
doc ─▶ [编码器] ─▶ d_vec ─┘ (doc 向量可以离线预计算)
交叉编码器(Cross-Encoder)—— 重排用
[CLS] query [SEP] doc [SEP] ─▶ [编码器] ─▶ 分类头 ─▶ 相关性分数
(必须在查询时联合前向,无法预计算)
| 对比项 | 双塔 | 交叉编码器 |
|---|---|---|
| 文档向量能否预计算 | 能,索引一次查千次 | 不能,每次查询都要现算 |
| 候选规模 | 百万到亿级 | 20~100 条 |
| 精度 | 中 | 高,能捕捉否定、限定、数字对应 |
| 单条延迟 | 微秒级(点积) | 毫秒级(GPU 上 1~5 ms) |
2. 为什么非要重排:召回阶段的四类系统性失真
| 失真来源 | 具体表现 |
|---|---|
| 交互缺失 | query 和 doc 分别压缩成一个向量,"不包含 X 的方案"与"包含 X 的方案"可能贴得很近 |
| ANN 近似误差 | 图索引只走"大概率正确"的邻居,真近邻可能被跳过 |
| 长片段均值化 | 500 字的块里只有一句相关,向量被其余内容稀释 |
| 分数不可比 | 不同查询的余弦分布不同,0.72 在这题是有希望,在另一题是噪声 |
重排模型读的是原文全文,能判断"这句话到底回答了问题没有",这是双塔结构永远做不到的。
3. 延迟预算怎么分配
| 阶段 | 条数 | 单条耗时 | 合计 |
|---|---|---|---|
| 向量/BM25 召回 | 50 + 50 | < 1 ms | ~30 ms |
| RRF 融合 | 100 → 20 | 微秒 | < 5 ms |
| 交叉编码器重排 | 20 | 3 ms(GPU) | 60 ms |
| 生成 | 1 次调用 | — | 0.8~2 s |
重排放在 20~50 条上是性价比最高的区间:再多,延迟线性上涨而召回收益趋零——因为正确片段早就在召回结果里了。
4. 代码:用 bge-reranker 做精排
from sentence_transformers import CrossEncoder
# 中文场景优先 bge-reranker-v2-m3(多语言)或 bge-reranker-base
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3", max_length=512)
def rerank(query: str, candidates: list[dict], top_n: int = 5,
min_score: float = 0.35) -> list[dict]:
pairs = [(query, c["text"]) for c in candidates]
scores = reranker.predict(pairs, batch_size=16, show_progress_bar=False)
for c, s in zip(candidates, scores):
c["rerank_score"] = float(s)
kept = [c for c in candidates if c["rerank_score"] >= min_score]
kept.sort(key=lambda c: c["rerank_score"], reverse=True)
# 没有一条过阈值就返回空列表,让上层走"无资料"分支而不是硬编
return kept[:top_n]
# 输出:[{'text': '差旅住宿标准为每晚上限 600 元', 'rerank_score': 0.91}, ...]
min_score 是抑制幻觉的第一道闸门:交叉编码器的分数在同一模型内可比,比向量余弦更适合当"相关性阈值"。
具体阈值要在评估集上扫出来,别照抄。
5. 重排与"中间遗忘"要一起处理
模型对长上下文的注意力是 U 形分布的:开头和结尾记得牢,中间容易忽略。所以重排之后不要简单按分数从高到低平铺, 可以把最相关的两条放首尾,其余放中间:
[资料1 最高分] ... [资料3] [资料4] ... [资料2 次高分]
↑ 首尾位置更容易被引用 ↑ 中间区域是遗忘区
6. 常见追问
Q:重排能提升召回率吗? 不能。重排只在候选集内重新排序,如果正确片段没被召回,重排无能为力。所以指标上:重排提 precision/NDCG,召回要靠混合检索与查询改写。
Q:为什么不用大模型直接打分? 可以,但 20 条候选 × 每次一条 LLM 调用,延迟和成本都不可接受。专用交叉编码器小两个数量级,效果在相关性判断上并不差。
Q:本地模型还是 API? 有 GPU 就本地部署 bge-reranker,无 GPU 可用 Cohere Rerank 这类接口;注意 API 有单次条数上限,且数据要出域。
Q:重排后再截断到几条? 3~8 条。条目数与单块大小要联动算总 token,超过 8k 就该做上下文压缩了。
小结
召回负责"别漏",重排负责"别错"。把两者混为一谈,就会出现"堆更多候选"这种越调越慢、效果不涨的优化。