Skip to content

策略梯度方法全解

策略梯度(Policy Gradient, PG)是强化学习中与价值方法(Value-based Methods)并列的另一大范式。DQN通过学习Q值来间接推导策略,而策略梯度方法直接对策略 \(\pi_\theta(a|s)\) 进行参数化并通过梯度上升来优化。从最朴素的REINFORCE到PPO、SAC,所有策略梯度算法都共享同一套Pipeline。本笔记将这套Pipeline从头到尾拆解,覆盖从数据收集到策略更新的每一个环节。

设计原则:Bias-Variance Tradeoff

Bias-Variance Tradeoff(偏差-方差权衡)是贯穿整个策略梯度Pipeline的核心张力。理解这一点,就理解了为什么会有那么多看似不同的PG变体——它们本质上都在同一根"跷跷板"上寻找不同的平衡点。

为什么这是RL中的核心矛盾

在监督学习中,Bias-Variance Tradeoff体现在模型复杂度的选择上。但在RL中,这个矛盾更加根本——它出现在回报估计这一步。

策略梯度的核心公式是:

\[ \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot \Psi_t \right] \]

其中 \(\Psi_t\) 是某种形式的"信号",用来告诉梯度"这个动作有多好"。不同PG方法的根本区别,就在于 \(\Psi_t\) 的选择。

MC vs TD:两个极端

Monte Carlo Return(蒙特卡洛回报):

\[ \Psi_t = G_t = \sum_{k=0}^{T-t} \gamma^k r_{t+k} \]
  • 零偏差: \(G_t\) 是回报的无偏估计,因为它使用了完整的真实奖励序列,不依赖任何函数近似。
  • 高方差: \(G_t\) 依赖从时刻 \(t\) 到回合结束的所有随机性(环境转移的随机性 + 策略选择的随机性),随机因素越多,估计值的波动越大。

TD Target(时序差分目标):

\[ \Psi_t = \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) \]
  • 低方差: \(\delta_t\) 只依赖一步的随机性(一个奖励 \(r_t\) 和一个转移 \(s_{t+1}\)),波动小得多。
  • 有偏差: \(V(s_{t+1})\) 是一个函数近似(神经网络的输出),不等于真实的状态价值,因此引入了偏差。
Bias-Variance Tradeoff 光谱
(偏差高,方差低)                                    (偏差零,方差高)
    |                                                    |
    TD(0)          n-step TD          GAE(λ)           MC
    δ_t         r+γr'+...+γⁿV      Σ(γλ)^l δ          G_t
    |                                                    |
    └────────────────────────────────────────────────────┘
                    GAE的λ参数在此间滑动

这个光谱上的每一种选择,都是一种不同的PG变体。GAE(Generalized Advantage Estimation)通过参数 \(\lambda\) 在这条光谱上自由滑动,是目前最主流的选择。

为什么不能"既要又要"

理想情况下,我们希望 \(\Psi_t\) 既无偏又低方差。但这两者在有限样本下是矛盾的:

  • 要降低偏差,就需要更多真实信息(更长的rollout),但这会引入更多随机性 → 方差升高
  • 要降低方差,就需要用函数近似来"平滑"估计,但近似本身不完美 → 偏差升高

整个PG Pipeline的设计——从数据收集到优势估计到策略更新——都在围绕这个矛盾做文章。


采样与数据收集 (Rollout)

策略梯度是一种基于采样的方法。我们无法精确计算期望 \(\mathbb{E}_{\tau \sim \pi_\theta}[\cdot]\),只能通过从策略 \(\pi_\theta\) 中采样轨迹来近似。这一步就是Rollout

什么是Rollout / Trajectory

一条轨迹(Trajectory) \(\tau\) 是智能体与环境交互的完整记录:

\[ \tau = (s_0, a_0, r_0, s_1, a_1, r_1, \ldots, s_{T-1}, a_{T-1}, r_{T-1}, s_T) \]

轨迹的概率由策略和环境动力学共同决定:

\[ p(\tau | \theta) = p(s_0) \prod_{t=0}^{T-1} \pi_\theta(a_t | s_t) \cdot p(s_{t+1} | s_t, a_t) \]

其中 \(p(s_0)\) 是初始状态分布,\(p(s_{t+1}|s_t,a_t)\) 是环境转移概率(model-free下未知)。

Rollout就是执行这个过程:用当前策略 \(\pi_\theta\) 与环境交互,收集一批轨迹数据。

On-Policy vs Off-Policy数据收集

On-Policy(同策略):

数据必须由当前策略 \(\pi_\theta\) 生成。每次更新策略参数后,旧数据必须丢弃,用新策略重新采样。

  • 优点:梯度估计无偏,理论保证强
  • 缺点:样本效率极低,每批数据只用一次(或几次,如PPO)

Off-Policy(异策略):

