RAG从入门到实战(九):重排序与结果多样性——Cross-Encoder 和 MMR 怎么用?
RAG从入门到实战九重排序与结果多样性——Cross-Encoder 和 MMR 怎么用文章目录RAG从入门到实战九重排序与结果多样性——Cross-Encoder 和 MMR 怎么用前言一、从召回到重排序二、为什么需要重排序三、Bi-Encoder 与 Cross-Encoder 的区别3.1 Bi-Encoder分别编码便于大规模召回3.2 Cross-Encoder联合判断查询与候选3.3 两者不是互斥关系四、Cross-Encoder 重排序流程4.1 把排序器接入候选结果4.2 输入长度和截断4.3 分数不能直接当概率4.4 外部排序服务与隐私五、MMR同时考虑相关性与多样性5.1 为什么需要多样性5.2 相似度和相关性必须考虑尺度5.3 多样性不是越高越好5.4 代码示例简化 MMR 选择5.5 在向量库中使用 MMR 检索器六、Cross-Encoder 与 MMR 怎么组合七、候选数量如何选择八、如何判断重排序真的有用九、总结参考资料前言上一篇介绍了稠密、稀疏和混合检索。多路召回后系统可能得到几十个候选片段但它们不一定按“最能回答当前问题”的顺序排列。同时前几个结果也可能来自同一段反复切分的文本看起来相关却缺少其他必要信息。这时可以考虑用 **重排序Reranking**重新评估查询与候选的相关性。用MMR等方法在相关性与结果多样性之间取得平衡。本文介绍二者的作用、差异、成本及评估方法并给出一个不依赖模型的 MMR 可运行示例。本文中的制度和候选片段均为虚构示例。算法结果依赖模型与数据不能保证一定提升最终答案质量。一、从召回到重排序一个典型流程用户问题 ↓ 稠密 / 稀疏 / 混合检索 ↓ 候选片段例如 30 条仅为示意 ↓ 去重 → 重排序 → 必要时控制多样性 ↓ 少量证据片段 → 上下文组装 → 回答第一阶段的主要任务是尽量不要漏掉有用证据第二阶段的主要任务是在候选中把更有用的内容排得靠前。两阶段的候选数量应依据测试结果与预算调整图中的数字不是通用最佳值。需要强调重排序通常只能处理已经召回的候选。如果正确片段根本没有进入候选集排序器不会凭空恢复它应先排查解析、切分、查询和检索策略。二、为什么需要重排序假设用户问入职满一年后申请年假需要提前多久提交初次检索可能返回年假可以按制度申请。差旅申请需要提前三个工作日提交。年假申请需提前三个工作日提交并经直属主管审批。年假相关常见问题汇总。第 2 条包含“提前三个工作日”和“申请”但主题是差旅第 3 条同时满足“年假”和“提前多久”的问题条件通常更直接。重排序的目的就是结合完整问题和候选内容重新判断它们之间的关系。不过即使排序靠前也不能保证文档是最新版本或者该片段包含全部适用条件。排序分数不是事实正确性认证。三、Bi-Encoder 与 Cross-Encoder 的区别3.1 Bi-Encoder分别编码便于大规模召回向量检索常用的双编码器思路是分别编码问题与文档问题 → 查询向量 ─┐ ├→ 向量相似度 → 大规模候选检索 文档 → 文档向量 ─┘文档向量可以提前计算并建立索引因此适合在大量文档中快速查找。但单个向量需要压缩文本信息精确比较问题和文档中的多个条件可能存在局限。3.2 Cross-Encoder联合判断查询与候选Cross-Encoder 将问题和一个候选片段一起输入模型输出一个相关性分数或其他模型定义的结果[问题候选 A] → 分数 A [问题候选 B] → 分数 B [问题候选 C] → 分数 C模型可以在一次前向计算中考虑问题与候选之间更细粒度的交互因此在一些任务中适合用于候选重排序。代价是需要对每个查询—候选对计算通常不能像预计算文档向量那样直接对整个大库做同等规模的搜索。不同模型对输入格式、最大长度、输出分数和语言有不同要求不要假定所有 Cross-Encoder 分数都处于[0,1]或可直接跨查询比较。3.3 两者不是互斥关系常见搭配是Bi-Encoder / BM25从大量资料中召回候选 Cross-Encoder对候选逐一打分并排序是否值得增加排序器应由真实问题集决定。如果候选已经很少或延迟预算很紧也可以先使用更简单的排序方法。四、Cross-Encoder 重排序流程一种通用实现流程召回候选片段并执行权限过滤。合并不同来源的结果依据可信片段 ID 去重。为每个候选构造(question, text)输入对。使用排序模型批量计算分数。按分数排序并选择前若干条。检查证据是否足够保留原始来源和版本。标题或章节有时可以帮助排序模型理解片段但也可能增加噪声或占用长度。应确保必要内容不被截断并通过对照实验决定输入结构。4.1 把排序器接入候选结果下面把“问题—候选文本配对、批量打分、按分数重排”封装成与服务商无关的函数。调用方提供的score_pairs负责调用已配置的远程 Cross-Encoder 重排序服务并按输入顺序返回分数示例不下载本地模型也不假定某一家服务的接口格式。示例中的虚拟打分仅用于检查排序流程不代表语义模型效果。fromcollections.abcimportCallable,Sequencefrommathimportisfinitedefrerank(question:str,documents:Sequence[dict],score_pairs:Callable[[list[tuple[str,str]]],Sequence[float]],top_k:int3)-list[dict]:iftop_k0:raiseValueError(top_k 不能为负数)ifnotdocumentsortop_k0:return[]pairs[(question,doc[content])fordocindocuments]scoreslist(score_pairs(pairs))iflen(scores)!len(documents)ornotall(isfinite(score)forscoreinscores):raiseValueError(排序服务必须为每个候选返回一个有限数值)ranked[{**doc,rerank_score:float(score)}fordoc,scoreinzip(documents,scores)]returnsorted(ranked,keylambdadoc:doc[rerank_score],reverseTrue)[:top_k]if__name____main__:candidates[{id:A,content:申请年假需提前三个工作日。},{id:B,content:出差报销需提供票据。},]# 假分数只验证流程替换为按所用服务文档实现的真实远程调用。print(rerank(年假需要提前多久申请,candidates,lambdapairs:[0.9,0.2]))集成真实服务时先完成权限过滤再按服务商的请求和响应格式实现score_pairs正确恢复输入顺序密钥从受控环境变量读取不要写入代码或公开日志。不同排序器的分数范围和意义不同不能照搬统一的“分数大于 0”阈值。远程服务示例在此未做真实调用将问题和候选原文发给第三方之前需确认数据授权和处理政策。4.2 输入长度和截断如果文档片段超过模型支持的最大长度关键条件可能被截掉。要明确模型实际采用的截断方式并在切分、输入构造和测试时覆盖“关键信息位于末尾”的情况。4.3 分数不能直接当概率例如某模型输出4.2和1.7它们可能只是该模型的排序分数。不能不经校准就说“第一个答案正确概率为 4.2”或“阈值 0.8 对所有模型通用”。排序器也可能偏爱主题相近但缺少条件的内容。需要比较原始文档、引用和最终答案不能只看分数。4.4 外部排序服务与隐私如果将用户问题和候选原文发送到外部排序 API这些内容会离开本地系统。使用前应确认数据授权、敏感信息处理、服务商政策和传输安全本地部署也仍需权限隔离与访问审计。五、MMR同时考虑相关性与多样性5.1 为什么需要多样性假设查询“年假怎么申请”召回前三条A年假申请需提前三个工作日提交。 B申请年假需要提前三个工作日提交。 C年假申请要提前三个工作日提交。这三条几乎重复。如果回答还需要“谁审批”那么它们可能挤占了审批规则的上下文位置。MMRMaximal Marginal Relevance最大边际相关性逐步选择候选同时考虑其与问题的相关性以及它与已选结果的相似程度MMR(d) λ × relevance(query, d) - (1 - λ) × max similarity(d, 已选片段)其中λ通常取[0, 1]越接近 1越偏重问题相关性。越接近 0越偏重减少重复。当已选集合为空时可以约定冗余项为 0λ 0 时第一步选择相关性最高的候选。λ 0 时按此约定首轮分数全为 0需要明确平分规则或单独规定初始化方式。不同实现的初始化和相似度定义可能有所区别。5.2 相似度和相关性必须考虑尺度公式中的相关性与冗余度可能来自不同模型例如 Cross-Encoder 分数与向量余弦相似度。它们的取值范围未必一致直接代入会导致某一项主导结果。实际使用应了解分数的方向、范围及是否可比较。使用适当的归一化或校准方法并在相同测试集上验证。处理零向量、重复片段、缺失值和跨版本 ID。不要假定某个λ对所有领域都适合。5.3 多样性不是越高越好如果问题只需要一个精确事实强行选择差异大的片段可能引入不相关资料。多样性是为了覆盖必要信息而不是为了让所有结果看起来不同。5.4 代码示例简化 MMR 选择下面代码使用人工定义的相关性和片段间相似度展示选择过程。它不是 Embedding 模型也不连接向量数据库。defselect_mmr(candidates,relevance,similarity,limit,lambda_0.5):按 MMR 逐个选择 ID相似度输入应使用可比较的预处理分数。ifnot0lambda_1:raiseValueError(lambda_ 必须在 [0, 1] 内)iflimit0:raiseValueError(limit 不能为负数)iflen(set(candidates))!len(candidates):raiseValueError(候选 ID 不能重复)remaininglist(candidates)selected[]whileremainingandlen(selected)limit:best_idNonebest_scorefloat(-inf)forcandidateinremaining:redundancymax((similarity(candidate,prior)forpriorinselected),default0.0,)scorelambda_*relevance[candidate]-(1-lambda_)*redundancy# 平分时保留输入顺序便于复现。ifscorebest_score:best_id,best_scorecandidate,score selected.append(best_id)remaining.remove(best_id)returnselectedif__name____main__:relevance{A:0.95,B:0.90,C:0.80}pair_similarity{(A,B):0.95,(A,C):0.10,(B,C):0.15,}defsimilarity(a,b):returnpair_similarity.get((a,b),pair_similarity.get((b,a),0.0))print(select_mmr([A,B,C],relevance,similarity,limit2,lambda_0.5))结果是[A, C]A 的相关性最高先被选择B 和 A 非常相似因此第二步的 MMR 分数低于 C。本例刻意把相关性和相似度放在相近尺度真实系统不能直接假定这一点。它也没有实现权限过滤、排序模型或证据充分性判断。5.5 在向量库中使用 MMR 检索器如果已经使用第二篇的InMemoryVectorStore可以把检索方式配置成 MMR而不必手写上面的选择循环。以下是接入片段store是已建立并写入文档的向量库需继承同一权限范围不能单独复制运行。retrieverstore.as_retriever(search_typemmr,search_kwargs{k:3,fetch_k:12,lambda_mult:0.5},)resultsretriever.invoke(年假申请需要提前多久)fordocumentinresults:print(document.metadata,document.page_content)fetch_k控制送入 MMR 的候选数量k控制最终返回数lambda_mult通常越大越重视相关性越小越重视多样性。支持的参数及权限过滤能力因向量库实现和版本而异不能假设每个后端完全相同。这里仍会调用已配置的 Embedding 服务但不需要下载本地排序模型。注意InMemoryVectorStore的 MMR 检索路径依赖numpy未安装时会直接抛出 ImportError如果第二篇的最小环境没有安装 numpy需要先执行pip install numpy。Cross-Encoder 则是另一条可选步骤实际使用时可以对候选调用组织已配置的远程重排序服务每个服务的字段、认证、长度限制和分数含义不同须以其文档为准。外发问题和文档前必须先确认授权。MMR 与 Cross-Encoder 解决的问题不同组合次序和候选数都应通过评估决定。六、Cross-Encoder 与 MMR 怎么组合两者解决的问题不同方法重点输入常见代价Cross-Encoder问题与候选的细粒度相关性问题和每个候选组成的文本对候选越多模型推理成本越高MMR相关性和已选结果的多样性相关性分数与候选间相似度需要计算或取得候选间相似度一种可能的流程混合召回 → 去重 → Cross-Encoder 重排序 → MMR 选取 → 组装上下文也可以先使用向量相似度做 MMR、再做重排序顺序会影响结果和成本没有无需评估的通用答案。特别是对需要多个证据的问题最终应检查证据覆盖而不只追求排序或多样性得分。七、候选数量如何选择需要关注至少三个数量每路检索取多少候选。多少候选进入排序器。最终给模型多少证据片段。如果召回数量太小相关证据可能不在候选中太大则增加排序器计算与噪声。最终片段过多可能导致输入成本增加、重复信息增加或关键信息被稀释。建议用同一测试集尝试不同组合并记录候选召回是否包含所需证据。排序后相关证据是否进入前 K。重复片段比例和多证据问题的覆盖情况。排序器延迟、吞吐、模型费用和最终答案质量。八、如何判断重排序真的有用建立对照基线混合检索 → 直接取前 K 实验 A混合检索 → Cross-Encoder → 取前 K 实验 B混合检索 → Cross-Encoder → MMR → 取前 K确保各实验使用相同的候选数据、权限范围和测试问题。可以观察 MRR、nDCG、RecallK 等指标但也要检查错误片段是否被排在正确证据前面。多条件问题是否缺少某个必要条件。是否因多样性过强把相关片段挤掉。引用是否支持最终结论。成本与延迟是否满足业务目标。无答案、跨文档、长片段、时间版本冲突和权限隔离等情况都应进入测试集排序分数高并不意味着可以忽略拒答策略。九、总结召回决定哪些候选有机会进入后续流程重排序改善候选顺序。Cross-Encoder 联合评估问题与候选通常适合在较小候选集上使用。MMR 在相关性和去重、多样性之间进行取舍但需要注意分数尺度。最终仍应检查来源、版本、权限和证据充分性而不是把排序得分当作事实保证。下一篇将继续介绍检索后的上下文优化包括片段提取、压缩、去重、父块扩展与引用。参考资料MMR 原始论文https://doi.org/10.1145/290941.291025Sentence Transformers 重排序器文档https://sbert.net/docs/cross_encoder/usage/usage.html