PPO 近端策略优化 本节摘要:A2C 每次更新后就扔掉滚动。PPO 用一个裁剪的重要性比率把策略梯度包起来,让你能在同一份数据上跑 10+ 个 epoch 而不让策略爆炸。Schulman et al. (2017),九年后仍是默认策略梯度算法。本节讲透它的核心:重要性比率 ;裁剪代理目标 (典型 ε=0.2)如何在「好的一侧」给梯度盖平顶、「坏的一侧」盖平地;完整训练循环(收集 → 算 GAE → 冻结旧策略 → K 个 epoch 小批量更新 → 丢弃);以及三大诊断——平均 KL、裁剪比例、解释方差。你会在 4×4 GridWorld 上看到它比 A2C 样本效率更高,并理解为什么 RLHF 几乎都用 PPO 做策略优化器。
本节摘要:A2C 每次更新后就扔掉滚动。PPO 用一个裁剪的重要性比率把策略梯度包起来,让你能在同一份数据上跑 10+ 个 epoch 而不让策略爆炸。Schulman et al. (2017),九年后仍是默认策略梯度算法。本节讲透它的核心:重要性比率
r_t(θ) = π_θ(a_t|s_t) / π_{θ_old}(a_t|s_t);裁剪代理目标min(r·A, clip(r, 1-ε, 1+ε)·A)(典型 ε=0.2)如何在「好的一侧」给梯度盖平顶、「坏的一侧」盖平地;完整训练循环(收集 → 算 GAE → 冻结旧策略 → K 个 epoch 小批量更新 → 丢弃);以及三大诊断——平均 KL、裁剪比例、解释方差。你会在 4×4 GridWorld 上看到它比 A2C 样本效率更高,并理解为什么 RLHF 几乎都用 PPO 做策略优化器。
对应原课程:Phase 9 · Lesson 08 ·
ppo(原英文phases/09-reinforcement-learning/08-ppo/docs/en.md)。
阅读完本节,你应当能够:
r_t(θ) = π_θ(a_t|s_t) / π_old(a_t|s_t) 并说明它的数值含义(r=2 表示什么)。min(r·A, clip(r, 1-ε, 1+ε)·A),解释四象限(优势正/负 × 比率大小)下梯度的行为。K_EPOCHS 与 LR。A2C(第 07 节)是 on-policy 的:梯度 E_{π_θ}[A · ∇ log π_θ] 要求当前 π_θ 采样的数据。更新一次,π_θ 变了;刚用的数据就成了 off-policy。再用它,你的梯度就有偏。
滚动很贵。Atari 上,8 环境 × 128 步 = 1024 转移,要十几秒环境时间。一次梯度步后扔掉,太浪费。
TRPO(Schulman 2015)是第一个修复:约束每次更新让新旧策略的 KL 散度低于 δ。理论上干净,但每次更新要解一次共轭梯度。2026 年没人跑 TRPO。
PPO(Schulman et al. 2017)用一个简单的裁剪目标替换硬信赖域约束。多一行代码。每滚动 10 个 epoch。无共轭梯度。理论保证够用。九年后,从 MuJoCo 到 RLHF,它仍是默认策略梯度算法。
r_t(θ) = π_θ(a_t | s_t) / π_{θ_old}(a_t | s_t)
这是新策略相对「采集数据时那个策略」的似然比。r_t = 1 表示没变;r_t = 2 表示新策略采取 a_t 的概率是旧策略的两倍。
L^{CLIP}(θ) = E_t [ min( r_t(θ) A_t, clip(r_t(θ), 1-ε, 1+ε) A_t ) ]
两项:
A_t > 0 且比率想涨过 1 + ε,裁剪把梯度抹平——别把好动作推到旧概率之上 +ε 以外。A_t < 0 且比率想涨过 1 - ε(意思是相对其裁剪后的降低,我们会让坏动作更可能),裁剪封顶梯度——别把坏动作压到 -ε 以下。min 处理另一方向:若比率朝有利方向移动,你仍得到梯度(不在会伤你的一侧裁剪)。
典型 ε = 0.2。把目标作为 r_t 的函数画出来:一个分段线性函数,「好的一侧」有平顶,「坏的一侧」有平地。
L(θ, φ) = L^{CLIP}(θ) - c_v · (V_φ(s_t) - V_t^{target})² + c_e · H(π_θ(·|s_t))
与 A2C 同结构。三个系数,通常 c_v = 0.5、c_e = 0.01、ε = 0.2。
N 个并行环境收集 N × T 转移,每环境 T 步。π_{θ_old} 冻结为当前 π_θ 的快照。K 个 epoch,对每个 (s, a, A, V_target, log π_old(a|s)) 小批量:
r_t(θ) = exp(log π_θ(a|s) - log π_old(a|s))。L^{CLIP} + 价值损失 + 熵。K = 10、小批量 64 是标准超参。PPO 鲁棒:在 ±50% 内具体数字很少要紧。
原论文还提了一个用自适应 KL 惩罚的替代:L = L^{PG} - β · KL(π_θ || π_old),β 按观测到的 KL 调。裁剪版成了主流;KL 版在 RLHF 里延续(那里对参考策略的 KL 是你本来就一直想要的独立约束)。
💡 PPO 的精髓是「允许数据被复用,但不许走太远」。
r_t衡量「走多远」,clip(·, 1±ε)是「不许超过 ε」。这个简单约束换来的是 10× 的样本效率——这是它在 RLHF 里活到 2026 年的根本原因:LLM 的每次滚动都要跑完整段生成,太贵了,扔不起。
log π_old(a | s)for step in range(T): probs = softmax(logits(theta, state_features(s))) a = sample(probs, rng) s_next, r, done = env.step(s, a) buffer.append({ "s": s, "a": a, "r": r, "done": done, "v_old": value(w, state_features(s)), "log_pi_old": log(probs[a] + 1e-12), }) s = s_next
快照在滚动时取一次,更新 epoch 里不变。
与 A2C 同。跨 batch 归一化。
for _ in range(K_EPOCHS): for mb in minibatches(buffer, size=64): for rec in mb: x = state_features(rec["s"]) probs = softmax(logits(theta, x)) logp = log(probs[rec["a"]] + 1e-12) ratio = exp(logp - rec["log_pi_old"]) adv = rec["advantage"] surrogate = min( ratio * adv, clamp(ratio, 1 - EPS, 1 + EPS) * adv, ) # 反传 -surrogate,加价值损失,减熵 grad_logpi = onehot(rec["a"]) - probs if (adv > 0 and ratio >= 1 + EPS) or (adv < 0 and ratio <= 1 - EPS): pg_grad = 0.0 # 被裁剪 else: pg_grad = ratio * adv for i in range(N_ACTIONS): for j in range(N_FEAT): theta[i][j] += LR * pg_grad * grad_logpi[i] * x[j]
「裁剪 → 零梯度」这个模式是 PPO 的核心。如果新策略已经朝有利方向飘得太远,更新就停。
加上对 critic 目标的标准 MSE 与 actor 上的熵正则,与 A2C 同。
每次更新盯三件事:
E[log π_old - log π_θ]。应留在 [0, 0.02]。若冲过 0.1,降 K_EPOCHS 或 LR。[1-ε, 1+ε] 外的样本占比。应 ~0.1-0.3。若 ~0,裁剪从不触发 → 升 LR 或 K_EPOCHS;若 ~0.5+,你在过拟合滚动 → 降它们。1 - Var(V_target - V_pred) / Var(V_target)。critic 质量指标,应随 critic 学习爬向 1。💡 裁剪比例是 PPO 的「体温计」。它直接告诉你「数据复用有没有过头」。0.1~0.3 是健康区间;持续 >0.4 说明策略飘离旧策略太远,该降 K 或 LR;接近 0 说明你太保守,该激进点。
PPO 是 2026 年跨多个领域的默认 RL 算法:
| 用例 | PPO 变体 |
|---|---|
| MuJoCo / 机器人控制 | PPO + 高斯策略 + GAE(0.95) |
| Atari / 离散博弈 | PPO + 分类型策略 + 滚动 128 步 |
| LLM 的 RLHF | PPO + 对参考模型的 KL 惩罚 + 响应末尾的 RM 奖励 |
| 大规模博弈智能体 | IMPALA + PPO(AlphaStar、OpenAI Five) |
| 推理 LLM | GRPO(第 12 节)——无 critic 的 PPO 变体 |
| 仅偏好数据 | DPO——PPO+KL 的闭式塌缩,无需在线采样 |
PPO 的损失形状——裁剪代理 + 价值 + 熵——是 DPO、GRPO 与几乎所有 RLHF 流水线的脚手架。
stable-baselines3 的 PPO 是控制任务的标准实现,默认带 GAE、优势归一化、裁剪、熵正则、学习率线性衰减。LLM 场景则用 HuggingFace TRL 的 PPOTrainer——它把 PPO 适配到序列生成:奖励来自响应末尾的奖励模型,价值头挂在策略主干上,且内置对参考模型的自适应 KL。第 09 节我们会看到它的完整用法。两套实现都把本节手写骨架工程化,值得对照阅读。
本节产出一个可复用 skill(位于原课程 outputs/skill-ppo-trainer.md)。骨架:
--- name: ppo-trainer description: 为给定环境产出 PPO 训练配置与诊断计划。 version: 1.0.0 phase: 9 lesson: 8 tags: [rl, ppo, policy-gradient] --- 给定一个环境与训练预算,输出: 1. 滚动大小。N 环境 × T 步。 2. 更新计划。K epoch、小批量大小、LR 调度。 3. 代理参数。ε(裁剪)、c_v、c_e,优势归一化开。 4. 优势。GAE(λ),给出显式 γ 与 λ。 5. 诊断计划。KL、裁剪比例、解释方差阈值与告警。 拒绝 K > 30 或 ε > 0.3(不安全的信赖域)。 拒绝任何没有优势归一化或 KL/裁剪监控的 PPO。 标记裁剪比例持续 > 0.4 为漂移。
ε=0.2, K=4,在匹配环境步数下对比 A2C(每滚动一 epoch)的样本效率。K ∈ {1, 4, 10, 30},画回报 vs 环境步,并跟踪每次更新的平均 KL。这个任务上 K 多大时 KL 爆?KL > 2·目标 时 β 翻倍,KL < 目标/2 时 β 减半)。对比最终回报、稳定性与「无裁剪」程度。ε = 0.2 是事实标准。降到 0.1 更新太保守;0.3+ 招致不稳。K > 20 常因策略飘离 π_old 太远而不稳。封顶 epoch,尤其对大网络。exp(log_new - log_old) 保证数值稳定,不要 new / old。-L^{CLIP}。符号反是最常见的 PPO bug。| 术语 | 俗称 | 实际含义 |
|---|---|---|
| 重要性比率 | 「r_t(θ)」 | π_θ(a|s) / π_old(a|s);相对采集数据时策略的偏离 |
| 裁剪代理 | 「PPO 的主技巧」 | min(r·A, clip(r, 1-ε, 1+ε)·A);有利一侧过裁剪后梯度为零 |
| 信赖域 | 「TRPO / PPO 的意图」 | 限制每次更新的 KL 以保证单调改进 |
| KL 惩罚 | 「软信赖域」 | 替代 PPO:L - β · KL(π_θ || π_old),自适应 β |
| 裁剪比例 | 「裁剪多常触发」 | 诊断——应 0.1-0.3;偏离说明没调好 |
| 多 epoch 训练 | 「数据复用」 | 每滚动 K epoch;用方差换样本效率 |
| 准 on-policy | 「基本是 on-policy」 | PPO 名义上 on-policy,但 K>1 epoch 安全地用了略 off-policy 的数据 |
| PPO-KL | 「另一个 PPO」 | KL 惩罚变体;用于 RLHF,那里对参考的 KL 本就是约束 |
r_t = π_θ/π_old 衡量新旧策略偏离;r=1 无变化,r=2 新策略概率翻倍。min(r·A, clip(r,1±ε)·A):好动作过 1+ε 后梯度抹平,坏动作过 1-ε 后封顶;ε=0.2 是标准。exp(log_new-log_old)。下一节,我们把 PPO 用在语言模型上——奖励建模 + RLHF,把 GPT-3 变成 ChatGPT 的三段式配方:SFT → 奖励模型 → 带 KL 惩罚的 PPO。这是大模型对齐的根基。