数据可以来自任意策略(称为行为策略 \(\mu\)),通过重要性采样修正分布偏差。

  • 优点:可以复用历史数据,样本效率高
  • 缺点:重要性权重方差大,需要额外技巧(如clipping、truncation)来稳定训练

PPO处于一个巧妙的中间地带:它是on-policy框架,但通过重要性采样允许对同一批数据做多次更新(通常3-10个epoch),再通过clipping限制新旧策略的偏离程度。

并行环境 (Vectorized Environments)

为了提高on-policy方法的数据收集效率,现代实现通常同时运行多个环境副本:

┌─────────────────────────────────────────────────────┐
│                     策略网络 π_θ                      │
│                   (一个共享网络)                       │
└───────┬──────┬──────┬──────┬──────┬──────┬──────────┘
        │      │      │      │      │      │
       ┌▼┐   ┌▼┐   ┌▼┐   ┌▼┐   ┌▼┐   ┌▼┐
       │E│   │E│   │E│   │E│   │E│   │E│     N个并行环境
       │1│   │2│   │3│   │4│   │5│   │6│
       └┬┘   └┬┘   └┬┘   └┬┘   └┬┘   └┬┘
        │      │      │      │      │      │
        └──────┴──────┴──────┴──────┴──────┘
                         │
                    Rollout Buffer
                (N × T 个 transition)

每个环境独立运行,但共享同一个策略网络。这样一次rollout就能收集 \(N \times T\) 个transition(\(N\) 个环境,每个跑 \(T\) 步),极大提高了数据吞吐量。

轨迹Buffer的数据结构

一次Rollout收集的数据通常存储为如下结构(以PPO为例):

字段 形状 含义
states \((N \times T, \text{obs\_dim})\) 观测到的状态
actions \((N \times T, \text{act\_dim})\) 执行的动作
rewards \((N \times T,)\) 环境返回的即时奖励
dones \((N \times T,)\) 是否终止(episode结束)
log_probs \((N \times T,)\) $\log \pi_{\theta_{\text{old}}}(a_t
values \((N \times T,)\) \(V_\phi(s_t)\),Critic的价值估计

注意 log_probsvalues 是在数据收集时用旧参数计算的,后续会用于计算重要性权重和优势函数。


回报与目标计算

收集完数据后的第一步,是为每个时间步计算一个"信号",告诉策略梯度:这个动作到底有多好?不同的计算方式对应不同的bias-variance特性。

Monte Carlo Return

最直接的方式:等到一个episode结束,计算从当前时步到回合结束的折扣累积奖励。

\[ G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdots + \gamma^{T-t} r_T = \sum_{k=0}^{T-t} \gamma^k r_{t+k} \]

其中 \(\gamma \in [0, 1)\)折扣因子(Discount Factor)

  • \(\gamma = 0\):只关心即时奖励,完全短视
  • \(\gamma \to 1\):几乎等权地考虑所有未来奖励,极度远视
  • 常用值:\(\gamma = 0.99\)(考虑约100步的未来)

递推计算: 从轨迹末端反向计算效率最高。

\[ G_T = r_T, \quad G_t = r_t + \gamma G_{t+1} \]

MC Return的性质:

  • 无偏:\(\mathbb{E}[G_t | s_t] = V^{\pi}(s_t)\)(在策略 \(\pi\) 下)
  • 高方差:\(\text{Var}[G_t]\) 随回合长度指数增长
  • 必须等到episode结束才能计算(不适合continuing tasks)

TD Target

TD(0) Target: 只看一步真实奖励,剩余部分用价值函数"自举"(Bootstrap)。

\[ y_t^{\text{TD}(0)} = r_t + \gamma V(s_{t+1}) \]
  • 低方差: 只涉及一步随机性
  • 有偏差: \(V(s_{t+1})\) 是函数近似,\(\mathbb{E}[y_t^{\text{TD}(0)}] \neq V^\pi(s_t)\)(除非 \(V\) 恰好等于真实价值函数)

TD Target是DQN和许多off-policy方法的核心。在on-policy的PG Pipeline中,它是GAE的构建模块。

n-step Return

n-step Return 是MC和TD(0)的折中:用 \(n\) 步真实奖励,加上第 \(n+1\) 步的Bootstrap。

\[ G_t^{(n)} = \sum_{k=0}^{n-1} \gamma^k r_{t+k} + \gamma^n V(s_{t+n}) \]
  • \(n = 1\):退化为TD(0) Target \(r_t + \gamma V(s_{t+1})\)
  • \(n = T - t\):退化为MC Return(不再Bootstrap)
  • \(n\) 越大,偏差越小,方差越大

Bias-Variance分析:

\[ \text{Bias}(G_t^{(n)}) = \gamma^n \left( V(s_{t+n}) - V^\pi(s_{t+n}) \right) \]

偏差来自 \(V(s_{t+n})\) 与真实价值的差距,但前面乘了 \(\gamma^n\),所以 \(n\) 越大偏差衰减越快。与此同时,方差随 \(n\) 增大而增大,因为包含了更多步的随机奖励。

n-step Return 示意图

    t    t+1   t+2   t+3   t+4   ...   T
    |────|────|────|────|────|─────|
    r_t  r_t+1 r_t+2 r_t+3

n=1: r_t + γV(s_{t+1})                          ← 大量Bootstrap,低方差,高偏差
n=2: r_t + γr_{t+1} + γ²V(s_{t+2})
n=3: r_t + γr_{t+1} + γ²r_{t+2} + γ³V(s_{t+3})
 .
 .
n=T: r_t + γr_{t+1} + ... + γ^{T-t}r_T          ← 纯MC,高方差,零偏差

实践中,单独使用固定的 \(n\) 不够灵活。GAE通过对所有 \(n\)-step return做指数加权平均,提供了更优雅的解法。


价值估计 (Critic Learning)

在Actor-Critic架构中,Critic网络 \(V_\phi(s)\) 负责估计状态价值函数。它的作用有两个:(1) 提供Baseline来降低策略梯度的方差;(2) 提供Bootstrap目标来计算TD误差和GAE。

TD误差

TD误差(Temporal Difference Error) 是Critic学习的核心信号:

\[ \delta_t = r_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t) \]

