🤖 roboto_origin_03 Wiki
首页 / 训练 / PPO 核心算法与超参数

近端策略优化(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

第一阶段 Rollouttorch.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 主体结构的情况下叠加高级功能:

此外,PPO 还有一个专门用于模仿学习的子类 PPOAMP,它在标准 PPO 基础上增加了 AMP 判别器与风格奖励混合逻辑。AMP 相关内容请参考 AMP 对抗运动先验算法

Sources: ppo.py, ppo_amp.py

典型配置示例

以下摘录自 example_config.yamlalgorithm 段落,展示了一个足式机器人行走任务的默认 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 = 4096num_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
训练后期回报停滞 陷入局部最优或优势估计偏差 调整 gammalam 组合;检查 GAE 归一化方式

Sources: ppo.py

下一步阅读

PPO 算法本身不定义网络结构,它依赖于外部注入的 Actor-Critic 模块。在理解了本页的训练循环与损失计算后,建议你继续深入了解策略网络的具体实现: