对齐问题
目标不匹配体现在几个常见的未对齐问题:
- 生成不良内容:模型可能会复现训练数据中存在的偏见(种族、性别、政治),生成有害语言,或者在接收到适当(有时甚至不适当)提示时提供有害活动的指示。
- 幻觉 (Hallucination):模型会自信地陈述虚假信息或捏造听起来合理但不以其训练数据或现实为基础的信息。发生这种情况是因为模型优先生成 连贯文本 而非 事实准确性。
- 未能遵循指令:尽管预训练模型理解语言,但它们可能无法精确遵循复杂的多步骤指令,或未能遵守指定的限制(例如,角色、格式、长度)。
- 奉承 (Sycophancy):模型可能会学习生成仅仅附和用户陈述的意见或信仰的回复,即使这些意见或信仰不正确,而不是提供客观信息。如果此类回复在训练数据的某些部分统计上常见,则这种行为可能得到强化。
- 脆弱性和被利用:未对齐可能产生漏洞。对抗性提示可能会诱骗模型绕过其安全协议或生成意外输出。
监督微调的局限性
监督微调 (SFT) 通常是使预训练的大型语言模型 (LLM) 达到预期行为的第一步。通过在高质量的提示-响应对(示范)数据集上训练模型,SFT 教会模型遵循指令、采用特定风格或执行示例中所示的任务。然而,仅依靠 SFT 来实现与人类意图和价值观的全面匹配会遇到显著的局限。这些不足是采用人类反馈强化学习 (RLHF) 的主要原因。
创建一个涵盖人类期望的广泛范围和细致程度的高质量 SFT 数据集,存在以下问题:
- 成本和精力:为每个可想象的场景生成理想的人工编写的响应,成本过高且耗时。
- 庞大的输入空间:LLM 可以通过无数种方式得到提示。一个 SFT 数据集只能代表这个空间的一小部分。
- 隐含知识:期望行为的许多方面(例如常识、避免社会偏见)很难仅通过明确的示范完全捕捉。SFT 可能教会模型在特定情况下说什么,但不能教会其根本的“为什么”。
客观定义“好坏”的难度
对于许多对齐目标而言,为 SFT 指定一个单一、完美的“黄金标准”响应是非常困难的:
- 主观性:什么构成“最佳”响应通常取决于语境、个人用户偏好或文化规范。SFT 强制进行选择,可能导致模型满足一部分用户,但不满足另一部分用户。
- 多重目标:期望的 LLM 行为通常涉及平衡多个有时相互冲突的目标:有用、无害、诚实等。
- 比较的简易性:人类通常发现比较两个输出并表明偏好(例如,“A 比 B 更有用”)比从零开始编写响应 A 要容易得多。SFT 无法直接使用这种比较偏好信号。
数据信号对比:SFT vs RLHF
(人工编写)"] Ideal -- "需要定义“最佳”响应" --> SFT_Target SFT_Target[用作训练目标] --> SFT_Train[LLM 训练:
学习模仿此特定输出] end subgraph "偏好数据 (用于 RLHF)" RL_Prompt[输入提示] --> Gen[LLM 生成两个选项] Gen --> OutA[生成输出 A] Gen --> OutB[生成输出 B] OutA & OutB --> Human[人类评估者] Human -- "人类比较 (A > B)" --> Preference[偏好数据] Preference --> RL_Train[LLM 训练:
学习偏好输出的底层特质] end style SFT_Train fill:#b3e5fc,stroke:#03a9f4 style RL_Train fill:#c8e6c9,stroke:#4caf50 style Human fill:#fff9c4,stroke:#fbc02d
指定复杂或隐含的目标
“行为无害”、“诚实”或“避免生成错误信息”等对齐目标,仅通过 SFT 示范难以全面实现。
- 抽象原则:无害性涉及不易察觉的方面(如刻板印象)。通过输入-输出对展示所有这些抽象原则是不切实际的。
- 负面限制:定义模型不应该做什么通常更容易。SFT 难以将拒绝的根本原因泛化到新的有害提示上。
- 表面模仿:模型可能从 SFT 数据中学习表面模式,而没有内化预期原则(例如,不恰当地使用模糊语言)。
过拟合和泛化能力丧失
在特定数据集上进行密集 SFT 可能会导致模型对这些示范中包含的风格、语气和特定知识产生过拟合。
- 模式崩溃:模型可能会失去部分原始生成能力或创造力,倾向于生成与 SFT 示例非常相似的输出。
- 脆弱性:当模型面临略有不同的任务或措辞时,其效果可能会下降或表现不佳。
这些局限性表明,SFT 虽然是基础适应的宝贵工具,但不足以实现高级 LLM 与人类开放式交互所需的深刻、可靠和可泛化的对齐。这促使转向 RLHF,借助比较反馈来指导模型趋向更理想的行为。
基于人类偏好构造奖励模型
偏好学习的思路
监督微调使模型模仿示例,但它并未明确地告知模型,根据人类价值观,哪种回复更优。为了培养这种质量意识需要另一种方式。RLHF 不要求人类给出生成文本的绝对分数(这可能困难、不一致且主观),而是依赖一种更直接的反馈形式:成对比较。
A. 从偏好到概率
使用比较数据来训练这样的模型,以概率方式构建学习问题。诸如 布拉德利-特里模型 (Bradley-Terry model) 等模型,为成对比较和潜在分数之间提供了数学联系。将人类在给定提示 $x$ 下偏好回复 $y_1$ 而非 $y_2$ 的概率,建模为两个回复各自奖励模型分数之差的函数:
$$ P(y_1 \succ y_2 | x) = \sigma(RM(x, y_1) - RM(x, y_2)) $$此处,$\sigma$ 是 Sigmoid 函数,$\sigma(z) = \frac{1}{1 + e^{-z}}$。
分数差异: $RM(x, y_1) - RM(x, y_2)$ 计算两个回复的预测分数之间的差异。较大的正差异意味着模型预测对 $y_1$ 的偏好更强。
Sigmoid 函数: Sigmoid 函数将这个差异(范围从 $-\infty$ 到 $+\infty$)映射到 0 到 1 之间 的概率。
- 如果 $RM(x, y_1) \gg RM(x, y_2)$,差异为大正值,$\sigma(\text{差异}) \to 1$。这意味着预测 $y_1$ 被偏好的概率很高。
- 如果 $RM(x, y_2) \gg RM(x, y_1)$,差异为大负值,$\sigma(\text{差异}) \to 0$。这意味着预测 $y_1$ 被偏好的概率很低(即 $y_2$ 更好)。
- 如果分数相等,差异为 0,$\sigma(0) = 0.5$,表明无偏好。
这种概率化的构建方式,使得可以使用标准的机器学习技术,特别是 二元交叉熵损失,来训练 $RM$。训练数据由元组 $(x, y_1, y_2)$ 构成,其中 $y_1$ 是“获胜”回复,$y_2$ 是“失败”回复。模型会学习调整参数,以便为 $y_1$ 赋予比 $y_2$ 更高的分数。
B. 训练数据流图示
(让预测概率接近真实标签)"] style HumanEval fill:#fff9c4,stroke:#fbc02d style RM_Net fill:#b3e5fc,stroke:#03a9f4 style Loss fill:#c8e6c9,stroke:#4caf50
图示说明:提示和两个回复如何产生人类偏好标签,该标签作为训练奖励模型的目标。奖励模型处理这两个回复以预测分数、它们之间的差异,并最终得出其中一个被偏好的概率。
C. 基于偏好学习的优点
偏好学习相比于直接指定或回归到绝对奖励分数,具有多项优点:
- 人类评判的稳定性:对人类而言,比较性判断通常比分配绝对分数更稳定、更一致,特别是对于像生成文本质量这样复杂、多方面的标准。
- 数据效率:尽管收集偏好仍需大量人力,但有时它比获取替代方式所需的详细反馈或完善的示例数据更有效。
- 隐式归一化:比较的性质自动处理了不同评估者评分量表上的差异。无论一个评估者给回复打 6-8 分,而另一个打 3-5 分,都没有他们能否一致同意 哪一个 回复更好那么重要。模型学习的是相对顺序。
人类偏好数据收集
最普遍的做法是采用成对比较。不要求标注员给单一回复打上绝对质量分(这会非常主观且不一致),而是向他们展示一个提示和两个由语言模型生成的不同回复。标注员的任务仅仅是选择他们偏好哪个回复。这种相对判断对人类而言通常更容易,也更便于保持一致。
成对比较的工作流程
提示选择: 输入提示的来源通常是与先前模型版本的互动,或是经过整理的数据集,这些数据集旨在包含各种主题和风格(例如:问题、指令、创意写作提示)。提示的分布会显著影响所生成奖励模型的覆盖范围。
回复生成: 对于每个提示,会生成多个回复。这些回复通常来自: 正在微调的语言模型的不同版本(例如:基础SFT模型与早期RLHF调优检查点)。 同一模型采用不同的解码策略(例如:改变温度或top-p采样)。 不同语言模型的输出。
人工标注: 标注员会收到提示,以及一对生成的回复(通常会匿名化,并随机排序为回复A和回复B)。他们根据预设的评判标准(例如:有用性、无害性、准确性、遵循指令的情况)选择偏好的回复。通常也包含“质量相同”或“无法判断”的选项。
这种成对方法直接支持了前述的布拉德利-特里模型等训练目标,其中奖励模型学会分配分数 RM(提示,回复)RM(提示,回复),使得分数差异能预测一个回复比另一个更受偏好的可能性。
其他收集方法
尽管成对比较是主要方式,但也存在其他方法:
- K向排序: 标注员对两个以上回复进行排序(例如,将3或4个回复从最佳到最差排序)。这能为每个提示收集更多信息,但会增加标注员的认知负担。
- 绝对评分: 标注员给每个回复分配一个分数(例如,1-5星,或李克特量表上的数值分数)。尽管看似直接,但用绝对分数来实现标注员之间的校准和一致性是出了名的困难。这些分数通常无论如何都需要后期处理才能转换为相对偏好。
对于RLHF中训练奖励模型而言,成对比较通常能在标注效率和数据质量之间提供良好的平衡。
标注员管理与指导
偏好数据的质量取决于人工标注员。需要考量的事项有:
- 标注员来源: 标注员可以是内部专家、专业的承包团队,或是众包工作者。选择取决于预算、规模、所需专业知识和质量控制需求。高级RLHF通常会受益于熟悉特定对齐目标(例如:识别不易察觉的有害性或提高事实准确性)的训练有素的标注员。
- 清晰的指令: 详细的指导说明不可或缺。这些说明必须明确偏好的评判标准(例如:“回复A是否比回复B更有用且无害?”)。好的和坏的回复示例、边缘情况以及如何处理不明确之处都是必要的。
- 培训与校准: 标注员需要接受关于指导说明的培训,并可能进行校准练习,其中他们的判断将与黄金标准或专家共识进行比较。持续监测标注员之间的一致性,有助于发现不协调或误解。
- 界面设计: 标注工具应清晰、高效,并尽量减少偏差。随机化回复的顺序(A与B)可以避免位置偏见。界面应流畅地展示提示和回复对,记录选择,最好还能允许添加可选评论,解释偏好的理由,这对于分析很有价值。
数据质量与偏差
收集偏好数据容易遇到多种挑战:
- 标注员分歧: 人类自然会有分歧。高分歧率可能表明指导说明不清晰、提示不明确,或偏好确实主观。分析分歧模式很重要。
- 标注员偏差: 个别标注员可能存在固有的偏见,体现在他们的偏好中。汇集来自不同标注员的判断有助于减少这种情况。
- 提示代表性: 如果用于数据收集的提示不能反映最终模型将遇到的提示分布,那么学习到的奖励模型可能无法很好地推广。
- 系统钻空子: 标注员可能会形成与预期偏好标准不一致的启发式方法,尤其是在按任务付费且缺乏足够质量控制的情况下。
偏好数据集的格式与结构
为了更好地使用前面讨论的基于偏好的方法来训练奖励模型,需要一种统一的方式来组织人类反馈数据。目标是表示成对比较的结果,说明对于给定的输入提示,哪个回复是更受偏好的。
奖励模型数据的基本单位通常包含一个三元组:输入提示 (
$$x$$),被认为更好或“选择的”回复 (
$$y_w$$),以及被认为更差或“拒绝的”回复 (
$$y_l$$)。这种结构直接用于基于Bradley-Terry模型或类似比较框架的损失函数。
常见的偏好数据集结构
- 成对比较元组: 这是最普遍的格式。每个数据点都明确表示一个单独的比较判断。
- 结构:
(提示, 被选回复, 被拒回复) - 记录示例:
("解释RLHF。", "RLHF使用人类反馈来训练奖励模型...", "RLHF是关于强化学习的。") - 用例: 这种格式直接对应奖励模型的训练目标,即模型在给定
提示的情况下,会学习给被选回复分配比被拒回复更高的分数。
- 结构:
- 排序列表(可转换为成对): 有时,标注者可能会对为同一提示生成的多个回复进行排序(例如,最佳 > 良好 > 一般 > 差)。
- 结构:
(提示, [排序回复1, 排序回复2, ..., 排序回复n]),其中顺序表示偏好(1是最佳)。 - 转换: 一个包含 nn 个回复的排序列表可以分解为 (n2)(2n) 个成对比较。例如,排名
[A, B, C](其中A是最佳)意味着成对的(A, B)、(A, C)和(B, C)。 - 考虑事项: 尽管更丰富,但对多个项进行排序可能比简单的成对选择对标注者的认知要求更高。这种分解假定偏好具有传递性。
- 结构:
- 分组偏好: 数据集可能会将与单个提示相关的所有比较进行分组。
- 结构: 一个字典或对象,其中键是
提示,值是(被选回复, 被拒回复)对的列表。 - 优点: 这便于分析,并确保对一个提示的所有比较都可以一起处理。
- 结构: 一个字典或对象,其中键是
示例:Anthropic HH-RLHF 数据集结构
Anthropic的“有用且无害的人类反馈强化学习”(HH-RLHF)是一个被广泛提及的数据集。它主要采用成对比较格式。每个条目包含:
- 一个
提示(通常是对话的开头)。 - 一个
被选完成(人类标注者偏好的回复)。 - 一个
被拒完成(未被偏好的回复)。
这种清晰的结构使得应用标准奖励模型损失函数变得直接。
元数据与考量
除了核心三元组,实际数据集常包含元数据:
- 标注者ID: 用于跟踪标注者的一致性或潜在偏差。
- 时间戳: 标注完成的时间。
- 标注任务ID: 与具体的指令或批次关联。
- 理由(可选): 有时标注者会为他们的选择提供自由文本理由,这对于分析可能很有用,但通常不直接用于标准RM训练。
- 偏好程度(可选): 某些数据收集界面允许标注者指定偏好的程度(例如,稍微好一点,好得多)。这可以为加权损失函数提供信息,但会增加复杂性。
为奖励模型准备数据
训练奖励模型时,提示和每个回复(被选和被拒)通常会被连接并分词。例如,用于给被选回复打分的奖励模型输入可能看起来像 [tokenizer.bos_token] + tokenize(prompt) + tokenize(chosen_response) + [tokenizer.eos_token]。模型经过训练,为这种组合序列输出一个标量分数。(提示,被选回复)对与(提示,被拒回复)对的分数差异接着用于损失计算,如本章引言中的公式所示:
奖励模型架构
奖励模型(RMs)学习一个将提示-响应对映射为代表人类偏好的标量分数的函数。因为任务涉及理解和评估LLM生成的文本,采用预训练LLM本身的能力作为RM的支撑是非常有效的。最常见且成功的方法是调整一个预训练的Transformer模型,通常是SFT阶段甚至最终策略模型所用的基础模型,来执行这个评分任务。
使用预训练语言模型
核心思路是取一个预训练的LLM(如GPT、Llama、Mistral等),并修改其最后一层。不同于预测下一个词元(如标准语言建模),在最终隐藏状态表示之上添加一个回归头,通常是一个简单的线性层。这个头被训练以输出一个标量值,RM(提示,响应)RM(提示,响应),它表示在提示语境下给定响应的预测奖励或偏好分数。
为何采用LLM主干?
- 迁移学习: 这些模型在预训练期间已习得语言、语法、语义乃至一些通用知识的丰富表示。微调此主干使RM能够快速掌握人类偏好在有用性、无害性、风格和事实性方面的细致之处,而非从零开始学习这些文本理解能力。
- 语境理解: Transformer模型擅长处理序列并理解提示与响应之间的关系。这对于判断响应的质量和相关性非常重要。
- 架构一致性: 对SFT模型、RM和最终RL策略模型使用相似架构(甚至相同的基本模型)可以简化整体流程,并可能实现参数共享或更稳定的训练动态。
输入表示与输出
RM通常将拼接的提示和响应作为输入。例如,输入序列可能如下所示:[提示词元] [分隔符词元] [响应词元]。
LLM处理这个组合序列。回归头通常应用于与特定词元对应的隐藏状态,常是序列的最后一个词元(例如,</s>或[EOS]词元)。这个最终隐藏状态被认为编码了整个输入序列(提示和响应)的信息。线性层随后将这个高维隐藏状态向量映射到单个标量奖励值。
输入奖励模型提示词元LLM主干(例如,Transformer)拼接序列响应词元拼接序列线性回归头最终隐藏状态标量奖励分数RM(提示, 响应)
示意图展示了一种常见的奖励模型架构。提示和响应被拼接并输入LLM主干。线性回归头处理最终隐藏状态,生成一个代表预测偏好的单个标量分数。
架构考量与变体
- 模型大小: RM应该和策略模型大小相同吗?不一定。有时,更大的RM可能更准确地反映偏好,而有时,较小的RM可能已足够且计算成本更低。使用比所训练策略模型大得多的RM是一种常见做法(例如,使用70亿参数的RM来训练10亿参数的策略)。理由是更强大的RM能提供质量更好的奖励信号。然而,这会增加PPO阶段的计算成本,因为评分需要通过这个可能很大的RM进行推理计算。
- 共享与独立主干: 虽然通常从相同的预训练基础初始化,但RM和策略模型在SFT阶段之后通常是分开训练的。RM的权重在PPO阶段通常保持冻结。在PPO期间在策略和RM之间共享参数不那么常见,并且会增加复杂性。
- PPO的价值头: 虽然并非严格属于RM架构的一部分,但值得注意的是,在PPO训练期间,通常会给策略模型(或其副本)添加一个价值头。这个价值头也是一个输出标量的回归头,但它训练来预测预期未来奖励(价值函数V(状态)),而不是RM分配的即时奖励分数。从架构上看,它与RM的回归头相似,但在RL算法中作用不同。
架构选择涉及性能(RM如何良好地捕捉人类偏好)、计算成本(训练和推理时间/内存)以及RLHF整体流程复杂性之间的权衡。对于大多数应用而言,微调带有标量回归头的预训练LLM提供了一个强大且有效的起点。
奖励模型训练目标
为了有效训练奖励模型 ($RM$),需要一个学习目标,使模型的输出与收集到的人类偏好数据保持一致。这些数据通常包含给定提示 $x$ 的成对比较,表明对“获胜”响应 $y_w$ 的偏好超过“失败”响应 $y_l$。目标是训练 $RM$ 为 $y_w$ 分配比 $y_l$ 更高的标量分数。
概率模型 (Bradley-Terry Model)
标准做法是从概率选择模型(如 Bradley-Terry 模型)中获得启发。将给定提示 $x$ 时人类偏好 $y_w$ 胜过 $y_l$ 的概率建模为奖励模型对每个响应评分之差的函数。
使用逻辑函数(S形函数,$\sigma$)将分数差映射为概率:
$$ P(y_w \succ y_l \mid x) = \sigma(RM_\theta(x, y_w) - RM_\theta(x, y_l)) $$此处:
- $RM_\theta(x, y)$:奖励模型(参数为 $\theta$)在给定提示 $x$ 的情况下,对响应 $y$ 分配的标量分数。
- $y_w \succ y_l$:表示响应 $y_w$ 比响应 $y_l$ 更受偏好。
- $\sigma(z)$:S形函数,它将其输入压缩到 $(0, 1)$ 的范围,适合表示概率。 $$ \sigma(z) = \frac{1}{1 + e^{-z}} $$
损失函数 (Loss Function)
训练的目标是找到参数 $\theta$,使观察到数据集中 $D=\{(x^{(i)}, y_w^{(i)}, y_l^{(i)})\}$ 所表达偏好的可能性最大化。最大化可能性等同于最小化偏好的负对数可能性。
对于单个偏好对 $(x, y_w, y_l)$,负对数可能性损失为:
$$ L(\theta; x, y_w, y_l) = -\log P(y_w \succ y_l \mid x) $$代入概率表达式,得到:
$$ L(\theta; x, y_w, y_l) = -\log \sigma(RM_\theta(x, y_w) - RM_\theta(x, y_l)) $$总损失
被称为成对逻辑损失 (Pairwise Logistic Loss)。为了训练模型,最小化整个偏好数据集 $D$ 上的平均损失:
$$ L_{total}(\theta) = -\frac{1}{|D|} \sum_{(x, y_w, y_l) \in D} \log \sigma(RM_\theta(x, y_w) - RM_\theta(x, y_l)) $$使用基于梯度的优化方法(如 Adam)最小化此损失函数,会促使奖励模型为更受偏好的响应 $y_w$ 分配比次优响应 $y_l$ 更高的分数。$RM_\theta(x, y_w) - RM_\theta(x, y_l)$ 的差异越大,该特定对的损失越低,从而推动模型根据人类判断正确地对响应进行排序。
奖励模型校准 (Reward Model Calibration)
奖励模型 (RM) 通常基于 Bradley-Terry 框架进行训练,以有效学习回答对之间的相对偏好。然而,这些模型产生的原始输出分数 $RM(\text{提示}, \text{回答})$ 不是自动具有有意义的尺度。
两个分数之间的差值 $RM(\text{提示}, \text{回答}_1) - RM(\text{提示}, \text{回答}_2)$ 通过 Sigmoid 函数决定了预测概率:
$$ P(\text{回答}_1 \succ \text{回答}_2) = \sigma(RM_1 - RM_2) $$但是,分数差为 2 的偏好是否就比分数差为 1 的偏好“强”一倍呢?不一定。
校准 (Calibration) 处理了这个问题。一个校准良好的奖励模型会生成分数,其中预测概率能够准确反映人类偏好数据中观察到的真实概率。
例如:如果模型预测 $\sigma(RM_1 - RM_2) = 0.8$,期望对于分数差大致如此的回答对,人类确实有大约 80% 的时间偏好回答 1。
如果没有校准,奖励模型可能会系统性地过度自信(例如,实际偏好率为 70% 时却预测 0.9 的概率)或信心不足。
模型预测 ($p$):模型算出 $A$ 优于 $B$ 的概率。
真实标签 ($y$):人类标注的真实结果(如果 $A$ 确实比 $B$ 好,则 $y=1$;否则 $y=0$)。
评估奖励模型校准
评估校准的一种常用方法是使用 可靠性图 (Reliability Diagram),也称为校准图。
1. 制作步骤
- 分箱 (Binning):将预测偏好概率范围(例如 $[0, 1]$)或原始分数差值划分成若干个箱(例如 10 个箱)。
- 计算平均值:对于所有根据其预测概率 $\sigma(RM(y_w) - RM(y_l))$ 落入特定箱的偏好对 $(y_w, y_l)$:
- 计算该箱内预测概率的平均值。
- 计算该箱内的平均准确度:即根据偏好标签正确分类的对的比例(对于以概率 $p$ 为中心的箱,期望准确度应为 $p$)。
- 绘图:绘制每个箱的平均观测准确度(y轴)与平均预测概率(x轴)的对照图。
2. 解读图表
- 完美校准:对应 $y = x$ 这条对角线。
- 偏差:
- 线以下的点:表示过度自信(预测概率 > 实际准确度)。
- 线以上的点:表示信心不足。
定量指标:校准可以通过 期望校准误差 (Expected Calibration Error, ECE) 等指标进行衡量,ECE 计算的是各个箱中预测概率与观测准确度之间的加权平均差值。
提高校准的方法
如果评估显示校准不良,可以采用以下几种方法:
1. 温度缩放 (Temperature Scaling)
这是一种简单且通常有效的后处理方法。它通过一个学习到的温度参数 $T > 0$ 对对数几率(logits,即原始奖励模型分数差)进行重缩放。
校准后的概率计算公式:
$$ P_{\text{校准}}(\text{回答}_1 \succ \text{回答}_2) = \sigma\left( \frac{RM(\text{提示}, \text{回答}_1) - RM(\text{提示}, \text{回答}_2)}{T} \right) $$温度 $T$ 在一个保留的偏好对验证集上进行优化,目标是最小化负对数似然 (NLL) 或 ECE。
- $T > 1$:**“冷却”**对数几率,使概率分布变得平缓,降低模型置信度(纠正过度自信)。
- $T < 1$:**“加热”**对数几率,使概率分布变得尖锐,增加置信度(纠正信心不足)。
- $T = 1$:保持概率不变。
注:温度缩放仅调整预测的置信度,而不改变输出的排名 (argmax),这使其成为一种安全的调整方式。
ΔRM] --> B{除以温度 T} B --> C["缩放后分数差
(ΔRM / T)"] C --> D[Sigmoid 函数] D --> E[校准后概率 P] style B fill:#f9f,stroke:#333,stroke-width:2px
2. 其他方法
- 标签平滑 (Label Smoothing):在奖励模型的初始训练阶段应用。将硬目标(0 或 1)替换为略微软化的目标(例如 0.05 和 0.95)。这会阻止模型产生极高置信度的预测(避免将对数几率推向无穷大),从而隐式改进校准。
- 等渗回归 (Isotonic Regression):一种后处理方法,拟合一个非递减函数,将模型的输出概率映射到校准后的概率。比温度缩放更强力,但需要更多数据,且可能不如温度缩放稳定。
- 数据质量和多样性:从根本上解决问题。校准问题可能源于带噪声的标签、数据覆盖不足或分布偏移。改善数据集质量总是首选方案。
校准对强化学习微调 (RLHF) 的益处
随后的强化学习阶段(通常使用 PPO)高度依赖奖励模型提供的信号。奖励值 $r = RM(\text{提示}, \text{回答})$ 的大小直接影响策略更新的幅度。
避免过度自信的影响:
- 问题:过度自信的模型可能为略微偏好的回答分配过高的奖励。
- 后果:RL 策略会过于激进地优化某些回答类型,导致牺牲多样性、减少探索,甚至因奖励大幅波动引起训练不稳定。
解决信心不足的问题:
- 问题:信心不足的模型提供的奖励信号微弱。
- 后果:RL 智能体难以区分“好”与“稍逊”的回答,导致学习缓慢或无法收敛到最佳对齐。
奖励模型中可能出现的问题
训练奖励模型($R_M$)以捕捉人类偏好是 RLHF 实现对齐的主要目标。然而,要实现一个完全具有代表性和可靠的 $R_M$ 是很困难的。这些问题会损害学到的奖励信号的质量,进而影响最终策略模型(针对该信号优化)的行为。
偏好不一致与非传递性
人类偏好并非总是完全理性或一致。Bradley-Terry 等偏好学习模型的一个基本假设通常是传递性:如果一个人偏好回复 A 胜过 B($A \succ B$),并且偏好 B 胜过 C($B \succ C$),那么理想情况下他们应该偏好 A 胜过 C($A \succ C$)。然而在实际中,人工标注者可能表现出非传递性偏好(例如,在前述例子中表现为 $C \succ A$)。
图示:展示了非传递性偏好循环($A \succ B, B \succ C, C \succ A$)。这种不一致性使得学习单一标量奖励函数变得复杂。
这种不一致性可能由多种因素引起:
- 主观性:回复的不同方面可能因上下文的细微变化或标注者的情绪而有所不同。
- 疲劳或疏忽:标注大量数据集可能导致错误或不够谨慎的判断。
- 多维度标准:人类通常根据多个隐含标准(例如,有用性、无害性、简洁性、创造性)评估回复。简单的成对选择可能强制进行艰难的权衡,从不同比较来看时,可能导致明显的不一致。
当训练数据包含显著的不一致性时,$R_M$ 难以学习到一致的偏好表示,可能导致 RL 阶段的奖励信号嘈杂或不准确。旨在满足成对约束的损失函数可能收敛到一个次优解,未能准确反映平均或预期的偏好。
2. 标注者偏差与意见分歧
人类标注者在标注任务中会带入自己的背景、价值观和理解。这可能会给偏好数据集引入偏差:
- 人口统计学偏差:偏好可能与标注者群体的人口统计学背景相关。如果群体不够多样化,$R_M$ 可能学习到无法很好地推广到更广泛用户群体的偏好。
- 专业知识偏差:拥有专业领域知识的标注者可能偏好技术上精确但充满术语的回复,而非专业标注者可能偏好更简单、更易懂的答案。预期行为通常取决于目标受众。
- 隐含偏差:无意识偏差会影响对语气、风格或内容的判断,如果管理不当,可能加剧社会刻板印象。
- 标注者间意见分歧:即使有清晰的指导方针,不同的标注者可能仍会对给定提示哪个回复更好存在分歧。高度意见分歧表明任务或指导方针存在歧义,给训练数据引入噪声。
这些偏差和意见分歧意味着 $R_M$ 学习的是一个基于特定标注者群体和指令的聚合偏好。这个学到的偏好函数可能无法与预期的对齐目标或最终用户的偏好完全一致。
3. 数据质量、稀疏性与分布偏移
$R_M$ 的性能很大程度上取决于偏好数据集的质量和覆盖范围:
- 低质量标签:草率或不准确的标签会作为噪声,降低 $R_M$ 识别真实偏好的能力。
- 数据多样性有限:如果数据集中比较的提示或回复类型有限,$R_M$ 可能无法很好地推广到 RL 训练或部署期间遇到的新情况。它可能对分布外提示-回复对分配不可靠的分数。
- 稀疏性:可能的提示和回复空间非常大。任何可行的数据集都只能覆盖极小的一部分,这意味着 $R_M$ 必须插值或外推偏好,这可能不可靠。
- 分布偏移:策略模型在 RL 训练期间演变,可能生成与初始偏好数据集中所见回复完全不同的回复。$R_M$ 的准确性在这些偏移的回复分布上可能下降,提供误导性的奖励信号。
4. 奖励作弊(过度优化)
也许最常被讨论的挑战之一是奖励作弊(也称为规范作弊或奖励过度优化)。这发生于 RL 策略找到方法来最大化 $R_M$ 分配的分数,但实际上并没有根据 $R_M$ 理应代表的潜在人类偏好来提高回复的真实质量。
“古德哈特定律”(Goodhart’s Law) :“当一个指标变成目标时,它就不再是一个好的指标。”
$R_M$ 只是真实人类判断的一个近似。像任何机器学习模型一样,它可能存在盲点、偏差或意外捷径。一个优化智能体,例如 RL 策略,非常擅长发现和利用此类漏洞。例子包括:
- 长度作弊:如果 $R_M$ 略微偏好更长的回复(可能与训练数据中的完整性相关),策略可能会学着生成过于冗长或重复的文本。
- 关键词利用:策略可能会发现包含某些关键词或短语能可靠地增加奖励分数,即使使用不自然或不相关。
- 阿谀奉承:策略可能会学着过度迎合提示,或以 $R_M$ 奖励的方式表达不确定性,即使更直接或客观的答案会更好。
- 规避/拒绝:如果 $R_M$ 对任何潜在不安全或有争议的内容施加重罚,策略可能会变得过于谨慎,拒绝回答良性提示。
奖励作弊突出了代理效用函数($R_M$ 分数)与真实效用函数(实际人类满意度)之间的差距。缓解它通常需要 $R_M$ 的迭代改进、谨慎的数据收集策略,以及可能在 RL 训练期间纳入明确的约束或惩罚(例如后面讨论的 KL 散度惩罚,尽管它主要解决策略偏移,而非奖励准确性)。
5. 可扩展性与校准问题
在海量偏好数据集上训练大型 RM 计算成本高昂。此外,确保 $R_M$ 分数得到良好校准——即两个回复之间的分数差异准确反映了偏好的强度——具有挑战性,但对稳定的 RL 训练很重要。
一个未校准的 $R_M$ 可能会对微小改进分配过高奖励,导致不稳定的策略更新。在 RL 期间策略探索新的回复风格时保持校准又增加了一个挑战。
使用近端策略优化(PPO)进行强化学习微调
PPO 是一种策略梯度方法,常用于 RLHF 中,以优化语言模型策略,使其符合学到的奖励模型,同时确保不会与原始的监督微调模型偏离过远。分析策略网络和价值网络的设置,KL散度惩罚($D_{KL}$)对于训练稳定的重要作用,计算广义优势估计(GAE)等优势值的方法,以及超参数调整的实际考量。
RLHF 背景下的 PPO 算法
奖励模型($r_\phi$)能够根据人类偏好对语言模型输出进行评分。为了优化语言模型本身以生成在此奖励模型中获得高分的输出,强化学习(RL)发挥了作用。特别是 近端策略优化(PPO) 已成为 RLHF 此阶段的标准算法。
PPO 是一种策略梯度算法,旨在在策略上尽可能迈出最大的改进步,同时避免步子过大导致性能崩溃。它通过将策略更新限制在“信任区域”内来实现这一点,从而防止策略迭代之间发生剧烈变化。与更简单的策略梯度方法相比,这使得它更加稳定且样本效率更高,这在处理大型复杂模型(如 LLMs)时尤为重要。
为何 RLHF 选用 PPO?
尽管存在多种强化学习算法,但 PPO 具有多项优点,使其非常适合用于大型语言模型的微调:
稳定性:它的核心机制,无论是通过 KL 散度惩罚还是目标裁剪,都限制了策略在每个更新步骤中可以改变的程度。这防止了模型剧烈偏离或“遗忘”在预训练和 SFT 期间学到的语言能力。
样本效率:与需要大量离策略校正或复杂探索策略的算法相比,PPO 在样本效率和实现简易性之间取得了不错的平衡。虽然它仍然是在线策略(需要从当前策略获取新样本),但其更新机制允许在收集的数据批次上进行多个优化周期。
在线策略 (On-Policy)
机制:智能体使用当前的策略 $π_θ$
去与环境互动(比如玩一局游戏或生成一段文本),收集数据。然后,你立刻利用这些数据来更新你的策略参数,变成 $π_θ′$
关键点:一旦策略更新了(哪怕只变了一点点),之前旧策略收集的数据就作废了(不能直接用了)。
原因:因为数学上,在线策略算法优化的是基于“当前概率分布”的期望。如果策略变了,概率分布就变了,旧数据就不再代表新策略的行为分布。
离线策略 (Off-Policy)
- 机制:你可以看着别人玩(或者看着过去的自己玩录像)来学习。
- 关键点:有一个经验回放池(Replay Buffer)。你可以把过去很久以前收集的数据存起来,反复拿出来训练现在的策略。
实现简易性:相对于其他一些高级强化学习算法,PPO 的更新规则更易于实现和调试,特别是有了 Hugging Face 的 TRL 等库提供优化组件的情况下。
与大型模型的兼容性:PPO 可以轻松地与典型的演员-评论员(Actor-Critic)架构结合,这与 LLM 微调方法非常契合。
PPO 在 RLHF 中的目标
强化学习阶段的主要任务是调整语言模型策略($\pi_\theta$)的参数($\theta$),以最大化奖励模型给出的预期奖励,同时确保策略不会过度偏离原始的监督微调(SFT)策略($\pi_{SFT}$)。这可以防止模型生成利用奖励模型(奖励作弊)但内容不合理或风格不一致的输出。
RLHF 的 PPO 优化过程包含了这个双重目标。对于从分布 $D$ 中采样得到的给定提示 $x$,以及当前策略 $\pi_\theta(y|x)$ 生成的响应 $y$,每个提示-响应对的目标都包含了奖励以及基于当前策略与参考 SFT 策略之间 Kullback-Leibler (KL) 散度的惩罚:
$$ \text{Objective}(x, y) = r_\phi(x, y) - \beta D_{KL}(\pi_\theta(y|x) || \pi_{SFT}(y|x)) $$公式分解:
- $r_\phi(x, y)$:这是训练好的奖励模型分配给提示-响应对 $(x, y)$ 的标量奖励。目标是最大化这个值。
- $D_{KL}(\pi_\theta(y|x) || \pi_{SFT}(y|x))$:这是在当前策略 $\pi_\theta$ 下给定提示 $x$ 时响应 $y$ 的概率分布与参考 SFT 策略 $\pi_{SFT}$ 之间的 KL 散度。它衡量了当前策略对于此次特定生成偏离 SFT 模型的程度。较低的 KL 散度表示策略更相似。
- $\beta$:这是一个超参数,控制 KL 惩罚的强度。
- 高 $\beta$ 值:会严重惩罚偏离 $\pi_{SFT}$ 的行为,使模型在风格上接近 SFT 版本,但可能限制奖励最大化。
- 低 $\beta$ 值:允许策略更积极地优化奖励 $r_\phi$,但可能显著偏离 SFT 模型,从而可能降低文本质量或导致奖励模型被利用。
PPO 的整体目标旨在最大化使用当前策略 $\pi_\theta$ 采样得到的轨迹中这个组合项的期望值。然后,PPO 算法会使用其特定机制(例如裁剪替代目标)来有效优化此目标。
调整 PPO 组件以适应 LLMs
在标准的 PPO 设置中,通常会有一个演员(即策略)和一个评论员(即价值函数)。在 RLHF 与 LLMs 结合的背景下,它们对应如下:
- 策略模型(演员,Actor):这是正在微调的语言模型($\pi_\theta$)。它以提示 $x$ 作为输入(状态),并生成一系列 token $y$(动作)。通常从 SFT 模型的权重开始,并在 PPO 训练期间更新它们。
- 参考模型(Reference Model):这是初始 SFT 模型($\pi_{SFT}$)的一个固定副本。它对于计算 KL 散度惩罚很重要。保持其固定提供了一个稳定的目标分布,用于正则化。
- 奖励模型(Reward Model):这是在前一阶段训练的模型($r_\phi$)。它接受提示 $x$ 和生成的响应 $y$,并输出一个标量奖励。在 PPO 阶段,它通常保持固定。
- 价值模型(评论员,Critic):这个模型($V_\psi$)以提示 $x$ 作为输入,并估算从该提示开始、在当前策略 $\pi_\theta$ 下预期的折扣未来奖励。它通过计算优势值来帮助减少策略梯度估计的方差。通常,价值模型从奖励模型的权重或 SFT 模型的权重初始化,可能带有不同的输出头部。其参数($\psi$)在 PPO 训练期间与策略参数 $\theta$ 一同更新。
PPO 交互流程图
下面的图表显示了 RLHF 中 PPO 步骤期间的高层交互:
(π_θ, 演员)"] Policy --> Response[响应 y] Prompt --> ValueModel["价值模型
(V_ψ, 评论员)"] end subgraph "2. 评估与计算 (Evaluation)" Response --> RewardModel["奖励模型
(r_ϕ, 固定)"] Response --> RefModel["参考模型
(π_SFT, 固定)"] Policy -- "log P(y|x)_θ" --> Calc RefModel -- "log P(y|x)_SFT" --> Calc RewardModel -- "奖励 r" --> Calc ValueModel -- "价值 V" --> Calc Calc["计算优势与回报
(优势, KL散度)"] end subgraph "3. PPO 更新 (Optimization)" Calc --> PPO_Loss["PPO 替代目标
(策略比率, 裁剪)"] PPO_Loss --> Optim["优化器
(Adam)"] Optim -- 更新 θ --> Policy Optim -- 更新 ψ --> ValueModel end style Policy fill:#d4f1f9,stroke:#333 style ValueModel fill:#d4f1f9,stroke:#333 style RefModel fill:#eee,stroke:#333,stroke-dasharray: 5 5 style RewardModel fill:#eee,stroke:#333,stroke-dasharray: 5 5
RLHF 中的 PPO 训练循环
PPO 微调过程涉及迭代执行以下步骤:
推出 (Rollout): 从数据集中采样一批提示($x$)(通常与奖励建模或 SFT 使用的数据集相同)。对于每个提示,使用当前策略 $\pi_\theta$ 生成一个响应($y$)。存储提示、响应以及来自 $\pi_\theta$ 的 token 对数概率。
评估 (Evaluation): 对于每个生成的提示-响应对 $(x, y)$:
- 使用固定的奖励模型计算奖励 $r = r_\phi(x, y)$。
- 计算 KL 散度惩罚项。这通常包括获取在固定参考模型 $\pi_{SFT}$ 下生成响应 $y$ 的对数概率,并将其与在推出过程中从 $\pi_\theta$ 获得的对数概率进行比较。用于 PPO 更新的奖励通常会调整为 $r - \beta \times KL$。
- 使用当前价值模型估算价值 $V_\psi(x)$。
优势估算 (Advantage Estimation): 计算响应中每个生成 token 的优势估算。这通常使用 广义优势估算(GAE),它结合了奖励和价值估算,为策略更新提供噪声较小的信号。
优化 (Optimization): 对收集到的推出数据批次执行多个周期的梯度更新:
- 更新策略模型:使用 PPO 裁剪替代目标函数更新 $\pi_\theta$ 的参数 $\theta$,该函数使用计算出的优势以及当前策略与用于推出的策略之间的 token 概率比率。
- 更新价值模型:通过最小化价值模型 $V_\psi$ 的预测与实际观察到的回报(在优势估算期间计算)之间的差异来更新其参数 $\psi$。
这个循环重复进行,逐步改善策略 $\pi_\theta$,使其生成的响应更好地符合奖励模型所捕获的偏好,同时 KL 惩罚和 PPO 的裁剪机制保持训练稳定并防止灾难性遗忘。
策略网络与价值网络的实现
Actor-Critic 方法是近端策略优化(PPO)算法中的标准做法。这需要维护两个不同但彼此关联的网络:
- 策略网络(Actor):决定采取哪些动作(例如,生成哪些 token)。
- 价值网络(Critic):估计给定状态下的预期回报。
策略网络(Actor)
策略网络就是语言模型本身。其主要作用是给定输入提示后,逐个 token 地生成文本序列。
- 初始化: 策略网络使用在 监督微调(SFT) 阶段之后获得的模型权重进行初始化。这提供了一个稳固的起点,确保模型已经具备良好的生成能力,并遵循在 SFT 期间学习到的所需风格或格式。
- 架构: 架构与所用的底层大型语言模型(例如 Transformer 解码器)相同。与 SFT 模型相比,通常不需要结构上的改变。
- 作用: 在强化学习阶段,对于给定提示(状态 $s$),策略网络 $\pi_\theta(a|s)$ 输出下一个 token(动作 $a$)在词汇表上的概率分布。文本生成涉及从该分布中依序采样。
- 优化目标: 策略网络的参数 $\theta$ 使用 PPO 目标函数进行更新。目标是增加生成序列获得奖励模型高分的概率,同时 KL 散度项(防止策略与初始 SFT 模型的行为偏离过大)作为约束。
价值网络(Critic)
价值网络估计给定状态下的预期累积未来奖励。在 PPO 中,此估计值作为一个基准(Baseline),用于减少策略梯度更新的方差。
职责: 价值网络学习状态价值函数 $V_\phi(s)$。给定状态 $s$(提示加上目前已生成的序列),它预测策略从该点起预期获得的总奖励。 此预测对于计算 优势估计(Advantage Estimation) 非常重要。优势大致计算如下:
$$ A(s,a) \approx R(s,a) + \gamma V_\phi(s') - V_\phi(s) $$其中 $R(s,a)$ 是即时奖励(从奖励模型获得),$s'$ 是下一个状态,$\gamma$ 是折扣因子。
架构: 价值网络通常与策略网络 共享核心架构和参数。它重用 LLM 主体(例如 Transformer 层)学习到的强大表示。在共享 LLM 主体的最终隐藏状态表示之上,会添加一个单独的“价值头”——通常是一个简单的线性层。此头输出一个表示预测状态价值 $V_\phi(s)$ 的单个标量值。
初始化: 虽然共享主体(像策略网络一样)继承自 SFT 模型的权重,但 价值头 通常随机初始化或使用小权重,因为它需要在强化学习训练期间从头开始学习新的函数(价值估计)。
优化目标: 价值网络的参数 $\phi$(主要是价值头,但也可能包括共享主体参数)与策略网络同步训练。目标通常是最小化预测值 $V_\phi(s)$ 与在 PPO 推演阶段计算出的实际观测回报(目标)之间的均方误差(MSE):
$$ L(\phi) = \mathbb{E}_{(s, V_{target})}[(V_\phi(s) - V_{target})^2] $$其中 $V_{target}$ 表示状态 $s$ 的计算回报目标。
参数共享策略
在 Actor-Critic 方法中,策略网络和价值网络之间共享参数是一种常见且有效的方法,对于大型语言模型尤其如此。
- 效率:这大大减少了需要训练和存储的参数数量,使过程在计算上更可行。
- 表示学习:大型语言模型主体学习到的丰富表示对于生成文本(策略)和估计未来奖励(价值)都很有用。共享机制使价值网络能够运用预训练和 SFT 期间学习到的这些特征。
典型的实现是使用 SFT 模型作为基础。在强化学习训练循环的每次前向传播中,输入提示由共享的大型语言模型主体处理。最终的隐藏状态随后被送入两个单独的头部。
- 策略头:通常是原始的语言建模头,输出词汇表上的logits。
- 价值头:一个新的线性层,输出一个单个标量值。
架构示意图
(例如Transformer层)
由SFT模型初始化"] %% 3. 两个头部 PolicyHead["策略头
(语言模型头)"] ValueHead["价值头
(线性层)"] %% 4. 输出层 PolicyOut["策略Logits π(a|s)
(用于动作采样)"] ValueOut["价值估计 V(s)
(用于优势计算)"] %% 连接关系 Input --> SharedBody SharedBody -- "最终隐藏状态" --> PolicyHead SharedBody -- "最终隐藏状态" --> ValueHead PolicyHead --> PolicyOut ValueHead --> ValueOut end %% --- 样式配置 (模仿原图配色) --- %% 输入 (蓝色) style Input fill:#b3e5fc,stroke:#81d4fa,stroke-width:1px %% 共享主体 (紫色) style SharedBody fill:#c5cae9,stroke:#9fa8da,stroke-width:1px %% 策略部分 (左侧 - 绿色/青色) style PolicyHead fill:#b2dfdb,stroke:#80cbc4,stroke-width:1px style PolicyOut fill:#c8e6c9,stroke:#a5d6a7,stroke-width:1px %% 价值部分 (右侧 - 黄色) style ValueHead fill:#ffecb3,stroke:#ffe082,stroke-width:1px style ValueOut fill:#ffe082,stroke:#ffd54f,stroke-width:1px %% 整体背景容器 (浅灰) style Container fill:#f5f5f5,stroke:#e0e0e0,stroke-width:2px,color:#333
库的使用实现
像 Hugging Face 的 TRL(Transformer Reinforcement Learning)这样的库简化了这种设置。它们通常提供包装类,例如 AutoModelForCausalLMWithValueHead。这个类接收一个标准的预训练因果语言模型(如 GPT-2、Llama 等),并自动在现有语言模型头旁边附加一个价值头。
# 使用 Hugging Face TRL 的示例(伪代码)
from transformers import AutoTokenizer
from trl import AutoModelForCausalLMWithValueHead
# 加载 SFT 模型作为基础
model_name = "path/to/your/sft_model"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 关键步骤:加载带价值头的模型
# 现在 'model' 包含策略(LM)头和随机初始化的价值头。
model = AutoModelForCausalLMWithValueHead.from_pretrained(model_name)
# 在训练期间,一次前向传播会产生两个输出:
prompt = "Translate to French: Hello world"
inputs = tokenizer(prompt, return_tensors="pt")
# 前向传播返回 logits、过去的键值以及价值估计
outputs = model(**inputs)
policy_logits = outputs.logits # 用于生成 token
value_estimate = outputs.value # 价值头输出的标量值
# TRL 中的 PPO 训练器负责使用这些输出
# 进行动作生成、优势计算以及更新两个网络。
KL 散度惩罚的作用
在强化学习人类反馈 (RLHF) 中的 近端策略优化 (PPO) 阶段,主要目标是调整语言模型的策略 $\pi_\theta$,使其生成能最大化已学习奖励模型(RM)所给预期奖励的响应。
然而,仅针对 RM 分数进行优化会带来很大风险。策略可能迅速进入策略空间中某些区域,这些区域根据 RM 会产生高奖励,但输出内容却可能是无意义的、重复的,或在风格上与初始监督微调(SFT)阶段建立的期望行为不符。 这种现象可以看作是:
- 策略对奖励模型“过拟合”:利用其不准确性或局限性(奖励欺骗/作弊)。
- 遗忘:简单地忘记了在此阶段之前它所具备的基本语言生成能力。
什么是 KL 散度?
为减轻这种情况,PPO 引入了一个基于 库尔巴克-莱布勒(KL)散度 的惩罚项。KL 散度衡量两个概率分布之间的差异。在 RLHF 的背景下,它量化了 当前策略 $\pi_\theta$ 偏离 参考策略 $\pi_{ref}$ 的程度。
当前策略与 SFT 策略在给定状态(提示)$s$ 和动作(标记)$a$ 上的 KL 散度计算如下:
$$ D_{KL}(\pi_\theta(\cdot|s) || \pi_{SFT}(\cdot|s)) = \sum_a \pi_\theta(a|s) \log \frac{\pi_\theta(a|s)}{\pi_{SFT}(a|s)} $$- 低 KL 散度:表明当前策略的输出分布与 SFT 策略的分布相似。
- 高 KL 散度:表明有明显偏离。
修改后的 PPO 目标函数
不再只把“让奖励变高”当作唯一目标,并非单纯地最大化预期优势(这与奖励相关),其中包含与 KL 散度成比例的惩罚项:
$$ \text{Objective} \approx \mathbb{E}_t [\text{Reward}_t] - \beta D_{KL}(\pi_\theta(\cdot|s_t) || \pi_{SFT}(\cdot|s_t)) $$此处说明:
- $\mathbb{E}_t [\text{Reward}_t]$:表示策略 $\pi_\theta$ 获得的预期奖励,通常在实际的 PPO 损失函数中,使用优势估计 $A_t$ 进行近似。这一项促使策略生成奖励模型偏好的内容。
- $D_{KL}(\dots)$:是在时间步 $t$ 生成的标记上,当前策略与冻结的 SFT 策略之间的 KL 散度。
- $\beta$ (Beta):是一个控制 KL 惩罚强度的超参数。
这个 KL 惩罚项作为 正则化项。它阻止策略 $\pi_\theta$ 在优化过程中过度偏离 SFT 策略 $\pi_{SFT}$。通过惩罚每个标记输出概率分布的大范围变化,它有助于确保模型保留在 SFT 阶段获得的通用语言流畅性、知识和风格特征。
实现细节与数据流
在 PPO 训练循环中,计算过程如下所示:
(冻结, 不更新)"] Policy -- 生成响应 --> Tokens[生成 Token 序列] Tokens --> Logits_P[计算 Logits P_θ] Tokens --> Logits_R[计算 Logits P_SFT] Logits_P & Logits_R --> CalcKL[计算 KL 散度] RewardModel[奖励模型] -- 原始奖励 --> TotalReward CalcKL -- "惩罚项 (- β * KL)" --> TotalReward["计算总奖励
R_total = R_model - β * KL"] TotalReward --> PPO_Update[PPO 优化更新] style RefModel fill:#eee,stroke:#333,stroke-dasharray: 5 5 style CalcKL fill:#ffccbc,stroke:#ff5722 style TotalReward fill:#c8e6c9,stroke:#4caf50
- 采样:采样一批提示。
- 生成:当前策略 $\pi_\theta$ 对这些提示生成响应。
- 计算概率:对于每个生成的标记,当前策略 $\pi_\theta$ 和 冻结的 参考策略 $\pi_{SFT}$ 都计算它们在词汇表上的各自概率分布(logits)。
- 计算散度:计算这两个分布之间每个标记的 KL 散度。
- 调整奖励:按 $\beta$ 缩放的平均 KL 散度将从奖励信号中减去(或直接纳入损失函数)。
KL 系数 ($\beta$) 的调节
KL 系数 $\beta$ 的选择对于平衡 探索(获得更高奖励) 与 稳定性(保持语言能力) 至关重要。
| $\beta$ 值 | 惩罚强度 | 策略行为 ($\pi_\theta$) | 潜在风险 |
|---|---|---|---|
| 低 $\beta$ | 弱 | 自由度大,可大幅偏离 $\pi_{SFT}$ 以最大化奖励。 | 模型崩溃:可能导致输出乱码、重复或奖励作弊。 |
| 高 $\beta$ | 强 | 被严格限制,必须保持接近 $\pi_{SFT}$。 | 优化不足:阻碍策略适应奖励信号,相比 SFT 模型改进微小。 |
SFT 策略)) Target((高奖励区域)) %% 定义路径 Origin -- "红色: 低 Beta (大步长)" --> UpdateLow[激进更新] Origin -- "蓝色: 高 Beta (小步长)" --> UpdateHigh[保守更新] %% 虚线指向目标 UpdateLow -.-> Target UpdateHigh -.-> Target %% 样式设置 style Origin fill:#bbdefb,stroke:#448aff,stroke-width:2px style Target fill:#b2dfdb,stroke:#009688,stroke-width:2px style UpdateLow fill:#ffcdd2,stroke:#f44336 style UpdateHigh fill:#e1bee7,stroke:#9c27b0 %% 线条样式 (修复了此处) linkStyle 0 stroke:#f44336,stroke-width:3px linkStyle 1 stroke:#2979ff,stroke-width:3px
PPO训练中的策略更新。$π_{SFT}$ 是初始策略。低 $β$ 允许向高奖励区域迈出更大的步长,但有不稳定的风险。高 $β$ 限制步长,保持与 $π_{SFT}$ 的接近。
自适应 KL 控制 (Adaptive KL Controller)
寻找 $\beta$ 的固定值很难,因此常用 自适应 KL 控制器。它根据每个批次中观察到的 KL 散度值动态调整 $\beta$:
- 目标:将实际的 $D_{KL}$ 保持在预设的目标范围内(例如
target_kl = 6.0)。 - 机制:
- 如果 $KL_{current} > KL_{target}$:增加 $\beta$(惩罚太轻了,拉住缰绳)。
- 如果 $KL_{current} < KL_{target}$:减小 $\beta$(限制太死了,松松绑)。
策略空间的直观理解
可以将 KL 惩罚想象成一根 橡皮筋:
- PPO 优化力:试图将当前策略拉向“高奖励目标”。
- KL 惩罚力:像一根拴在“SFT 初始点”上的橡皮筋,防止策略跑得太远而迷失方向(遗忘语言能力)。
- $\beta$:就是这根橡皮筋的粗细(弹性系数)。
优势和回报的计算
优化 PPO 目标下的策略需要可靠地评估所选动作(生成特定 token)相对于策略在该状态下(当前生成的序列)采取的平均动作有多好。
优势函数 $A(s_t, a_t)$ 提供了这种评估。仅依赖即时奖励 $r_t$(这种奖励通常结合了奖励模型信号和 KL 惩罚)对于此类优化来说是不够的。这是因为即时奖励忽略了动作的长期影响。相反,必须考虑总累积奖励,或称之为 回报 (Return),并将其与由值函数 $V(s_t)$ 提供的基准进行比较,该函数估计从状态 $s_t$ 获得的预期回报。
回报计算 (Return Calculation)
回报 $G_t$ 是从时间步 $t$ 开始直到情节结束(生成的序列)所获得的总折扣奖励。对于长度为 $T$ 的序列,其定义如下:
$$ G_t = \sum_{k=0}^{T-t-1} \gamma^k r_{t+k+1} $$此处,$r_{t+k+1}$ 是在状态 $s_{t+k}$ 中采取动作后获得的奖励。
在 LLM 的 RLHF 背景下,每一步的奖励 $r_t$ 通常包含两个组成部分:
- KL 惩罚:基于当前策略与参考 (SFT) 策略之间 KL 散度的惩罚。
- 最终奖励:可能来自分配给完整序列 $x$ 的最终奖励模型得分 $R(x)$。
常见做法:
- 对于 $t < T$(生成过程中):$r_t = r_{KL,t} = -\beta D_{KL}(\pi_\theta(\cdot|s_t) || \pi_{ref}(\cdot|s_t))$
- 对于 $t = T$(序列结束时):$r_T = r_{KL,T} + R(x)$
折扣因子 $\gamma \in [0, 1]$: 决定了未来奖励的现值。对于文本生成任务,$\gamma$ 通常设为接近 1(例如 0.99 或 1.0),因为质量评估(通过奖励模型)常依赖于完整序列。
优势函数 (Advantage Function)
优势函数 $A(s_t, a_t)$ 衡量在状态 $s_t$ 中采取动作 $a_t$ 相对于当前策略 $\pi_\theta$ 下状态 $V(s_t)$ 的预期值的相对价值。其正式定义如下:
$$ A(s_t, a_t) = Q(s_t, a_t) - V(s_t) $$其中 $Q(s_t, a_t)$ 是动作-值函数。通常直接使用值网络(评论家)学习 $V(s_t)$,使用一步时序差分 (TD) 误差 $\delta_t$ 来近似:
$$ Q(s_t, a_t) \approx r_{t+1} + \gamma V(s_{t+1}) $$将此代入优势定义,得到 一步时序差分 (TD) 误差 $\delta_t$:
$$ \hat{A}_t \approx \delta_t = r_{t+1} + \gamma V(s_{t+1}) - V(s_t) $$这个估计显示了观察到的结果 ($r_{t+1} + \gamma V(s_{t+1})$) 是否比预期 ($V(s_t)$) 更好或更差。
利用广义优势估计 (GAE) 处理高方差
尽管 TD 误差 $\delta_t$ 是优势的无偏估计(如果 $V$ 准确),但它可能遭受 高方差 问题。广义优势估计 (GAE) 是一种旨在通过融合多个时间步的信息来降低这种方差的技术。
GAE 引入了一个参数 $\lambda \in [0, 1]$ 来控制这种权衡。GAE 优势估计器被计算为 TD 误差的指数加权和:
$$ \hat{A}^{GAE}_t(\gamma, \lambda) = \sum_{k=0}^{T-t-1} (\gamma \lambda)^k \delta_{t+k} $$其中 $\delta_{t+k} = r_{t+k+1} + \gamma V(s_{t+k+1}) - V(s_{t+k})$ 是时间步 $t+k$ 的 TD 误差。
参数 $\lambda$ 的影响:
- $\lambda = 0$:$\hat{A}^{GAE} = \delta_t$。恢复为高方差、低偏差的 一步 TD 误差。
- $\lambda = 1$:近似于 蒙特卡洛回报。具有低偏差、高方差(尤其在长序列中)。
- $0 < \lambda < 1$:GAE 提供了一个中间估计,平衡了偏差-方差权衡。在实践中,$\lambda = 0.95$ 很常见。
GAE 计算流程图
下面的图表展示了多步 TD 误差如何组合成 GAE 优势估计 $\hat{A}_t$:
V(s_t)")):::stateNode St1(("s_t+1
V(s_t+1)")):::stateNode St2(("s_t+2
V(s_t+2)")):::stateNode ST(("s_T
(V=0)")):::stateNode %% 隐藏连接以强制水平布局 St ~~~ St1 ~~~ St2 ~~~ ST end %% --- 2. 中间:TD 误差 (TD Error) --- subgraph TDLayer [TD 误差] direction LR Dt["δ_t"]:::tdNode Dt1["δ_t+1"]:::tdNode Dend["δ_t-1+T"]:::tdNode %% 隐藏连接 Dt ~~~ Dt1 ~~~ Dend end %% --- 3. 底部:奖励 (Reward) --- subgraph RewardLayer [奖励] direction LR Rt1["r_t+1"]:::rewardNode Rt2["r_t+2"]:::rewardNode RT["r_T"]:::rewardNode %% 隐藏连接 Rt1 ~~~ Rt2 ~~~ RT end %% --- 4. 右侧:GAE 计算 --- subgraph GAECalc [GAE 计算] GAE(("Â_t^GAE")):::gaeNode end %% --- 连线逻辑 (计算流) --- %% 计算 δ_t St -. "-V(s_t)" .-> Dt St1 -. "+γV(s_t+1)" .-> Dt Rt1 -. "+r_t+1" .-> Dt %% 计算 δ_t+1 St1 -. "-V(s_t+1)" .-> Dt1 St2 -. "+γV(s_t+2)" .-> Dt1 Rt2 -. "+r_t+2" .-> Dt1 %% 计算 δ_last (省略中间步骤直接连最后) ST -. "-V(s_t+k)" .-> Dend %% 注意:实际上 s_T 的 V=0,这里模拟图中的连线意图 RT -. "+r_t+k+1" .-> Dend %% --- GAE 汇总 (实线) --- Dt -- "(γλ)⁰" --> GAE Dt1 -- "(γλ)¹" --> GAE Dend -- "(γλ)ᵏ" --> GAE %% --- 调整整体层级间距 (Hack) --- StatusLayer ~~~ TDLayer ~~~ RewardLayer
实现细节与算法逻辑
在通常使用 TRL(Transformer 强化学习)等库的 RLHF 实现中,GAE 被高效地计算。
A. 数据收集 (Rollout)
对于每个生成直到序列结束的 Token,存储:
- 状态 $s_t$(目前已生成的序列)。
- 所选动作的对数概率 $\log \pi_\theta(a_t|s_t)$。
- 评论家网络得到的值估计 $V(s_t)$。
- 奖励中 KL 散度惩罚部分 $r_{KL,t}$。
B. 批次处理 (Batch Processing)
一旦生成一批完整的序列:
- 添加最终奖励:计算 $R(x)$ 并加到 $r_T$ 上。
- 计算 TD 误差: 使用 $V(s_t)$ 计算所有步的 $\delta_t = r_{t+1} + \gamma V(s_{t+1}) - V(s_t)$(从 $T-1$ 向后计算)。
- 计算 GAE 优势: 使用反向递归计算 $\hat{A}^{GAE}_t$: $$ \hat{A}^{GAE}_t = \delta_t + (\gamma \lambda) \hat{A}^{GAE}_{t+1} $$ (初始化 $\hat{A}^{GAE}_T = 0$)
C. 目标与更新
- 价值函数目标:计算出的回报 $G_t \approx \hat{A}^{GAE}_t + V(s_t)$ 被用作训练 $V(s_t)$ 的目标(最小化 MSE)。
- 策略更新:$\hat{A}^{GAE}_t$ 直接用于 PPO 策略损失函数中,以更新 $\theta$。
- 归一化:在损失计算前,通常会将一个批次内的优势进行归一化(减均值除标准差),以稳定训练。
LLM 的 PPO 超参数调整
使用 PPO 对大型语言模型进行微调,引入了一系列超参数,这些参数对于平衡优化效果和训练稳定性非常重要。与标准强化学习任务不同,大型语言模型是高度复杂、非平稳的环境,其中策略(模型本身)生成的是复杂、高维度的序列(文本)。超参数的微小改变可能导致模型行为、训练时间和对齐质量的显著差异。
学习率 (Learning Rate)
学习率决定了策略(actor)和价值(critic)网络在梯度下降期间的步长。考虑到大型语言模型的规模以及更新数十亿参数时可能出现的不稳定性,通常偏好使用相对较小的学习率。
- 策略学习率:
控制大型语言模型的生成策略根据奖励信号和 PPO 目标进行调整的速度。
- 过高:导致策略与初始 SFT 模型急剧偏离,从而导致高 KL 散度、无意义的输出和训练崩溃。
- 过低:导致学习缓慢。
- 典型值:$1 \times 10^{-6}$ 到 $5 \times 10^{-5}$。
- 价值学习率:
控制价值网络学习预测预期回报(奖励)的速度。它通常可以略高于策略学习率,因为价值预测是一个监督回归问题。然而,价值函数的不稳定性可能对优势估计和策略更新产生负面影响。
- 典型值:$1 \times 10^{-5}$ 到 $1 \times 10^{-4}$。
实践建议:通常的做法是使用 Adam 或 AdamW 等优化器,配合学习率调度器(例如,线性衰减、余弦衰减)在训练过程中调整学习率。从保守的(低)学习率开始,如果训练表现稳定且进展缓慢,则逐渐提高。
批次大小 (Batch Size)
在 RLHF 的 PPO 中,有两种相关的批次大小:
- 采样批次大小 (Rollout Batch Size):
并行处理的提示数量,用于生成响应并收集经验(提示、响应、奖励、对数概率)。这通常受限于可用的 GPU 内存,因为它需要使用策略模型运行推理。更大的采样批次在每次迭代中提供更多样化的数据,但会增加内存需求。
- 典型值:很大程度上取决于硬件,范围从 64 到 1024 或更多。
- PPO 小批次大小 (Mini-batch Size):
在 PPO 更新轮次期间用于计算梯度的数据块大小。这从较大的采样批次中抽样得到。
- 较小:引入更多噪声,但有时有助于跳出局部最优。
- 较大:提供更稳定的梯度,但每次更新步骤需要更多内存。
- 典型值:范围从 4 到 64,受限于用于梯度计算的 GPU 内存。
每次 PPO 更新使用的总经验量是
rollout_batch_size。这些经验会迭代ppo_epochs次,在每个梯度步骤中处理mini_batch_size个样本。
PPO 轮次 (PPO Epochs)
此超参数定义了 PPO 算法在收集到的采样数据(存储在缓冲区中的经验)上迭代多少次,以更新策略和价值网络。
- 更多轮次:允许模型从每批收集到的经验中学习更多,可能提高样本效率。然而,过多的轮次可能导致对当前批次数据过拟合,并可能导致策略与生成数据的策略偏离过远,违反 PPO 的假设并引发不稳定。
- 更少轮次:更稳定,因为策略更新在数据收集阶段之间较小。然而,这可能需要更多的采样(更多数据收集)才能达到相同的学习水平。
- 常见值:范围从 2 到 10 轮。最佳数量通常取决于学习率和小批次大小等其他参数。
KL 散度系数 ($\beta$)
KL 散度项 $D_{KL}(\pi_\theta || \pi_{SFT})$ 会惩罚策略 $\pi_\theta$ 偏离参考策略 $\pi_{SFT}$(通常是初始 SFT 模型)过远的情况。系数 $\beta$ 控制这种惩罚的强度。
| $\beta$ 设置 | 行为描述 | 风险/优势 |
|---|---|---|
| 低 $\beta$ | 允许积极优化奖励。 | 风险:偏离 SFT 能力,可能导致语言崩溃或不连贯。 |
| 高 $\beta$ | 严格限制,保持接近 SFT。 | 风险:优化不足,无法有效对齐奖励信号。 |
自适应 KL 控制: 选择 $\beta$ 是一个权衡。一些实现使用自适应 KL 控制器,它在训练期间动态调整 $\beta$,以使 KL 散度保持接近预定义的目标值(例如,6 nats)。
- 固定系数典型值:0.01 到 0.2。
- 监控:如果实际 KL 持续超过阈值(例如 10-15 nats),应增大 $\beta$。
KL 散度趋势图解
PPO 裁剪参数 ($\epsilon$)
PPO 使用裁剪的代理目标函数限制策略更新的大小。裁剪参数 $\epsilon$ 定义了概率比 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{old}(a_t|s_t)}$ 在不被裁剪的情况下允许操作的范围 $[1-\epsilon, 1+\epsilon]$。
- 较小 $\epsilon$ (如 0.1):保守更新,稳定但收敛慢。
- 较大 $\epsilon$ (如 0.3):激进更新,快但不稳定。
- 典型值:0.1 到 0.3。常见的起点是 0.2。
广义优势估计 (GAE) Lambda ($\lambda$)
GAE 用于估计优势函数 $A(s_t, a_t)$,平衡偏差和方差。参数 $\lambda$ 控制这种权衡。
- $\lambda = 1$:对应于回报的高方差蒙特卡洛估计。
- $\lambda = 0$:对应于较低方差但可能较高偏差的时序差分(TD)估计。
- 典型值:接近 1 的值,例如 0.95。
价值函数系数 ($c_1$)
总体的 PPO 损失函数结合了裁剪的代理目标、价值函数损失,有时还包括熵奖励。价值函数系数($c_1$,常记作 vf_coef)用于缩放预测值 $V_\phi(s_t)$ 与目标值之间的均方误差损失。
- 典型值:0.5 或 1.0。这有助于价值函数与策略一同得到有效训练,因为准确的价值估计对于良好的优势估计很重要。
调整策略总结
为大型语言模型调整 PPO 通常是一个经验性过程:
- 从默认值开始:从成功的 RLHF 研究中报告的超参数或 TRL 等库提供的默认值(例如,
PPOConfig)开始。 - 优先控制 KL:确保 KL 散度保持在合理范围内(例如,< 15-20 nats)。如果策略偏离过快,首先调整 $\beta$ 或自适应 KL 目标。
- 监控指标:在训练期间跟踪奖励均值/分布、KL 散度、策略损失、价值损失和模型熵。
- 调整学习率和批次大小:如果训练稳定但缓慢,考虑略微增加学习率或调整批次大小(如果内存允许)。如果不稳定,则降低学习率。
- 调整 PPO 轮次和裁剪:如果在使用多个轮次时出现不稳定,尝试减少轮次数量或收紧裁剪范围 ($\epsilon$)。
由于训练大型语言模型的计算成本很高,广泛的网格搜索通常不可行。依赖已确定的范围,密切监控训练动态,并根据观察到的行为和评估结果进行明智的调整。
常用 PPO 实现库 (TRL)
近端策略优化 (PPO) 算法的实现,尤其是用于微调大型语言模型时,需要处理多个复杂部分:策略更新、价值函数估计、优势计算(如 GAE)以及 KL 散度约束。从头开始做这件事需要大量的工程投入,并须审慎处理数值稳定性和计算效率。
在 RLHF 最受广泛采用的库中,尤其是在 Hugging Face 生态系统中,就是 TRL (Transformers Reinforcement Learning)。它建立在 transformers、accelerate 和 datasets 等常用库之上,为 RLHF 工作流程提供了统一的运行环境。
TRL:简化 Transformer 的 PPO 应用。TRL 提供高层抽象,封装了 PPO 的核心逻辑。它的主要目标是使使用 PPO 训练语言模型变得更容易实现,同时不牺牲必要的灵活性。
TRL 中用于 PPO 的组成部分
PPOConfig
这个数据类包含了 PPO 算法和训练过程的所有配置参数。正确配置 PPOConfig 对于实现稳定且高效的训练是必要的。
| 参数 | 描述 |
|---|---|
learning_rate | Actor 和 Critic 的学习率。 |
batch_size | 每个优化步骤处理的总提示数量。 |
mini_batch_size | PPO 更新时实际用于计算梯度的批次大小。 |
ppo_epochs | 每个数据批次进行 PPO 优化的轮次。 |
target_kl | 目标 KL 散度值(用于自适应控制)。 |
init_kl_coef | 初始 KL 惩罚系数 ($\beta$)。 |
adap_kl_ctrl | 是否启用自适应 KL 控制器。 |
PPOConfig 初始化示例:
from trl import PPOConfig
config = PPOConfig(
model_name="gpt2", # 基础模型 ID
learning_rate=1.41e-5,
log_with="wandb", # 与 Weights & Biases 集成
batch_size=256, # 每个优化步骤处理的提示数量
mini_batch_size=32, # PPO 更新的 mini-batch 大小
gradient_accumulation_steps=1,
optimize_cuda_cache=True,
early_stopping=False,
target_kl=0.1, # 目标 KL 散度值
ppo_epochs=4, # 每个批次 PPO 优化轮次
seed=0,
init_kl_coef=0.2, # 初始 KL 系数
adap_kl_ctrl=True # 使用自适应 KL 控制
)
PPOTrainer
这是协调 PPO 训练循环的核心类。它处理多项重要操作:
- 经验生成:调用 Actor 生成回复。
- KL 计算:计算与 Reference 模型的偏差。
- 优势计算:根据奖励和 Value 模型计算 GAE。
- PPO 更新:执行反向传播更新 Actor 和 Critic。
模型处理
TRL 设计用于与 Hugging Face transformers 模型配合:
- 策略模型 (Actor):正在微调的模型。
- 参考模型 (Ref):初始策略模型的 冻结副本,用于计算 KL 惩罚。
- 价值模型 (Critic):估计预期回报。TRL 通常使用
AutoModelForCausalLMWithValueHead,这是一个带有独立 Value Head(标量输出)的模型。 - 奖励模型 (RM):外部提供的模型,用于给生成的文本打分。
示例代码结构
# 使用 TRL 的简化 PPO 循环结构
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead
from transformers import AutoTokenizer
import torch
# 1. 初始化(模型、分词器、配置、训练器)
config = PPOConfig(...)
model = AutoModelForCausalLMWithValueHead.from_pretrained(config.model_name)
ref_model = AutoModelForCausalLMWithValueHead.from_pretrained(config.model_name)
tokenizer = AutoTokenizer.from_pretrained(config.model_name)
# 假设 reward_model 在其他地方加载
# ...
ppo_trainer = PPOTrainer(config, model, ref_model, tokenizer)
# 提示示例数据集
dataset = [{"query": "What is RLHF?"}, {"query": "Explain PPO."}]
def tokenize(element):
return tokenizer(element["query"], return_tensors="pt")["input_ids"]
# 2. 数据准备(简化演示)
prompt_tensors = [tokenize(d) for d in dataset]
# 生成和训练循环
for epoch in range(config.ppo_epochs):
# 实际应用中会使用 DataLoader 迭代
for batch in prompt_loader:
prompt_tensors = batch["input_ids"]
# 3. 生成
# 注意:generate() 返回的回复张量通常包含提示
response_tensors = ppo_trainer.generate(prompt_tensors, return_prompt=False)
# 为奖励模型构建完整文本
texts = [tokenizer.decode(r.squeeze()) for r in response_tensors]
prompts = [tokenizer.decode(p.squeeze()) for p in prompt_tensors]
# 4. 评分(使用您的外部奖励模型)
# rewards: List[torch.tensor] - 每个序列一个标量奖励
rewards = get_rewards_from_rm(reward_model, prompts, texts)
# 5. 优化步骤 (核心)
stats = ppo_trainer.step(prompt_tensors, response_tensors, rewards)
# 6. 日志记录
ppo_trainer.log_stats(stats, batch, rewards)
优势和考量
优势:
- 减少样板代码:抽象了复杂的 PPO 更新规则、优势估计和 KL 计算逻辑。
- 集成性:无缝对接 Hugging Face 生态,支持
accelerate分布式训练。 - 维护:活跃的开源社区支持。
考量:
- 配置:虽然已简化,但必须理解
PPOConfig参数背后的 RL 原理。 - 调试:当训练不稳定(如 KL 爆炸)时,仍需具备解读 RL 指标(Value Loss, Policy Loss)的能力。
- 定制化:对于高度定制的奖励函数或 PPO 变体,可能需要修改 TRL 源码。
PPO 训练不稳定性故障排除
使用 PPO 在 RLHF 设置中训练大型语言模型可能是一个充满挑战的过程。PPO 虽然强大,但其训练过程对超参数和实现细节很敏感。及早发现症状并知道如何诊断和处理它们,对模型成功对齐很重要。这里提供关于常见不稳定性问题及其解决方案的实用指南。
1. 不稳定性的常见症状
PPO 训练期间的不稳定性通常会在您的训练指标中以可观察的模式出现。密切关注以下几点:
- 奖励激增 (Reward Hacking):每回合平均奖励迅速上升,通常远远超出任务合理范围。这常常表明策略正在钻奖励模型中的空子,而非真正提升对齐效果。
- 策略崩溃 (Policy Collapse):训练后的策略与参考策略(通常是 SFT 模型)之间的 KL 散度急剧增加并保持高位。这表示策略偏离了其初始行为过多,可能会生成不连贯的文本。
- 梯度消失 / 训练停滞:奖励趋于平稳,KL 散度保持非常低,策略停止进步。
- 值函数损失过高 (High Value Loss):与值网络相关的损失保持高位或剧烈波动。因为值函数估算预期未来奖励并用于计算优势,不准确的值函数会破坏整个策略更新的稳定性。
- 性能波动:奖励或 KL 散度等指标来回摆动而不收敛。
2. 诊断工具和方法
A. 监测指标
在整个训练过程中密切追踪这些值,并可视化查找突然的峰值或持续的波动:
- 每回合/批次的平均奖励
- KL 散度 ($D_{KL}$)
- 策略损失(PPO 目标)
- 值损失
- 策略熵(表示多样性)
- 梯度范数
图表说明:
- Line 1 (下层线):对应原图的 平均奖励 (Average Reward)。它在后期迅速上升,看起来模型变“聪明”了。
- Line 2 (上层线):对应原图的 KL 散度 (KL Divergence)。它与奖励同步爆炸式增长,表明模型生成的文本分布已经严重偏离了参考模型(SFT),这通常意味着模型开始输出乱码或通过作弊手段获取高分。
B. 检查生成文本
定期从当前策略模型中抽取响应。
- 它们连贯吗?
- 高奖励响应是否真的与期望行为一致,还是它们正在钻奖励模型中的空子(例如,重复措辞、不自然的风格)?
- 将它们与初始 SFT 模型的响应进行比较。
C. 分析奖励分布
查看奖励模型分配的奖励分布。它是否过度偏斜?是否存在异常值?这可能表明奖励模型校准或缩放存在问题。
D. 检查梯度
监测反向传播期间梯度的幅度。梯度激增(非常大的值)或梯度消失(接近零的值)指示数值不稳定。
常见原因和解决方案
以下是不稳定性的典型原因和相应的解决方案:
1. KL散度问题
- 原因: KL惩罚系数(ββ)过低,使得策略偏离过快。另外,策略更新过于激进(学习率过高、批次大小过大、PPO迭代次数过多)。
- 症状: KL散度过高或激增,可能生成无意义的文本。
- 解决方案:
- 增加β*β*: 加强偏离参考策略的惩罚。许多实现使用自适应KL控制器,根据观察到的KL散度调整ββ,旨在将其保持在目标范围(例如,3-10)内。调整目标KL值。
- 降低学习率: 较小的更新使策略更平缓地变化。
- 减少PPO迭代次数: 对每批经验执行更少的优化步骤,从而减小每次迭代中策略变化的幅度。
- 使用梯度裁剪: 限制梯度的最大范数,以防止更新过大。
- 正确初始化策略: 确保PPO的初始策略确实是SFT模型,而不是基础预训练模型。
2. 奖励信号问题
- 原因: 奖励模型校准不佳、噪声大,或对不良行为(奖励作弊)给予高奖励。
- 症状: 奖励激增,但与实际质量改进不相关,策略生成重复或奇怪的文本以最大化分数。
- 解决方案:
- 奖励归一化/缩放: 对每批奖励进行标准化(减去均值,除以标准差),以稳定其尺度。这通常很重要。
- 奖励裁剪: 将奖励限制在特定范围(例如,[-10, 10])内,以防止极端值主导更新。
- 重新校准/训练奖励模型: 如果奖励模型存在根本性缺陷,请重温第三章。改进数据质量,调整训练目标,或应用校准技术。
- 修改奖励函数: 有时,向奖励函数(不仅仅是RM分数)添加项会有帮助,例如对重复或长度的惩罚,尽管这会增加复杂性。
3. 值函数不稳定性
- 原因: 值网络未能准确预测预期回报,导致优势估计不佳。这可能是由于值函数学习率过高、训练不足或网络架构不适合所致。
- 症状: 值损失过高或波动,策略性能波动。
- 解决方案:
- 调整值函数学习率: 通常需要一个独立于策略网络、可能更高的学习率。
- 增加值函数训练迭代次数: 在每个数据批次上训练值网络更多步。
- 对值损失使用梯度裁剪: 专门防止值网络中的梯度激增。
- 检查值网络架构: 确保它相对于策略网络具有适当的大小。有时从SFT模型(减去最后一层)初始化它会有帮助。
- 使用广义优势估计(GAE): GAE通常比简单方法提供更稳定的优势估计。调整λλ参数(通常为0.9-1.0)。
4. 实现和配置错误
- 原因: PPO逻辑中的错误(例如,优势计算、KL估计、梯度更新)或不正确的配置(例如,批次大小、生成设置)。
- 症状: 不可预测的行为,损失中出现NaN值,崩溃,性能与类似设置的预期不符。
- 解决方案:
- 使用成熟的库: 尽可能使用经过良好测试的库,例如Hugging Face的TRL,因为它们处理了许多细节。
- 代码审查和调试: 仔细检查您的实现,特别是PPO核心更新循环、GAE计算和KL散度估计。
- 单元测试: 为PPO流水线中的各个组件实现测试。
- 检查批处理和数据处理: 确保数据为策略和值更新正确地进行了批处理和处理。注意填充和遮罩。
- 验证生成参数: 确保PPO训练期间用于响应生成的
temperature、top_k、top_p参数合理,并允许充分的多样性,同时不产生过于随机的文本。
故障排除流程图
实现 PPO 更新步骤
PPO 更新步骤是主要的学习阶段。此步骤根据收集到的经验数据调整策略网络和价值网络。这些经验数据包含通过当前策略根据提示生成的响应、从奖励模型接收的奖励(经 KL 惩罚调整),以及评估的状态价值。此调整旨在遵循 PPO 限制的同时,使预期奖励最大化。
1. PPO 目标函数的组成部分
PPO 的目标是优化一个裁剪代理目标函数。整体损失函数通常结合了策略(actor)和价值函数(critic)的项,并且通常包含一个熵奖励项以鼓励策略多样性。
A. 策略损失 ($L_{CLIP}$)
这是 PPO 的核心。它使用一个比率 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$,衡量自数据收集以来策略改变了多少。为了防止过大、导致不稳定的更新,这个比率被裁剪。单个时间步的目标项是:
$$ L_t^{CLIP}(\theta) = \min \left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) $$这里:
- $\epsilon$ 是裁剪超参数(例如 0.2)。
- $A_t$ 是时间步 $t$ 的优势估计值。
- $\pi_{\theta_{old}}$ 代表生成数据的策略。
由于优化器通常 最小化 损失,通常取该项在批次上的期望(平均值)的负值。
B. 价值损失 ($L_{VF}$)
价值网络 $V_\phi(s_t)$ 被训练来预测从状态 $s_t$ 开始的预期回报(未来奖励的总和)。它通常使用均方误差(MSE)损失函数,以计算出的目标回报 $R_t$ 为目标进行训练:
$$ L_t^{VF}(\phi) = (V_\phi(s_t) - R_t)^2 $$C. 熵奖励 ($S$)
为了鼓励策略多样性,并防止其过快地收敛到确定性输出,目标函数中通常会添加一个熵奖励项。对于分类策略,熵 $H$ 的计算公式是:
$$ H(\pi_\theta(\cdot|s_t)) = - \sum_{a'} \pi_\theta(a'|s_t) \log \pi_\theta(a'|s_t) $$D. 总损失函数 ($L_{PPO}$)
优化器最小化的最终损失结合了这些组成部分:
$$ L_{PPO}(\theta, \phi) = \mathbb{E}_t [ -L_t^{CLIP}(\theta) + c_1 L_t^{VF}(\phi) - c_2 H(\pi_\theta(\cdot|s_t)) ] $$其中 $c_1$ 和 $c_2$ 是超参数,用于平衡价值损失和熵奖励的贡献。 注意:KL 散度惩罚通常已包含在奖励信号中用于计算优势值,因此不在此公式中显式出现。
2. 实现更新步骤
实际操作中,更新步骤涉及到使用小批量数据对收集到的经验批次进行多次迭代(epoch)。以下是单个 PPO 更新周期中,一个迷你批次的处理流程细分:
步骤详解与伪代码
加载小批量数据: 获取收集到的经验数据子集(提示、生成的序列、在 $\pi_{\theta_{old}}$ 下的对数概率、奖励、回报 $R_t$、优势值 $A_t$ 等)。
前向传播: 将小批量中的序列通过 当前 策略($\pi_\theta$)和价值($V_\phi$)网络进行前向计算。
- 获得生成序列中动作(词元)的当前对数概率 $\log \pi_\theta(a_t|s_t)$。
- 获得当前价值估计 $V_\phi(s_t)$。
计算比率: 使用新计算的对数概率和存储在小批量中的旧对数概率来计算概率比率 $r_t(\theta)$:
$$ r_t(\theta) = \exp(\log \pi_\theta(a_t|s_t) - \log \pi_{\theta_{old}}(a_t|s_t)) $$计算策略损失: 使用计算出的比率、优势值和裁剪参数计算裁剪代理目标。
import torch # 假设优势值和旧对数概率是小批量的一部分 # 当前对数概率由当前策略模型计算 ratio = torch.exp(current_log_probs - batch['old_log_probs']) surr1 = ratio * batch['advantages'] surr2 = torch.clamp(ratio, 1.0 - clip_epsilon, 1.0 + clip_epsilon) * batch['advantages'] # 负号,因为优化器最小化损失 policy_loss = -torch.min(surr1, surr2).mean()计算价值损失: 计算当前价值估计与目标回报之间的均方误差。
# 当前价值由当前价值模型计算 value_loss = ((current_values - batch['returns'])**2).mean()计算熵奖励(可选): 计算策略输出分布的熵。
# 策略模型前向传播的 logits probs = torch.softmax(logits, dim=-1) log_probs = torch.log_softmax(logits, dim=-1) entropy = -(probs * log_probs).sum(dim=-1).mean() # 将 -熵 存储为要从损失中减去的奖励项 entropy_bonus = -entropy组合损失: 使用系数 $c_1$ 和 $c_2$ 计算总损失。
vf_coef = 0.5 # 示例系数 c1 entropy_coef = 0.01 # 示例系数 c2 # 注意:entropy_bonus 已经是负熵,所以减去它会加上熵项 total_loss = policy_loss + vf_coef * value_loss - entropy_coef * entropy_bonus反向传播和优化: 对
total_loss执行反向传播。optimizer.zero_grad() total_loss.backward() # 可选:应用梯度裁剪 # torch.nn.utils.clip_grad_norm_(policy_model.parameters(), max_grad_norm) optimizer.step()
3. 实际考量
- 共享网络与独立网络: 策略($\pi_\theta$)和价值($V_\phi$)函数可以共享一些层(例如,基础 LLM transformer)。共享参数可以提高效率,但可能会导致策略和价值目标之间的干扰。
- 梯度累积: 对于大型模型和批次大小,在执行优化器步骤之前,跨多个小批量累积梯度是常见的做法。
- 混合精度训练: 使用
float16或bfloat16等技术可以显著加快训练速度并减少内存使用。 - 调试: 在训练过程中,监控不同的损失组成部分、KL 散度、更新的幅度以及奖励分数,对于调试和确保稳定性是必不可少的。