RLHF and PPO

给定同一个输入,模型生成的多个候选回答中,人类会对这些回答进行排序。RM 需要学会预测这个排序。


1. 数据格式

假设对于同一个 prompt,我们有两段候选回答:

  • chosen (c) → 人类更喜欢的回答
  • rejected (r) → 人类不喜欢的回答

Reward Model 会给它们各自输出一个分数:

[
R_\theta(x, y_c), \quad R_\theta(x, y_r)
]

其中:

  • (x) 是输入(prompt),
  • (y_c, y_r) 是回答,
  • (R_\theta) 是 RM 的打分函数。

2. 目标

我们希望 RM 的分数满足:

[
R_\theta(x, y_c) > R_\theta(x, y_r)
]


3. 损失函数(Reward Model loss)

常用的是 pairwise ranking loss(成对排序损失),公式是:

[
L(\theta) = – \mathbb{E}{(x, y_c, y_r)} \Big[ \log \sigma\big( R\theta(x, y_c) – R_\theta(x, y_r) \big) \Big]
]

其中:

  • (\sigma(z) = \frac{1}{1 + e^{-z}}) 是 sigmoid 函数,
  • 直观理解:RM 学会把 “chosen” 的分数推高,“rejected” 的分数压低。

4. 简单解释

  • 如果 (R_\theta(y_c) \gg R_\theta(y_r)),说明 RM 判断正确,loss 会很小。
  • 如果 (R_\theta(y_c) \approx R_\theta(y_r)),loss 会比较大,因为模型没有区分开两者。
  • 如果 (R_\theta(y_c) < R_\theta(y_r)),loss 最大,说明模型完全判断错了。

轨迹概率公式

对于一个轨迹在某个策略下的可能性,分析它的影响因素:

  • 初始环境:ρ0(s0)
  • 策略选择概率:Π(at|st),即在某状态下,该策略选择行动at的概率
  • 环境影响因素:P(st+1|st,at),有的时候事情并不都如我们所料,因为现实环境具有复杂的特征。在一个下棋的游戏中,我们可以认为这一项恒为1,因为这是一个很确定的场景。但假如,我们是在讨论一个现实场景中的机器人,它不一定会根据选择的策略做出响应的行动,可能关节磨损,可能断电了等等。这一项的核心,在于环境因素会影响agent的状态转移,然后我们把这个转移的概率引入轨迹的计算公式中。

对应的discount reward(折扣回报)

  • 为了防止奖励的发散,同时鼓励更短的path,为奖励的累加添加了根据步数的折扣因子

语言模型中的trajectories:从这张图中能很显然的看出来state和action。state指每次都会把当前得到的prompt输进去,action指根据这个prompt选择一个token,然后重复这个过程

梯度优化策略的公式推导过程:

  • 1. 期望展开
  • 2. 梯度符号移动
  • 3. 一个对数的小策略:把概率提取出来
  • 4. 把提取出来的概率转化回期望的形式
  • 5. 代入轨迹概率公式,发现除了策略选择概率之外,初始环境和中间的环境影响和 θ 无关被消掉
  • 补充:这里的 θ 就是参数,通常是神经网络权重和偏置。根据常识,我们的神经网络回根据这些参数,将input转化为一个确定的output,也因此,这个参数实际上决定了策略选择概率

当然,我们在计算这样的期望的时候,不可能真的计算所有轨迹的情况。因此在这里,采用的是样本估计的方法。这时候我们很容易就能把它和所谓的“训练数据”联系起来,也能理解为什么数据有质量的差别,因为本质上,他们都是用来逼近理想期望的。

因此,我们能得到如上图的强化学习基本流程

  • 1. 定义网络和策略
  • 2. 用策略在网络上跑,得到一些轨迹对应的reward
  • 3. 用这些样本去计算梯度
  • 4. 用随机梯度上升法,更新网络里面的参数
  • 5. 回到第二步

