📑 本页目录(点开跳转)
11 · PPO
⏱ 30 分钟 | ⭐⭐ 现在实际在用的那一个
🎯 一句话
策略梯度有一个要命的毛病:步子迈大一点,策略就崩了,而且再也回不来。 PPO 用一个极其简单的裁剪操作解决了它 —— 简单到几行代码, 但它是 ChatGPT、机器人控制、游戏 AI 背后共同的那个算法。
💥 一、先看清那个毛病
监督学习:参数走错了 → 下一批数据还是原来那批 → 能纠正回来 ✅
强化学习:参数走错了 → 策略变差
→ 【采到的数据也跟着变差】⭐
→ 用更差的数据继续训练
→ 更差…… 💀 【正反馈崩溃】
🔑 这就是 RL 和监督学习最本质的区别之一: 你的模型决定了你的训练数据。 一次糟糕的更新不只是"这一步没学好",而是污染了后面所有的数据。
⭐ 所以核心诉求变成了:
「每次更新,别让新策略离旧策略太远」
📏 二、怎么衡量"离得远"
TRPO 的答案(PPO 的前身):用 KL 散度做硬约束
$$\max_\theta \mathbb{E}\Big[\frac{\pi_\theta(a|s)}{\pi_{old}(a|s)}A\Big] \quad \text{s.t.}\quad \text{KL}(\pi_{old}\,\|\,\pi_\theta) \le \delta$$
🔗 KL 散度到底是什么、为什么它能当「两个策略离多远」的尺子: 数学原理 01b · KL 散度。 ⭐ 去那里主要是为了一件在这里会反复咬你的事:KL 不对称,$\text{KL}(p\|q)\ne\text{KL}(q\|p)$, 而且两个方向的行为完全相反(一个逼你「铺开、别漏掉任何峰」,一个逼你「找一个峰钻进去」)。 TRPO 这里写的是 $\text{KL}(\pi_{old}\|\pi_\theta)$,第六节 RLHF 那个惩罚项写的是反过来的方向—— 顺序不是随便写的,谁在前谁在后决定了它到底在罚什么。
✅ 有理论保证(单调改进)
❌ 但要解一个带约束的优化问题:需要二阶导、共轭梯度、线搜索
→ 实现复杂,跑得慢 💀
PPO 的答案:别做约束优化了,直接把目标函数裁掉。
✂️ 三、裁剪目标(PPO-Clip)
先定义概率比:
$$r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{old}(a_t|s_t)}$$
💡 人话:新策略选这个动作的概率,是旧策略的几倍。
r = 1 表示没变,r = 1.5 表示新策略更倾向于选它了。
目标函数:
$$L = \mathbb{E}\Big[\min\big(r_tA_t,\ \text{clip}(r_t, 1-\epsilon, 1+\epsilon)A_t\big)\Big]$$
(通常 ε = 0.2)
💡 用两种情况把它讲清楚
情况 ①:A > 0(这个动作比预期好,想提高它的概率)
r 从 1 涨到 1.2 → 目标跟着涨 ✅ 鼓励
r 涨到 1.5 → clip 到 1.2 → 【目标不再增加】⭐
→ 再怎么推高这个动作的概率,也拿不到更多好处
→ 【失去了继续迈大步的动力】
情况 ②:A < 0(这个动作比预期差,想降低它的概率)
r 从 1 降到 0.8 → 目标涨 ✅
r 降到 0.5 → clip 到 0.8 → 目标不再涨
→ 同样刹住了
⭐ 那个 min 的作用:
它让目标函数成为真实目标的一个【悲观下界】
→ 只在"改进有限"的方向上给奖励,
在"步子太大"的方向上不给任何额外好处
🔑 PPO 的精妙之处在于它的朴素: 不是"禁止"迈大步(那需要约束优化),而是"迈大步没有奖励" —— 用一个
min和一个clip就把二阶优化问题变成了普通的梯度下降。
🔁 四、为什么能重复利用数据
⭐ 有了概率比 r,同一批数据可以【重复训练多轮】(通常 3~10 轮)
因为:即使策略已经更新过了,
r 会如实反映"现在离采样时那个策略有多远"
→ clip 会自动刹住
✅ 这大幅提升了 on-policy 算法的样本效率
💡 这是 PPO 实用性的关键: 纯 on-policy 算法(如 REINFORCE)每批数据只能用一次,样本效率极低。 PPO 靠重要性采样比 + 裁剪,让数据能重复用几轮,同时不会跑飞。
🔨 五、完整实现
# 采样阶段(用旧策略跑一批,记下 logp 和优势)
import torch
import torch.nn as nn
import torch.nn.functional as F
with torch.no_grad():
old_logp = dist.log_prob(actions)
advantages = compute_gae(rewards, values, dones, gamma=0.99, lam=0.95)
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8) # ⭐ 标准化
# 训练阶段(同一批数据跑 K 轮)
for _ in range(K_EPOCHS):
for batch in minibatches(data):
logp = policy(batch.states).log_prob(batch.actions)
ratio = torch.exp(logp - batch.old_logp) # ⭐ 在 log 空间做除法
s1 = ratio * batch.adv
s2 = torch.clamp(ratio, 1 - eps, 1 + eps) * batch.adv
policy_loss = -torch.min(s1, s2).mean() # ⭐ 核心就这三行
value_loss = F.mse_loss(critic(batch.states), batch.returns)
entropy = dist.entropy().mean()
loss = policy_loss + 0.5 * value_loss - 0.01 * entropy
opt.zero_grad(); loss.backward()
nn.utils.clip_grad_norm_(params, 0.5) # ⭐ 梯度裁剪,必加
opt.step()
⚠️ 六个真正影响成败的实现细节
| 细节 | 为什么 |
|---|---|
| 优势标准化 ⭐ | 不做的话不同批次的优势量级差很多,学习率没法统一 |
| 梯度裁剪(0.5) | RL 的梯度偶尔会爆炸 |
ratio 在 log 空间算 |
exp(logp − old_logp) 比直接除稳定得多 |
| 熵奖励系数 | 太小 → 过早收敛;太大 → 学不到东西 |
| K_EPOCHS 不能太大 ⭐ | 太多轮会让新旧策略偏离过远,clip 也救不回来 |
| 观测归一化 | 连续控制任务里几乎是必需的 |
💡 有一篇著名的论文专门研究这件事("实现细节对 PPO 有多重要"): 结论是 PPO 的效果很大程度上来自这些工程细节,而不只是那个 clip 公式。 这也是复现 RL 论文特别难的原因。
🎯 六、PPO 在 RLHF 里的样子
语言模型的 RLHF 里,PPO 的各个部件对应:
├─ 状态 s = 到目前为止的对话 + 已生成的 token
├─ 动作 a = 下一个 token ⭐
├─ 策略 π = 语言模型本身
├─ 奖励 r = 奖励模型给整个回复打的分(只在结尾给)⭐
├─ Critic = 一个额外的价值头
└─ ⭐ 额外一项:KL 惩罚
r_total = 奖励模型分数 − β·KL(π ‖ π_SFT)
↑ 别离原始模型太远
🔑 为什么需要那个 KL 惩罚: 只优化奖励模型的分数,模型会去钻奖励模型的漏洞 (生成一些人类觉得莫名其妙、但奖励模型给高分的文本)——这叫奖励攻击。 KL 惩罚把它拴在原始模型附近。
💡 注意这里有两层"别走太远": clip 管的是"这一步别更新太多",KL 惩罚管的是"整体别偏离 SFT 模型太远"。 两者解决不同的问题。
🔗 第 12 章会把整个流程串起来。
📊 七、超参速查
| 超参 | 常用值 |
|---|---|
| clip ε | 0.2(RLHF 里常用 0.2 或更小) |
| γ | 0.99(RLHF 里常用 1.0) |
| GAE λ | 0.95 |
| K_EPOCHS | 3–10(RLHF 里常用 1–4)⭐ |
| 学习率 | 3e-4(控制)/ 1e-6 ~ 1e-5(RLHF)⭐ |
| 熵系数 | 0.01(RLHF 里常设 0) |
| 梯度裁剪 | 0.5 |
⚠️ 注意 RLHF 的学习率比常规 RL 小两三个数量级 —— 因为你是在微调一个已经很好的模型,不是从零学。
🔗 和站内其他章的关系
| 相关的地方 | 这里的位置 |
|---|---|
| 第 9 章 策略梯度 | PPO 是它的稳定化版本 |
| 第 10 章 GAE | PPO 的标配优势估计 ⭐ |
| 第 10 章 熵奖励 | PPO 里同样保留 |
| 全景导论第 4 章 RLHF | 本章是它的算法内核 ⭐ |
| 第 13 章 DPO | 试图完全绕开 PPO 的路线 |
✅ 检查点
- RL 里"一次糟糕的更新"为什么比监督学习严重得多?
- TRPO 的做法是什么?它的问题在哪?
- 概率比 r 的含义是什么?r=1.5 表示什么?
- 用 A>0 和 A<0 两种情况解释 clip 怎么起作用。
- 那个
min的作用是什么? - PPO 为什么能重复利用同一批数据?
- 六个实现细节里,哪两个最容易被忽略?
- RLHF 里 PPO 的状态、动作、奖励分别是什么?
- 为什么 RLHF 需要 KL 惩罚?它和 clip 解决的是同一个问题吗?
- 为什么 RLHF 的学习率比常规 RL 小那么多?
👀 答案
- 因为你的模型决定了你的训练数据。参数走错 → 策略变差 → 采到的数据也跟着变差 → 用更差的数据继续训练 → 正反馈崩溃。监督学习里数据是固定的,能纠正回来。
- 用 KL 散度做硬约束(最大化代理目标,约束 KL ≤ δ)。问题:要解带约束的优化问题,需要二阶导、共轭梯度、线搜索,实现复杂且慢。
- 新策略选这个动作的概率是旧策略的几倍。r=1.5 表示新策略更倾向于选它了(概率涨了 50%)。
- A>0(想提高概率):r 涨到 1.2 内目标跟着涨,超过就被 clip 到 1.2,目标不再增加 → 失去继续迈大步的动力。A<0(想降低概率):r 降到 0.8 内目标涨,再低就被 clip 住,同样刹车。
- 让目标函数成为真实目标的悲观下界——只在"改进有限"的方向给奖励,在"步子太大"的方向不给任何额外好处。
- 因为概率比 r 会如实反映"现在离采样时那个策略有多远",即使已经更新过几轮,clip 也会自动刹住。这大幅提升了 on-policy 的样本效率。
- 优势标准化(不做的话不同批次量级差很多,学习率无法统一)和 K_EPOCHS 不能太大(太多轮会让新旧策略偏离过远,clip 也救不回来)。
- 状态 = 到目前为止的对话+已生成 token;动作 = 下一个 token;奖励 = 奖励模型给整个回复的打分(只在结尾给)。
- 因为只优化奖励分数,模型会去钻奖励模型的漏洞(生成人类觉得莫名其妙但奖励模型给高分的文本)——奖励攻击。不是同一个问题:clip 管"这一步别更新太多",KL 惩罚管"整体别偏离 SFT 模型太远"。
- 因为是在微调一个已经很好的模型,不是从零学。常规 RL 用 3e-4,RLHF 用 1e-6~1e-5。
🛑 可以停在这里
⚡ 走神救援
⭐RL 的要命毛病:你的模型决定了你的训练数据——参数走错 → 策略变差 → 采到的数据也跟着变差 → 正反馈崩溃(监督学习里数据固定,能纠正)。诉求变成"每次更新别让新策略离旧策略太远"。TRPO 用 KL 硬约束(有理论保证但要二阶导+共轭梯度,复杂又慢);⭐PPO 的答案:别做约束优化,直接把目标裁掉。概率比 r = π_new/π_old(新策略选这动作的概率是旧的几倍);目标 L = min(r·A, clip(r,1−ε,1+ε)·A),ε=0.2。两种情况:A>0 时 r 涨过 1.2 就被裁 → 目标不再增加 → 失去迈大步的动力;A<0 时 r 降过 0.8 同样刹车。min 让目标成为真实目标的悲观下界。⭐PPO 的精妙在于朴素:不是"禁止"迈大步,而是"迈大步没有奖励"——把二阶优化变成普通梯度下降。⭐能重复利用同一批数据 3~10 轮(r 如实反映离采样策略多远,clip 自动刹住)→ 大幅提升 on-policy 样本效率。六个实现细节比公式更影响成败:⭐优势标准化、梯度裁剪 0.5、ratio 在 log 空间算、熵系数、⭐K_EPOCHS 不能太大、观测归一化——有论文专门证明 PPO 的效果很大程度来自这些细节,这也是复现 RL 论文特别难的原因。RLHF 里:状态=已生成的对话、动作=下一个 token、奖励=奖励模型给整个回复打分(只在结尾给);⭐额外的 KL 惩罚 r = RM分数 − β·KL(π‖π_SFT),防奖励攻击(模型钻奖励模型漏洞);⭐注意两层"别走太远":clip 管"这一步别更新太多",KL 管"整体别偏离 SFT 太远"。⚠️RLHF 学习率 1e-6~1e-5,比常规 RL 小两三个数量级(在微调一个已经很好的模型)。
下一节 👉 12-RLHF全流程.md ⭐⭐