本文内容是基于policy-gradient的数学基础进行的policy algorithm(代码部分参考cleanrl的实现)的介绍,主要是介绍ppo和dqn 首先我们从PPO的损失函数开始看起,可以知道ppo的损失函数主要由三个部分组成 $ J^\text{CLIP'} (\theta) = \mathbb{E} [ J^\text{CLIP} (\theta) - \color{red}{c_1 (V_\theta(s) - V_\text{target})^2} + \color{blue}{c_2 H(s, \pi_\theta(.))} ] $ 其中c1,c2位超参数 第一项是PPO可以复用之前数据的核心,我们最后讲,其中第二项是在Actor-Critic中或者说一些比较基础的算法中常见的MSE损失,最后的熵是确保说可以学习到一些随机性 从最简单的例子开始讲起. cleanrl/ppo.py (example 为 cartpole -v4) action : discrete
Actor-Critic 网络
class Agent(nn.Module):
def __init__(self, envs):
super().__init__()
self.critic = nn.Sequential(
layer_init(nn.Linear(np.array(envs.single_observation_space.shape).prod(), 64)),
nn.Tanh(),
layer_init(nn.Linear(64, 64)),
nn.Tanh(),
layer_init(nn.Linear(64, 1), std=1.0),
)
self.actor = nn.Sequential(
layer_init(nn.Linear(np.array(envs.single_observation_space.shape).prod(), 64)),
nn.Tanh(),
layer_init(nn.Linear(64, 64)),
nn.Tanh(),
layer_init(nn.Linear(64, envs.single_action_space.n), std=0.01),
)
def get_value(self, x):
return self.critic(x)
def get_action_and_value(self, x, action=None):
logits = self.actor(x)
probs = Categorical(logits=logits)
if action is None:
action = probs.sample()
return action, probs.log_prob(action), probs.entropy(), self.critic(x)
从这个网络的设计,可以看到就是两个简单的linear层,因为任务比较简单.
- Actor : policy net $\pi_\theta$ 决定了每一步的reward
- Critic : 通过MSE来逼近准确的valude function $Q^\pi$
update critic
Critic 的更新本质上就是 TD learning,目标是最小化 value estimate 和 target 之间的差距。
Critic 的 loss 是 MSE:
$$ \mathcal{L}_{\text{critic}}(\phi) = \mathbb{E}_{s \sim d^\pi} \left[ \frac{1}{2} \big( V_\phi(s) - V_{\text{target}}(s) \big)^2 \right] $$梯度更新:
$$ \phi \leftarrow \phi - \alpha \cdot \mathbb{E} \left[ \big(V_\phi(s) - V_{\text{target}}(s)\big) \nabla_\phi V_\phi(s) \right] $$关键在于 $V_{\text{target}}(s)$ 怎么算。
两种 Target
1. Monte Carlo target(REINFORCE with baseline)
$$ V_{\text{target}}(s_t) = G_t = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1} $$用整条 trajectory 的真实 return。无偏但方差大。
2. TD target(Actor-Critic 标配)
$$ V_{\text{target}}(s_t) = R_{t+1} + \gamma V_\phi(s_{t+1}) $$也就是 TD(0) target(单步 bootstrap)。也可以推广到 n-step:
$$ V_{\text{target}}(s_t) = \sum_{k=0}^{n-1} \gamma^k R_{t+k+1} + \gamma^n V_\phi(s_{t+n}) $$为什么用 TD target?
1. 在线更新
MC 要等 episode 结束才能算 $G_t$,TD 只需要下一步。Actor-Critic 可以逐 step 更新,不依赖 episode 边界,也适用于 continuing task。
2. 减小方差 → Actor 梯度更稳定
Actor 的梯度里乘的是 $\hat{A}_t$(advantage),由 critic 给出。如果 critic 的 target 是 MC return,advantage 的方差会直接传导给 actor 的 policy gradient,actor 更新就会震荡。TD target 的方差小得多,整个系统更稳定。
3. Bootstrap 引入的 bias 在可接受范围内
TD 的 bias 来自 $V_\phi$ 的不准确——初期 critic 随机初始化,bootstrap 确实有偏。但随着训练进行 bias 逐步减小。这是经典的 bias-variance tradeoff:牺牲一点可收敛的 bias,换取大幅方差下降。
TD error:
$$ \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) $$Critic 的 update 就是让 $\delta_t \to 0$。$\delta_t$ 同时也是 advantage 的一步估计,直接喂给 actor。所以 critic 学得越好,actor 的梯度方向就越准。
核心思想
PPO 想做多次 gradient step(提高样本效率),但又不能像 TRPO 那样搞约束优化。解决方案:clip 掉更新太激进的那部分梯度。
Clipped Surrogate Objective
记新旧策略的概率比:
$$ r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)} $$$r_t = 1$ 表示参数没变;$r_t > 1$ 表示新策略更倾向选这个动作;$r_t < 1$ 表示相反。
PPO 的 loss 是:
$$ \mathcal{L}^{\text{CLIP}}(\theta) = \mathbb{E}_t \left[ \min\Big( \underbrace{r_t(\theta) \hat{A}_t}_{\text{不做限制的 PG}}, \underbrace{\text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t}_{\text{限制更新幅度}} \Big) \right] $$其中 $\epsilon$ 典型值是 0.1 或 0.2。
为什么是 min?
关键在于 $\hat{A}_t$ 的符号:
当 advantage > 0(这个动作比平均好,应该鼓励):
- $r_t$ 越大,梯度的正向推动越大
- 但 clip 把 $r_t$ 限制在 $1+\epsilon$,不让它超过
- min 取 clip 这一项 → 阻止策略变得"太确信"这个好动作
当 advantage < 0(这个动作比平均差,应该抑制):
- $r_t$ 越小(趋近 0),梯度的负向推动越大
- 但 clip 把 $r_t$ 限制在 $1-\epsilon$,不让它降太多
- min 取 unclipped 这一项 → 阻止策略对坏动作"矫枉过正"
一张图总结 min 的效果:
advantage > 0: r 增大到 1+ε 后梯度被截断(不再受益)
advantage < 0: r 减小到 1-ε 后梯度被截断(不再惩罚)
完整 Loss
实际训练时 PPO 的 loss 还包含 value function loss 和 entropy bonus:
$$ \mathcal{L}^{\text{total}} = \mathcal{L}^{\text{CLIP}} - c_1 \underbrace{\mathcal{L}^{\text{VF}}}_{(V_\theta(s) - V_{\text{target}})^2} + c_2 \underbrace{\mathcal{S}[\pi_\theta](\cdot|s_t)}_{\text{entropy bonus}} $$- Value loss:让 critic 学得更准,MSE loss
- Entropy bonus:鼓励探索,防止策略过早收敛(系数通常 $c_2 \approx 0.01$)
PPO-Clip vs PPO-Penalty
PPO-Penalty(早期 OpenAI 论文里的另一个变体)在 loss 里加 KL 惩罚项 $\beta \cdot \text{KL}(\pi_{\text{old}} \| \pi_\theta)$,并自适应调整 $\beta$。实践中 PPO-Clip 更简单、效果也好,所以大家说的 PPO 几乎都指 clip 版本。
一句话
PPO loss = min(unclipped_ratio × advantage, clipped_ratio × advantage) → 好动作别推太过,坏动作别踩太狠,更新始终被限制在 $[1-\epsilon, 1+\epsilon]$ 的通道内。