直觉理解:TD误差衡量的是"惊喜程度"。如果 \(\delta_t > 0\),说明实际获得的回报(\(r_t + \gamma V_\phi(s_{t+1})\))比预期(\(V_\phi(s_t)\))好,这是一个"正惊喜";反之则是"负惊喜"。

Critic的目标是让 \(\delta_t \to 0\),即让价值估计和实际回报一致。

用神经网络拟合 \(V(s)\)

Critic网络 \(V_\phi(s)\) 是一个参数为 \(\phi\) 的神经网络,输入状态 \(s\),输出一个标量值。

损失函数: 最小化价值估计与目标值之间的均方误差。

\[ L^{V}(\phi) = \frac{1}{|B|} \sum_{t \in B} \left( V_\phi(s_t) - V_t^{\text{target}} \right)^2 \]

其中目标值 \(V_t^{\text{target}}\) 可以取:

  1. MC Return: \(V_t^{\text{target}} = G_t\)(无偏但高方差)
  2. TD Target: \(V_t^{\text{target}} = r_t + \gamma V_{\phi_{\text{old}}}(s_{t+1})\)(低方差但有偏)
  3. GAE Return: \(V_t^{\text{target}} = \hat{A}_t^{\text{GAE}} + V_{\phi_{\text{old}}}(s_t)\)(PPO中的常用做法)

目标网络的稳定性

在DQN中,目标网络(Target Network)是通过周期性复制或软更新来维护的一个"冻结"网络,用于计算TD Target中的 \(V(s_{t+1})\)

在on-policy PG方法(如PPO)中,情况稍有不同。PPO使用的是rollout开始时的网络参数 \(\phi_{\text{old}}\) 来计算 \(V_t^{\text{target}}\),这些值在数据收集阶段就已经计算并存储在buffer中。在多个epoch的更新过程中,目标值保持不变,只有 \(V_\phi(s_t)\) 随参数更新而变化。这本身就起到了类似目标网络的"锚定"作用。

对于off-policy方法(如SAC),目标网络仍然是标配:

\[ \phi_{\text{target}} \leftarrow \tau \phi + (1 - \tau) \phi_{\text{target}}, \quad \tau \in [0.005, 0.01] \]

训练流程

Critic训练循环:

1. 从Buffer中采样一个mini-batch {(s_t, a_t, r_t, s_{t+1}, done_t)}
2. 计算目标值:V_target = A_t^GAE + V_old(s_t)  (或 r_t + γV_target_net(s_{t+1}))
3. 计算预测值:V_pred = V_φ(s_t)
4. 计算损失:L = MSE(V_pred, V_target)
5. 反向传播,更新 φ

优势估计 (Advantage Estimation)

优势估计是PG Pipeline中最关键的环节之一。好的优势估计直接决定了策略梯度的质量。

Baseline Subtraction —— 为什么要减去基线

原始的REINFORCE算法使用完整回报 \(G_t\) 作为信号:

\[ \nabla_\theta J(\theta) = \mathbb{E} \left[ \sum_t \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot G_t \right] \]

问题在于 \(G_t\) 通常是一个很大的正数(尤其在奖励全为正的环境中),导致所有动作的梯度方向都是"增大概率",只是幅度不同。这使得梯度估计的方差极大。

基线减除(Baseline Subtraction): 引入一个只依赖状态的基线函数 \(b(s_t)\),将信号改为 \(G_t - b(s_t)\)

\[ \nabla_\theta J(\theta) = \mathbb{E} \left[ \sum_t \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot (G_t - b(s_t)) \right] \]

