RAG07:重排序

理解重排序的核心机制,包括Cross Encoder、规则重排和上下文压缩,提升检索结果的准确性和相关性。

字数 1717 阅读时长 ≈ 5 分钟 2026-7-26 2026-7-27
RAG07:重排序

重排序是检索流程中的关键环节,它能显著提升检索结果的质量。经过初步检索后,重排序可以对候选结果进行精细排序,让最相关的内容排在前面。

重排序的必要性

初步检索的局限性

初步检索(如向量检索、关键词检索)存在以下问题:

问题表现影响
语义匹配粗糙只考虑单向量相似度可能遗漏深层语义相关的内容
缺乏上下文理解不考虑查询与文档的交互无法理解复杂的语义关系
噪声干扰返回的结果包含不相关内容降低用户体验和回答质量
排序不够精细只按单一指标排序无法满足复杂的排序需求

重排序的价值

重排序通过更精细的计算提升结果质量:

初步检索(Recall)→ 召回尽可能多的相关内容

重排序(Precision)→ 精确定位最相关的内容

最终结果 → 既全面又精准

Cross Encoder 重排序

Cross Encoder 原理

Cross Encoder 是一种基于 Transformer 的重排序模型,它同时处理查询和文档对:

阶段说明输出
输入层查询 + 文档拼接[CLS] query [SEP] document [SEP]
编码层Transformer 编码上下文表示
输出层分类/回归头相关性分数

Cross Encoder vs Bi Encoder

维度Bi EncoderCross 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 或学习排序
  • 需要持续优化 → 建立重排序效果评估机制