所谓的强化,从这个流程上来讲,就是一个这样迭代的过程,目的是使J(Πθ )最大化,也即优化策略使agent在进行action的时候,尽可能获得更多的reward


对于语言模型的强化学习流程:

  • logits 是一个长度为词表大小的向量,每个分量是“候选下一个 token 的未归一化分数”。
  • 把 logits 经过 softmax → 概率分布,再通过 贪心/采样/温度/Top-k/Top-p 等策略,就能最终决定下一个 token。
  • 在上图中的一个实例中,我们可以看出,在经过transformer处理之后,每一个token处都对应生成了一个logits,对应了当输入了这个token及之前的所有token时,model对下一个token的判断
  • 根据轨迹的知识,很容易这样得到每个位置对应的概率选择分数。比如在“?”这个state下,进行action“Shanghai”的概率,其实就是这个位置的logits归一化之后,向量对应“Shanghai”位置的数值。
  • 这个值就可以这样被记录下来,作为后续计算的依据
  • 一个需要注意的事:本质上这个input是由question和answer拼接起来的,而我们其实只需要anwer的那部分。因此question可以被视作是开始的环境,而选择“Shanghai”,才是第一个action。实际上我们需要计算的也只有四步。
  • 对于Reward计算,类似的可以这样进行,对每一个需要计算reward的地方,过一个线性层输出分数。(回忆之前的discount reward,这里实际上也要这么计算,因为本质上生成一个token类似于cat走一步,所以在计算reward时,实际上是一个累加)
  • 纠正一个思维误区:我之前好像认为对于项,R(r)这个值是在这一步之前所有的奖励分量累加,但这实际上是一个错误,因为在公式推导的过程中,并没有任何一步表示了这个意思,从头到尾这一项都代表了对于整条轨迹来说的奖励项之和,也就是对于一条轨迹来说,这一项是定值,只不过在计算的时候需要考虑在每一步中的奖励分量。
  • 补充一个训练上的疑惑:之前一直在想,如果这样输入的话,那”比较“这个概念就不知道怎样体现了。实际上,真正训练时候,是以batch为单位,每一个batch对应一个Q和若干A,A带有人为的标注。这样的一个batch才是一个有效的训练数据,可以进行训练。

更好的奖励函数:Reward-to-go

  • 之前提到,在计算每一项的时候,奖励项是一个定值。但从常识的角度上来讲,未来的动作不会影响过去的奖励,也因此,对于比较后面的动作来说,由于它实际上根本不会改变前面的奖励项,这些项对这里的梯度是没有贡献的。相当于噪声。
  • 因此将原本的全轨迹回报改为reward-to-go,只计算这之后的奖励项的影响
  • 全轨迹回报:R(τ)=1+2+3=6
  • reward-to-go
  • G0=1+2+3=6
  • G1=2+3=5
  • G2=3
  • Reward-to-go 的核心原理是 因果性:当前动作只影响未来奖励
  • 公式成立的原因:对于 t 时刻之前的奖励,它对 ∇θlog⁡πθ(at∣st)\nabla_\theta \log \pi_\theta(a_t|s_t)∇θ​logπθ​(at​∣st​) 的期望为 0
  • 好处:降低梯度方差,使策略梯度更稳定

另一种方式:baseline

  • 在奖励项中引入一个baseline,可以是常数,也可以是和s和Π有关的一个函数,称为Value函数
  • VΠ(s),在这里的意义和“未来的期望奖励”有关,表示在当前策略和状态下,对未来能得到奖励的预期
  • 和Reward类似的,这个函数也可以用那种模式来预测

引入这两项后,得到了最后的形态:Aπ(s,a) —Advantage function

优势函数 Aπ(s,a)A^\pi(s,a)Aπ(s,a) 描述的是 在状态 s 下,采取动作 a 相对于平均水平的好坏: Aπ(s,a)=Qπ(s,a)−Vπ(s)

  • Qπ(s,a):策略 π 下,从状态 s 采取动作 a 的期望累积回报
  • Vπ(s):策略 π 下,从状态 s 开始的平均期望回报(即采取动作的平均水平)