关键定理:减去任何只依赖状态的基线,不会引入偏差。

\[ \mathbb{E}_{a \sim \pi_\theta} \left[ \nabla_\theta \log \pi_\theta(a|s) \cdot b(s) \right] = b(s) \cdot \nabla_\theta \sum_a \pi_\theta(a|s) = b(s) \cdot \nabla_\theta 1 = 0 \]

证明的核心在于 \(\sum_a \pi_\theta(a|s) = 1\) 对所有 \(\theta\) 恒成立,所以其对 \(\theta\) 的梯度恒为零。

最优基线: 理论上可以证明,使方差最小的最优基线为:

\[ b^*(s) = \frac{\mathbb{E}_a \left[ \| \nabla_\theta \log \pi_\theta(a|s) \|^2 \cdot G_t \right]}{\mathbb{E}_a \left[ \| \nabla_\theta \log \pi_\theta(a|s) \|^2 \right]} \]

实践中,这个最优基线难以精确计算。经验表明,\(b(s) = V(s)\)(状态价值函数)已经是一个非常好的近似——它正好把"绝对价值"转化为"相对于平均水平的优势"。

Advantage Function

优势函数(Advantage Function) 定义为:

\[ A^\pi(s, a) = Q^\pi(s, a) - V^\pi(s) \]

其中:

  • \(Q^\pi(s,a) = \mathbb{E}_\pi \left[ \sum_{k=0}^{\infty} \gamma^k r_{t+k} \mid s_t = s, a_t = a \right]\):在状态 \(s\) 执行动作 \(a\) 后遵循策略 \(\pi\) 的期望回报
  • \(V^\pi(s) = \mathbb{E}_{a \sim \pi} [Q^\pi(s,a)]\):在状态 \(s\) 遵循策略 \(\pi\) 的期望回报

性质:

  1. \(\mathbb{E}_{a \sim \pi}[A^\pi(s,a)] = 0\):优势的期望为零(好坏相消)
  2. \(A^\pi(s,a) > 0\):动作 \(a\) 优于策略 \(\pi\) 在状态 \(s\) 的平均水平
  3. \(A^\pi(s,a) < 0\):动作 \(a\) 劣于平均水平

用优势函数替代原始回报,策略梯度变为:

\[ \nabla_\theta J(\theta) = \mathbb{E} \left[ \sum_t \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot A^{\pi_\theta}(s_t, a_t) \right] \]

这就是我们在PPO和其他现代PG算法中看到的标准形式。

GAE (Generalized Advantage Estimation)

GAE是Schulman et al.在2016年提出的优势估计方法,也是目前工业界的事实标准。它的核心思想是:对不同步长的TD误差做指数加权平均,用参数 \(\lambda\) 控制bias-variance的平衡点。

推导过程

首先,定义 \(n\)-step优势估计:

\[ \hat{A}_t^{(1)} = \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) \]
\[ \hat{A}_t^{(2)} = \delta_t + \gamma \delta_{t+1} = r_t + \gamma r_{t+1} + \gamma^2 V(s_{t+2}) - V(s_t) \]
\[ \hat{A}_t^{(n)} = \sum_{l=0}^{n-1} \gamma^l \delta_{t+l} \]

GAE被定义为这些 \(n\)-step优势的指数加权平均(权重为 \((1-\lambda)\lambda^{n-1}\)):

\[ \hat{A}_t^{\text{GAE}(\gamma,\lambda)} = (1 - \lambda) \left( \hat{A}_t^{(1)} + \lambda \hat{A}_t^{(2)} + \lambda^2 \hat{A}_t^{(3)} + \cdots \right) \]

展开并化简(利用几何级数的性质),可以得到一个极其优雅的形式:

\[ \hat{A}_t^{\text{GAE}(\gamma,\lambda)} = \sum_{l=0}^{T-t} (\gamma \lambda)^l \delta_{t+l} \]
\[ = \delta_t + (\gamma\lambda)\delta_{t+1} + (\gamma\lambda)^2\delta_{t+2} + \cdots \]

两个极端

  • \(\lambda = 0\) \(\hat{A}_t = \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)\)
    • 等价于TD(0)优势估计
    • 低方差,高偏差(完全依赖 \(V\) 的准确性)
  • \(\lambda = 1\) \(\hat{A}_t = \sum_{l=0}^{T-t} \gamma^l \delta_{t+l} = G_t - V(s_t)\)
    • 等价于MC Return减去基线
    • 零偏差,高方差

递推计算

在实现中,GAE可以从轨迹末端反向递推计算,效率极高:

\[ \hat{A}_T = \delta_T \]
\[ \hat{A}_t = \delta_t + \gamma \lambda \hat{A}_{t+1} \]

伪代码:

