PPO算法:从策略梯度到裁剪目标函数
PPO(Proximal Policy Optimization,近端策略优化)是深度强化学习中非常常用的一类策略优化算法。
如果前面已经理解了 MDP、奖励、价值函数和策略,那么 PPO 要解决的问题可以这样理解:
智能体已经有一个策略了。
现在我们根据新采样到的经验,让这个策略变得更好。
但每次更新不能太激进,否则好不容易学到的行为可能被一下子破坏。
这也是 PPO 名字里 “Proximal” 的含义:更新策略时,希望新策略离旧策略近一点。
一句话概括:
PPO 是一种策略梯度算法,它通过裁剪新旧策略概率比值,限制单次策略更新幅度,从而在实现简单和训练稳定之间取得平衡。
1. 为什么需要 PPO
在强化学习中,我们最终想学到一个策略:
$$
\pi_\theta(a|s)
$$
它表示在状态 $s$ 下选择动作 $a$ 的概率。这里的 $\theta$ 是神经网络参数。
如果某个动作带来了更高的长期回报,我们就希望以后在类似状态下更倾向于选择它;如果某个动作表现很差,就希望降低它被选中的概率。
这就是策略梯度的基本思想:
好动作的概率提高
坏动作的概率降低
但普通策略梯度有一个问题:更新步子不好控制。
如果步子太小,学习会非常慢;如果步子太大,策略可能突然发生巨大变化,导致性能崩掉。尤其在深度强化学习里,神经网络参数的一次更新可能让动作分布明显变化。
PPO 的核心就是给策略更新加一个“软限制”:
可以改进,但不要离旧策略太远
2. PPO 属于哪类算法
强化学习算法可以粗略分成两类:
| 类型 | 代表算法 | 学习对象 |
|---|---|---|
| 价值方法 | Q-Learning、DQN | 学习动作价值 $Q(s,a)$ |
| 策略方法 | REINFORCE、TRPO、PPO | 直接学习策略 $\pi(a |
PPO 属于策略梯度方法,并且通常以 Actor-Critic 的形式实现:
- Actor:策略网络,输出动作分布。
- Critic:价值网络,估计当前状态的价值。
Actor 负责“怎么做”,Critic 负责“这个状态大概有多好”。Critic 的估计可以降低策略梯度的方差,让训练更稳定。
3. 从策略梯度开始
策略梯度希望最大化期望回报:
$$
J(\theta)=\mathbb{E}_{\tau \sim \pi_\theta}[R(\tau)]
$$
其中:
- $\tau$ 表示一条轨迹,例如 $(s_0,a_0,r_1,s_1,a_1,r_2,\dots)$。
- $R(\tau)$ 表示这条轨迹的累计回报。
- $\pi_\theta$ 表示当前策略。
经典策略梯度可以写成:
$$
\nabla_\theta J(\theta)
=
\mathbb{E}
\left[
\nabla_\theta \log \pi_\theta(a_t|s_t) A_t
\right]
$$
其中 $A_t$ 是优势函数(Advantage Function)。
优势函数可以理解为:
当前这个动作比平均水平好多少。
如果 $A_t>0$,说明这个动作比预期更好,应该提高它的概率。
如果 $A_t<0$,说明这个动作比预期更差,应该降低它的概率。
4. 优势函数:动作到底好不好
动作价值函数 $Q^\pi(s,a)$ 评价的是:在状态 $s$ 下执行动作 $a$,之后按策略 $\pi$ 行动,长期回报期望是多少。
状态价值函数 $V^\pi(s)$ 评价的是:在状态 $s$ 下按当前策略行动,长期回报期望是多少。
优势函数定义为:
$$
A^\pi(s,a)=Q^\pi(s,a)-V^\pi(s)
$$
它比较的是“这个动作”和“当前状态平均水平”的差距。
例如:
- 当前状态平均能拿到 10 分。
- 某个动作预计能拿到 13 分。
- 那么优势就是 $+3$。
这说明这个动作值得鼓励。
如果某个动作预计只能拿到 7 分,那么优势就是 $-3$,说明它应该被抑制。
实际实现中,PPO 经常使用 GAE(Generalized Advantage Estimation,广义优势估计)来估计优势。它在偏差和方差之间做折中,比直接用完整回报更稳定。
5. 为什么需要新旧策略概率比值
PPO 在更新策略时,会使用旧策略采样到的数据。
采样时使用的是旧策略:
$$
\pi_{\theta_{old}}(a_t|s_t)
$$
但更新时我们优化的是新策略:
$$
\pi_\theta(a_t|s_t)
$$
为了衡量新策略相对于旧策略有多大变化,PPO 定义了概率比值:
$$
r_t(\theta)=
\frac{\pi_\theta(a_t|s_t)}
{\pi_{\theta_{old}}(a_t|s_t)}
$$
这个比值很直观:
- $r_t(\theta)=1$:新旧策略对这个动作的概率一样。
- $r_t(\theta)>1$:新策略更倾向于选择这个动作。
- $r_t(\theta)<1$:新策略不那么倾向于选择这个动作。
如果某个动作优势为正,我们希望 $r_t$ 变大;如果优势为负,我们希望 $r_t$ 变小。
但问题也在这里:如果 $r_t$ 变得太大或太小,说明策略改动过猛。
6. PPO 的裁剪目标函数
PPO 最经典的是 clipped surrogate objective,也就是裁剪替代目标函数。
先看没有裁剪的形式:
$$
L^{PG}(\theta)=
\mathbb{E}_t
\left[
r_t(\theta)A_t
\right]
$$
如果 $A_t>0$,增大 $r_t$ 会让目标变大;如果 $A_t<0$,减小 $r_t$ 会让目标变大。
这符合策略梯度的直觉,但它没有限制更新幅度。
PPO 加入裁剪:
$$
L^{CLIP}(\theta)=
\mathbb{E}_t
\left[
\min
\left(
r_t(\theta)A_t,;
\text{clip}(r_t(\theta),1-\epsilon,1+\epsilon)A_t
\right)
\right]
$$
其中 $\epsilon$ 是裁剪范围,常见取值如 $0.1$ 或 $0.2$。
这个公式看起来有点绕,但核心思想很简单:
如果策略更新带来的收益来自“过度改变动作概率”,那就把这部分收益砍掉。
也就是说,PPO 不直接禁止策略改变,而是不鼓励策略通过过大的概率变化来获得目标函数收益。
7. 裁剪项如何工作
可以分两种情况理解。
7.1 当优势为正
如果 $A_t>0$,说明这个动作比预期好。
我们希望新策略提高这个动作的概率,也就是让 $r_t$ 变大。
但是如果 $r_t$ 已经超过 $1+\epsilon$,PPO 会把它裁剪到 $1+\epsilon$,不再继续奖励更大的概率提升。
直观地说:
好动作可以更常选,但不要一下子提高太多。
7.2 当优势为负
如果 $A_t<0$,说明这个动作比预期差。
我们希望新策略降低这个动作的概率,也就是让 $r_t$ 变小。
但是如果 $r_t$ 已经低于 $1-\epsilon$,PPO 会限制继续下降带来的收益。
直观地说:
坏动作可以少选,但不要一下子压得太狠。
这就是 PPO 稳定性的来源之一:它不会让策略因为一批样本而被过度推向某个方向。
8. PPO 和 TRPO 的关系
PPO 的前身思想可以追溯到 TRPO(Trust Region Policy Optimization)。
TRPO 的目标是:更新策略时限制新旧策略之间的 KL 散度,让新策略不要离旧策略太远。
可以粗略理解为:
TRPO:明确约束新旧策略距离,但实现更复杂。
PPO:用裁剪目标函数近似限制更新幅度,实现更简单。
PPO 没有严格求解带约束优化问题,而是把“不要改太远”这个想法融入损失函数。这样可以直接使用常规的梯度下降和小批量训练。
所以 PPO 的流行,很大一部分原因是:
- 比普通策略梯度稳定。
- 比 TRPO 更容易实现。
- 可以复用同一批轨迹数据做多轮 minibatch 更新。
- 在连续控制和游戏任务中表现通常不错。
9. PPO 的完整损失函数
实际训练中,PPO 通常不只优化策略损失,还会同时训练价值函数,并加入熵奖励鼓励探索。
常见总目标可以写成:
$$
L(\theta,\phi)=
L^{CLIP}(\theta)
-c_1 L^{VF}(\phi)
+c_2 H(\pi_\theta)
$$
其中:
- $L^{CLIP}$ 是策略裁剪目标。
- $L^{VF}$ 是价值函数损失。
- $H(\pi_\theta)$ 是策略熵。
- $c_1$ 和 $c_2$ 是权重系数。
价值函数损失通常是均方误差:
$$
L^{VF}(\phi)=
\mathbb{E}_t
\left[
(V_\phi(s_t)-R_t)^2
\right]
$$
熵项用于鼓励策略保持一定随机性:
熵越大,动作分布越分散,探索越多。
熵越小,动作分布越确定,探索越少。
训练早期适当鼓励探索通常有帮助;训练后期策略会逐渐变得更确定。
10. GAE:更稳定地估计优势
PPO 中常用 GAE 来估计优势。
先定义 TD 误差:
$$
\delta_t =
r_t + \gamma V(s_{t+1}) - V(s_t)
$$
如果使用 GAE,优势估计为:
$$
\hat{A}t =
\delta_t
+(\gamma\lambda)\delta{t+1}
+(\gamma\lambda)^2\delta_{t+2}
+\cdots
$$
其中:
- $\gamma$ 是折扣因子。
- $\lambda$ 控制偏差和方差的折中。
当 $\lambda$ 较小,估计更依赖短期 TD 误差,方差较小但偏差可能较大。
当 $\lambda$ 接近 1,估计更接近长回报,偏差较小但方差可能较大。
实践中常见取值是 $\lambda=0.95$。
11. PPO 的训练流程
PPO 的训练通常按下面流程进行:
- 使用当前策略 $\pi_{\theta_{old}}$ 和环境交互,收集一批轨迹。
- 用奖励和价值函数计算回报 $R_t$。
- 用 GAE 或其他方法计算优势 $\hat{A}_t$。
- 保存旧策略下每个动作的 log probability。
- 把轨迹数据打乱,分成多个 mini-batch。
- 对同一批数据训练多个 epoch。
- 每次更新时计算新旧策略概率比 $r_t(\theta)$。
- 优化裁剪策略损失、价值损失和熵奖励。
- 用更新后的策略继续采样,进入下一轮。
这和 DQN 这类 off-policy 算法不同。PPO 通常被归为 on-policy 算法,因为它主要使用当前策略刚采集的数据。虽然 PPO 会对同一批数据做多轮更新,但数据不能像 DQN 的经验回放那样长期反复使用。
12. PPO 伪代码
PPO 的伪代码可以写成:
initialize actor network pi_theta
initialize critic network V_phi
for iteration in training_iterations:
collect trajectories using current policy pi_theta_old
compute returns R_t
compute advantages A_t
store old log probabilities log pi_theta_old(a_t|s_t)
for epoch in update_epochs:
shuffle rollout data
for minibatch in data:
compute new log probabilities log pi_theta(a_t|s_t)
compute ratio r_t = exp(new_log_prob - old_log_prob)
unclipped = r_t * A_t
clipped = clip(r_t, 1-eps, 1+eps) * A_t
policy_loss = -mean(min(unclipped, clipped))
value_loss = mean((V_phi(s_t) - R_t)^2)
entropy_bonus = mean(entropy(pi_theta(.|s_t)))
total_loss = policy_loss + c1 * value_loss - c2 * entropy_bonus
update actor and critic by gradient descent
set theta_old = theta
注意这里的 policy_loss 前面有负号,是因为很多深度学习框架默认做梯度下降,而 PPO 的策略目标本身是要最大化。
13. PyTorch 风格简化代码
下面不是完整可运行的训练脚本,而是展示 PPO 更新部分最核心的代码结构。
import torch
import torch.nn.functional as F
def ppo_update(
actor,
critic,
optimizer,
states,
actions,
old_log_probs,
returns,
advantages,
clip_eps=0.2,
value_coef=0.5,
entropy_coef=0.01,
):
# 根据当前 actor 重新计算动作分布
dist = actor(states)
# 新策略下,旧动作的 log probability
new_log_probs = dist.log_prob(actions)
# 新旧策略概率比:pi_new(a|s) / pi_old(a|s)
ratio = torch.exp(new_log_probs - old_log_probs)
# 裁剪前后的策略目标
unclipped_objective = ratio * advantages
clipped_ratio = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps)
clipped_objective = clipped_ratio * advantages
# PPO 最大化 min,这里取负号变成最小化 loss
policy_loss = -torch.mean(torch.min(unclipped_objective, clipped_objective))
# critic 预测状态价值
values = critic(states).squeeze(-1)
value_loss = F.mse_loss(values, returns)
# 熵奖励鼓励探索
entropy = dist.entropy().mean()
loss = policy_loss + value_coef * value_loss - entropy_coef * entropy
optimizer.zero_grad()
loss.backward()
optimizer.step()
return {
"policy_loss": policy_loss.item(),
"value_loss": value_loss.item(),
"entropy": entropy.item(),
"ratio_mean": ratio.mean().item(),
}
这里有几个关键点:
old_log_probs必须来自采样时的旧策略。new_log_probs来自当前正在更新的策略。ratio用exp(new_log_prob - old_log_prob)计算,数值上更稳定。advantages通常要标准化,例如减均值、除以标准差。policy_loss、value_loss和entropy一起构成总损失。
14. PPO 的关键超参数
PPO 的效果对超参数比较敏感,常见超参数包括:
| 超参数 | 含义 | 常见取值 |
|---|---|---|
| $\gamma$ | 折扣因子 | 0.99 |
| $\lambda$ | GAE 参数 | 0.95 |
| $\epsilon$ | 裁剪范围 | 0.1 或 0.2 |
| rollout length | 每轮采样步数 | 1024、2048、4096 |
| update epochs | 每批数据更新轮数 | 3 到 10 |
| minibatch size | 小批量大小 | 64、128、256 |
| entropy coef | 熵奖励权重 | 0.001 到 0.01 |
| value coef | 价值损失权重 | 0.5 |
| learning rate | 学习率 | 3e-4 左右 |
其中最容易影响稳定性的通常是:
- 学习率。
- 裁剪范围 $\epsilon$。
- 每批数据重复更新的 epoch 数。
- 优势是否标准化。
如果学习率太大、裁剪范围太宽、同一批数据更新太多轮,PPO 仍然可能不稳定。
15. PPO 的优点
PPO 的优点可以概括为:
- 实现相对简单,不需要像 TRPO 那样处理复杂的二阶优化。
- 比普通策略梯度稳定。
- 能处理离散动作和连续动作。
- 可以和神经网络自然结合。
- 同一批采样数据可以做多轮 mini-batch 更新,样本利用率比最朴素的策略梯度更高。
- 在很多强化学习基准任务中表现可靠。
这也是为什么很多强化学习入门项目、机器人控制任务和 RLHF 相关训练流程里都会提到 PPO。
16. PPO 的局限
PPO 不是万能的。
第一,它仍然是 on-policy 方法,样本效率不如很多 off-policy 方法。
也就是说,PPO 不能无限期复用很久以前的数据。策略变化后,旧数据和当前策略差距太大,继续使用会带来偏差。
第二,它对奖励尺度敏感。
如果奖励非常大或非常不稳定,优势估计也会不稳定。实践中经常需要奖励归一化、优势标准化或 value clipping。
第三,裁剪不等于严格信赖域。
PPO 的裁剪目标只是限制某些样本上的概率比,并不严格保证整体 KL 散度一定很小。因此实践中也常监控 approximate KL,如果 KL 太大就提前停止更新。
第四,探索能力有限。
如果环境奖励稀疏,PPO 可能很难发现有效行为,需要额外探索策略、奖励塑形或课程学习。
17. PPO 和 DQN、A2C、TRPO 的对比
| 算法 | 类型 | 主要特点 |
|---|---|---|
| DQN | 价值方法、off-policy | 学习 $Q(s,a)$,适合离散动作 |
| A2C | Actor-Critic、on-policy | 同时训练策略和价值函数 |
| TRPO | 策略优化、on-policy | 用 KL 约束限制策略更新,较复杂 |
| PPO | 策略优化、on-policy | 用裁剪目标近似限制更新,简单稳定 |
可以这样记:
DQN:学哪个动作价值高
A2C:边学策略,边学价值
TRPO:严格限制策略变化
PPO:用裁剪近似限制策略变化
18. 实践建议
实现或调试 PPO 时,可以重点关注这些细节:
- 保存采样时的
old_log_probs,不要在更新后重新计算旧概率。 - 优势
advantages通常要标准化。 - 监控 entropy,太低说明策略可能过早变得确定。
- 监控 approximate KL,过大说明策略更新太猛。
- 监控 clip fraction,过高说明大量样本触发裁剪。
- value loss 如果长期很大,说明 critic 学得不好,优势估计也会受影响。
- 奖励尺度差异大时,可以考虑 reward normalization。
- 连续动作任务中,要注意动作标准差是否塌缩得太快。
- 同一批 rollout 不要更新过多 epoch,否则 on-policy 假设会被破坏。
- 先在 CartPole、Pendulum 等小环境验证,再上复杂任务。
19. 总结
PPO 的核心思想并不神秘:
根据优势函数判断动作好坏,
用新旧策略概率比调整动作概率,
再用裁剪限制单次策略更新幅度。
它最关键的公式是:
$$
r_t(\theta)=
\frac{\pi_\theta(a_t|s_t)}
{\pi_{\theta_{old}}(a_t|s_t)}
$$
以及:
$$
L^{CLIP}(\theta)=
\mathbb{E}_t
\left[
\min
\left(
r_t(\theta)A_t,;
\text{clip}(r_t(\theta),1-\epsilon,1+\epsilon)A_t
\right)
\right]
$$
理解 PPO 时,可以抓住三个关键词:
- 优势函数:判断动作比预期好还是差。
- 概率比值:衡量新策略相对旧策略变化多少。
- 裁剪:限制策略更新不要太激进。
学完 PPO 后,再看 RLHF、机器人控制、连续动作强化学习和大模型策略优化时,会更容易理解为什么训练过程总是在“提高奖励”和“别偏离太远”之间小心平衡。
参考文献
本文主要参考了以下资料:
- John Schulman, Filip Wolski, Prafulla Dhariwal, Alec Radford, Oleg Klimov, Proximal Policy Optimization Algorithms, 2017. arXiv: https://arxiv.org/abs/1707.06347
- John Schulman, Sergey Levine, Philipp Moritz, Michael I. Jordan, Pieter Abbeel, Trust Region Policy Optimization, ICML 2015.
- OpenAI Spinning Up, Proximal Policy Optimization: https://spinningup.openai.com/en/latest/algorithms/ppo.html
- OpenAI Spinning Up, Key Concepts in RL: https://spinningup.openai.com/en/latest/spinningup/rl_intro.html
