遇事不决 PPO

OpenAI 的默认算法。它在连续控制(机器人)和离散控制(游戏)上都表现出色。

稳定性强

通过限制策略更新的幅度(Clip 机制),避免了传统策略梯度算法中因步长过大导致的崩溃。

实现简单

相比于它的前身 TRPO(需要求解复杂的约束优化),PPO 只需要标准的一阶优化器(如 Adam)。

预备知识:重要性采样 (Importance Sampling)

在展开 PPO 之前,我们需要理解一个统计学概念。假设我们需要计算分布 $p(x)$ 下函数 $f(x)$ 的期望,但我们很难直接从 $p(x)$ 中采样。 这时,我们可以从另一个容易采样的分布 $q(x)$ 中采样,并引入一个重要性权重来修正偏差。

$$ \mathbb{E}_{x \sim p}[f(x)] = \int f(x) p(x) dx = \int f(x) \frac{p(x)}{q(x)} q(x) dx = \mathbb{E}_{x \sim q}\left[ f(x) \frac{p(x)}{q(x)} \right] $$

其中 $\frac{p(x)}{q(x)}$ 就是重要性权重。这告诉我们:我们可以用“旧策略”产生的样本,来估计“新策略”的梯度,只要乘上这个权重即可。 但为了保证估计的方差不要太大,分布 $p$ 和 $q$ 不能差得太远,即权重应该接近 1。这正是 PPO 核心思想的数学基础。

核心机制:Clip (截断)

PPO 的前身 TRPO 使用复杂的 KL 散度约束来限制更新幅度。PPO 简化了这一点,直接在目标函数里进行 Clip(截断)

定义概率比 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$。当策略没有变化时,$r_t(\theta) = 1$。 PPO 的损失函数试图最大化这一项,但如果不加限制,策略可能会更新过头。

$$ L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min(r_t(\theta)\hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t) \right] $$

Clip 机制可视化

我们希望 $r_t(\theta)$ 在 $(1-\epsilon, 1+\epsilon)$ 的安全范围内变动(通常 $\epsilon=0.2$)。如果超出这个范围,并且优势函数 $\hat{A}_t$ 指示我们应该进一步偏离时,PPO 会“切断”梯度,阻止更新。

Safe Zone (1-ε, 1+ε)
概率比 Ratio r(θ)

关键技术:GAE (Generalized Advantage Estimation)

在 PPO 中,我们需要估计优势函数 $\hat{A}_t$,即“在当前状态下,动作 $a_t$ 比平均水平好多少”。 常用的方法是 GAE,它在“偏差”和“方差”之间做了完美的平衡。

$$ \hat{A}_t^{GAE} = \sum_{l=0}^\infty (\gamma \lambda)^l \delta_{t+l}^V $$
$$ \text{其中 } \delta_t^V = r_t + \gamma V(s_{t+1}) - V(s_t) \text{ (TD Error)} $$
  • $\lambda = 0$ 时:$\hat{A}_t = \delta_t^V$,即传统的 TD Error。偏差大,方差小。
  • $\lambda = 1$ 时:$\hat{A}_t$ 接近 Monte Carlo 估计。偏差小,方差大。
  • 通常取 $\lambda = 0.95$,取得最佳平衡。

PPO 训练流程

1. 收集轨迹

使用旧策略 $\pi_{\theta_{old}}$ 在环境中运行 $T$ 步,收集 $(s, a, r, s')$。

2. 计算优势 (GAE)

利用 Critic 网络 $V(s)$ 计算 GAE 优势 $\hat{A}_t$ 和回报目标 $R_t$。

3. 多次更新 (Epochs)

将数据打乱(Shuffle)并分批(Batch),优化 Surrogate Loss。这里可以使用梯度下降更新多次,因为 Clip 机制保证了安全。

常见问题与思考

PPO 是 On-policy 还是 Off-policy?
这是一个常见的误区。虽然 PPO 使用了 importance sampling 来利用“旧策略”的数据,看起来像 Off-policy,但它的“旧策略”必须是刚刚采集数据的那个策略,不能是很久以前的策略。本质上,PPO 优化的目标分布和采样分布被限制得很近,因此它被归类为 On-policy 算法。
为什么 DQN 和 DDPG 不用重要性采样?
DQN 和 DDPG 是典型的 Off-policy 算法,它们使用 Replay Buffer 存储历史经验。它们的目标是学习最优 Q 值(Bellman Optimality Equation),天然支持使用任何分布的数据进行训练(只要能覆盖状态空间),因此不需要重要性采样来修正分布差异。
PPO 更新时可以将样本顺序打乱吗?
可以,而且必须打乱。 在计算完 GAE 和回报后,时序相关性已经被编码在这些值里了。在进行 SGD 更新时,打乱样本(Sample Shuffling)可以打破数据间的相关性,降低过拟合风险,就像监督学习中训练图片分类一样。

PyTorch 代码核心

ppo_agent.py
def update(self):
    # 1. 计算 Monte Carlo 回报和 GAE 优势
    rewards = []
    discounted_reward = 0
    for reward, is_terminal in zip(reversed(self.buffer.rewards), reversed(self.buffer.is_terminals)):
        if is_terminal:
            discounted_reward = 0
        discounted_reward = reward + (self.gamma * discounted_reward)
        rewards.insert(0, discounted_reward)
        
    # ... 标准化 rewards / 计算 advantages ...

    # 2. PPO Epochs
    for _ in range(self.K_epochs):
        # 计算新策略的 log probs
        logprobs, state_values, dist_entropy = self.policy.evaluate(old_states, old_actions)

        # 计算比率 r(θ)
        ratios = torch.exp(logprobs - old_logprobs.detach())

        # 计算 Surrogate Loss (Clip)
        surr1 = ratios * advantages
        surr2 = torch.clamp(ratios, 1-self.eps_clip, 1+self.eps_clip) * advantages
        
        # 总 Loss = Actor Loss + Critic Loss - Entropy Bonus
        loss = -torch.min(surr1, surr2) + 0.5 * self.MseLoss(state_values, rewards) - 0.01 * dist_entropy

        # 梯度下降
        self.optimizer.zero_grad()
        loss.mean().backward()
        self.optimizer.step()