直观解释

Advantage function 衡量某个 action 在当前状态下,比“平均动作”多赚了多少奖励。

  • A(s,a)>0:这个动作比平均水平好
  • A(s,a)<0:这个动作比平均水平差
  • A(s,a)=0:动作与平均水平一样好

而本质上我们要做的梯度上升的过程,就是让更好的动作,更可能发生。

PS:这个减去平均水平的比方真的很形象,一下感觉回到了高中数学,做统计表的时候


解决计算问题:怎样计算Aπ(s,a)

  • 上面这些计算体现出一些问题
  • 第一种方式:使用了一个平均值代替了后面所有的具体行动值,high bias
  • 随着展开的越来越多,bias降低了,但是方差variance提高了
  • 这是一个在两者之间平衡的问题
  • 因此最后,通过下面这个递归的方式计算Aπ(s,a)

1 Off-Policy Learning(离策略学习)

  • 定义:使用 行为策略 (\mu(a|s)) 生成数据,但训练/优化的是 目标策略 (\pi_\theta(a|s))
  • 优点
    • 可以复用历史数据、旧模型数据、人类演示
    • 提高训练效率,不必每次都用最新策略生成
  • 问题:直接使用 (\mu) 的数据更新 (\pi_\theta) 会有偏差 → 需要修正

2 Importance Sampling(重要性采样)

  • 目的:在分布不一致时做期望估计
  • 核心公式
    [
    \mathbb{E}{a\sim \pi}[f(a)] = \mathbb{E}{a\sim \mu} \Big[ \frac{\pi(a|s)}{\mu(a|s)} f(a) \Big]
    ]
  • 含义:用权重 (\frac{\pi}{\mu}) 调整旧数据,使梯度更新无偏
  • 注意事项
    • 权重过大 → 梯度方差大
    • 实践中常用 截断 / Clipped IS 降低方差

3 PPO Loss(Proximal Policy Optimization)

3.1 Clipped Surrogate Loss

[
L^{CLIP}(\theta) = \mathbb{E}_t \Big[ \min \big( r_t(\theta) A_t, \ \text{clip}(r_t(\theta), 1-\epsilon,1+\epsilon) A_t \big) \Big]
]

  • (r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_\text{old}}(a_t|s_t)}) → 重要性采样比率
  • (A_t = G_t – V(s_t)) → Advantage function
  • clip/min → 限制策略更新幅度,保证稳定性

3.2 Value Loss(状态价值损失)

[
L^{VF}(\phi) = (V_\phi(s_t) – G_t)^2
]

  • 用于辅助 Advantage 估计,降低方差

3.3 Entropy Bonus

[
L^S = -\beta H[\pi_\theta(\cdot|s_t)]
]

  • 保持策略探索,防止过早收敛

3.4 总 Loss

[
L^{PPO} = L^{CLIP} – c_1 L^{VF} + c_2 L^S
]

  • 核心思想
    • 利用旧策略数据更新新策略(off-policy + IS)
    • 用 Advantage 指导动作选择
    • Clip 保证梯度稳定,Entropy 保持探索

Reward Hacking

刚刚提到的所有策略都是为了让model生成更令人满意的结果,但是这些结果不一定是make sense的。比如如果我想让一个model更加的polite,然后把它训成了只会thank you的model,这显然是不好的

因此,我们希望最终生成的模型,不会与原本的模型发生较大的偏移,以此来保证make sense

我们的做法是,保存一个frozen model,当使用Reward model去评判一些token的时候,在奖励函数上减去一个term,代表frozen model和current model对于同样的一个hidden states生成的probability差异。

通过这样的方式,来惩罚对于原始model过大的偏移。


Code

阶段1:数据准备与前向传播

1. _step_safety_checker – 输入安全检查 – line 647

