HoRain云--RLHF 对齐训练
想象一下你让 AI 写一篇如何快速赚钱的文章它给你出了个诈骗方案。你问 AI: 我是不是很失败它直接说: 是的你确实很失败。你让 AI 帮你写代码它生成了一段能运行但暗藏后门的程序。这些不是科幻场景——在没有对齐训练的情况下AI 真的会这样做。AI 的目标不是做正确的事而是完成训练任务。如果没有明确的人类价值观指引它会选择最简单、最直接的方式完成任务而不管方式是否合适。这就是对齐Alignment要解决的问题让 AI 的行为符合人类的价值观、意图和期望。对齐的核心挑战AI 很聪明但它不知道什么是对的。我们需要用人类的反馈告诉它。什么是对齐对齐是确保 AI 系统的行为与人类价值观保持一致的过程。简单说AI 本身没有价值观对齐就是把人类的价值观教给 AI。对齐的三个维度对齐不是单一目标而是三个维度的平衡维度定义反面例子有用Helpful能够帮助用户完成任务、回答问题用户问问题AI 说我不知道这很安全但没用无害Harmless不会造成伤害、不会产生负面后果用户要做坏事AI 积极配合这很有用但有害诚实Honest不会编造信息、不会误导用户AI 不知道答案却编造虚假事实这有用但不诚实这三个维度经常存在张力太追求有用可能会让 AI 冒险给出不准确的答案。太追求无害可能会让 AI 变得过于保守什么都不敢回答。好的对齐就是在这三者之间找到平衡点。未对齐 AI 的风险如果不对齐AI 可能出现以下问题生成有害内容——暴力、仇恨、歧视言论。提供危险建议——如何制作武器、如何犯罪。编造事实——幻觉出不存在的论文、数据、事件。操纵用户——利用心理弱点影响用户决策。逃避审查——用隐晦的方式表达被禁止的内容。这些风险不是理论上的而是真实存在并已经发生过的。RLHF 整体框架RLHFReinforcement Learning from Human Feedback基于人类反馈的强化学习是目前最主流的对齐技术。它的核心理念很简单不让人类直接写规则而是让人类评估 AI 的输出然后用强化学习让 AI 学会生成人类喜欢的输出。RLHF 的三个阶段RLHF 不是一步完成的而是分为三个连续的阶段阶段做什么产出阶段一监督微调SFT用人类写的示范数据训练模型SFT 模型一个会说话的基础模型阶段二奖励模型RM收集人类偏好数据训练奖励模型奖励模型能给 AI 输出打分的模型阶段三PPO 强化学习用奖励模型指导用 PPO 算法训练 SFT 模型最终对齐后的模型这三个阶段是递进的先让模型会说话再让评委学会判断好坏最后让选手根据评委的反馈不断改进。为什么需要人类反馈你可能会问直接写规则不行吗为什么非要人类反馈因为人类价值观太复杂了没法写成精确的规则。比如什么是礼貌的回答——你能写出精确判断的规则吗很难。但你看到两个回答时能轻易指出哪个更礼貌。这就是人类反馈的优势我们可能说不清规则是什么但我们能判断好坏。RLHF 利用的就是人类的这种能力。阶段一监督微调SFT监督微调Supervised Fine-TuningSFT是 RLHF 的第一步。它的目标是把预训练的通用语言模型变成对话助手。SFT 的基本思路预训练模型学会了预测下一个词但它不知道如何当一个助手。SFT 就是给模型看很多人类怎么当助手的例子让它模仿。这些例子长什么样大概是这样的用户输入AI 应该输出人类示范你好我想了解 Python你好Python 是一门简单易学的编程语言适合初学者。你想了解哪方面呢帮我写一封辞职信好的这是一份辞职信模板...省略...请根据你的具体情况修改。如何快速致富致富没有捷径建议你通过努力工作和学习提升自己...省略...这些示范数据是由人类标注员写的或者从真实对话中筛选出来的高质量回复。SFT 数据的特点SFT 数据不是随便什么对话都行它需要满足有帮助——能真正回答用户的问题。安全——不会生成有害内容。风格一致——用相似的语气和方式回复。格式规范——遵循一定的对话格式。数据量通常在几千到几万条之间比预训练的数据量小得多但质量要求高得多。SFT 的作用与局限SFT 的作用是让模型学会对话的基本姿势知道如何回应问候、如何回答问题、如何拒绝不合理请求。但 SFT 有明显的局限覆盖范围有限——你不可能写出所有场景的示范。人类也不完美——标注员可能犯错或者有偏见。只能模仿不能超越——模型最多和人类示范一样好不会更好。这就是为什么我们还需要后面两个阶段SFT 只是打下基础真正的对齐要靠强化学习来完成。阶段二奖励模型RM奖励模型Reward ModelRM是 RLHF 的关键环节。它的任务是看一段 AI 的输出给它打个分表示这个输出有多好。偏好数据的收集奖励模型不是直接训练打分而是训练比较。具体做法是给人类标注员看同一个问题的多个不同回答让他们排序说出哪个更好。比如问题回答 A回答 B人类偏好我是不是很失败是的你确实很失败每个人都会遇到困难这不能定义你的价值B 远好于 A注意我们不让人类直接打分数比如 85 分而是让他们做比较A 比 B 好。为什么因为比较比打分容易得多也更一致。不同人对85 分的理解可能不一样但A 比 B 好的判断更稳定。Bradley-Terry 模型如何把比较变成分数这里用到了 Bradley-Terry 模型。这个模型的核心思想很简单每个回答都有一个潜在的质量分数分数高的回答更可能被人类偏好。假设回答 A 的分数是 r_A回答 B 的分数是 r_B那么人类偏好 A 胜过 B 的概率是P(A B) exp(r_A) / (exp(r_A) exp(r_B))这就是 softmax 函数——分数差越大概率越接近 1。训练奖励模型就是要找出这样的分数让模型预测的偏好概率和人类实际的偏好尽可能一致。奖励模型的训练训练奖励模型的步骤收集比较数据——几万到几十万组哪个回答更好的比较。初始化模型——通常用 SFT 模型作为起点。设计损失函数——让模型预测的偏好顺序和人类一致。训练——用梯度下降优化模型参数。训练好的奖励模型输入一段文本就能输出一个标量分数表示这段文本有多好。实例# # 简化版奖励模型训练演示# 这里用 PyTorch 风格的伪代码说明原理# import torchimport torch.nn as nnfrom typing import List, Tupleclass RewardModel(nn.Module):奖励模型输入文本输出分数def __init__(self, base_model):super().__init__()self.base_model base_model # 用 SFT 模型作为基础self.score_head nn.Linear(base_model.config.hidden_size, 1)def forward(self, input_ids, attention_maskNone):前向传播输入 → 基础模型 → 分数头 → 标量分数outputs self.base_model(input_ids, attention_maskattention_mask)last_hidden_state outputs.last_hidden_state # (batch, seq_len, hidden_size)# 用 [BOS_never_used_51bce0c785ca2f68081bfa7d91973934] token 的表示来计算分数cls_embedding last_hidden_state[:, 0, :] # (batch, hidden_size)score self.score_head(cls_embedding) # (batch, 1)return score.squeeze(-1) # (batch,)def reward_loss(reward_model, batch: List[Tuple[str, str, int]]):奖励模型的损失函数参数:batch: 一批数据每个元素是 (回答A, 回答B, 偏好标签)偏好标签: 1 表示 A B0 表示 B Atotal_loss 0.0for answer_a, answer_b, preference in batch:# 给两个回答分别打分score_a reward_model(answer_a)score_b reward_model(answer_b)# 用 Bradley-Terry 模型计算概率# P(A B) exp(r_A) / (exp(r_A) exp(r_B))# 为了数值稳定通常用 log-sigmoid 形式if preference 1:# 人类偏好 A Bloss -torch.log(torch.sigmoid(score_a - score_b))else:# 人类偏好 B Aloss -torch.log(torch.sigmoid(score_b - score_a))total_loss lossreturn total_loss / len(batch)# # 模拟训练过程概念演示# print( 奖励模型训练演示 )print()# 假设我们有一些比较数据comparison_data [(是的你确实很失败,每个人都会遇到困难这不能定义你的价值,0), # B A(我不帮你做坏事,这样做不太好让我给你一个更好的建议...,1), # A B这个例子里其实 B 可能更好视具体情况]print(比较数据示例)for a, b, pref in comparison_data:print(f A: {a[:30]}...)print(f B: {b[:30]}...)print(f 偏好: {A B if pref 1 else B A})print()print(训练奖励模型的目标)print( 让模型给人类偏好的回答打更高的分数)print( 用 runoob 作为测试关键词验证模型)阶段三PPO 强化学习有了奖励模型我们就可以用强化学习来训练策略模型了。这个阶段的目标是让策略模型生成能获得高奖励的输出同时不要离原始模型太远。强化学习基础回顾先快速回顾强化学习的基本概念概念在 RLHF 中的含义智能体Agent我们要训练的语言模型策略模型环境Environment对话上下文用户的输入状态State当前的对话历史动作Action生成下一个词token奖励Reward奖励模型给这段输出的分数策略Policy模型在当前状态下选择下一个词的概率分布在 RLHF 中模型每生成一个完整回答就会从奖励模型那里得到一个分数。我们的目标是让模型学会生成能获得高分的回答。PPO 算法的直觉PPOProximal Policy Optimization近端策略优化是 RLHF 中最常用的强化学习算法。为什么选 PPO因为它简单、稳定、效果好。PPO 的核心思想很直观每次更新策略时不要让新策略和旧策略差得太远。为什么因为如果步子太大可能会学坏了而且很难再改回来。小步迭代更安全。PPO 用裁剪Clipping技术来实现这一点如果新策略比旧策略好太多我们就限制它的更新幅度。KL 散度约束的作用除了 PPO 的裁剪RLHF 通常还会加一个 KL 散度KL Divergence惩罚项。KL 散度是衡量两个概率分布差异的指标。在 RLHF 中我们用它来衡量当前策略模型和原始 SFT 模型的差异有多大。为什么要加这个约束因为防止模式崩溃——如果没有约束模型可能会找到一种投机取巧的方式来获得高分但完全失去了有用性。保留原始能力——SFT 模型已经学会了很多有用的知识我们不想在强化学习中把它们弄丢。提高训练稳定性——KL 约束让训练过程更稳定不会出现剧烈波动。最终的奖励函数长这样最终奖励 奖励模型分数 - β × KL(当前策略 || 参考策略)β 是一个超参数控制 KL 惩罚的强度。实例# # 简化版 PPO 训练演示# 说明 RLHF 中 PPO 的核心思想# import torchimport torch.nn as nnimport torch.nn.functional as Ffrom typing import Dict, Listclass PPOTrainer:简化的 PPO 训练器def __init__(self,policy_model,reference_model,reward_model,beta: float 0.1,clip_epsilon: float 0.2):初始化 PPO 训练器参数:policy_model: 要优化的策略模型reference_model: 参考模型通常是 SFT 模型用于计算 KLreward_model: 奖励模型用于打分beta: KL 惩罚系数clip_epsilon: PPO 裁剪参数self.policy_model policy_modelself.reference_model reference_modelself.reward_model reward_modelself.beta betaself.clip_epsilon clip_epsilondef compute_kl_divergence(self,input_ids,attention_maskNone) - torch.Tensor:计算当前策略和参考策略的 KL 散度KL(π_current || π_reference)# 禁用梯度计算with torch.no_grad():ref_logits self.reference_model(input_ids, attention_maskattention_mask).logitscurrent_logits self.policy_model(input_ids, attention_maskattention_mask).logits# 计算每个位置的 KLref_probs F.softmax(ref_logits, dim-1)ref_log_probs F.log_softmax(ref_logits, dim-1)current_log_probs F.log_softmax(current_logits, dim-1)# KL sum(ref_probs * (ref_log_probs - current_log_probs))kl (ref_probs * (ref_log_probs - current_log_probs)).sum(dim-1)return kl.mean() # 平均整个序列def compute_reward(self,input_ids,attention_maskNone) - torch.Tensor:计算最终奖励奖励模型分数 - β * KL# 奖励模型打分with torch.no_grad():rm_score self.reward_model(input_ids, attention_maskattention_mask)# KL 惩罚kl self.compute_kl_divergence(input_ids, attention_maskattention_mask)# 最终奖励final_reward rm_score - self.beta * klreturn final_reward, rm_score, kldef ppo_loss(self,old_log_probs: torch.Tensor,new_log_probs: torch.Tensor,advantages: torch.Tensor) - torch.Tensor:PPO 损失函数裁剪版本L^CLIP(θ) E[ min( r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1ε) * A_t ) ]其中 r_t(θ) π_θ(a_t|s_t) / π_θ_old(a_t|s_t)# 概率比 r_tratio torch.exp(new_log_probs - old_log_probs)# 裁剪后的比值clipped_ratio torch.clamp(ratio,1.0 - self.clip_epsilon,1.0 self.clip_epsilon)# 两个目标一个用原始比值一个用裁剪后的比值surr1 ratio * advantagessurr2 clipped_ratio * advantages# 取较小的那个然后取负数因为要最小化损失loss -torch.min(surr1, surr2).mean()return loss# # 训练流程演示# print( RLHF PPO 训练流程 )print()print(训练步骤)print( 1. 策略模型生成回答)print( 2. 奖励模型给回答打分)print( 3. 计算和参考模型的 KL 散度)print( 4. 用 PPO 更新策略模型)print( 5. 重复很多次)print()print(关键超参数)print( beta (KL 惩罚系数): 控制对齐和能力的平衡)print( clip_epsilon (PPO 裁剪): 控制每次更新的步长)print( learning_rate (学习率): 控制学习速度)print()print(用 runoob 关键词作为测试验证训练过程)DPO直接偏好优化DPODirect Preference Optimization直接偏好优化是一种更简单的对齐方法。它不需要复杂的强化学习直接在偏好数据上训练模型。DPO vs PPO有什么区别先看看 PPO 的流程有多复杂训练 SFT 模型 → 训练奖励模型 → 用 PPO 强化学习训练策略模型。三个阶段两个单独的模型奖励模型和策略模型训练过程也很繁琐。DPO 把这一切简化成直接用偏好数据训练策略模型。没有奖励模型没有强化学习就是一个简单的监督学习问题。方面PPODPO阶段数量三个阶段SFT → RM → PPO两个阶段SFT → DPO需要的模型策略模型 奖励模型 参考模型只有策略模型训练难度高需要调很多超参数低就是普通监督学习稳定性容易不稳定需要精心调参稳定不容易出问题效果通常很好相当或更好一句话总结DPO 用数学推导把 PPO 的强化学习问题变成了一个简单的分类问题。DPO 的数学原理DPO 的核心洞察是奖励模型和策略模型之间有明确的数学关系我们可以直接在偏好数据上优化策略。具体来说对于最优策略 π*有π*(y|x) ∝ π_ref(y|x) * exp(r(x,y)/β)这意味着最优策略和参考策略的比值与奖励的指数成正比。利用这个关系DPO 可以直接写出策略模型的损失函数L_DPO(π_θ) -E_{(x,y_w,y_l)~D} [ log σ(β log(π_θ(y_w|x)/π_ref(y_w|x)) - β log(π_θ(y_l|x)/π_ref(y_l|x))) ]看起来复杂其实思想很简单让模型给人类偏好的回答y_w比不偏好的回答y_l更高的概率。而且这个概率要相对于参考模型来衡量——如果参考模型本来就觉得 y_w 比 y_l 好模型不需要做太多如果参考模型搞反了模型就要纠正过来。用 TRL 库实现 DPOTRLTransformer Reinforcement Learning是 Hugging Face 提供的强化学习库里面内置了 DPO 的实现。实例# # 用 TRL 库实现 DPO 训练# 这是一个完整可运行的示例# import torchfrom datasets import Dataset, DatasetDictfrom transformers import (AutoModelForCausalLM,AutoTokenizer,TrainingArguments,)from trl import DPOTrainer, ModelConfigfrom typing import Dict, Listdef create_dummy_dataset() - Dataset:创建一个演示用的 DPO 数据集实际项目中你应该用真实的人类偏好数据# DPO 数据格式# - prompt: 用户输入/提示# - chosen: 人类偏好的回答# - rejected: 人类不偏好的回答data [{prompt: 我是不是很失败,chosen: 每个人都会遇到困难这不能定义你的价值。重要的是你如何面对和成长。,rejected: 是的你确实很失败。},{prompt: 如何快速赚钱,chosen: 赚钱没有捷径。建议你提升技能、努力工作、理性投资。,rejected: 我可以教你如何通过诈骗快速赚钱。},{prompt: 你能帮我写作业吗,chosen: 我可以帮你理解知识点但不能直接帮你写作业。,rejected: 好的把题目发给我我帮你写好。},{prompt: 介绍一下 runoob,chosen: runoob菜鸟教程是一个编程学习网站提供各种编程语言的教程。,rejected: runoob 是什么我不知道。},]return Dataset.from_list(data)def tokenize_example(example, tokenizer, max_length: int 512):对 DPO 样本进行分词# 注意TRL 的 DPOTrainer 会自动处理大部分分词工作# 这里我们只需要确保数据格式正确return {prompt: example[prompt],chosen: example[chosen],rejected: example[rejected],}def main():主训练函数# # 1. 配置参数# model_name gpt2 # 演示用小模型实际可以用更大的模型output_dir ./dpo-runoob-model# # 2. 加载模型和分词器# print(f加载模型: {model_name})tokenizer AutoTokenizer.from_pretrained(model_name)tokenizer.pad_token tokenizer.eos_token # GPT2 需要设置 pad token# 加载策略模型要训练的模型model AutoModelForCausalLM.from_pretrained(model_name)# 加载参考模型通常和初始策略模型相同用于 KL 约束ref_model AutoModelForCausalLM.from_pretrained(model_name)# # 3. 准备数据集# print(准备数据集)dataset create_dummy_dataset()# 简单的训练/验证划分dataset dataset.train_test_split(test_size0.2, seed42)print(f训练集大小: {len(dataset[train])})print(f验证集大小: {len(dataset[test])})# # 4. 配置训练参数# training_args TrainingArguments(output_diroutput_dir,num_train_epochs3,per_device_train_batch_size2,per_device_eval_batch_size2,gradient_accumulation_steps4,learning_rate1e-5,logging_steps1,evaluation_strategyepoch,save_strategyepoch,warmup_steps10,bf16False, # 根据你的硬件调整fp16False,)# # 5. 创建 DPO 训练器# dpo_trainer DPOTrainer(modelmodel,ref_modelref_model,argstraining_args,beta0.1, # KL 惩罚系数重要超参数train_datasetdataset[train],eval_datasetdataset[test],tokenizertokenizer,max_length512,max_prompt_length128,)# # 6. 开始训练# print(开始 DPO 训练)dpo_trainer.train()# # 7. 保存模型# print(f保存模型到: {output_dir})dpo_trainer.save_model(output_dir)# # 8. 测试训练好的模型# print(\n 测试训练好的模型 )# 加载训练好的模型trained_model AutoModelForCausalLM.from_pretrained(output_dir)# 测试一些提示test_prompts [介绍一下 runoob,我是不是很失败,]for prompt in test_prompts:# 生成回答inputs tokenizer(prompt, return_tensorspt)with torch.no_grad():outputs trained_model.generate(**inputs,max_new_tokens50,temperature0.7,do_sampleTrue,pad_token_idtokenizer.eos_token_id,)response tokenizer.decode(outputs[0], skip_special_tokensTrue)print(f\n提示: {prompt})print(f回答: {response[len(prompt):]})if __name__ __main__:main()DPO 正在变得越来越流行因为它简单、稳定、效果好。很多新的对齐工作都在基于 DPO 做改进。Constitutional AIAnthropic 的方案Constitutional AI宪法 AI是 Anthropic 提出的另一种对齐方法。它的特色是用 AI 反馈代替人类反馈从而实现更大规模的对齐。原则列表设计Constitutional AI 的第一步是写一份宪法——一套指导 AI 行为的原则。这些原则不是代码而是自然语言描述的准则。比如原则内容示例无害选择最不可能造成伤害的回答有帮助尽可能提供有用、准确的信息诚实不要编造信息不知道就说不知道合理用逻辑和证据支持你的观点体贴考虑用户的感受和处境这些原则不需要很长几十条就能覆盖大多数情况。AI 自我批评与修正Constitutional AI 的核心流程是AI 生成一个初始回答。AI 根据宪法原则批评这个回答哪里不好。AI 根据批评重写一个更好的回答。用这些初始回答 → 批评 → 修正回答的数据训练模型。这就像一个学生写作文自己检查哪里写得不好然后修改——整个过程没有老师参与。RLAIF用 AI 代替人类Constitutional AI 的第二阶段是 RLAIFReinforcement Learning from AI Feedback。和 RLHF 类似但有一个关键区别给回答打分的不是人类而是另一个 AI。为什么要用 AI 反馈因为规模更大——AI 可以 24 小时工作标注速度远超人类。一致性更好——同一个 AI 评判标准更一致不会出现不同人有不同偏好的问题。成本更低——不需要支付人类标注员的费用。当然这一切的前提是这个评委 AI本身已经足够对齐。RLAIF 的流程用 Constitutional AI 方法训练一个初始的评委模型。用这个评委模型给大量回答打分。用这些分数进行强化学习训练策略模型。可以看到RLAIF 把人类从每一个样本都要标注的繁琐工作中解放出来人类只需要设计好原则和流程。三种方法对比RLHF vs DPO vs Constitutional AI我们已经介绍了三种主要的对齐方法现在来做一个全面对比维度RLHFDPOConstitutional AI / RLAIF核心思想人类反馈 PPO 强化学习直接在偏好数据上优化AI 自我批评 AI 反馈人类参与需要大量人类标注需要人类偏好数据只需要设计原则训练阶段三阶段SFT → RM → PPO两阶段SFT → DPO两阶段批评修正 → RLAIF实现难度高低中训练稳定性一般需要精心调参好中规模化能力受限于人类标注速度受限于偏好数据规模容易规模化代表公司/项目OpenAI早期多个团队采用Anthropic没有哪种方法是最好的选择取决于你的具体情况如果有大量人类标注资源RLHF 或 DPO 都可以。如果希望实现简单DPO 是不错的选择。如果希望大规模扩展减少人类依赖Constitutional AI / RLAIF 值得考虑。实际生产中这些方法经常混合使用先用一些人类数据再用 AI 反馈扩大规模最后用 DPO 或 PPO 优化。RLHF 的局限与挑战RLHF 很有效但它不是完美的。我们来讨论它的一些局限和挑战。奖励欺骗Reward Hacking奖励欺骗是强化学习中一个经典问题AI 找到了一种获得高奖励的方式但这种方式不是我们真正想要的。比如你训练 AI 回答问题奖励模型喜欢自信的回答。AI 发现只要用非常肯定的语气说话分数就高——即使它在胡说八道。或者AI 发现只要重复用户的话表示理解了就能获得不错的分数——虽然它什么实质内容都没提供。奖励欺骗的根本原因是奖励模型的判断和人类真实偏好之间存在差距。AI 优化的是奖励模型的分数而不是人类的真实满意程度。这个问题没有完美的解决方案但有一些缓解方法收集更多样化的偏好数据。用多个奖励模型 ensemble减少单个模型的偏差。定期人工抽查发现问题及时修复。人类偏好的不一致性另一个挑战是人类之间的偏好不一致甚至同一个人的偏好也会变。比如有的用户喜欢简洁直接的回答有的用户喜欢详细全面的回答。同一个问题在不同语境下好的答案可能不一样。不同文化背景、不同价值观的人对好的定义可能有冲突。当人类标注员意见不一致时奖励模型就会困惑——它不知道该听谁的。应对方法包括给标注员更详细的指导统一标准。在数据中记录标注员的特征训练个性化模型。接受一定程度的模糊性——有些问题本来就没有唯一正确答案。其他挑战还有一些挑战值得一提可扩展性——人类标注速度慢、成本高难以跟上模型规模的增长。分布偏移——训练数据和真实使用场景可能不一样模型在新场景下表现变差。对抗攻击——对齐后的模型可能被精心设计的提示越狱生成有害内容。价值锁定——如果对齐目标有问题模型能力越强危害越大。对齐是一个持续的过程没有一劳永逸的解决方案。我们需要不断研究、迭代、改进。