PPO 原理 从概率比到裁剪梯度
GAE 优势估计把轨迹变成固定的优势估计。接下来更新 Actor 时,能否对同一批数据多做几轮优化,又不让已经改善的样本无限推动策略远离采样时的状态?
PPO-Clip 修改的是优化目标:某些方向上的改进达到阈值后,不再给该样本额外收益。它没有把新策略投影回概率比区间,也没有保证所有状态上的 KL 小于某个常数。下面从动作概率比进入分段目标,并运行一个可以独立核验梯度的标准库实验。
先冻结采样时的三个量
一批数据由行为策略 π_old 采集,记录状态 s、实际执行动作 a,以及当时这个动作的 log π_old(a|s)。再按固定 Critic 快照构造优势 A_hat。Actor 优化期间,这些旧 log 概率和优势作为常量使用,不对它们反向传播。
| 量 | 一批数据内如何处理 | 混用的后果 |
|---|---|---|
| 实际执行动作 a | 保留采样记录 | 用新策略重新采样动作,再拿旧优势套上去 |
| old log probability | 保留采样时数值 | 每轮覆盖分母,使概率比总回到 1 |
| A_hat | 在本次 Actor 更新中固定 | 不小心把梯度传入优势计算或 Critic |
| 当前 log probability | 随 Actor 参数更新 | 错误地复用旧 log 概率,Actor 没有梯度 |
概率比是实际采样动作在同一状态下的新旧概率之比:
1 | r(θ) = π_θ(a|s) / π_old(a|s) |
分母必须在采样动作上为正。若旧策略永远不选某动作,旧数据中也就没有这个动作的直接观测;不能靠比值制造未采到的反馈。连续动作时使用相同动作的密度比,不能把密度值误叫作单点概率。
在固定状态 s 且旧动作分布有足够支持时,Σ_a π_old(a|s)r(a)f(a)=Σ_a π_θ(a|s)f(a)。但这只改变动作条件分布的权重,并没有同时修复旧策略与新策略的状态访问分布差异,也不是完整轨迹的重要性权重。
裁剪目标为什么取较小值
设 ε=0.2,clip(r)=min(max(r,0.8),1.2)。对一个采样点定义最大化目标:
1 | l(r,A) = min(rA, clip(r)A) |
这个目标来自 PPO 原始论文式 7。min 选择两个候选值中较保守的那个;优势为负时,负数乘法会改变比较方向,不能机械地认为 r 离开区间就把梯度清零。
flowchart TD A[固定动作 旧 log 概率和优势] --> B[当前 Actor 重算动作 log 概率] B --> C[指数化差值 得到概率比 r] C --> D[计算 rA 与裁剪后的 rA] D --> E[逐样本取较小值] E --> F[平均后取负 得到下降用 loss] F --> G[若继续更新 同一批旧分母仍保持固定]
正负优势决定哪一边会变平
当 A>0,希望提高该动作的概率。r 超过 1+ε 后,l 不再继续增长;r 过低时仍应有把它拉回来的梯度。当 A<0,希望降低概率;r 低于 1−ε 后不再奖励继续降低,但 r 过高时仍会受惩罚。
1 | A > 0:l = A × min(r, 1+ε) |
在非拐点处,导数为:
1 | ∂l/∂r = A,若 A>0 且 r<1+ε,或 A<0 且 r>1−ε |
在拐点上目标不可微;实现可以使用选定的次梯度约定。本篇代码选择平坦侧的零梯度,数值梯度检查避开拐点,不声称所有框架在等号处返回相同导数。
| A | r | rA | clip(r)A | l | ∂l/∂r |
|---|---|---|---|---|---|
| +2 | 1.4 | 2.8 | 2.4 | 2.4 | 0 |
| +2 | 0.6 | 1.2 | 1.6 | 1.2 | +2 |
| −2 | 0.6 | −1.2 | −1.6 | −1.6 | 0 |
| −2 | 1.4 | −2.8 | −2.4 | −2.8 | −2 |
第三行不是“−1.2 比较好,所以取 −1.2”;目标明确定义了 min,因此取 −1.6。第四行则说明“超出区间即没有梯度”是错误实现。
一个目标相同而 KL 不同的反例
只考虑一个状态、两个动作。旧策略对动作 1 的概率为 0.5,数据中两个动作各出现一次,固定优势分别为 +1 和 −1。新策略动作 1 的概率为 p,则两个样本的比值为 2p 与 2(1−p)。
当 p≥0.6,正优势样本已经进入上侧平坦区,负优势样本也进入下侧平坦区,平均目标一直为 (1.2−0.8)/2=0.2。
| 新 p | L_clip | KL(旧 ‖ 新),约 |
|---|---|---|
| 0.5 | 0 | 0 |
| 0.6 | 0.2 | 0.020411 |
| 0.9 | 0.2 | 0.510826 |
这里可以算出完整分布上的精确 KL,而不是由采样动作近似:
1 | KL(旧 ‖ 新) = 0.5 log(0.5/p) + 0.5 log(0.5/(1−p)) |
对 p→1,KL 可趋于无穷,而目标仍为 0.2。因此“目标被裁剪”不能推出“新旧策略一定足够接近”。即使当前点梯度已经为零,前一优化步也可能跨过边界;共享参数还可能被其他状态的梯度推动。
完整标准库实验与梯度
用一个 logit z 参数化 Bernoulli 策略 p=σ(z)。实际动作 a 的 log 概率导数为 a−p,所以 ∂r/∂z=r(a−p)。代码用稳定的 softplus 计算 log 概率,并返回真实梯度、目标平坦比例与精确 KL。
为避免把教学程序当成通用数值库,本实现显式限制 |z|、|old_z|≤20、|A|≤10^6;它拒绝不符合范围的输入,不通过偷偷修改概率比来掩盖溢出。
1 | import json |
实际运行中,第一次上升把 z 从 0 推到 0.4,p 约为 0.5987;第二次更新使 p 超过 0.6,随后该批两个样本都进入平坦区。这个演示没有环境、GAE、Critic、神经网络或完整采样循环,只核验裁剪目标与更新方向。
像 p=0.6 这样的理论拐点,经过 log 和 exp 的浮点运算后,比值可能略落在边界某一侧;不能要求其数值梯度一定等于某个选定次梯度。梯度验证使用远离拐点的样本,目标值仍在舍入误差内符合手算。
outside_fraction 统计概率比是否在区间外,flat_fraction 统计非零优势样本是否落在目标平坦区,两者不是同一个指标。真实实现常见的 clip fraction 也必须结合其具体定义解释。
设 batch 大小为 N,这个标量示例用 O(N) 时间、O(N) 额外存储;真实 Actor 还要加上网络前向、反向与优化器成本。用 log 概率计算比值改善计算方式,但不保证任意输入的指数化都不会溢出。
flowchart TD
A[一次新策略更新] --> B[检查 loss 符号与旧快照]
B --> C[记录目标和区间外比例]
C --> D[另行监控 KL 与实际回报]
D --> E{触及预定停止条件吗}
E -->|是| F[停止本批优化并重新采样]
E -->|否| G[按预定轮数继续]
G --> B
接入 PPO 训练时还缺什么
| 检查点 | 失败边界 |
|---|---|
| 优势与价值目标 | Actor 优势标准化不应覆盖 Critic 原始回归目标 |
| 动作分布 | 离散 log 概率取实际动作;连续多维动作通常要合并各维 log 密度 |
| 行为策略 | old log 概率必须对应采样时的同一分布、动作变换和掩码 |
| 批量更新 | 同一批可重复优化有限轮,不等于旧轨迹可以永久重放 |
| 更新距离 | 裁剪不提供硬 KL 约束;监控或早停要独立制定 |
| 真实效果 | 目标改善不自动推出期望回报单调上升,需要独立评测 |
原论文还讨论了价值损失和熵奖励的组合。本篇故意只实现 PPO-Clip Actor 项,不能把上述二动作轨迹称为完整 PPO 训练结果。
自检:ε=0.2,是否意味着所有动作的新旧概率比永远位于 0.8 到 1.2?
不是。目标在部分方向变平,并没有投影或约束新策略。越界的相反方向仍有梯度,共享参数和一次较大的优化步也可能把比值推离区间。本文 p=0.6 与 0.9 的反例具有相同裁剪目标而 KL 不同。
验证程序 tests/verify-ppo-clipping-article.cjs 直接运行完整代码,另外用非裁剪区间的独立目标、中心差分和动作重命名检查梯度。继续沿策略梯度 → GAE → 本文阅读;评估更新效果时,再参考配对 Bootstrap与配对置换检验,先确认评测单位与推断假设。