输入:

  • queries: List[torch.LongTensor] – 提示文本的token IDs
  • responses: List[torch.LongTensor] – 旧模型生成响应的token IDs
  • scores: List[torch.FloatTensor] – 每个response对应的奖励分数
  • response_masks: Optional – 响应token的掩码

输出: 经过验证和格式化的相同结构数据
作用: 确保输入数据格式正确,避免运行时错误

2. prepare_model_inputs – 构建模型输入 – line 691

输入: queries, responses
输出: Dict包含:

  • input_ids: (batch_size, seq_len) – 拼接后的query+response
  • attention_mask: (batch_size, seq_len) – padding掩码
    作用: 将query和response拼接成统一的模型输入格式

!!! 3. batched_forward_pass – 策略模型前向传播 – line 730 核心函数:用于计算log概率和价格函数估计,参考基础知识,这些是计算梯度的重要参数部分

输入: self.model, queries, responses, model_inputs
输出:

  • all_logprobs: (Batch_Size, Seq_Len-1) – 每个token的log概率
  • logits_or_none: logits或None
  • values: (Batch_Size, Seq_Len-1) – 价值函数估计
  • masks: (Batch_Size, Seq_Len-1) – 有效token掩码
    作用: 计算当前策略下各token的概率分布和价值估计

4. batched_forward_pass – 参考模型前向传播 – line 741

输入: self.ref_model, queries, responses, model_inputs
输出: 同上,但基于参考模型
作用: 计算参考策略的log概率,用于KL散度计算

!!! 5. compute_rewards – 奖励计算 – line 758 定义在 line 1139

输入: scores, all_logprobs, ref_logprobs, masks

  • scores:是从数据集中得到的原始数据的评分,成为reward需要加上KL散度


输出:

  • rewards: (Batch_Size, Seq_Len-1) – 每个token的奖励
  • non_score_reward: 非分数奖励部分
  • kls: KL散度值
    作用: 结合奖励分数和KL惩罚计算每个token的最终奖励

!!! 6. compute_advantages – 优势函数计算 line 772 定义在line 1196

这一步的根据是之前提到的递推公式

输入: values, rewards, masks
输出:

  • values: 价值估计
  • advantages: (Batch_Size, Seq_Len-1) – 优势函数
  • returns: (Batch_Size, Seq_Len-1) – 实际上是A + V = Q,是用来做估计用的
    作用: 使用GAE(广义优势估计)计算优势函数

阶段2:PPO优化

7. Mini-batch训练循环

在整个batch上执行多轮(ppo_epochs)优化:

外层循环: 随机打乱样本顺序
中层循环: 按backward_batch_size分块
内层循环: 按mini_batch_size进一步分块

8. batched_forward_pass – 在线模型前向传播 line 828

输入: 当前模型参数 + mini-batch数据
输出: 当前策略的logprobs, logits, vpreds(这个就是value)
作用: 获取当前策略的输出用于损失计算

9. train_minibatch – 核心优化步骤 line 837

输入:

  • 旧策略的logprobs, values
  • 新策略的logprobs, logits, vpreds
  • advantages, returns, masks

输出: 训练统计信息dict
作用: 计算PPO损失并执行梯度更新

!!!核心函数PPO Loss计算 定义在 line 1224

  • 参考PPO Loss的定义,进行计算

阶段3:统计记录与更新

10. record_step_stats – 记录训练统计

输入: 所有前向传播和训练过程的输出
输出: 汇总的训练统计信息
作用: 记录KL散度、奖励、比率等关键指标

11. kl_ctl.update – KL控制器更新

输入: 当前KL散度值, batch_size
输出: 更新KL惩罚系数
作用: 自适应调整KL惩罚强度

  1. 准备阶段:验证输入 → 构建模型输入
  2. 评估阶段:策略模型前向 → 参考模型前向 → 奖励计算 → 优势估计
  3. 优化阶段:多轮mini-batch训练 → 损失计算 → 参数更新
  4. 记录阶段:统计收集 → KL控制更新 → 学习率调整
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