RAG07:重排序
理解重排序的核心机制,包括Cross Encoder、规则重排和上下文压缩,提升检索结果的准确性和相关性。
重排序是检索流程中的关键环节,它能显著提升检索结果的质量。经过初步检索后,重排序可以对候选结果进行精细排序,让最相关的内容排在前面。
重排序的必要性
初步检索的局限性
初步检索(如向量检索、关键词检索)存在以下问题:
| 问题 | 表现 | 影响 |
|---|---|---|
| 语义匹配粗糙 | 只考虑单向量相似度 | 可能遗漏深层语义相关的内容 |
| 缺乏上下文理解 | 不考虑查询与文档的交互 | 无法理解复杂的语义关系 |
| 噪声干扰 | 返回的结果包含不相关内容 | 降低用户体验和回答质量 |
| 排序不够精细 | 只按单一指标排序 | 无法满足复杂的排序需求 |
重排序的价值
重排序通过更精细的计算提升结果质量:
初步检索(Recall)→ 召回尽可能多的相关内容
↓
重排序(Precision)→ 精确定位最相关的内容
↓
最终结果 → 既全面又精准
Cross Encoder 重排序
Cross Encoder 原理
Cross Encoder 是一种基于 Transformer 的重排序模型,它同时处理查询和文档对:
| 阶段 | 说明 | 输出 |
|---|---|---|
| 输入层 | 查询 + 文档拼接 | [CLS] query [SEP] document [SEP] |
| 编码层 | Transformer 编码 | 上下文表示 |
| 输出层 | 分类/回归头 | 相关性分数 |
Cross Encoder vs Bi Encoder:
| 维度 | Bi Encoder | Cross Encoder |
|---|---|---|
| 编码方式 | 独立编码查询和文档 | 联合编码查询和文档 |
| 交互信息 | 无 | 有丰富的交互信息 |
| 计算效率 | 高(预计算向量) | 低(实时计算) |
| 检索阶段 | 初步检索 | 重排序 |
| 适用场景 | 大规模召回 | 小规模精排 |
Cross Encoder 实现
from sentence_transformers import CrossEncoder
class CrossEncoderReranker:
def __init__(self, model_name='cross-encoder/ms-marco-MiniLM-L-6-v2'):
self.model = CrossEncoder(model_name)
def rerank(self, query, documents, top_k=10):
"""使用 Cross Encoder 进行重排序"""
# 构建查询-文档对
pairs = [(query, doc) for doc in documents]
# 计算相关性分数
scores = self.model.predict(pairs)
# 排序并返回
results = sorted(enumerate(scores), key=lambda x: x[1], reverse=True)
return [(idx, score) for idx, score in results[:top_k]]
Cross Encoder 优化
| 优化方式 | 说明 | 效果 |
|---|---|---|
| 模型选择 | 使用轻量级模型 | 速度更快 |
| 批量处理 | 批量计算多个文档 | 提高效率 |
| 缓存机制 | 缓存常见查询的结果 | 减少重复计算 |
| 蒸馏模型 | 使用知识蒸馏的小模型 | 保持效果的同时加速 |
规则重排序
规则重排序的优势
规则重排序具有以下优势:
| 优势 | 说明 | 示例 |
|---|---|---|
| 可解释性 | 规则透明,易于理解和调整 | ”优先展示最近更新的文档” |
| 实时性 | 计算快速,无模型推理延迟 | 毫秒级响应 |
| 灵活性 | 可随时添加或修改规则 | 根据业务需求调整 |
| 成本低 | 无需训练和维护模型 | 节省资源 |
常见重排序规则
| 规则类型 | 说明 | 计算公式 |
|---|---|---|
| 时间衰减 | 最近更新的文档权重更高 | score * 0.5^(days/half_life) |
| 位置加权 | 文档开头匹配权重更高 | score * (1 + position_factor) |
| 长度惩罚 | 过长或过短的文档适当降权 | score * length_factor |
| 来源权重 | 不同来源的文档权重不同 | score * source_weight |
| 点击反馈 | 根据用户点击行为调整 | score * click_factor |
规则重排序实现
def rule_based_rerank(query, results, docs):
"""基于规则的重排序"""
reranked = []
for doc_id, initial_score in results:
doc = docs[doc_id]
score = initial_score
# 时间衰减规则
days_since_update = (datetime.now() - doc.update_time).days
time_decay = 0.5 ** (days_since_update / 30)
score *= time_decay
# 长度惩罚规则(文档长度在 100-500 词之间最优)
if len(doc.text) < 100:
score *= 0.7
elif len(doc.text) > 500:
score *= 0.8
# 来源权重规则
source_weights = {'official': 1.2, 'community': 1.0, 'third-party': 0.8}
score *= source_weights.get(doc.source, 1.0)
# 位置加权规则(关键词在标题中的权重更高)
if query.lower() in doc.title.lower():
score *= 1.3
reranked.append((doc_id, score))
return sorted(reranked, key=lambda x: x[1], reverse=True)
上下文压缩
上下文压缩的必要性
大模型的上下文窗口有限,需要压缩检索结果:
| 限制 | 说明 | 影响 |
|---|---|---|
| 上下文窗口 | 大多数模型有 4K-128K token 限制 | 无法输入太多文档 |
| Token 成本 | 更多上下文意味着更高成本 | 增加使用成本 |
| 注意力分散 | 太多信息会分散模型注意力 | 降低回答质量 |
上下文压缩策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 摘要压缩 | 使用模型生成文档摘要 | 长文档压缩 |
| 关键词提取 | 提取文档的关键词 | 快速理解文档主题 |
| 相关性截断 | 只保留与查询最相关的部分 | 部分相关文档 |
| 层次压缩 | 先按主题分组,再选代表 | 大量相似文档 |
上下文压缩实现
from transformers import pipeline
class ContextCompressor:
def __init__(self, max_tokens=2000):
self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
self.max_tokens = max_tokens
def compress(self, documents, query=None):
"""压缩上下文,适应模型窗口限制"""
compressed_docs = []
total_tokens = 0
for doc in documents:
# 如果文档太长,生成摘要
if len(doc.text) > 500:
summary = self.summarizer(
doc.text,
max_length=200,
min_length=50,
do_sample=False
)[0]['summary_text']
compressed_text = summary
else:
compressed_text = doc.text
# 检查 token 数量
doc_tokens = len(compressed_text) // 4 # 粗略估算
if total_tokens + doc_tokens > self.max_tokens:
break
compressed_docs.append({
'title': doc.title,
'text': compressed_text,
'source': doc.source
})
total_tokens += doc_tokens
return compressed_docs
学习排序(Learning to Rank)
学习排序原理
学习排序是一种机器学习方法,它从训练数据中学习排序模型:
| 阶段 | 说明 | 输出 |
|---|---|---|
| 特征提取 | 提取查询-文档对的特征 | 特征向量 |
| 模型训练 | 使用排序损失训练模型 | 排序模型 |
| 预测排序 | 使用模型预测相关性 | 排序结果 |
常用学习排序算法
| 算法 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| LambdaRank | 列表级 | 直接优化 NDCG | 大多数场景 |
| XGBoost/LightGBM | 点级/列表级 | 高效、灵活 | 需要快速迭代 |
| Transformer-based | 深度学习 | 捕捉复杂语义 | 需要高精度 |
学习排序特征
| 特征类型 | 示例 | 说明 |
|---|---|---|
| 检索特征 | BM25 分数、向量相似度 | 来自初步检索 |
| 文本特征 | 关键词匹配数、重叠度 | 文本层面的匹配 |
| 结构特征 | 文档长度、位置、来源 | 文档结构信息 |
| 用户特征 | 点击次数、停留时间 | 用户行为反馈 |
| 语义特征 | Cross Encoder 分数 | 深度语义匹配 |
重排序的常见问题
问题1:重排序延迟高
表现:重排序增加了检索延迟
解决方案:
- 使用轻量级模型
- 限制重排序的文档数量(如只重排 Top 50)
- 使用缓存机制
- 异步处理非关键路径
问题2:过拟合
表现:重排序模型在训练集上表现好,但线上效果差
解决方案:
- 使用更多样化的训练数据
- 添加正则化
- 定期在线评估和更新模型
问题3:规则冲突
表现:多个规则之间产生冲突
解决方案:
- 明确规则优先级
- 使用加权融合而非硬规则
- 定期评估规则效果
问题4:上下文不足
表现:压缩后的上下文丢失关键信息
解决方案:
- 使用更智能的压缩策略
- 保留关键信息(如公式、代码、关键数据)
- 提供原文链接供用户参考
重排序的最佳实践
重排序流程
初步检索结果(Top 50-100)
↓
┌─────────────────────────────┐
│ 规则重排序 │
│ - 时间衰减 │
│ - 来源权重 │
│ - 位置加权 │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ Cross Encoder 重排序 │
│ - 计算查询-文档对相关性 │
│ - 精细排序 │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ 上下文压缩 │
│ - 生成摘要 │
│ - 截断过长内容 │
│ - 控制 token 数量 │
└─────────────────────────────┘
↓
最终检索结果(Top 5-10)
性能优化策略
| 策略 | 说明 | 效果 |
|---|---|---|
| 减少候选数量 | 只对 Top 50 结果重排序 | 降低计算量 |
| 使用轻量级模型 | 如 MiniLM 系列 | 速度提升 3-5 倍 |
| 批量处理 | 批量计算多个查询-文档对 | 提高吞吐量 |
| 缓存 | 缓存常见查询的重排序结果 | 减少重复计算 |
评估指标
| 指标 | 定义 | 目标值 |
|---|---|---|
| NDCG@10 | 归一化折损累积增益 | > 0.8 |
| MAP | 平均准确率 | > 0.7 |
| MRR | 平均倒数排名 | > 0.8 |
| 延迟 | 重排序耗时 | < 50ms |
项目判断清单
- 初步检索结果质量差 → 添加 Cross Encoder 重排序
- 需要业务规则介入 → 使用规则重排序
- 上下文超限 → 实现上下文压缩
- 有大量用户行为数据 → 使用学习排序
- 重排序延迟高 → 减少候选数量或使用轻量级模型
- 需要可解释性 → 使用规则重排序为主
- 需要高精度 → 使用 Cross Encoder 或学习排序
- 需要持续优化 → 建立重排序效果评估机制