多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

用DPO训练大模型选择性信任上下文:RAG幻觉的偏好优化解法

用DPO训练大模型选择性信任上下文:RAG幻觉的偏好优化解法 大模型的上下文幻觉问题正在从工程痛点变成研究热点。做过 RAG 应用的开发者应该都有同感检索出来的文档明明包含正确答案模型却偏偏抓住一段无关文本推理出错误结论。而更麻烦的是当你把正确答案放到上下文里模型也未必会采用——它不知道自己该信任哪一部分输入。这个现象的技术根源并不是模型不够聪明而是它缺少一种能力判断上下文中哪些信息值得信任。Learning When to Trust via Selective Context Preference Optimization通过选择性上下文偏好优化学习何时信任正是针对这个问题提出的一条技术路线。它的核心思想可以概括为一句话与其无止境地优化模型对上下文的注意力分配不如直接用偏好优化直接训练模型学会选择性信任。这篇文章会从原理、数据构造、训练流程、效果验证和工程落地几个角度拆解这条技术路线并给出一个最小可运行的 DPO 风格训练示例帮助读者理解它到底在做什么、能做到什么、边界在哪里。1. 大模型的盲目信任问题为什么检索对了还是会答错先看一个非常典型的场景。你用 RAG 做一个企业知识库问答系统。用户问公司年假政策是什么检索模块召回了 5 个文档片段其中第 3 段包含了正确政策第 4 段是某个旧版本的作废政策。模型最终给出的答案居然是基于第 4 段旧政策生成的。这个现象在 RAG 应用里极其常见原因有三层第一位置偏差position bias。模型对上下文不同位置的敏感度并不均匀中间位置的文档容易被忽略而前后位置的文档影响力更大。即使正确信息在中间模型也可能取用首尾的低质量信息。第二信噪比失衡。当上下文里出现多个相互矛盾的片段模型缺乏一个显式的裁决机制它只能依赖自回归生成时的隐式概率这天然偏向语言流畅、格式规范但不一定正确的文本。第三上下文过长导致注意力稀释。把 8K、16K Token 的上下文全量喂给模型模型需要同时完成理解指令、甄别信息、组织答案三个任务注意力资源被大幅分散。传统解决方案是什么基本是两派。一派是输入端治理在做检索时收紧相关性阈值、做重排rerank、做去重和压缩。这个思路在管线层面解决问题但一旦检索结果里仍有噪声模型端依然没有自我纠错能力。另一派是后处理纠偏让模型在回答前先判断哪些证据可用也就是给模型设计一个独立的分析步骤比如请先审视上下文再回答。这在 short context 场景有效但会增加推理延迟而且模型的分析结果不一定可靠——它可能在分析阶段就做出了错误的信任判断。这两种方案都没有触及一个根本问题模型的信任决策本身缺少训练信号。2. 为什么选择 Preference Optimization从教会知识到教会判断理解了盲目信任的问题之后自然要问怎么让模型学会判断上下文是否可信一个直接的想法是收集专家标注数据告诉模型这段上下文是可靠的那段是不可靠的然后做监督微调SFT。但这里有个实际困难可靠的样本和不可靠的样本之间往往只有细微差别人工标注难以稳定区分而且 SFT 只能让模型学会模仿正确答案的措辞很难塑造出一种泛化的判断倾向。这就是偏好优化Preference Optimization的用武之地。偏好优化的基本思路不是告诉模型正确答案是什么而是给模型一组对比样本对于一个输入方案 A 更好方案 B 更差。模型的任务是学会更偏好 A 的行为。如果你熟悉 RLHF基于人类反馈的强化学习会发现偏好优化正好处在它的核心位置——先用人类或规则收集偏好对再通过奖励模型或直接优化来调整模型策略。但 RLHF 的工程链路非常重需要训练奖励模型、做强化学习采样、处理奖励 hacking对大多数团队来说门槛太高。2023 年提出的 DPODirect Preference Optimization则把这个过程简化了不需要单独训练奖励模型直接用偏好对数据在监督式损失上做优化通过隐式奖励函数直接对齐模型行为。DPO 的损失函数用一句话概括就是增大被选答案的似然减小被拒答案的似然同时加一个参考模型做正则化防止模型在已经学会的通用能力上发生灾难性遗忘。公式表达为L_DPO(π_θ; π_ref) -E_{(x, y_w, y_l) ~ D}[ log σ( β * log(π_θ(y_w | x) / π_ref(y_w | x)) - β * log(π_θ(y_l | x) / π_ref(y_l | x)) ) ]这个形式里π_θ 是待训练的策略模型π_ref 是参考模型通常是 SFT 之后的固定模型y_w 是被偏好的回答y_l 是被拒绝的回答β 控制对参考模型的偏离程度从直觉上理解如果模型给 y_w 的概率越高、给 y_l 的概率越低那么括号里的差值就越大sigmoid 输出越接近 1loss 越小。这个机制放到选择性上下文信任问题上恰好合适。为什么因为是否信任某段上下文本质上是一个行为选择信任并采用它生成正确答案不信任并忽略它拒绝错误信息。这两个行为之间天然构成一个偏好对。于是问题就变成了如何构造这样的偏好对。3. Selective Context Preference Optimization 的核心机制Learning When to Trust via Selective Context Preference Optimization这条技术路线把前面分析的信任问题转化成了一个偏好优化问题核心步骤有三个。3.1 定义信任的训练信号信任不是一个可以直接监督的标签。在偏好优化的框架下我们可以把信任信号转化为回答质量的对比正样本chosen模型在给定上下文中选择了正确的信息源生成了正确且忠实的回答。负样本rejected模型在同样的上下文中被噪声信息误导生成了错误或与上下文矛盾的回答。关键在于两个样本的输入上下文是相同的只有模型的信任决策不同。这样训练信号就直接对应到何时信任、何时忽略这个能力上。3.2 Selective Context 如何生效Selective Context选择性上下文这个名字需要仔细理解。它不是在输入端做检索过滤不是用外部模型挑出重要段落然后丢弃其余部分。它强调的是模型内部要学会在给定上下文中做选择性读取。训练完成后模型面对一段包含噪声的上下文时不是均匀地对待所有 Token而是像人类阅读一样主动忽略无关信息、聚焦证据信息。这种能力不是通过修改注意力权重实现的而是通过偏好优化让模型学会行为上更多地依赖可信信息。从这个意义上它跟提示词让模型忽略无关内容有本质区别。提示词只是给模型一个指令模型能不能执行取决于它是否有这个能力而偏好优化是在模型参数层面塑造这种能力。3.3 与让模型拒绝回答的边界需要澄清一个容易混淆的点选择性信任不等于让模型动不动拒绝回答。很多开发者处理噪声上下文时倾向让模型说根据提供的信息我无法回答。这在部分场景是对的但有些时候正确答案就在上下文中只是模型没找到。真正的选择性信任应该是有可信证据时跟随证据生成答案上下文全部无关时才拒绝回答或声明信息不足关键证据和噪声证据冲突时优先采用可信来源。这三种行为的训练数据都应该在偏好对构造时体现出来。4. 训练数据构造偏好对怎么来数据构造是这条技术路线里最关键的工程环节。根据我对这个方向的梳理偏好对的来源主要有四种。4.1 场景 1检索噪声注入这个场景模拟 RAG 中的噪声做法是取一个问题 q 和它的标准答案 a构造上下文 C_good包含正确答案对应的证据段落在 C_good 中注入若干干扰段落 C_noise干扰段落可以是相似主题但错误信息的段落来自旧版本的过时信息完全不相关的文本。然后让模型分别在只有干净上下文和含噪声上下文下生成回答。如果模型在含噪声场景下答错了就生成一个偏好对chosen模型在干净上下文下的正确回答rejected模型在含噪声上下文下的错误回答。这个构造思路的精妙之处在于它不需要人工写答案只需要让模型自生成并做质量筛选成本远低于完全人工标注。4.2 场景 2矛盾信息对决这个场景专门训练冲突消解能力。构造方法在上下文中放入两段针对同一问题的相互矛盾的文本其中一段是正确的另一段是错误的。然后让模型回答。如果模型选择跟从错误文本那么chosen正确版本的答案rejected模型被错误文本误导的答案。这个场景对应的是真实业务里最常见的知识源冲突问题。4.3 场景 3不可回答问题构造一个上下文其中包含诱导性信息但并没有真正回答问题的内容。例如用户问A 产品的价格是多少上下文中只有 A 产品的介绍没有价格信息。此时chosen模型承认上下文中没有找到价格信息rejected模型从上下文中强行猜测一个价格。这个偏好对教会模型区分信息缺失和信息错误避免在噪音上下文中产生幻觉。4.4 偏好对的质量过滤自动生成偏好对之后需要对生成结果做质量过滤。常见做法包括用规则判断负样本是否确实与上下文矛盾用另一个更强的模型对 chosen/rejected 做相关性打分过滤掉 chosen 和 rejected 几乎相同的样本避免无效训练信号。值得强调的是负样本的质量决定了训练效果。如果负样本只是轻微不如正样本模型学到的信任信号会非常弱如果负样本错得离谱模型又会产生对所有上下文都怀疑的偏差。理想情况是负样本看起来合理、实际上错误这样最能锻炼信任判断能力。5. 最小代码示例DPO 风格的选择性上下文训练下面用一个最小示例演示如何用 Python 和 PyTorch 构造训练循环。这个示例不为复现论文效果而是帮助读者理解数据格式和训练流程。5.1 安装依赖pip install torch transformers datasets peft trl这里使用 Hugging Face TRL 库因为它内置了 DPOTrainer可以极大简化训练代码。5.2 构造偏好数据集假设我们用一个模拟场景给模型一个问题、一段正确上下文、一段错误上下文让模型生成两个回答。其中 chosen 是基于正确上下文的回答rejected 是基于错误上下文的回答。# 文件路径build_preference_data.py from datasets import Dataset # 模拟数据每个样本包含完整上下文 问题 好坏两个回答 # chosen 是信任正确证据的回答rejected 是信任错误证据的回答 samples [ { context: 系统提示这是一份产品说明文档。\n 【文档片段1】A 产品的价格是 299 元支持 7 天无理由退换。\n 【文档片段2】A 产品的价格是 999 元注此条目已作废仅作内部参考。\n 用户问题A 产品的价格是多少, chosen: 根据文档片段1A 产品的价格是 299 元。, rejected: 根据文档片段2A 产品的价格是 999 元。, }, { context: 系统提示以下是公司考勤制度。\n 【文档片段1】正式员工每年享受 10 天带薪年假。\n 【文档片段2】临时员工每年享受 2 天带薪年假。\n 用户问题正式员工的年假天数是多少, chosen: 正式员工每年享受 10 天带薪年假。, rejected: 临时员工每年享受 2 天带薪年假。, }, ] def format_prompt(example): # 将上下文和问题拼接成统一的 prompt return { prompt: example[context], chosen: example[chosen], rejected: example[rejected], } dataset Dataset.from_list(samples).map(format_prompt) dataset.save_to_disk(./data/preference_dataset) print(dataset)5.3 使用 TRL 的 DPOTrainer 训练# 文件路径train_dpo.py from datasets import load_from_disk from transformers import AutoModelForCausalLM, AutoTokenizer from trl import DPOConfig, DPOTrainer # 1. 加载基础模型和 tokenizer # 实操中建议选择 SFT 过的基座模型作为起点 model_name Qwen/Qwen2.5-1.5B-Instruct model AutoModelForCausalLM.from_pretrained(model_name) tokenizer AutoTokenizer.from_pretrained(model_name) tokenizer.pad_token tokenizer.eos_token # 2. 加载偏好数据集 dataset load_from_disk(./data/preference_dataset) # 3. 配置 DPO 训练参数 training_args DPOConfig( output_dir./output/selective_context_dpo, per_device_train_batch_size1, gradient_accumulation_steps4, learning_rate5e-7, max_steps100, logging_steps10, save_steps50, beta0.1, # DPO 的温度参数控制对参考模型的偏离 max_length2048, max_prompt_length1024, remove_unused_columnsFalse, ) # 4. 创建 DPOTrainer trainer DPOTrainer( modelmodel, ref_modelNone, # 不指定时会自动从 model 复制一份作为参考模型 argstraining_args, train_datasetdataset, tokenizertokenizer, ) # 5. 开始训练 trainer.train()5.4 代码关键点解释这段代码有三个地方需要重点说明。第一beta参数控制模型偏离参考模型的程度。beta越大模型对新偏好信号的拟合越保守beta越小模型越激进地改变行为。在选择性上下文任务中建议从0.1左右开始调参不要一开始就设很小的值。第二ref_modelNone时 TRL 会自动复制当前模型作为参考模型但更推荐的做法是先加载一个训练中断保存的 SFT 模型作为ref_model否则参考模型和策略模型是同一个初始权重DPO 的损失会偏离设计意图。第三负样本rejected的长度不要跟正样本差异过大。如果 rejected 明显更长模型会学到回答更短更好这样的虚假信号而不是真正学到信任判断。5.5 运行训练python build_preference_data.py python train_dpo.py对于演示数据几分钟即可完成训练。实际项目中偏好对数量建议至少数千条起步单一场景的数据很难让模型泛化出通用的信任判断能力。5.6 用训练后的模型做推理测试# 文件路径inference_test.py from transformers import AutoModelForCausalLM, AutoTokenizer model_path ./output/selective_context_dpo tokenizer AutoTokenizer.from_pretrained(model_path) model AutoModelForCausalLM.from_pretrained(model_path) prompt ( 系统提示以下是商品信息。\n 【文档片段1】B 产品重 1.5kg适合户外携带。\n 【文档片段2】B 产品重 5kg笨重不适合户外注旧版描述已更新。\n 用户问题B 产品适合户外携带吗 ) inputs tokenizer(prompt, return_tensorspt) outputs model.generate(**inputs, max_new_tokens128) print(tokenizer.decode(outputs[0], skip_special_tokensTrue))如果训练有效模型应该输出基于文档片段1的回答而不是被文档片段2误导。6. 效果验证如何判断模型真的学会了何时信任判断训练是否成功不能只看 loss 下降。在选择性上下文任务上建议用以下几类指标做系统性评估。6.1 信任准确率Trust Accuracy构造一组测试样本每一样本中包含一个正确证据源和若干干扰信息源。统计模型最终回答是否采用了正确证据源。这个指标直接衡量模型在冲突信息中的决策正确率。测试集的构造必须和训练集分源避免数据泄漏。例如训练集用文档冲突测试集用相同的冲突模式但换成完全不同领域的内容。6.2 噪声鲁棒性Noise Robustness固定一组干净上下文逐个注入不同比例、不同类型的噪声段落观察模型答案正确率随噪声比例的变化曲线。一个好模型的表现应该是噪声比例增加时正确率只是缓慢下降而未训练模型通常在某个噪声阈值之后快速崩溃。6.3 忠实度指标Faithfulness可以用 RAGAS 中的 faithfulness 指标衡量生成答案是否从上下文中找到依据。它通过判断回答中的每个陈述是否能被上下文支撑来计算。如果训练成功faithfulness 分数应该明显提升。6.4 通用能力不退化检查这是最容易被忽略的一步。偏好优化有潜在风险模型在学习信任判断时可能牺牲通用问答能力。建议在训练前后分别跑一遍通用 benchmark如 MMLU 子集、GSM8K 子集或自建业务 QA 集确认分数没有明显下滑。下面给出一个简单的评估脚本框架# 文件路径evaluate_trust.py from transformers import AutoModelForCausalLM, AutoTokenizer def evaluate_trust(model, tokenizer, eval_samples): correct 0 total len(eval_samples) for sample in eval_samples: prompt sample[context] \n sample[question] inputs tokenizer(prompt, return_tensorspt) outputs model.generate(**inputs, max_new_tokens128) answer tokenizer.decode(outputs[0], skip_special_tokensTrue) if sample[expected_keyword] in answer: correct 1 return correct / total # 使用示例 # eval_samples load_eval_set() # accuracy evaluate_trust(model, tokenizer, eval_samples) # print(fTrust Accuracy: {accuracy:.2%})7. 常见问题与排查方法在实际应用这条技术路线时可能会遇到下面这些问题。问题现象可能原因排查方式解决方案训练后模型泛化能力下降通用 QA 变差偏好对数据过于单一模型过度拟合特定数据模式对比训练前后通用 benchmark 分数增加数据多样性提高 beta减少训练步数模型开始过度怀疑一切上下文频繁拒绝回答负样本中信息缺失但模型硬猜的比例过高统计训练集中拒绝类样本占比平衡信息缺失和信息冲突两类样本的比例训练 loss 下降但信任准确率不涨模型记住了数据格式而不是信任判断检查测试集是否与训练集同源构建分布外测试集换领域验证训练不稳定loss 震荡明显正负样本长度差异过大或 prompt 过长触发截断检查数据统计chosen/rejected 平均长度统一输出长度提高 max_length推理时模型对长上下文的信任判断不稳定训练时的 max_length 覆盖不了超长上下文用不同长度上下文做消融测试分级训练先训练短上下文再在长上下文上继续训练DPO 训练后模型输出重复或空白参考模型和策略模型初始化不一致检查 ref_model 加载方式显式传入同一 SFT 模型的 checkpoint 作为 ref_model8. 工程实践与落地建议如果要把这条技术路线应用到实际业务下面几条建议值得认真对待。8.1 先从 SFT 开始不要直接对基座模型做 DPODPO 的前置条件是模型已经具备基本的指令跟随和文本生成能力。直接对基座模型做偏好优化模型可能还没有学会回答问题就被迫学习在噪声中回答问题效果会大打折扣。正确的顺序是先做 SFT 让模型学会任务格式再做 DPO 让模型学会信任判断。8.2 数据构造是核心资产这个方向的技术壁垒不在训练代码而在数据。建议把偏好对的构造做成自动化流水线而不是一次性人工标注。流水线可以这样设计从业务日志中收集真实用户问题和检索结果用规则或更强模型标记正确答案所在段落自动构造噪声注入和矛盾信息场景用强模型生成正负回答用规则过滤低质量样本。8.3 建立信任评估集作为回归测试像是任何模型迭代都会引入回归风险一样建议在训练完成后将信任评估集固化到 CI 流程中。每次更新基座模型、微调数据或提示词策略都先跑一遍信任评估集确保模型不会在优化其他能力时丢掉选择性信任能力。8.4 与 RAG 检索管线配合使用选择性上下文偏好优化不能替代检索质量治理应该与检索管线叠加使用。理想的分工是检索层负责提高候选池的信噪比重排层负责把最相关文档放到重要位置模型层通过选择性信任能力在候选池仍然存在噪声时做出正确决策。三层协同才能得到一个真正可靠的问答系统。8.5 注意上下文格式设计训练时使用的上下文格式推理时必须保持完全一致。如果训练时用【文档片段1】这样的标记推理时也要用同样的标记。格式不一致会直接破坏模型学到的选择性读取习惯。8.6 小模型起步验证后再放大不建议一上来就用 70B 级别的模型做全量 DPO 实验。先用 1.5B 或 3B 模型构造小规模偏好数据集跑通完整流程确认信任准确率有明显提升再迁移到更大模型。这能显著降低实验成本和时间周期。9. 总结与下一步方向回到最初的问题大模型为什么会盲目信任上下文因为模型缺少何时信任、何时忽略的训练信号。选择性上下文偏好优化的思路就是把这个信号显式化通过构造正负回答对让模型在参数层面学会选择性读取证据而不是依赖提示词的外在约束。这篇文章讲清楚了几件事Preference Optimization 为什么适合解决信任问题Selective Context 与输入端过滤、提示词约束的本质区别偏好对数据的四种构造场景和过滤方法一个基于 TRL 的最小 DPO 训练示例信任准确率、噪声鲁棒性、通用能力不退化等评估指标从数据流水线到回归测试的工程落地路径。如果你正在做 RAG 应用并且已经遇到了检索对了但模型答错的问题可以沿着这篇文章的思路先从构造 100 条偏好对开始跑一次小规模的 DPO 训练看看模型在冲突信息下的表现变化。这是一个成本可控、收益明确的实验方向。下一步值得深入的方向还有很多如何用 AI Feedback 自动生成更高质量偏好对、如何把选择性信任能力与长上下文位置编码结合、如何在多轮对话场景中建模信任的历史信息、如何评估模型在对抗性噪声攻击下的稳定性。这些方向本质上都在回答同一个问题让大模型不再无差别吸收上下文而是在每一条信息面前先问一句我该信你吗。
返回列表