# 假设已有: rewards[0:T], values[0:T+1], dones[0:T], gamma, lam
advantages = zeros(T)
last_gae = 0
for t in reversed(range(T)):
    delta = rewards[t] + gamma * values[t+1] * (1 - dones[t]) - values[t]
    advantages[t] = last_gae = delta + gamma * lam * (1 - dones[t]) * last_gae
returns = advantages + values[0:T]  # V_target = A + V_old

注意 (1 - dones[t]) 的处理:当episode结束时,下一个状态属于新episode,不应该Bootstrap。

如何选择 \(\lambda\)

\(\lambda\) 特性 适用场景
0.0 纯TD(0),最低方差,最高偏差 Critic很准确时
0.9 偏TD方向,方差较低 奖励密集、Critic较好
0.95 经典默认值 大多数任务
0.97-0.99 偏MC方向,方差较高 奖励稀疏、回合较短
1.0 纯MC,零偏差,最高方差 极稀疏奖励

实践经验:\(\lambda = 0.95\) 配合 \(\gamma = 0.99\) 是最常见的组合,在Atari、MuJoCo等标准benchmark上都表现良好。


策略优化

有了优势估计 \(\hat{A}_t\),下一步就是用它来更新策略参数 \(\theta\)。这一步有很多种做法,从最朴素的Vanilla Policy Gradient到PPO的Clipped Objective,复杂度和性能逐步提升。

策略梯度目标函数

策略梯度的目标是最大化期望回报 \(J(\theta)\)。根据策略梯度定理:

\[ \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot \hat{A}_t \right] \]

在实践中,我们通过定义一个代理目标函数(Surrogate Objective)来使用自动微分框架:

\[ L^{PG}(\theta) = \mathbb{E}_t \left[ \log \pi_\theta(a_t|s_t) \cdot \hat{A}_t \right] \]

然后对 \(L^{PG}\) 做梯度上升(即最大化)。这个目标函数的梯度恰好等于策略梯度。

Vanilla Policy Gradient的问题:

  1. 步长敏感: 学习率太大 → 策略崩溃;太小 → 收敛极慢
  2. 参数空间 ≠ 策略空间: 参数空间中的"小步"可能导致策略空间中的"大跳",反之亦然
  3. 无单调改进保证: 一次更新可能让策略变差,而变差的策略会收集到更差的数据 → 恶性循环

Natural Gradient

为什么Vanilla梯度在参数空间中是"错误"的方向?

考虑一个简单的例子:假设策略网络输出两个动作的logits为 \((10, 0)\),经过softmax后概率为 \((0.99995, 0.00005)\)。此时把logits从 \((10, 0)\) 改为 \((11, 0)\),策略几乎没有变化(概率从0.99995变成0.99998)。但如果logits从 \((0.5, 0)\) 改为 \((1.5, 0)\),策略变化就很显著(概率从0.62变成0.73)。

问题出在:欧氏距离衡量的是参数的变化量 \(\|\Delta\theta\|_2\),但我们真正关心的是策略分布的变化量。

Fisher信息矩阵(Fisher Information Matrix) 提供了策略空间中正确的"度量":

\[ F(\theta) = \mathbb{E}_{s \sim d^\pi, a \sim \pi_\theta} \left[ \nabla_\theta \log \pi_\theta(a|s) \cdot \nabla_\theta \log \pi_\theta(a|s)^T \right] \]

Fisher矩阵的物理含义:它描述了参数空间中每个方向上,策略分布变化的"灵敏度"。在灵敏度高的方向上应该走小步,灵敏度低的方向上可以走大步。

自然梯度(Natural Gradient):

\[ \tilde{\nabla}_\theta J(\theta) = F(\theta)^{-1} \nabla_\theta J(\theta) \]

自然梯度通过Fisher矩阵的逆来"矫正"梯度方向,使得在策略空间中的变化是均匀的。这等价于解如下约束优化问题:

\[ \max_{\Delta\theta} \; \nabla_\theta J(\theta)^T \Delta\theta \quad \text{s.t.} \quad \frac{1}{2} \Delta\theta^T F \Delta\theta \leq \delta \]

即在KL散度约束下,找到使目标函数改进最大的更新方向。

实际困难: Fisher矩阵 \(F\) 的大小是 \(|\theta| \times |\theta|\),对于百万参数的网络,存储和求逆都不可行。这就是TRPO要解决的问题。

Trust Region

信赖域(Trust Region) 方法的核心思想:在当前参数附近构造目标函数的局部近似,并限制更新步长在这个近似"可信"的范围内。

在强化学习的语境下,信赖域约束通常用KL散度来定义:

\[ D_{\text{KL}}(\pi_{\theta_{\text{old}}} \| \pi_\theta) = \mathbb{E}_{s} \left[ \sum_a \pi_{\theta_{\text{old}}}(a|s) \log \frac{\pi_{\theta_{\text{old}}}(a|s)}{\pi_\theta(a|s)} \right] \]

为什么KL散度是正确的约束?

