Drill · 手撕

PPO Clipped Surrogate Objective 学习演练

本仓库通过一个独立的实现文件,从零开始研究 PPO(Proximal Policy Optimization)的裁剪代理目标函数。这是一个聚焦于理解 PPO 核心数学与实现细节的专项练习。


1. 数学基础 / Mathematical Foundation

PPO 的裁剪代理目标旨在限制策略更新幅度,其核心公式如下。

概率比 rtr_t: rt(θ)=πθ(atst)πθold(atst)r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)} 代码实现: ratio = torch.exp(action_logprobs - old_action_logprobs)

裁剪代理目标 LCLIPL^{CLIP}: LCLIP(θ)=Et[min(rt(θ)A^t, clip(rt(θ),1ϵ,1+ϵ)A^t)]L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min\left( r_t(\theta) \hat{A}_t, \ \text{clip}\left(r_t(\theta), 1-\epsilon, 1+\epsilon\right) \hat{A}_t \right) \right] 其中 A^t\hat{A}_t 为优势函数估计,ϵ\epsilon 为裁剪超参数。

代码实现对应:

surrogate_unclipped = ratio * advantages
ratio_clipped = torch.clamp(ratio, 1.0 - clip_epsilon, 1.0 + clip_epsilon)
surrogate_clipped = ratio_clipped * advantages
surrogate_per_sample = torch.min(surrogate_unclipped, surrogate_clipped)
loss = -surrogate_per_sample.mean()  # 因为优化器最小化损失,故取负

诊断指标:


2. 直觉与复杂度 / Intuition & Complexity

直觉:

实现复杂度:


3. 文件 / Files


4. 运行 / Run

运行演示与自检:

python from_scratch.py

运行测试套件:

python test_ppo_clip.py

5. 追问分层 / Stratified Follow-ups

L1 基础

  1. 为什么 PPO 要对策略更新进行裁剪(clipping)?如果不裁剪,可能会发生什么?
  2. 解释 ratio 为什么在代码中通过 exp(log_ratio) 计算,而不是直接计算概率的比值。
  3. min 操作中,为什么要对 surrogate_unclippedsurrogate_clipped 取较小值?

L2 中级

  1. 代码中 approx_kl 的计算公式 ((ratio - 1) - log_ratio).mean() 的来源是什么?它近似了什么量?
  2. 解释超参数 clip_epsilon 的作用。如果将其设为 0 或非常大(如 100),优化行为会如何变化?
  3. GAE(Generalized Advantage Estimation)函数中的 lamλ\lambda)参数有什么作用?它如何权衡偏差(bias)与方差(variance)?

L3 深入

  1. 讨论 PPO 裁剪目标与 TRPO(Trust Region Policy Optimization)中 KL 散度约束在数学形式和实际行为上的异同。
  2. 在反向传播时,梯度如何通过 min 操作流回 cur_logprobs?从自动微分的角度分析其行为。
  3. 代码中 advantages 在演示部分进行了白化(whitening)。为什么需要对优势函数进行归一化?如果不这样做,对训练稳定性可能产生什么影响?