近端策略优化(Proximal Policy Optimization, PPO)是足式机器人强化学习训练中最广泛采用的 on-policy 算法。在本项目中,RSL-RL 库对 PPO 进行了面向大规模并行环境的工程化实现:支持数千个环境实例同时采集数据、通过广义优势估计(GAE)稳定方差、以 clipped surrogate objective 约束策略更新的幅度,并内置自适应学习率调度与分布式多卡梯度同步。掌握本页内容后,你将能够读懂训练日志中的每一项损失指标,并根据任务特性调整超参数。
Sources: ppo.py, on_policy_runner.py
PPO 在训练管线中的位置
在整体架构中,PPO 算法类处于承上启下的核心位置。它向下通过 RolloutStorage 管理采样数据,向上通过 OnPolicyRunner 与环境交互并驱动训练循环。策略网络(Actor)与价值网络(Critic)作为独立模块被注入到 PPO 中,使得算法本身不耦合网络结构细节。
flowchart TB
Env[并行矢量环境 VecEnv] -->|obs, reward, done| Runner[OnPolicyRunner]
Runner -->|obs| PPO[PPO 算法]
PPO -->|actions| Runner
Runner -->|step data| Storage[RolloutStorage]
PPO -.->|读取| Storage
PPO -->|loss, grad| Policy[ActorCritic 网络]
Policy -->|updated params| PPO
上图展示了单次训练迭代的闭环:Runner 负责调用环境步进与算法接口;PPO 负责在 act() 中采样动作、在 process_env_step() 中存储转移、在 compute_returns() 中计算回报,最后在 update() 中执行多轮 mini-batch 梯度更新。
Sources: ppo.py, on_policy_runner.py
核心训练循环:采样、估计、更新
一个完整的 PPO 训练迭代可分为三个阶段,全部在 OnPolicyRunner.learn() 的循环体内顺序执行。
sequenceDiagram
participant Runner as OnPolicyRunner
participant PPO as PPO
participant Env as VecEnv
participant Storage as RolloutStorage
rect rgb(240,248,255)
Note over Runner,Storage: Phase 1: Rollout(推理模式)
loop num_steps_per_env 次
Runner->>PPO: act(obs)
PPO-->>Runner: actions
Runner->>Env: step(actions)
Env-->>Runner: next_obs, rewards, dones, extras
Runner->>PPO: process_env_step(obs, rewards, dones, extras)
PPO->>Storage: add_transition(transition)
end
end
rect rgb(255,250,240)
Note over Runner,Storage: Phase 2: 计算回报
Runner->>PPO: compute_returns(last_obs)
PPO->>PPO: GAE 反向递归
PPO->>Storage: returns, advantages
end
rect rgb(245,255,245)
Note over Runner,Storage: Phase 3: 策略更新(训练模式)
Runner->>PPO: update()
loop num_learning_epochs × num_mini_batches
PPO->>Storage: mini_batch_generator
Storage-->>PPO: batch
PPO->>PPO: 计算 surrogate / value / entropy loss
PPO->>PPO: backward() + clip_grad_norm()
PPO->>PPO: optimizer.step()
end
PPO-->>Runner: loss_dict
end
第一阶段 Rollout 在 torch.inference_mode() 上下文中执行,确保前向传播不构建计算图。act() 方法从当前策略采样动作,同时记录该状态下的价值估计、动作对数概率、分布均值与方差。process_env_step() 在环境步进后被调用,将上述转移数据写入 RolloutStorage,并更新观测归一化统计量。若发生超时(time_outs),还会对奖励进行 bootstrap 修正。
第二阶段 计算回报 通过 compute_returns() 完成。该方法从最后一个状态的价值估计开始,反向遍历整条轨迹,利用 GAE 计算优势函数 A(s_t, a_t) 与回报 R_t。
第三阶段 策略更新 调用 update(),从 RolloutStorage 中按 mini-batch 反复采样,执行多次 epoch 的梯度下降。每次迭代都会重新计算当前策略下的动作对数概率,并与旧策略的比值构建 surrogate loss。
Sources: ppo.py, on_policy_runner.py
GAE 与回报计算
广义优势估计(Generalized Advantage Estimation)是 PPO 稳定训练的关键。本项目中的实现采用反向递归方式,同时计算回报与优势。
具体而言,对于每个时间步 t(从轨迹末端向前遍历),算法首先计算 TD 残差:
delta_t = r_t + gamma * V(s_{t+1}) * (1 - done_t) - V(s_t)
随后更新优势估计:
A_t = delta_t + gamma * lambda * (1 - done_t) * A_{t+1}
最终回报为优势与价值估计之和:R_t = A_t + V(s_t)。在默认配置下,优势会在整批数据上全局归一化(均值为 0、标准差为 1);若启用 normalize_advantage_per_mini_batch,则改为在每个 mini-batch 内部独立归一化,适用于批次间差异极大的场景。
Sources: ppo.py
损失函数拆解
PPO 的总损失由三项核心组件构成,源码中在 update() 内逐 batch 计算并求和:
graph LR
A[obs_batch] --> B[重新计算<br/>log_prob, value, entropy]
B --> C[Surrogate Loss]
B --> D[Value Loss]
B --> E[Entropy Loss]
C --> F[Total Loss]
D --> F
E --> F
F --> G[backward + step]
Surrogate Loss(代理损失) 是 PPO 的灵魂。设旧策略下的动作对数概率为 log_pi_old,当前策略下为 log_pi,则概率比 ratio = exp(log_pi - log_pi_old)。PPO 采用 clipped 目标函数约束更新幅度:
surrogate = -advantage * ratio
surrogate_clipped = -advantage * clamp(ratio, 1 - clip_param, 1 + clip_param)
surrogate_loss = max(surrogate, surrogate_clipped).mean()
取两者中的较大值(注意前面有负号,因此实际上是取保守估计),防止策略因优势估计的噪声而发生剧烈变化。
Value Loss(价值损失) 默认启用 clipped 版本。与策略损失类似,价值更新也被限制在 clip_param 范围内,避免价值函数过度拟合单次采样数据:
value_clipped = old_value + clamp(new_value - old_value, -clip_param, +clip_param)
value_loss = max((new_value - returns)^2, (value_clipped - returns)^2).mean()
若关闭 use_clipped_value_loss,则退化为普通的均方误差 (returns - value)^2.mean()。
Entropy Bonus(熵奖励) 通过在总损失中减去 entropy_coef * entropy.mean() 来鼓励策略探索。entropy 由动作分布(高斯分布)的熵计算得到,维度上对所有动作求和。
最终总损失为:
loss = surrogate_loss + value_loss_coef * value_loss - entropy_coef * entropy
Sources: ppo.py
超参数全景与调优指南
下表汇总了 PPO.__init__ 中所有核心超参数及其在配置文件中的典型值。理解这些参数之间的相互作用,是调优训练稳定性的前提。
| 参数名 | 类型 | 典型值 | 作用说明 | 调优建议 |
|---|---|---|---|---|
learning_rate |
float | 1e-3 | 策略与价值网络共享的 Adam 初始学习率 | 若训练发散,优先降低至 3e-4 或 1e-4 |
num_learning_epochs |
int | 5 | 每次迭代对同一份数据重复更新的 epoch 数 | 增大可提升样本效率,但过高会导致过拟合 |
num_mini_batches |
int | 4 | 将 rollout 数据切分为多少个 mini-batch | 决定 mini-batch size = num_envs × num_steps / num_mini_batches |
clip_param |
float | 0.2 | PPO 裁剪阈值 ε | 一般保持 0.2;对高噪声任务可尝试 0.1~0.3 |
gamma |
float | 0.99 | 折扣因子 | 足式机器人通常用 0.99;若奖励稀疏可尝试 0.995 |
lam |
float | 0.95 | GAE 指数加权系数 λ | 增大 λ 偏差更小、方差更大;常用 0.92~0.95 |
value_loss_coef |
float | 1.0 | 价值损失的权重系数 | 若价值损失主导训练,可适当降低至 0.5 |
entropy_coef |
float | 0.01 | 熵奖励的权重系数 | 训练初期可稍高(0.01~0.02),后期衰减 |
max_grad_norm |
float | 1.0 | 梯度裁剪的 L2 范数上限 | 防止梯度爆炸,通常保持 1.0 |
use_clipped_value_loss |
bool | true | 是否对价值损失应用 clipped 更新 | 建议保持开启,稳定价值估计 |
normalize_advantage_per_mini_batch |
bool | false | 是否按 mini-batch 而非全局归一化优势 | 批次分布极不均匀时开启 |
schedule |
str | adaptive | 学习率调度策略:adaptive 或 fixed | 建议 adaptive,根据 KL 自动调节 |
desired_kl |
float | 0.01 | 自适应学习率的目标 KL 散度 | 若策略更新过快,降低 desired_kl |
Sources: ppo.py, example_config.yaml
自适应学习率调度
本项目实现了基于 KL 散度的自适应学习率机制。当 schedule 设为 adaptive 时,每次 mini-batch 更新前会计算旧策略与新策略之间的 KL 散度,并据此缩放学习率:
flowchart TD
A[计算 KL mean] --> B{KL > desired_kl × 2 ?}
B -->|是| C[lr = max(1e-5, lr / 1.5)]
B -->|否| D{KL < desired_kl / 2<br/>且 KL > 0 ?}
D -->|是| E[lr = min(1e-2, lr × 1.5)]
D -->|否| F[保持当前 lr]
C --> G[同步到所有 GPU]
E --> G
F --> G
KL 的计算采用高斯分布的闭式表达式,涉及均值与标准差的逐维度求和。在分布式多卡训练中,KL 均值会先通过 all_reduce 聚合,再由主进程(rank 0)判断学习率调整方向,最后通过 broadcast 同步到所有进程,确保各卡学习率严格一致。
若将 schedule 设为 fixed,则跳过上述逻辑,学习率始终保持初始值不变。
Sources: ppo.py
数据存储与采样机制
RolloutStorage 是 PPO 的数据中枢,其形状由 num_transitions_per_env(即 num_steps_per_env)和 num_envs 共同决定。在 Rollout 阶段,每个时间步的观测、动作、奖励、价值、动作对数概率、分布参数等都被写入预分配的零张量中,避免动态内存分配带来的开销。
进入更新阶段后,mini_batch_generator 将所有环境的所有时间步展平为一维索引,通过 torch.randperm 打乱顺序,然后按 num_mini_batches 均匀切片。每个 mini-batch 包含 num_envs × num_steps / num_mini_batches 条转移记录。若策略为循环网络(Recurrent),则改用 recurrent_mini_batch_generator,该生成器会以 episode 边界为界切分轨迹并进行填充(padding),确保隐藏状态在时间维度上正确传递。
Sources: rollout_storage.py, rollout_storage.py
与扩展模块的集成接口
本项目的 PPO 实现预留了多个扩展接口,使核心算法代码保持简洁,同时允许在不修改 PPO 主体结构的情况下叠加高级功能:
- RND(随机网络蒸馏):通过
rnd_cfg配置注入,在process_env_step()中将内在奖励叠加到外在奖励上,在update()中独立优化 RND 预测器。RND 的详细原理与配置请参考 RND 好奇心探索与对称性增强。 - 对称性增强:通过
symmetry_cfg配置注入,支持数据增强(data augmentation)与镜像损失(mirror loss)两种模式。对称性逻辑在update()的 mini-batch 循环内部被调用,对观测和动作进行对称变换后重新计算损失。详细内容请参考 RND 好奇心探索与对称性增强。 - 辅助损失(Aux Loss):通过
enable_aux_loss与aux_loss_coef控制,允许 Actor-Critic 网络输出额外的辅助任务损失并汇入总损失。
此外,PPO 还有一个专门用于模仿学习的子类 PPOAMP,它在标准 PPO 基础上增加了 AMP 判别器与风格奖励混合逻辑。AMP 相关内容请参考 AMP 对抗运动先验算法。
Sources: ppo.py, ppo_amp.py
典型配置示例
以下摘录自 example_config.yaml 的 algorithm 段落,展示了一个足式机器人行走任务的默认 PPO 配置:
algorithm:
class_name: PPO
learning_rate: 0.001
num_learning_epochs: 5
num_mini_batches: 4
schedule: adaptive
value_loss_coef: 1.0
clip_param: 0.2
use_clipped_value_loss: true
desired_kl: 0.01
entropy_coef: 0.01
gamma: 0.99
lam: 0.95
max_grad_norm: 1.0
normalize_advantage_per_mini_batch: false
其中 num_mini_batches: 4 配合常见的 num_envs = 4096 与 num_steps_per_env = 24,可得到单个 mini-batch 大小为 4096 × 24 / 4 = 24576。该批次规模足以保证梯度估计的稳定性,同时每个 epoch 内存在 4 次参数更新,兼顾了样本效率与计算效率。
Sources: example_config.yaml
调试与常见问题
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 策略损失(surrogate)剧烈震荡 | learning_rate 过高或 clip_param 过大 |
查看 KL 散度日志,若持续超过 desired_kl × 2,降低初始学习率或启用更保守的 clip_param |
| 价值损失(value)持续增大 | 价值函数拟合困难或 value_loss_coef 失衡 |
检查 use_clipped_value_loss 是否开启;尝试降低 value_loss_coef |
| 熵(entropy)快速衰减至接近 0 | 探索不足,entropy_coef 过低 |
适当提高 entropy_coef 或加入熵衰减 schedule |
| 训练后期回报停滞 | 陷入局部最优或优势估计偏差 | 调整 gamma 与 lam 组合;检查 GAE 归一化方式 |
Sources: ppo.py
下一步阅读
PPO 算法本身不定义网络结构,它依赖于外部注入的 Actor-Critic 模块。在理解了本页的训练循环与损失计算后,建议你继续深入了解策略网络的具体实现:
- Actor-Critic 网络架构详解 —— 解析 Actor 与 Critic 的 MLP 结构、观测归一化、动作噪声建模
- On-Policy Runner 训练循环 —— 完整剖析 Runner 如何 orchestrate 环境与算法之间的交互
- AMP 对抗运动先验算法 —— 了解 PPOAMP 子类如何融合模仿学习