KL散度直接衡量两个策略分布之间的差异。如果新旧策略的KL散度小,那么:

  1. 重要性采样的权重 \(r_t(\theta)\) 接近1,方差可控
  2. 代理目标函数对真实目标函数的近似误差有界
  3. Kakade-Langford不等式保证策略性能的单调改进

KL散度与Fisher矩阵的关系:

\[ D_{\text{KL}}(\pi_{\theta_{\text{old}}} \| \pi_\theta) \approx \frac{1}{2} (\theta - \theta_{\text{old}})^T F (\theta - \theta_{\text{old}}) \]

这是KL散度在 \(\theta_{\text{old}}\) 处的二阶Taylor展开。可以看到,Fisher矩阵就是KL散度的Hessian矩阵(在 \(\theta_{\text{old}}\) 处)。

TRPO详解

TRPO(Trust Region Policy Optimization,Schulman et al., 2015)将上述思想形式化为一个带约束的优化问题:

\[ \max_\theta \quad L(\theta) = \mathbb{E}_t \left[ \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)} \hat{A}_t \right] \]
\[ \text{s.t.} \quad \mathbb{E}_s \left[ D_{\text{KL}}(\pi_{\theta_{\text{old}}}(\cdot|s) \| \pi_\theta(\cdot|s)) \right] \leq \delta \]

求解步骤:

Step 1: 线性化目标函数

\[ L(\theta) \approx g^T (\theta - \theta_{\text{old}}), \quad g = \nabla_\theta L \big|_{\theta_{\text{old}}} \]

Step 2: 二次化KL约束

\[ D_{\text{KL}} \approx \frac{1}{2} (\theta - \theta_{\text{old}})^T F (\theta - \theta_{\text{old}}) \leq \delta \]

Step 3: 用Lagrange对偶求解

这是一个经典的线性目标+二次约束问题,解析解为:

\[ \theta^* = \theta_{\text{old}} + \sqrt{\frac{2\delta}{g^T F^{-1} g}} \cdot F^{-1} g \]

即沿自然梯度方向 \(F^{-1}g\) 更新,步长由 \(\delta\) 决定。

Step 4: 共轭梯度法(Conjugate Gradient)

直接计算 \(F^{-1}g\) 需要 \(O(|\theta|^2)\) 的存储和 \(O(|\theta|^3)\) 的计算。TRPO使用共轭梯度法,只需要Fisher-向量积 \(Fv\)(可通过自动微分 \(O(|\theta|)\) 完成),迭代约10-20步就能得到 \(F^{-1}g\) 的良好近似。

Step 5: 线搜索(Line Search)

共轭梯度给出的是近似解,不一定满足KL约束。TRPO在更新方向上做回溯线搜索(Backtracking Line Search),找到满足约束且目标函数确实改进的最大步长。

TRPO的理论保证: 每一步更新都满足:

\[ J(\pi_{\theta_{\text{new}}}) \geq J(\pi_{\theta_{\text{old}}}) - C \cdot D_{\text{KL}}^{\max}(\pi_{\theta_{\text{old}}}, \pi_{\theta_{\text{new}}}) \]

即策略性能单调不递减(在近似误差范围内)。

TRPO的工程问题:

  • 实现复杂度高(共轭梯度 + Fisher向量积 + 线搜索)
  • 不兼容参数共享(Actor-Critic共享层时KL约束难以处理)
  • 不兼容Dropout/BatchNorm等正则化技术
  • 每步计算开销大

PPO的Clipped Objective

PPO用一种极其简洁的方式近似了TRPO的信赖域约束(详见PPO.md)。核心公式:

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

其中 \(r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\) 是概率比,\(\epsilon = 0.2\) 是截断范围。

Clipping vs Trust Region: Clipping不是严格的信赖域方法,它不保证KL散度小于某个阈值。但在实践中,Clipping对概率比的限制间接地限制了策略变化的幅度,效果出奇地好。而且Clipping只需要一阶优化器(Adam),代码量是TRPO的十分之一。

PPO的详细分析(四种情况的逐一拆解、值函数损失、熵奖励等)请参考 PPO.md

KL Penalty方法

PPO原论文中还提出了另一种变体:PPO-Penalty,用KL散度惩罚代替Clipping:

\[ L^{\text{KL-Pen}}(\theta) = \mathbb{E}_t \left[ r_t(\theta) \hat{A}_t - \beta \cdot D_{\text{KL}}(\pi_{\theta_{\text{old}}}(\cdot|s_t) \| \pi_\theta(\cdot|s_t)) \right] \]

其中 \(\beta\) 是自适应调整的系数:

  • 如果 \(D_{\text{KL}} > d_{\text{target}}\)\(\beta \leftarrow 2\beta\)(KL太大,加强惩罚)
  • 如果 \(D_{\text{KL}} < d_{\text{target}} / 1.5\)\(\beta \leftarrow \beta / 2\)(KL太小,放松惩罚)

