倒立摆
本例学习如何用离线运动轨迹训练倒立摆世界模型,再训练 PPO 策略,将摆杆从不同初态摆起并保持直立。按下面七个步骤完成数据准备、训练和 Gym 仿真评估。
1. 任务背景与目标
摆起与平衡是两个连续的控制过程
倒立摆由一根绕固定转轴旋转的摆杆和转轴电机构成。摆杆自然下垂时接近稳定平衡,竖直向上时则很容易因小扰动倒下。电机施加的力矩有限,控制器需要先利用来回摆动积累动能,再在接近正上方时减速,最后持续修正偏差。
这个任务把“如何到达目标”和“到达后如何保持”放在同一段控制过程中。只记录角度无法判断摆杆正在靠近还是远离目标,所以观测还必须包含角速度。同样的角度下,高速摆动与缓慢偏移需要不同的动作。
角度用 cos θ、sin θ 表示,避免从 π 跳到 −π 时出现数值突变。世界模型学习“当前姿态+角速度+力矩”如何决定下一时刻状态;策略利用该模型提前比较动作对后续运动的影响。
状态、动作与目标
控制器通过力矩将摆杆摆起,并尽量稳定在正上方(θ = 0)。 状态为 states = (cos θ, sin θ, θ̇),动作为 actions = torque,范围为 [-2, 2]。 任务既要完成摆起,也要减少倒立后的角度偏差与角速度;不能只看最后一步姿态。
图:任务对象及其作用与反馈关系。实线表示物理作用,虚线表示观测与控制信号。
examples/pendulum/reward.py 使用当前状态和裁剪后的动作计算:
θ = atan2(sin θ, cos θ)
r_t = -(θ² + 0.1 × θ̇² + 0.001 × u²), u = clip(actions, -2, 2)回报越接近 0 越好。它包含角度、角速度和控制代价,不是单独的角度 MAE。 最终评估使用 Gym 环境返回的 reward;比较时需固定后端版本、初态种子、动作处理与回合长度。
2. 数据与准备
数据必须同时覆盖摆起阶段和目标附近的平衡阶段:前者体现大范围姿态变化,后者体现细小力矩对稳定性的影响。每行记录同一控制时刻的状态与实际执行力矩,并保留完整轨迹边界,以便从相邻记录学习状态转移。
标准数据 data/pendulum.npz 是 100 条轨迹 × 200 步,共 20000 条记录。
| Key | 标准 Shape | 含义 |
|---|---|---|
states | [20000, 3] | 当前状态 (cos θ, sin θ, θ̇) |
actions | [20000, 1] | 历史控制力矩 |
index | [100] | 每条轨迹的结束下标(exclusive) |
next_states 由数据加载器在每条轨迹内部派生,不能跨越 index 指定的轨迹边界。 数据可直接用于训练,无需另写预处理脚本;20000 条原始记录不等于 20000 个有效相邻状态转移。
标准数据已随源码提供,完整检出后直接使用 examples/pendulum/data/pendulum.npz。 不需要额外下载或环境交互采集。
训练与离线选模只使用数据和学到的世界模型,不需要原采集控制器,也不调用 Gym。 最终仿真测试才需要对应的 Gym 依赖。
3. 建模与配置
下面先展示 examples/pendulum/config.yaml 的配置,再结合本任务逐块解释。训练命令使用同一文件。
name: pendulum_revive_repro
version: '1.0'
graph:
nodes:
actions:
inputs: [states]
network:
backbone: res
hidden_dims: [256, 256, 256, 256]
activation: leakyrelu
output_dist: TanhNormal
delta_states:
inputs: [states, actions]
network:
backbone: res
hidden_dims: [256, 256, 256, 256]
activation: leakyrelu
output_dist: TanhNormal
next_states:
inputs: [states, delta_states]
function: builtin.delta_add
transitions: auto
columns:
states:
- {name: cos_theta, min: -1.0, max: 1.0}
- {name: sin_theta, min: -1.0, max: 1.0}
- {name: theta_dot, min: -8.0, max: 8.0}
actions:
- {name: torque, min: -2.0, max: 2.0}
data: {train_ratio: 0.5, batch_size: 256}
training:
device: auto
stages:
- name: venv
algorithm: venv.revive_p
hyperparameters:
epochs: 1000
bc:
optimizer: {lr: 0.0001, weight_decay: 1.0e-06, scheduler: none, lr_decay: 0.99}
grad_clip: 50
loss: nll
adversarial:
start_epoch: 0
rollout: {horizon: 50, batch_size: 1024}
validation:
selection: {metric: val/one_step/mae}
one_step: {force_final: true}
rollout: {enabled: true, horizon: 50, interval: 10, num_trajectories: 10}
- name: policy
algorithm: policy.ppo
inherit_from: venv
hyperparameters:
epsilon: 0.2
gamma: 0.99
gae_lambda: 0.95
entropy_coef: 0.0
value_coef: 1.0
segment_sampling_mode: timestep_uniform
ppo_runs: 2
ppo_batch_num: 4
w_vl2: 0.001
bpc_type: bc
bpc_weight: 0.01
generate_deter: 1
num_rollout_trajs: 256
value_hidden_dims: [256, 256, 256, 256]
policy_nodes: [actions]
epochs: 1000
bc_pretrain_epochs: 0
rollout_horizon: 100
optimizer: {lr: 1.0e-05}
grad_clip: 50
reward: {path: reward.py, function: get_reward}
output:
save_freq: 10
tensorboard: true
onnx: {required: true}配置入口
| 配置 | 世界模型阶段 | 策略阶段 | 用途与运行规模 |
|---|---|---|---|
examples/pendulum/config.min.yaml | venv.bc | policy.ppo | 最小声明;结合 smoke 检查两阶段流程 |
examples/pendulum/configs/bc_ppo.yaml | venv.bc | policy.ppo | 监督世界模型与 PPO;500 + 1000 epoch |
examples/pendulum/config.yaml | venv.revive_p | policy.ppo | REVIVE-P + PPO 完整训练方案;1000 + 1000 epoch |
建议先 smoke,再选择一条完整训练方案。config.min.yaml 包含世界模型和 PPO 两个阶段; “最小”是字段少,不是单阶段,也不保证默认训练规模短。缩减验证规模的是 --profile smoke。 BC + PPO 概念上更易理解,但其正式训练规模仍较大,不能把它当作快速冒烟。
bash run_all.sh 的默认配置仍是 config.yaml,不会因为本文推荐 BC 入门而自动切换。 两条完整路线的区别不止是否对抗:
| 项目 | config.yaml | configs/bc_ppo.yaml |
|---|---|---|
| 世界模型算法/epoch | REVIVE-P / 1000 | BC / 500 |
| 策略与动力学网络 | RES,4 × 256 | MLP,3 × 256 |
| PPO 学习率 | 1e-5 | 4e-5 |
| 状态边界 | 显式写 cos/sin/角速度边界 | 仅声明列名 |
| 世界模型验证 | 显式单步选模及 50 步 rollout | 使用默认验证配置 |
| PPO 行为约束/确定性推演 | 显式配置 | 使用默认值,需核对解析配置 |
两套方案同时调整了网络、训练轮数与优化参数,结果按完整方案比较。 未声明参数和实际生效值见训练结果中的 config.resolved.yaml。
图结构
policy: states -> actions (TanhNormal)
dynamics: states, actions -> delta_states (TanhNormal)
compose: states, delta_states -> next_states (builtin.delta_add)
transition: states <- next_statesstates 是策略观测,actions 是控制输出,delta_states 是未知动力学的学习对象。 PPO 的交互对象是学到的状态转移模型;最终测试的交互对象才是独立 Gym 环境。
配置逐块讲解
graph:变量与模型结构
策略根据姿态与角速度输出力矩,动力学网络根据状态和力矩预测增量。两者采用四层 256 维残差网络。这样既保留动作如何影响运动的关系,也把已知的状态加法与需要学习的动力学区分开。
列定义中 cos/sin 范围为 ±1、角速度范围为 ±8、力矩范围为 ±2,对应本基准定义。 transitions: auto 从 next_states 推断 states <- next_states; builtin.delta_add 将增量与当前状态相加。动作边界也用于部署约定,迁移任务时需按实际执行器范围重新设置。
data:划分与批量
train_ratio: 0.5 将数据分为训练与验证两部分,batch_size: 256 决定每批用于学习转移关系的样本数。轨迹边界用于保证下一状态来自同一次摆动过程。验证集用于观察模型对未参与参数更新的轨迹的预测质量。
training.stages[venv]:世界模型
| 参数 | 当前设置与作用 |
|---|---|
algorithm / epochs | venv.revive_p / 1000 |
bc.loss / bc.optimizer.lr / bc.grad_clip | nll / 1e-4 / 50 |
adversarial.start_epoch | 0,从首轮进入对抗训练 |
adversarial.rollout.horizon / batch_size | 50 / 1024,训练多步窗口与批量 |
validation.selection.metric | val/one_step/mae,沿用该配置方案的单步选模 |
validation.rollout | 每 10 轮记录 50 步 rollout,10 条验证轨迹窗口 |
training.stages[policy]:PPO 策略
algorithm: policy.ppo,inherit_from: venv 消费上一阶段选出的世界模型。
| 参数 | 当前设置与作用 |
|---|---|
epochs / rollout_horizon | 1000 / 100,在模型内优化多步回报 |
num_rollout_trajs | 256,这是采样数量,不是状态覆盖保证 |
bpc_type / bpc_weight | bc / 0.01,对偏离历史行为施加约束,不是硬安全边界 |
optimizer.lr | 1e-5 |
generate_deter | 1,确定性模型内推演 |
epsilon / gamma / gae_lambda | 0.2 / 0.99 / 0.95 |
reward.path / reward.function | reward.py / get_reward |
reward.path 相对 YAML 所在目录解析,因此 BC 配置写作 ../reward.py,仍指向同一奖励文件。 PPO 阶段未在这份 YAML 中显式写出验证块,使用框架的策略验证/回报选模默认; 完整生效设置以解析配置和报告为准,不应假设与所有变体完全一致。
output:输出文件与导出
默认 best_only 保留最优模型及同轮续训态,tensorboard: true 记录训练指标;save_freq 仅在 legacy 模式生效。 onnx.required: true 要求 ONNX 导出和一致性校验通过。 训练成功、导出通过与策略控制效果达标是三件不同的事。
4. 训练与选模
数据准备完成后,进入案例目录。后续训练与评估命令均在此目录执行:
cd examples/pendulum先检查最小两阶段流程
revive validate --config config.min.yaml --data data/pendulum.npz
revive train --config config.min.yaml --train-data data/pendulum.npz \
--run-id pendulum-case-smoke --log-dir logs --seed 42 --profile smoke检查 logs/pendulum-case-smoke/report.md 与 models/。设备未指定时使用框架默认选择; 需要固定 CPU 时,在自己的配置副本中设置 training.device: cpu,保留原有 stage 声明。 smoke 用于检查数据、训练与导出流程。
再选择一条完整训练方案
源码用户可选择 BC + PPO 入门训练:
revive validate --config configs/bc_ppo.yaml --data data/pendulum.npz
revive train --config configs/bc_ppo.yaml --train-data data/pendulum.npz \
--run-id repro_bc --log-dir logs --seed 42选择 REVIVE-P + PPO 时,使用以下训练命令:
revive validate --config config.yaml --data data/pendulum.npz
revive train --config config.yaml --train-data data/pendulum.npz \
--run-id repro --log-dir logs --seed 42训练按 YAML 的 training.stages 顺序执行,CLI 不接受 --stages; 若只训练世界模型,可在配置副本中保留世界模型阶段。 每次训练使用独立 run ID。
查看对应 run 的报告、解析配置和阶段验证指标,确认世界模型的选模指标、策略的模型内回报及导出状态。 最终加载 logs/<run_id>/models/policy.pt,不要仅看训练损失就宣称可上线。
可选分析与一键运行
需要汇总训练指标与控制效果时,可运行:
python analyze.py --run_id repro_bc --config configs/bc_ppo.yaml --episodes 100 --steps 200analyze.py 不只是读取日志:存在策略模型时,它会再运行一次 Gym 评估,并写入/覆盖 reports/<run_id>/analysis.md。 已完成评估但只想看报告时,直接打开已有文件即可。
一键脚本是分步命令的替代,不要重复执行同名训练:
bash run_all.sh repro
bash run_all.sh repro_bc configs/bc_ppo.yaml
SEED=7 bash run_all.sh repro_s7默认仍为 config.yaml、训练 seed 42。脚本先训练,再调用 analyze.py,最后调用 evaluate.py --json 写 logs/<run_id>/flagship.json,因此通常会重复调用独立仿真评估两次。 默认协议与 seeds 相同,不能计作两组新增的独立测试样本。 一键脚本包含外部仿真测试。只进行离线训练时,可使用前面的分步命令;需要仿真评估时,先固定策略和测试规模。
核心文件为 examples/pendulum/reward.py、examples/pendulum/evaluate.py、examples/pendulum/analyze.py 和 examples/pendulum/run_all.sh。训练日志与导出模型在 logs/<run_id>/,可选分析报告在 reports/<run_id>/。
5. 模型使用与评估
以 BC + PPO 的训练结果为例:
python evaluate.py --model logs/repro_bc/models/policy.pt --config configs/bc_ppo.yaml \
--episodes 100 --steps 200 --backend gymREVIVE-P + PPO 对应:
python evaluate.py --model logs/repro/models/policy.pt --config config.yaml \
--episodes 100 --steps 200 --backend gym评估协议
| 参数 | 默认值 |
|---|---|
--episodes | 100 |
--steps | 200 |
--backend | gym |
默认确定性动作,执行前裁剪至 [-2, 2],每集重置策略状态;评估种子为 0~99。 可选 gymnasium 后端用于兼容性验证,但不能未经核对就与另一后端的结果混算。 训练 seed 42 与评估环境的初态 seeds 是不同概念。
评估器打印回报的均值、标准差、分位数、极值,以及超过回报阈值的比例和动作饱和比例。 当前人类可读输出中的 return > -150、return < -500 等只是基准诊断,不是通用业务验收门槛。 比较时同时看平均控制效果、低回报尾部和波动,不能只看最优一集。 加 --json 输出回报均值/标准差/中位数/极值及动作饱和比例;该选项仍会重新运行仿真。
外部评估的 real_return_mean 是每集奖励之和的均值;当前框架的 val/rollout/reward_mean 是平均每步奖励,val/rollout/return_mean 才是整段回报。 本例 PPO 的训练推演窗口是 100 步,外部测试是 200 步,不能直接对两者的回报和作差; 归一到每步后仍要考虑初态与访问状态分布的差异。
6. 结果与分析
评估环境为 Gym Pendulum-v1,训练 seed 42,测试 100 集、每集 200 步,环境 seeds 为 0~99。评估使用确定性动作,并将力矩裁剪到 [-2, 2]。
策略控制效果
| 指标 | 数据行为 | BC + PPO | REVIVE-P + PPO |
|---|---|---|---|
| 平均回报 | -610.72 | -146.77 | -185.41 |
| 回报标准差 | 129.75 | 79.75 | 106.19 |
| 回报高于 -150 的比例 | 0.00 | 0.74 | — |
BC + PPO 对应 configs/bc_ppo.yaml,REVIVE-P + PPO 对应 config.yaml。两种策略的平均回报都更接近 0;BC + PPO 的回报波动较小,100 集中有 74 集高于 −150。
数据行为列按 NPZ 的 index 汇总每条采集轨迹的奖励。它用于理解数据中的控制水平;策略间的比较则使用相同的测试初态和回合长度。
世界模型与控制效果
| 指标 | BC + PPO | REVIVE-P + PPO |
|---|---|---|
| 世界模型多步 MAE | 0.2941 | 0.2898 |
| 世界模型单步 MAE | 0.1131 | 0.1075 |
REVIVE-P 的模型误差较低,而 BC + PPO 的控制回报较好,说明选模时需要分别观察预测质量与控制效果。两份配置还包含网络结构和优化参数差异,适合作为完整训练方案的对比。
阅读自己的结果时,先看是否成功摆起,再看直立后的摆动、动作饱和比例和低回报回合。多次训练可使用不同训练 seed,并保持同一套测试初态。
7. 迁移到实际业务
迁移到自己的业务
迁移到业务时依次确认变量与单位、轨迹边界、观测和因果关系,再调整训练阶段、奖励与独立验收标准。 不要照搬倒立摆的动作范围、奖励系数或预算。世界模型误差、模型内回报、外部控制效果应分别报告; 没有可运行的原控制器时,要明确日志对照的局限。
阅读任务页约定,区分数据准备、训练验证和独立评估。