策略梯度方法全解
策略梯度(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中,这个矛盾更加根本——它出现在回报估计这一步。
策略梯度的核心公式是:
其中 \(\Psi_t\) 是某种形式的"信号",用来告诉梯度"这个动作有多好"。不同PG方法的根本区别,就在于 \(\Psi_t\) 的选择。
MC vs TD:两个极端
Monte Carlo Return(蒙特卡洛回报):
- 零偏差: \(G_t\) 是回报的无偏估计,因为它使用了完整的真实奖励序列,不依赖任何函数近似。
- 高方差: \(G_t\) 依赖从时刻 \(t\) 到回合结束的所有随机性(环境转移的随机性 + 策略选择的随机性),随机因素越多,估计值的波动越大。
TD Target(时序差分目标):
- 低方差: \(\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\) 是智能体与环境交互的完整记录:
轨迹的概率由策略和环境动力学共同决定:
其中 \(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_probs 和 values 是在数据收集时用旧参数计算的,后续会用于计算重要性权重和优势函数。
回报与目标计算
收集完数据后的第一步,是为每个时间步计算一个"信号",告诉策略梯度:这个动作到底有多好?不同的计算方式对应不同的bias-variance特性。
Monte Carlo Return
最直接的方式:等到一个episode结束,计算从当前时步到回合结束的折扣累积奖励。
其中 \(\gamma \in [0, 1)\) 是折扣因子(Discount Factor):
- \(\gamma = 0\):只关心即时奖励,完全短视
- \(\gamma \to 1\):几乎等权地考虑所有未来奖励,极度远视
- 常用值:\(\gamma = 0.99\)(考虑约100步的未来)
递推计算: 从轨迹末端反向计算效率最高。
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)。
- 低方差: 只涉及一步随机性
- 有偏差: \(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。
- \(n = 1\):退化为TD(0) Target \(r_t + \gamma V(s_{t+1})\)
- \(n = T - t\):退化为MC Return(不再Bootstrap)
- \(n\) 越大,偏差越小,方差越大
Bias-Variance分析:
偏差来自 \(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学习的核心信号:
直觉理解: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\),输出一个标量值。
损失函数: 最小化价值估计与目标值之间的均方误差。
其中目标值 \(V_t^{\text{target}}\) 可以取:
- MC Return: \(V_t^{\text{target}} = G_t\)(无偏但高方差)
- TD Target: \(V_t^{\text{target}} = r_t + \gamma V_{\phi_{\text{old}}}(s_{t+1})\)(低方差但有偏)
- 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),目标网络仍然是标配:
训练流程
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\) 作为信号:
问题在于 \(G_t\) 通常是一个很大的正数(尤其在奖励全为正的环境中),导致所有动作的梯度方向都是"增大概率",只是幅度不同。这使得梯度估计的方差极大。
基线减除(Baseline Subtraction): 引入一个只依赖状态的基线函数 \(b(s_t)\),将信号改为 \(G_t - b(s_t)\):
关键定理:减去任何只依赖状态的基线,不会引入偏差。
证明的核心在于 \(\sum_a \pi_\theta(a|s) = 1\) 对所有 \(\theta\) 恒成立,所以其对 \(\theta\) 的梯度恒为零。
最优基线: 理论上可以证明,使方差最小的最优基线为:
实践中,这个最优基线难以精确计算。经验表明,\(b(s) = V(s)\)(状态价值函数)已经是一个非常好的近似——它正好把"绝对价值"转化为"相对于平均水平的优势"。
Advantage Function
优势函数(Advantage Function) 定义为:
其中:
- \(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\) 的期望回报
性质:
- \(\mathbb{E}_{a \sim \pi}[A^\pi(s,a)] = 0\):优势的期望为零(好坏相消)
- \(A^\pi(s,a) > 0\):动作 \(a\) 优于策略 \(\pi\) 在状态 \(s\) 的平均水平
- \(A^\pi(s,a) < 0\):动作 \(a\) 劣于平均水平
用优势函数替代原始回报,策略梯度变为:
这就是我们在PPO和其他现代PG算法中看到的标准形式。
GAE (Generalized Advantage Estimation)
GAE是Schulman et al.在2016年提出的优势估计方法,也是目前工业界的事实标准。它的核心思想是:对不同步长的TD误差做指数加权平均,用参数 \(\lambda\) 控制bias-variance的平衡点。
推导过程
首先,定义 \(n\)-step优势估计:
GAE被定义为这些 \(n\)-step优势的指数加权平均(权重为 \((1-\lambda)\lambda^{n-1}\)):
展开并化简(利用几何级数的性质),可以得到一个极其优雅的形式:
两个极端
- \(\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可以从轨迹末端反向递推计算,效率极高:
伪代码:
# 假设已有: 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)\)。根据策略梯度定理:
在实践中,我们通过定义一个代理目标函数(Surrogate Objective)来使用自动微分框架:
然后对 \(L^{PG}\) 做梯度上升(即最大化)。这个目标函数的梯度恰好等于策略梯度。
Vanilla Policy Gradient的问题:
- 步长敏感: 学习率太大 → 策略崩溃;太小 → 收敛极慢
- 参数空间 ≠ 策略空间: 参数空间中的"小步"可能导致策略空间中的"大跳",反之亦然
- 无单调改进保证: 一次更新可能让策略变差,而变差的策略会收集到更差的数据 → 恶性循环
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) 提供了策略空间中正确的"度量":
Fisher矩阵的物理含义:它描述了参数空间中每个方向上,策略分布变化的"灵敏度"。在灵敏度高的方向上应该走小步,灵敏度低的方向上可以走大步。
自然梯度(Natural Gradient):
自然梯度通过Fisher矩阵的逆来"矫正"梯度方向,使得在策略空间中的变化是均匀的。这等价于解如下约束优化问题:
即在KL散度约束下,找到使目标函数改进最大的更新方向。
实际困难: Fisher矩阵 \(F\) 的大小是 \(|\theta| \times |\theta|\),对于百万参数的网络,存储和求逆都不可行。这就是TRPO要解决的问题。
Trust Region
信赖域(Trust Region) 方法的核心思想:在当前参数附近构造目标函数的局部近似,并限制更新步长在这个近似"可信"的范围内。
在强化学习的语境下,信赖域约束通常用KL散度来定义:
为什么KL散度是正确的约束?
KL散度直接衡量两个策略分布之间的差异。如果新旧策略的KL散度小,那么:
- 重要性采样的权重 \(r_t(\theta)\) 接近1,方差可控
- 代理目标函数对真实目标函数的近似误差有界
- Kakade-Langford不等式保证策略性能的单调改进
KL散度与Fisher矩阵的关系:
这是KL散度在 \(\theta_{\text{old}}\) 处的二阶Taylor展开。可以看到,Fisher矩阵就是KL散度的Hessian矩阵(在 \(\theta_{\text{old}}\) 处)。
TRPO详解
TRPO(Trust Region Policy Optimization,Schulman et al., 2015)将上述思想形式化为一个带约束的优化问题:
求解步骤:
Step 1: 线性化目标函数
Step 2: 二次化KL约束
Step 3: 用Lagrange对偶求解
这是一个经典的线性目标+二次约束问题,解析解为:
即沿自然梯度方向 \(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的理论保证: 每一步更新都满足:
即策略性能单调不递减(在近似误差范围内)。
TRPO的工程问题:
- 实现复杂度高(共轭梯度 + Fisher向量积 + 线搜索)
- 不兼容参数共享(Actor-Critic共享层时KL约束难以处理)
- 不兼容Dropout/BatchNorm等正则化技术
- 每步计算开销大
PPO的Clipped Objective
PPO用一种极其简洁的方式近似了TRPO的信赖域约束(详见PPO.md)。核心公式:
其中 \(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:
其中 \(\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的完整目标函数中,熵奖励项鼓励策略保持一定的随机性:
其中策略熵 \(H(\pi_\theta(\cdot|s)) = -\sum_a \pi_\theta(a|s) \log \pi_\theta(a|s)\)。
作用:
- 防止早熟收敛: 策略可能在训练初期就坍缩到某个局部最优的确定性动作,熵奖励阻止这种情况
- 数值稳定: 防止动作概率趋近于零(\(\log 0 = -\infty\))
- 鼓励探索: 维持策略的多样性,有助于发现更优的策略
典型系数 \(c_2 = 0.01\)。对于探索需求高的环境,可以增大到 \(0.05\)。随训练进行可以线性衰减。
Mini-batch SGD
收集一批rollout数据后,不是一次性用全部数据算一个梯度,而是将数据打乱后分成多个mini-batch:
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_{\max} = 0.5\)。这在RL中尤其重要,因为RL的梯度方差本来就很大,偶尔出现的极大梯度会破坏训练。
Advantage Normalization(优势归一化)
在每个mini-batch内,对优势值做标准化:
其中 \(\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次 |