PPO-Penalty在理论上更接近TRPO(显式地使用KL约束),但在实践中PPO-Clip更稳定、更常用。原因之一是KL散度的计算需要新旧策略的完整分布,而Clipping只需要概率比(一个标量),计算更简单。


正则化与工程优化

理论上正确的算法,在实际训练中往往需要大量工程技巧才能稳定工作。以下是现代PG实现中常用的正则化和优化手段。

Entropy Bonus(熵奖励)

在PPO的完整目标函数中,熵奖励项鼓励策略保持一定的随机性:

\[ L_{\text{total}} = L^{\text{CLIP}} - c_1 L^{VF} + c_2 H(\pi_\theta) \]

其中策略熵 \(H(\pi_\theta(\cdot|s)) = -\sum_a \pi_\theta(a|s) \log \pi_\theta(a|s)\)

作用:

  1. 防止早熟收敛: 策略可能在训练初期就坍缩到某个局部最优的确定性动作,熵奖励阻止这种情况
  2. 数值稳定: 防止动作概率趋近于零(\(\log 0 = -\infty\)
  3. 鼓励探索: 维持策略的多样性,有助于发现更优的策略

典型系数 \(c_2 = 0.01\)。对于探索需求高的环境,可以增大到 \(0.05\)。随训练进行可以线性衰减。

Mini-batch SGD

收集一批rollout数据后,不是一次性用全部数据算一个梯度,而是将数据打乱后分成多个mini-batch:

\[ \text{数据总量} = N \times T, \quad \text{mini-batch大小} = M \]
\[ \text{mini-batch数量} = \frac{N \times T}{M} \]

Mini-batch SGD的好处:

  • 引入随机性,有助于跳出局部最优
  • 减少内存占用(不需要一次性计算所有数据的梯度)
  • 更频繁的参数更新

多Epoch更新 (PPO-style)

On-policy数据本来只能用一次。PPO的关键工程创新是:对同一批rollout数据做多个epoch的SGD更新(通常3-10个epoch),通过Clipping来防止策略偏离过远。

PPO单次迭代:
1. 用 π_θ_old 收集 N×T 个 transitions
2. 计算 GAE 优势估计 A_t
3. for epoch = 1, 2, ..., K:        ← 多次复用同一批数据
       打乱数据
       for each mini-batch:
           计算 r_t(θ) = π_θ(a|s) / π_θ_old(a|s)
           计算 L^CLIP, L^VF, H
           梯度上升更新 θ 和 φ
4. θ_old ← θ                        ← 更新旧策略

\(K\) 不能太大,否则策略偏离旧数据的分布太远,clipping也救不回来。

Gradient Clipping(梯度裁剪)

限制梯度的范数,防止单次更新步长过大:

\[ g \leftarrow \frac{g}{\max(1, \|g\|_2 / g_{\max})} \]

典型值 \(g_{\max} = 0.5\)。这在RL中尤其重要,因为RL的梯度方差本来就很大,偶尔出现的极大梯度会破坏训练。

Advantage Normalization(优势归一化)

在每个mini-batch内,对优势值做标准化:

\[ \hat{A}_t \leftarrow \frac{\hat{A}_t - \mu(\hat{A})}{\sigma(\hat{A}) + \epsilon} \]

其中 \(\mu\)\(\sigma\) 是当前mini-batch中优势值的均值和标准差,\(\epsilon = 10^{-8}\) 防止除零。

为什么有效: 归一化后的优势约一半为正、一半为负,策略梯度变成"同时增大好动作概率、减小坏动作概率",比全是正信号(或全是负信号)更稳定。

Orthogonal Initialization(正交初始化)

对Actor和Critic网络的权重使用正交初始化,对最后一层使用较小的尺度(如0.01)。这有助于:

  • 保持梯度在前向和反向传播中的尺度一致
  • 防止策略网络初始时过于确定性
  • 加速训练初期的收敛
# 典型初始化方案
for layer in network.layers:
    nn.init.orthogonal_(layer.weight, gain=np.sqrt(2))
    nn.init.constant_(layer.bias, 0.0)
# 最后的策略输出层用小尺度
nn.init.orthogonal_(policy_head.weight, gain=0.01)
# 最后的价值输出层用尺度1
nn.init.orthogonal_(value_head.weight, gain=1.0)

完整Pipeline总结

全流程图

┌──────────────────────────────────────────────────────────────────────┐
│                    Policy Gradient Pipeline                         │
└──────────────────────────────────────────────────────────────────────┘

  ┌──────────────┐
  │  环境 (Env)   │──── N个并行环境
  └──────┬───────┘
         │  (s, a, r, s', done)
  ┌──────▼───────┐
  │  1. Rollout   │──── 用 π_θ_old 与环境交互,收集 N×T 个 transitions
  │  (数据收集)    │     记录 log π_old(a|s), V_old(s)
  └──────┬───────┘
         │
  ┌──────▼───────┐
  │  2. 计算回报   │──── 计算 TD 误差 δ_t = r + γV(s') - V(s)
  │  & GAE优势    │     递推计算 GAE: A_t = δ_t + γλ·A_{t+1}
  │               │     计算 Return: R_t = A_t + V_old(s_t)
  └──────┬───────┘
         │
  ┌──────▼───────┐
  │  3. 多Epoch   │──── for epoch = 1 to K:
  │  Mini-batch   │       for each mini-batch:
  │  优化         │
  │  ┌──────────┐ │        ┌────────────────────────────────────────┐
  │  │ Actor    │ │        │ r(θ) = π_θ(a|s) / π_old(a|s)          │
  │  │ 策略更新  │ │        │ L_clip = min(r·A, clip(r,1±ε)·A)     │
  │  └──────────┘ │        │ + c2 · H(π_θ)   ← 熵奖励             │
  │  ┌──────────┐ │        ├────────────────────────────────────────┤
  │  │ Critic   │ │        │ L_vf = (V_φ(s) - R_t)²               │
  │  │ 价值更新  │ │        └────────────────────────────────────────┘
  │  └──────────┘ │
  └──────┬───────┘
         │
  ┌──────▼───────┐
  │  4. 更新旧策略 │──── θ_old ← θ
  │  清空 Buffer   │
  └──────┬───────┘
         │
         └──── 回到 Step 1,重复

通用PG训练循环伪代码

# ─── 初始化 ─────────────────────────────────
policy_net = PolicyNetwork(obs_dim, act_dim)    # Actor: π_θ(a|s)
value_net  = ValueNetwork(obs_dim)               # Critic: V_φ(s)
optimizer  = Adam([policy_net.params, value_net.params], lr=3e-4)
envs       = VectorizedEnv(env_name, num_envs=N)

# ─── 超参数 ─────────────────────────────────
gamma       = 0.99     # 折扣因子
lam         = 0.95     # GAE λ
epsilon     = 0.2      # PPO clipping
c1          = 0.5      # 值函数损失系数
c2          = 0.01     # 熵奖励系数
K_epochs    = 4        # 每次rollout的更新轮数
T_horizon   = 128      # 每个环境的rollout长度
M_batch     = 256      # mini-batch大小
max_grad    = 0.5      # 梯度裁剪阈值

# ─── 主循环 ─────────────────────────────────
for iteration in range(max_iterations):
    # ── Step 1: Rollout ──────────────────────
    buffer = RolloutBuffer()
    obs = envs.reset()
    for t in range(T_horizon):
        with no_grad():
            action, log_prob = policy_net.sample(obs)
            value = value_net(obs)
        next_obs, reward, done, info = envs.step(action)
        buffer.store(obs, action, reward, done, log_prob, value)
        obs = next_obs

    # ── Step 2: 计算 GAE & Returns ──────────
    with no_grad():
        last_value = value_net(obs)     # Bootstrap最后一步
    buffer.compute_gae(last_value, gamma, lam)

    # ── Step 3: 多Epoch Mini-batch优化 ──────
    for epoch in range(K_epochs):
        for batch in buffer.get_minibatches(M_batch):
            states, actions, old_log_probs, returns, advantages = batch

            # Advantage Normalization
            advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

            # Actor Loss (PPO-Clip)
            new_log_probs = policy_net.log_prob(states, actions)
            ratio = exp(new_log_probs - old_log_probs)
            surr1 = ratio * advantages
            surr2 = clip(ratio, 1 - epsilon, 1 + epsilon) * advantages
            policy_loss = -mean(min(surr1, surr2))

            # Critic Loss
            value_pred = value_net(states)
            value_loss = mean((value_pred - returns) ** 2)

            # Entropy Bonus
            entropy = policy_net.entropy(states)

            # Total Loss
            loss = policy_loss + c1 * value_loss - c2 * entropy

            # Update
            optimizer.zero_grad()
            loss.backward()
            clip_grad_norm_(all_params, max_grad)
            optimizer.step()

    # ── Step 4: 更新旧策略 ──────────────────
    # PPO: θ_old 隐式通过 buffer 中存储的 old_log_probs 实现
    buffer.clear()

各PG算法在Pipeline中的差异点

算法 优势估计 \(\hat{A}_t\) 策略更新方式 数据复用
REINFORCE \(G_t\)(MC Return) \(\nabla\log\pi \cdot G_t\) 1次
REINFORCE w/ Baseline \(G_t - V(s_t)\) \(\nabla\log\pi \cdot (G_t - V)\) 1次
A2C \(\delta_t\) 或 n-step \(\nabla\log\pi \cdot \hat{A}\) 1次
TRPO GAE 自然梯度 + KL约束 1次
PPO GAE Clipped Objective K次

评论 #