PPO(Proximal Policy Optimization,近端策略优化)是深度强化学习中非常常用的一类策略优化算法。

如果前面已经理解了 MDP、奖励、价值函数和策略,那么 PPO 要解决的问题可以这样理解:

智能体已经有一个策略了。
现在我们根据新采样到的经验,让这个策略变得更好。
但每次更新不能太激进,否则好不容易学到的行为可能被一下子破坏。

这也是 PPO 名字里 “Proximal” 的含义:更新策略时,希望新策略离旧策略近一点。

一句话概括:

PPO 是一种策略梯度算法,它通过裁剪新旧策略概率比值,限制单次策略更新幅度,从而在实现简单和训练稳定之间取得平衡。

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 常用 Actor-Critic 结构估计策略和优势

实际实现中,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 用裁剪目标函数限制概率比值变化

这个公式看起来有点绕,但核心思想很简单:

如果策略更新带来的收益来自“过度改变动作概率”,那就把这部分收益砍掉。

也就是说,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 根据优势正负决定提高或降低动作概率

这就是 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 的训练通常按下面流程进行:

  1. 使用当前策略 $\pi_{\theta_{old}}$ 和环境交互,收集一批轨迹。
  2. 用奖励和价值函数计算回报 $R_t$。
  3. 用 GAE 或其他方法计算优势 $\hat{A}_t$。
  4. 保存旧策略下每个动作的 log probability。
  5. 把轨迹数据打乱,分成多个 mini-batch。
  6. 对同一批数据训练多个 epoch。
  7. 每次更新时计算新旧策略概率比 $r_t(\theta)$。
  8. 优化裁剪策略损失、价值损失和熵奖励。
  9. 用更新后的策略继续采样,进入下一轮。

PPO 的训练流程

这和 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 来自当前正在更新的策略。
  • ratioexp(new_log_prob - old_log_prob) 计算,数值上更稳定。
  • advantages 通常要标准化,例如减均值、除以标准差。
  • policy_lossvalue_lossentropy 一起构成总损失。

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 时,可以重点关注这些细节:

  1. 保存采样时的 old_log_probs,不要在更新后重新计算旧概率。
  2. 优势 advantages 通常要标准化。
  3. 监控 entropy,太低说明策略可能过早变得确定。
  4. 监控 approximate KL,过大说明策略更新太猛。
  5. 监控 clip fraction,过高说明大量样本触发裁剪。
  6. value loss 如果长期很大,说明 critic 学得不好,优势估计也会受影响。
  7. 奖励尺度差异大时,可以考虑 reward normalization。
  8. 连续动作任务中,要注意动作标准差是否塌缩得太快。
  9. 同一批 rollout 不要更新过多 epoch,否则 on-policy 假设会被破坏。
  10. 先在 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、机器人控制、连续动作强化学习和大模型策略优化时,会更容易理解为什么训练过程总是在“提高奖励”和“别偏离太远”之间小心平衡。

参考文献

本文主要参考了以下资料:

  1. John Schulman, Filip Wolski, Prafulla Dhariwal, Alec Radford, Oleg Klimov, Proximal Policy Optimization Algorithms, 2017. arXiv: https://arxiv.org/abs/1707.06347
  2. John Schulman, Sergey Levine, Philipp Moritz, Michael I. Jordan, Pieter Abbeel, Trust Region Policy Optimization, ICML 2015.
  3. OpenAI Spinning Up, Proximal Policy Optimization: https://spinningup.openai.com/en/latest/algorithms/ppo.html
  4. OpenAI Spinning Up, Key Concepts in RL: https://spinningup.openai.com/en/latest/spinningup/rl_intro.html