本文档属于 Robotics Tutorial 项目,作者:Pengfei Guo,达妙科技。采用 CC BY 4.0 协议,转载请注明出处。
第 63 章 腿足 RL 训练栈——IsaacGym/Lab + rsl_rl + 奖励设计 + Domain Randomization + Teacher-Student¶
难度: ⭐⭐ ~ ⭐⭐⭐⭐ | 预计学时: 30-40 小时(1.5 周) | text:code = 7:3(理论+实践混合章节)
一句话概要: 腿足 RL 不是"换个环境跑 PPO"——从 GPU 并行仿真到奖励工程、从 Domain Randomization 的贝叶斯解释到 Teacher-Student 的信息论本质,本章系统构建腿足 RL 训练栈的完整知识体系。
向前承接: Ch61-62 搭好了实时 C++ 和硬件栈的完整链路。Ch55 展示了 OCS2 MPC 的工业级方案。本章转入控制的另一条路线——用强化学习端到端地学习运动策略,而 MPC 的概念(如步态、接触约束)将作为奖励设计的参照基准。
向后指向: Ch64 将把本章训好的策略部署到 C++/ros2_control 实时控制循环。Ch65 则探索 RL 与 MPC 混合的前沿范式——两条路线的融合。
前置自测¶
📋 答不出 \(\geq\) 2 题 → 先回对应章节复习
- [RL 基础] PPO 的 clipped objective 要解决什么问题?如果不 clip,直接用 TRPO 的 KL 约束,有什么工程缺陷?
- [RL 基础] GAE(\(\lambda\)) 中的 \(\lambda\) 参数控制什么权衡?\(\lambda=0\) 和 \(\lambda=1\) 分别退化为什么?
- [Ch61] 为什么实时控制循环内不能调用
malloc?在 1 kHz 频率下,动态内存分配会导致什么后果? - [Ch55] OCS2 的 MPC 求解频率一般是多少?为什么不直接把 MPC 跑到 1 kHz?
- [Ch62] 四足机器人的关节电机延迟大约在什么量级?这对 RL 策略的 sim-to-real 有何影响?
本章目标¶
学完本章,你应能:
- 解释腿足 RL 与通用 RL 的关键差异,理解为什么需要专用的训练基础设施
- 在 IsaacLab 中配置并训练一个四足 trot 策略,理解 GPU 并行仿真的架构
- 精读 rsl_rl 的 PPO 实现——包括 GAE 完整推导和 clipped objective 的数学意义
- 系统地设计奖励函数,理解每个奖励项的物理动机和数学形式
- 从贝叶斯视角解释 Domain Randomization 为什么能提高鲁棒性
- 从信息论角度理解 Teacher-Student 蒸馏的本质
- 独立训练一个 Go2 trot 策略并在仿真中验证
63.1 腿足 RL 与通用 RL 的关键差异 ⭐¶
动机:为什么不能直接用 Atari/MuJoCo Gym 的方法?¶
如果你在 OpenAI Gym 的 HalfCheetah-v4 上训过 PPO,可能觉得 RL 训练就是"写好环境,跑 PPO,调调超参"。但当你把同样的方法搬到真实四足机器人上,几乎一定会失败。这不是算法不行——PPO 在腿足场景下仍然是最常用的算法——而是腿足 RL 面临的工程挑战与通用 RL 有本质不同。
理解这些差异,是选择正确工具链的前提。
如果不理解差异会怎样¶
一个常见的失败路径:研究者在 MuJoCo 的 Humanoid-v4 上训出了漂亮的行走策略,然后直接迁移到真机。结果:
- MuJoCo 的接触模型过于理想化,真机接触力完全不同
- 单环境训练太慢,训 48 小时还没收敛
- 训好的策略在真机上第一步就摔倒——sim-to-real gap 太大
- 没有 Domain Randomization,策略对摩擦系数极其敏感
这些失败促使了腿足 RL 专用训练栈的诞生。
差异的系统分析¶
| 维度 | 通用 RL (Atari/MuJoCo Gym) | 腿足 RL |
|---|---|---|
| 环境复杂度 | 规则固定(游戏/简化物理) | 地形、摩擦、电机动力学都是变量 |
| 观测维度 | 几十到几百 | 50-200(本体感知),加视觉可达数万 |
| 动作维度 | 几个到几十个 | 12-30 关节(四足12,人形20+) |
| 训练并行度 | 单环境或少量并行 | 4096+ 环境 GPU 并行——必须 |
| 训练时长 | 百万步/小时(小任务) | 数千万到数亿步,24-72 小时 GPU |
| 最大挑战 | 探索效率、奖励稀疏 | sim-to-real gap——仿真训好 \(\neq\) 真机能用 |
| 物理引擎 | MuJoCo / PyBullet (CPU) | IsaacGym / IsaacLab (GPU 原生) |
| RL 库 | SB3 / RLlib (通用) | rsl_rl (轻量,腿足专用) |
这些差异导致了三个核心技术选择:
- **GPU 原生仿真器**替代 CPU 仿真器——10-100 倍加速
- **轻量 RL 库**替代通用库——减少数据管道开销
- Domain Randomization + Curriculum + Teacher-Student——系统对抗 sim-to-real gap
历史脉络:腿足 RL 的三个时代¶
腿足 RL 并非一蹴而就。理解历史演进有助于把握当前技术栈为什么长这个样子:
第一时代:CPU 仿真 + 通用 RL (2017-2019)。早期工作如 Hwangbo et al. (2019, Science Robotics) 在 RaiSim(CPU 仿真器)上训练 ANYmal。训练一个策略需要数天到数周。这个阶段证明了"仿真训练,真机部署"的可行性,但训练效率是瓶颈。
第二时代:GPU 并行仿真爆发 (2020-2023)。IsaacGym (2021) 让训练时间从天降到小时。Rudin et al. (2022, CoRL) 的 "Learning to Walk in Minutes" 展示了 4096 并行环境下 20 分钟训好 ANYmal 行走。legged_gym + rsl_rl 成为事实标准。
第三时代:IsaacLab + 多模态 (2023-至今)。IsaacLab 逐步取代 IsaacGym,支持更丰富的传感器(深度相机、LiDAR)、更完整的场景资产和更活跃的维护生态。2026 年的 Isaac Lab 3.0 beta 开始引入 Newton backend 等新能力,但 API 仍需按官方版本锁定。训练范式从纯本体感知扩展到视觉+本体感知,rsl_rl 也开始支持更大规模的多 GPU 训练。
时间线:
2018: Tan et al. (RSS) — 首次四足 sim-to-real, 域随机化+执行器建模
2019: Hwangbo (Science Robotics) — ANYmal RL, 神经网络执行器建模, CPU 仿真
2019: Haarnoja et al. (RSS) — 真机直接训练 SAC, 自动温度调节
2020: Miki前身 (Science Robotics) — Teacher-Student + 地形课程, ANYmal
2021: IsaacGym 发布 — GPU 并行仿真, 小时级训练
2021: Kumar RMA (RSS) — 在线自适应, A1 真机
2022: Miki (Science Robotics) — Teacher-Student + 感知, ANYmal Alps 徒步
2022: Rudin (CoRL) — 20 分钟训练, legged_gym 开源
2022: Margolis Walk These Ways (CoRL) — 行为多样性, Go1
2023: IsaacLab 发布 — 取代 IsaacGym
2024: Hoeller (Science Robotics) — ANYmal Parkour
2026: IsaacLab 3.0 beta — Newton backend, 多 GPU, API 快速演进
值得展开的是 2018-2019 年的两项奠基性工作,它们确立了至今仍在沿用的 sim-to-real 范式:
Tan et al. (RSS 2018) 是首个在四足机器人上实现零样本 sim-to-real RL 迁移的工作。其核心公式——"精确执行器建模 + 域随机化 = 成功迁移"——至今仍是黄金法则。该工作的关键工程决策包括:(1) 使用直流电机物理方程(包含反电动势和磁饱和)替代物理引擎的理想化电机模型;(2) 通过维护观测历史记录并线性插值来模拟真实系统延迟;(3) 将控制器解耦为"开环参考信号 + 策略残差"的形式——即使策略输出为零,机器人也能按照参考轨迹执行基本步态,策略只需学习如何微调。这种"基准步态先验 + 残差调制"的范式大幅降低了学习难度。
Hwangbo et al. (Science Robotics 2019) 将执行器建模推进了一步:对于串联弹性执行器(SEA)这类复杂传动机构,解析建模的难度极高(涉及近百个参数),因此直接用监督学习训练一个"动作到力矩"的神经网络模型。训练数据的采集非常简单——用正弦波足端轨迹驱动机器人,配合人工干扰(推动和阻碍)来丰富数据集。该工作还揭示了一个违反直觉的发现:RL 策略学会了在运动学奇异点(膝盖完全伸直)附近操作以节省能量——传统方法必须刻意回避这些奇异点,而 RL 策略不直接依赖逆运动学矩阵求逆,因此规避了传统解析控制中的奇异点求解问题。不过这不等于奇异构型在物理上"无影响":力矩裕度、速度限幅、接触冲击和电机带宽仍然会约束策略可用的动作。
💡 执行器建模:sim-to-real 的隐形杀手
真实电机的力矩输出远非理想。Tan et al. (2018) 首次系统建模了以下非理想特性,其解析执行器模型由以下几个关键方程组成:
- 力矩-电流关系: \(\tau = K_t I\),电机输出的机械力矩与流过线圈的电流成正比
- 电压平衡方程: \(I = \frac{V_{pwm} - V_{emf}}{R}\),流过电机的电流并不直接等于电压除以电阻,施加在电机两端的有效电压必须先克服反电动势
- 反电动势(Back-EMF): \(V_{emf} = K_e \dot{q}\),当电机转动时产生一个与转速成正比、方向与输入电压相反的电压。这解释了为什么高速运动时力矩会衰减——随着转速 \(\dot{q}\) 增加,\(V_{emf}\) 增大,有效电压差 \((V_{pwm} - V_{emf})\) 减小,电流下降,最终**高速时实际力矩显著低于命令力矩**
- 非线性饱和(Saturation): 真实电机的铁芯存在磁饱和现象,力矩不会随电流无限线性增长,需要引入分段线性函数截断过大的力矩
- PD 伺服控制: 先由 $\(u_{pwm}=k_p(\bar{q}-q_n)+k_d(\bar{\dot{q}}-\dot{q}_n)\)$ 生成归一化 PWM / duty-cycle 命令,再计算 \(V_{pwm}=V_{battery}\cdot \text{clip}(u_{pwm},-1,1)\)。这里的 \(k_p,k_d\) 是底层伺服器中的无量纲缩放系数,不是关节力矩控制里的物理 PD 增益(\(\mathrm{Nm/rad}\)、\(\mathrm{Nms/rad}\));目标速度通常设为 \(\bar{\dot{q}}=0\)。
Hwangbo et al. (2019) 则提出用**神经网络直接学习执行器模型**(Actuator Net),避免手工建模的复杂性: - 数据采集:用简单控制器生成正弦波足端轨迹,通过逆运动学计算关节位置指令,改变振幅和频率扩大覆盖范围,并通过手动推动/阻碍机器人进一步丰富数据集。由于电机的高频特性,每秒可为单个电机产生数百个样本 - 网络输入:当前及过去两个时间步(\(t\), \(t-0.01\)s, \(t-0.02\)s)的关节位置误差和关节速度历史窗口——因为执行器内部状态(如控制器状态、电机转速)无法直接测量,需要用历史信息来估计 - 训练:监督学习,预测实际输出力矩。最终误差接近传感器分辨率,远优于理想执行器模型 - 部署:在仿真中替换理想力矩模型,让 RL 策略"感受到"真实电机的非理想特性
⚠️ 常见陷阱¶
⚠️ 概念误区:认为"RL 算法创新"是腿足 RL 的核心 新手想法: "PPO 太旧了,应该用 SAC / DreamerV3 / TD-MPC 来训腿足。" 实际情况: 腿足 RL 的核心挑战不在算法,而在**工程**——奖励设计、Domain Randomization、仿真精度、部署流水线。PPO 在腿足场景下已经工作得非常好,绝大多数 SOTA 工作(ANYmal Parkour、Walk These Ways、RMA)都使用 PPO。换算法的边际收益远小于优化训练管道。 正确认知: 先把 PPO + legged_gym 跑通,再考虑算法创新。
⚠️ 思维陷阱:低估 sim-to-real gap 的严重性 新手想法: "仿真里跑得很好,应该直接能上真机。" 现象: 仿真里完美的策略,真机第一步就摔倒。 根本原因: 仿真器的接触模型、电机模型、延迟模型都与真实世界有系统偏差。没有 Domain Randomization 的策略会 overfit 到仿真器的特定参数。 正确做法: sim-to-real gap 是腿足 RL 的**主要工程挑战**,必须用 DR + Teacher-Student + 真机微调来系统解决。
⚠️ 工程警示:真机直接训练的风险与缓解
直接在真实机器人上进行 RL 探索(而非 sim-to-real)可以避免仿真-现实差距,但面临严重的安全和成本问题:
风险 后果 缓解机制 随机探索初期碰撞/摔倒 电机过流、齿轮损坏、结构变形 安全感知 RL(约束探索空间) 关节力矩/速度超限 硬件不可逆损伤 硬件层力矩限幅 + 软件安全监控 探索效率极低 训练时间数天至数周 仿真预训练 + 真机微调(fine-tune) 失败后无法自动恢复 人工干预成本高 跌倒检测 + 自主恢复策略 实践建议:除非研究目标本身就是真机在线学习(如 Dreamer/MBPO 等 model-based 方法),否则推荐 sim-to-real 路线(仿真中训练 → Domain Randomization → 零样本迁移),真机仅做验证和少量微调。
真机在线训练的系统工程(以 SAC 为例)¶
Haarnoja et al. (RSS 2019) 在 Minitaur 四足上实现了真机直接训练 SAC,其系统设计远不只是"换个算法":
异步采集-训练架构:
采集进程 (100 Hz) 训练进程 (异步)
└─ 执行策略 → 收集 (s,a,r,s') └─ 从 Replay Buffer 采样
└─ 写入 Replay Buffer └─ 更新 Q/π 网络
└─ 定期同步最新策略参数 └─ 推送更新后的策略
安全机制: | 机制 | 实现方式 | 作用 | |------|---------|------| | 力矩硬限幅 | 底层驱动器层面截断 | 防止电机过载(不可绕过) | | 动作空间裁剪 | 策略输出 tanh + scale | 限制关节目标位置范围 | | 姿态监控 | IMU roll/pitch 超阈值触发 | 检测即将摔倒 | | 自动重置 | 检测到摔倒后执行恢复动作序列 | 减少人工干预 | | 早期终止 | 关节速度/力矩异常时中断 episode | 保护硬件 |
自动重置的工程挑战:
真机无法像仿真一样 env.reset()。Haarnoja 的方案是检测到不可恢复状态后,切换到预编程的恢复控制器(如 PD 回到站立姿态),成功站立后再切回 RL 策略继续探索。这要求恢复控制器本身足够鲁棒。
⚠️ 关键限制:真机训练的样本效率远低于仿真(无并行、每步真实时间)。Haarnoja et al. (RSS 2019) 报告 Minitaur 上约 2 小时、约 400 个 rollout、约 160k control steps 才获得基本行走策略。这个样本量在 GPU 并行仿真中只是很小的一段 rollout;真机训练的价值不在效率,而在**消除 sim-to-real gap**。
⚠️ 编程陷阱:在 CPU 仿真器上训腿足策略 错误做法: 用 MuJoCo(CPU)训四足行走,单环境串行。 后果: 训练一个策略需要 3-7 天,迭代一次奖励设计就浪费一周。 根本原因: 腿足 RL 需要数亿步交互,CPU 串行速度远远不够。GPU 并行仿真(4096 环境)将这个过程压缩到小时级。 正确做法: 使用 IsaacLab + rsl_rl,一天内完成多轮奖励迭代。
练习¶
练习 63.1.1: 列出你知道的 RL 环境(Atari、MuJoCo、DMControl 等),与腿足环境对比,哪些维度的差异最大?为什么腿足需要特殊的训练基础设施?
练习 63.1.2: 如果你有一个已经在 MuJoCo 的 Ant-v4 上训好的 PPO 策略,列出 3 个它不能直接部署到 Unitree Go2 真机的原因。对于每个原因,说明腿足 RL 训练栈是如何解决的。
上节分析了腿足 RL 与通用 RL 的差异,引出了对 GPU 原生仿真器的需求。下面我们深入 IsaacGym 到 IsaacLab 的演进,理解当前训练基础设施的架构。
63.2 IsaacGym → IsaacLab:GPU 并行仿真的演进 ⭐⭐¶
动机:为什么需要 GPU 原生仿真?¶
一个直觉的问题:为什么不直接在 MuJoCo 上并行跑多个 CPU 进程?
答案在于**数据传输开销**。传统方案中,物理仿真在 CPU 上运行,RL 算法(PyTorch)在 GPU 上运行。每一步都需要:
这个 CPU-GPU 数据拷贝在 4096 并行环境下成为严重瓶颈。IsaacGym 的革命性创新是:把物理仿真也放到 GPU 上,消除所有 CPU-GPU 数据拷贝:
如果不用 GPU 仿真会怎样¶
以训练一个 Go2 trot 策略为例,比较训练速度:
| 方案 | 并行环境数 | 每秒仿真步 | 训练 1 亿步耗时 |
|---|---|---|---|
| MuJoCo (单 CPU) | 1 | ~5,000 | ~5.6 小时 |
| MuJoCo (32 CPU 进程) | 32 | ~100,000 | ~16.7 分钟(理论值) |
| IsaacGym (RTX 4090) | 4096 | ~4,000,000 | ~25 秒 |
| IsaacLab (RTX 4090) | 4096 | ~3,500,000 | ~29 秒 |
差距是**两个数量级**。这意味着在 GPU 仿真下,你可以在一个下午完成 10 轮奖励设计迭代;而在 CPU 仿真下,同样的迭代需要两周。
口径说明:上表为**裸仿真吞吐量**(纯物理步进 + 数据收集,不含策略网络前向/反向传播和梯度更新)。完整 PPO 训练的 wall-clock 时间通常比裸仿真慢 10-100 倍(参见下文 IsaacLab 性能数据)。
IsaacGym:开创者(2021-2023,已 legacy)¶
IsaacGym 由 NVIDIA 在 2021 年发布,是第一个广泛使用的 GPU 原生物理引擎:
- 底层: NVIDIA PhysX 5.x(GPU 加速的刚体/关节/接触仿真)
- 接口: Python API(通过 CUDA 与底层交互)
- 并行能力: 单 GPU 上 4096+ 个机器人并行
- 地位: legged_gym + rsl_rl 的原始仿真后端
IsaacGym 有显著的历史局限:
| 局限 | 影响 |
|---|---|
| 渲染能力弱 | 不支持高质量视觉传感器(深度相机、LiDAR) |
| 传感器模型有限 | 难以训练视觉 RL 策略 |
| 独立运行 | 不与 NVIDIA 的完整仿真生态集成 |
| 停止维护 | 2024 年起不再更新,用户被建议迁移到 IsaacLab |
IsaacLab:当前主流(2023-至今)¶
IsaacLab(原名 Orbit,后被 NVIDIA 官方吸收)是 IsaacGym 的继任者。截至撰写时,Isaac Lab 已迭代至 3.x 版本,配合 Isaac Sim 使用(请以 NVIDIA 官方文档确认当前最新版本)(注:截至撰写时 Isaac Lab 仍处于快速迭代阶段,API 可能在版本间有较大变化):
| 特性 | IsaacGym | IsaacLab / Isaac Lab 3.x |
|---|---|---|
| 物理后端 | PhysX 5.x | PhysX 5.x;3.0 beta 提供 Newton backend |
| 渲染 | 基础 | 光线追踪(RTX),支持深度相机、LiDAR |
| 传感器 | IMU、接触力 | IMU、接触力、深度相机、LiDAR、RGB |
| 机器人模型 | URDF | URDF + USD(更丰富的资产描述) |
| RL 库集成 | rsl_rl(手动配置) | rsl_rl + rl_games(原生支持) |
| 多 GPU | 不支持 | 新版本支持多 GPU 训练,具体能力需按版本核实 |
| 维护状态 | 已停止 | 活跃维护,NVIDIA 官方项目 |
| 社区 | 大量历史代码 | 快速增长,已成新标准 |
IsaacLab 的架构¶
IsaacLab 采用**管理器驱动(Manager-Based)的环境设计。每个环境由多个 Manager 组成,各自负责不同职责。这种设计的好处是**模块化:修改奖励不需要改环境代码,只需要替换 RewardManager 的配置。
# IsaacLab 的 ManagerBasedRLEnv 架构概览
class Go2FlatEnv(ManagerBasedRLEnv):
"""Go2 平地行走环境"""
# 各 Manager 负责不同职责:
# ObservationManager → 构建观测(哪些信号给 Actor,哪些给 Critic)
# ActionManager → 定义动作空间(关节位置/速度/扭矩)
# RewardManager → 计算奖励(多个奖励项加权求和)
# TerminationManager → 判断 episode 是否结束
# CurriculumManager → 管理课程学习(地形难度等)
# CommandManager → 生成速度命令(vx, vy, wz)
# EventManager → Domain Randomization(质量、摩擦等随机化)
以下是一个完整的环境配置示例:
from omni.isaac.lab.envs import ManagerBasedRLEnvCfg
from omni.isaac.lab.managers import (
ObservationGroupCfg, ObservationTermCfg,
RewardTermCfg, TerminationTermCfg,
)
@configclass
class Go2FlatEnvCfg(ManagerBasedRLEnvCfg):
"""Go2 平地行走——完整配置"""
# 场景:4096 个 Go2 并行
scene = Go2SceneCfg(num_envs=4096, env_spacing=2.5)
# 观测:分为 policy 组(给 Actor)和 critic 组(给 Critic)
observations = ObservationsCfg(
policy=ObservationGroupCfg(
terms={
"base_ang_vel": ObservationTermCfg(func=base_ang_vel), # (3,)
"projected_gravity": ObservationTermCfg(func=proj_gravity), # (3,)
"velocity_commands": ObservationTermCfg(func=vel_commands), # (3,)
"joint_pos": ObservationTermCfg(func=joint_pos_rel), # (12,)
"joint_vel": ObservationTermCfg(func=joint_vel_rel), # (12,)
"last_action": ObservationTermCfg(func=last_action), # (12,)
}
# 总观测维度: 3+3+3+12+12+12 = 45
)
)
为什么需要历史观测:POMDP 视角¶
腿足机器人的控制问题本质上是一个**部分可观测马尔可夫决策过程(POMDP)**,而非标准 MDP。原因很直接:
- 状态 ≠ 观测:系统的完整状态包含基座速度、地面摩擦系数、负载质量、地形形状等——但真机的传感器只能提供关节编码器和 IMU 数据(部分观测)
- 在 POMDP 中,最优策略不是 \(\pi(a|o_t)\)(基于当前观测),而是 \(\pi(a|b_t)\)(基于**信念状态** belief state)
- 信念状态 \(b_t\) 是对完整状态的后验概率分布,理论上需要贝叶斯滤波器维护——但在高维连续空间中不可行
工程上的近似:理论上,策略需要基于 belief state 做决策;工程上通常用**观测历史** \((o_{t-H}, a_{t-H}, \ldots, o_{t-1}, a_{t-1}, o_t)\)、RNN 隐状态或适应模块 latent 来近似这个后验分布。这不是单纯"提高性能的技巧"——单帧观测在 POMDP 中通常信息不足,必须用某种记忆机制补上被遮蔽的状态信息。
这就是为什么腿足 RL 的各种方法都需要历史信息: | 方法 | 历史编码方式 | 推断目标 | |------|------------|---------| | 直接堆叠 | MLP 输入拼接 \([o_{t-k}, \ldots, o_t]\) | 隐式推断速度/接触 | | RMA Adaptation Module | MLP/1D-CNN 处理 50 步历史 | 显式推断环境 latent \(\hat{z}_t\) | | Miki2022 TCN | 时序卷积网络 | 推断接触/打滑/地形隐变量 | | RNN/LSTM/GRU | 循环网络维护隐状态 | 维护 belief state 近似 |
理解这一点后,Teacher-Student 框架的设计就很自然了:Teacher 直接访问完整的特权状态,Student 必须从历史观测中重建一个可用于控制的近似 latent。这个 latent 可以被理解为 belief state 的工程替代品,但 Teacher 并不真的维护贝叶斯后验——它只是读取了仿真中真机不可直接获得的信息。
观测空间的三分法¶
腿足 RL 的观测输入可系统性地分为三类:
| 类别 | 内容 | 来源 | 真机可用性 |
|---|---|---|---|
| 本体感知 (Proprioception) | 关节角度/角速度/力矩、IMU(姿态/角速度/加速度)、基座角速度 | 电机编码器、IMU | ✓ 直接可用 |
| 外感知 (Exteroception) | 地形高程图、深度图、点云、接触力 | 深度相机、LiDAR、力传感器 | ✓ 但有噪声/延迟 |
| 任务输入 (Task Command) | 期望速度 (\(v_x, v_y, \omega_z\))、步态选择、目标位置 | 操作员/高层规划器 | ✓ |
外感知不是"把地形真值送给策略"。真实机器人通常先从深度相机或 LiDAR 得到点云,再融合成高程图或局部 traversability map。这个管线有两个局限:(1) 高程图是 2.5D 表示,对草丛、灌木、泥浆、雪地这类非刚性或语义复杂地形容易误判;(2) 感知更新有 25-50 ms 甚至更高延迟,高速运动时旧地图会把落脚点推向错误位置。因此感知型 RL 不能只依赖"更大的 CNN"解决问题,还要在训练中注入感知噪声、延迟、遮挡和错误地形标签,并在 Ch66/Ch67 的高程图与 Perceptive MPC 中继续处理这些结构性误差。
💡 工程要点: - 仿真调试时可用 cheater state(地面真值)快速验证控制逻辑。但训练 Actor 时应区分:Actor 输入只能包含真机可获取的观测;Critic/Teacher 可以使用特权真值作为辅助信号。部署时必须确认所有 Actor 输入均来自状态估计器,而非仿真真值 - 特权信息(摩擦系数、质量、地形参数)只在 Teacher 网络中使用。Student 网络只能依赖**真机可获取的观测**——可以是本体感知,也可以包括有噪声/延迟的外感知(如深度相机),但不能依赖仿真特权真值 - 观测历史可隐式编码环境动态特性:短历史(3-5 步)用于缓解部分可观测性;RMA 等适应模块通常需要更长窗口(如 50 步)以辨识环境参数
# 动作:关节位置偏移量
actions = ActionsCfg(
joint_pos=JointPositionActionCfg(
asset_name="robot",
joint_names=[".*"], # 所有 12 个关节
scale=0.25, # action 缩放因子
use_default_offset=True # 以默认站立姿态为偏移基准
)
)
动作空间设计选择¶
| 设计方式 | 输出内容 | 优点 | 缺点 | 代表工作 |
|---|---|---|---|---|
| 关节位置 PD | 目标关节角度 \(q_d\),底层 PD 跟踪 | 最常用、稳定、易调 | PD 增益是先验假设 | rsl_rl, Walk These Ways |
| 关节力矩直接 | 关节力矩 \(\tau\) | 最大控制自由度 | 训练困难、真机危险 | 早期工作 |
| 结构化动作 | 足端轨迹参数(步高/步长/频率) | 物理先验约束动作空间 | 灵活性受限 | ETH ANYmal |
| 残差 RL + CPG | CPG 基础步态 + 学习残差 \(\delta q\) | 安全初始行为、稳定探索 | CPG 设计引入偏置 | 多篇2023-2024工作 |
💡 工程建议:新手推荐从**关节位置 PD** 起步(rsl_rl 默认方式)。PD 模式提供了天然的"安全网"——策略输出的是目标位置偏移量而非直接力矩,实际安全由 action clip(限制输出范围)、action scale(缩放幅度)、关节限位和底层力矩限幅共同保障。相比直接力矩输出,PD 模式的输出空间更紧凑、探索更温和。直接输出力矩需要更精细的奖励设计和安全机制。
性能数据: RTX 4090 上,4096 个 Go2 并行训练,完整 PPO wall-clock 约 1 亿步 2.5 小时(含网络前向/反向传播、梯度更新、日志记录等开销,远慢于上表的裸仿真吞吐),24 小时可达 10 亿步。
legged_gym 的维护现状¶
需要特别说明的是:legged_gym 仓库(leggedrobotics/legged_gym)基于 IsaacGym,目前已声明**仅提供有限更新和支持**,ETH RSL 团队鼓励用户迁移到 IsaacLab。但 legged_gym 的代码结构和设计思想仍然是理解腿足 RL 工程的最佳教材——大量论文和项目依然基于它。新项目推荐直接使用 IsaacLab 并锁定具体版本;若采用 Isaac Lab 3.0 beta 的 Newton backend 等能力,需要预留 API 迁移成本。也可以使用 fan-ziqi 的 robot_lab(legged_gym 概念到 IsaacLab 的迁移封装)作为过渡。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:混淆 IsaacGym 和 IsaacLab 的 API 错误做法: 在 2025/2026 年的新项目中使用
from isaacgym import gymapi。 后果: IsaacGym 已停止更新,新特性(Newton 物理引擎、多 GPU、深度相机)都不可用。且社区支持逐渐消失。 根本原因: legged_gym 基于 IsaacGym,但 ETH RSL 已将新工作迁移到 IsaacLab。legged_gym 仓库本身声明"limited updates and support"。 正确做法: 新项目优先使用 IsaacLab,并在项目文档中锁定具体版本。若使用 Isaac Lab 3.0 beta/预览版特性(如 Newton backend),需要明确 API 可能变化;老代码迁移参考 NVIDIA 官方迁移指南。⚠️ 概念误区:认为 GPU 仿真 = 更准确的物理 新手想法: "GPU 算力更强,所以物理仿真更精确。" 实际情况: GPU 仿真的优势是**并行度**,不是精度。PhysX 的接触模型(penalty-based)本身就有局限——接触穿透、摩擦力近似。Isaac Lab 3.0 beta 引入的 Newton backend 在接触求解方面提供了新选择,但仍然不是"真实物理"本身。 关键认知: 仿真精度靠 Domain Randomization 来补偿,而不是靠仿真器本身完美。
⚠️ 思维陷阱:等 IsaacLab 完全稳定再开始学 新手想法: "IsaacLab API 还在变,等稳定了再学。" 实际情况: IsaacLab 3.x 仍在快速演进,3.0 发布阶段也带有 beta/预览性质,API 可能随版本调整。但腿足 RL 的**核心概念**(并行仿真、奖励设计、DR、Teacher-Student)不依赖于特定 API——这些概念在任何仿真器上都适用。 正确做法: 先学概念,再学 API。概念不变,API 可以适配。
练习¶
练习 63.2.1: 计算在 RTX 4090(4096 并行环境)上,如果每个环境每步仿真耗时 0.25 ms、PPO 前向推理耗时 0.1 ms,理论上每秒能仿真多少步?与 MuJoCo 单 CPU 环境(每步 0.2 ms)对比。
练习 63.2.2: IsaacLab 的 ManagerBasedRLEnv 与传统 Gym 的 step(action) -> obs, reward, done, info 接口有什么区别?这种 Manager 驱动的设计对奖励工程有什么好处?
练习 63.2.3: 阅读 IsaacLab 官方文档中 Go2 velocity tracking 的环境配置,列出所有观测项及其维度。为什么不把机器人的全局位置(x, y, z)作为观测?
GPU 并行仿真提供了训练效率,但"用什么 RL 算法来训"同样关键。下一节深入 rsl_rl 的 PPO 实现,完整推导 GAE 和 clipped objective。
63.3 rsl_rl 的 PPO 实现详解 ⭐⭐¶
动机:为什么不用 Stable-Baselines3?¶
这是一个合理的问题。Stable-Baselines3 (SB3) 是最流行的 RL 库,支持 PPO/SAC/DQN 等多种算法,文档完善,社区庞大。为什么腿足 RL 社区放弃 SB3,自己写了 rsl_rl?
答案在于**开销**。SB3 为通用性付出的代价:
| 特性 | SB3 | rsl_rl |
|---|---|---|
| 代码量 | ~30,000 行 | ~2,000 行 |
| 支持算法 | PPO, SAC, DQN, A2C, TD3, HER 等 | 仅 PPO(+ Student-Teacher 蒸馏) |
| 数据流 | CPU/GPU 切换、Gym wrapper 层层包装 | 纯 GPU tensor 操作,无包装 |
| rollout 存储 | 通用 Buffer(支持各种算法) | 精简 Buffer(仅 PPO 需要的字段) |
| 网络定义 | 通用 policies.py(处理各种空间) |
专用 ActorCritic(MLP / MLP+RNN) |
| 每步开销 | ~2 ms(包含各种检查和转换) | ~0.1 ms |
在 4096 环境 x 24 步 = 98,304 步/iteration 的规模下,SB3 的每步 2 ms 开销意味着每次 rollout 多花 200 ms——累积起来显著拖慢训练。
rsl_rl 在 2025 年发布了 arXiv 预印本(arXiv:2509.10771 "RSL-RL: A Learning Library for Robotics Research"),系统整理了库的设计与机器人训练实践。新版本增加了多 GPU 训练支持,并可通过 PyPI 直接安装。
PPO 算法核心回顾与完整推导¶
你已经熟悉 PPO 的基本思想。这里我们做完整推导,重点是**每一步的数学动机**。
从策略梯度到 PPO 的演进¶
起点:策略梯度定理 (Sutton et al., 2000)
策略梯度的核心思想是:直接对策略参数 \(\theta\) 求目标函数的梯度。目标函数是期望累积回报:
策略梯度定理告诉我们:
其中 \(A^{\pi_\theta}(s_t, a_t) = Q^{\pi_\theta}(s_t, a_t) - V^{\pi_\theta}(s_t)\) 是优势函数(advantage function)。为什么要用优势函数而不是直接用 \(Q\) 或累积回报?因为优势函数**减去了一个基线** \(V(s_t)\),保持梯度估计无偏的同时大幅降低方差。直觉上:如果某个动作 \(a_t\) 的优势为正(即它比"在该状态下的平均表现"好),就增大 \(\pi_\theta(a_t|s_t)\);如果优势为负,就减小。
问题 1:方差太大
直接用 Monte Carlo 估计 \(A(s_t, a_t)\) 方差极大——因为需要从 \(t\) 到 episode 结束的所有奖励,包含了未来所有时间步里环境和策略的随机波动。从统计学角度看,回报 \(G_t = \sum_k \gamma^k r_{t+k}\) 是 \(V^\pi(s_t)\) 的无偏估计,但方差随 horizon 指数增长;而 TD 目标 \(r_t + \gamma V(s_{t+1})\) 是有偏估计(依赖于 \(V\) 的近似精度),但方差极低(只涉及一步随机性)。这就是 bias-variance 权衡的核心矛盾。解决方案:Generalized Advantage Estimation (GAE)——通过指数加权平均在两个极端之间找到最优折中。
问题 2:步长太大
策略梯度一步更新可能走得太远,导致策略崩溃(性能骤降且不可恢复)。这在监督学习中不常见(损失函数通常是光滑的),但在 RL 中,策略的微小变化可能导致采样分布剧变,一旦策略"走偏",就很难恢复。TRPO 通过 KL 散度约束 \(D_{KL}(\pi_{\theta_{old}} \| \pi_\theta) \leq \delta\) 来限制策略在**分布空间**(而非参数空间)中的变化——这一点至关重要,因为参数空间中的微小变化可能对应策略分布的巨大变化(例如高斯分布的方差参数微调就能让分布从窄变宽)。TRPO 的 KL 约束经二阶泰勒展开可近似为 \(\frac{1}{2}(\theta' - \theta)^T F (\theta' - \theta) \leq \delta\),其中 \(F\) 是 Fisher 信息矩阵,由此得到自然梯度更新 \(\theta \leftarrow \theta + \alpha F^{-1} \nabla_\theta J\)。但这需要求解 Fisher 矩阵的逆(共轭梯度法),工程复杂度高。PPO 用 clipping 近似同样的效果,只用一阶梯度,实现极其简单。
为什么腿足 RL 用 PPO 而非 SAC/DDPG 等 off-policy 算法? On-policy 算法(PPO)要求数据必须由当前策略产生,用一次就丢弃,样本效率低;off-policy 算法(SAC、DDPG)可以重用历史数据(经验回放池),样本效率高。但在 GPU 并行仿真(4096 环境)下,采样速度极快,on-policy 的样本效率劣势被并行度完全抵消。而 off-policy 算法面临的"致命三要素"(Deadly Triad)——函数近似 + 自举 + 离策略数据——在腿足场景中更容易导致训练不稳定。PPO 的优势在于梯度估计无偏、收敛曲线平滑、对超参数相对鲁棒,这在需要快速迭代奖励设计的腿足 RL 工程中至关重要。
⚠️ 为什么需要截断:重要性采样的方差爆炸
在 off-policy 策略梯度中,重要性权重是多步概率比的连乘: $\(w = \prod_{t=0}^{T} \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}\)$
即使每一步的比值只有微小偏差(如 0.99 到 1.01),100 步连乘后方差会指数爆炸。更危险的是,如果采样分布 \(q(x)\) 在某处概率极小(如 0.0001),而目标分布 \(p(x)\) 在该处概率较大(如 0.1),那么权重 \(\rho = p(x)/q(x) = 1000\)——只要抽到一个这样的罕见样本,梯度的值就会瞬间放大 1000 倍,导致网络参数巨幅震荡甚至直接 NaN。这就是为什么 TRPO/PPO 必须限制策略更新幅度——不是出于理论优雅,而是**数值生存需要**。PPO 的 clip 操作本质上是截断了这个连乘链中的极端值。
GAE 完整推导¶
GAE (Schulman et al., 2016) 的目标是在 偏差(bias) 和 方差(variance) 之间取平衡。这个 bias-variance 权衡贯穿了 RL 优势估计的方方面面。
💡 TD 自举的直觉
TD 更新的核心思想:用"一部分事实(即时奖励 \(r_t\) 是确定的)+ 一个估计(\(V(s_{t+1})\) 是猜的)"来修正"另一个估计(\(V(s_t)\))"。即时奖励是锚点——它提供了真实信号,防止估计值完全脱离现实。在最开始阶段,所有的状态价值都是随机初始化的,此时状态价值完全不准确,但仍然可以通过奖励来进行估计和更新,因为真实奖励带来了绝对准确的数值。这也是 GAE 的基础:\(\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)\) 中的 \(r_t\) 就是那个"事实锚点"。这种用"一部分事实 + 一个估计"去更新"另一个估计"的方法,在强化学习中被称为自举(Bootstrapping)。
Step 1: TD 残差定义
一步 TD 残差(temporal difference residual):
直觉:\(\delta_t^V\) 是"实际收到的即时回报 + 对下一状态的估值"与"对当前状态的估值"的差。如果 \(V\) 是完美的值函数,则 \(\mathbb{E}[\delta_t^V] = A(s_t, a_t)\)——TD 残差的期望就是真实优势函数。这一点可以严格证明:
但如果 \(V\) 不完美(实际训练中总是近似的),则 \(\delta_t^V\) 是有偏的优势估计。
Step 2: 多步优势估计
用 1 步 TD 估计优势:
特性:偏差高(只看一步,严重依赖 \(V\) 的准确性),方差低(只有一个随机项 \(r_t\))。
用 2 步 TD 估计:
用 \(k\) 步 TD 估计:
\(k \to \infty\) 时退化为 Monte Carlo:
特性:偏差零(用完整回报,不依赖 \(V\) 近似),方差高(所有 \(r_{t+l}\) 都是随机变量,方差累积)。
这形成了一个连续谱:\(k\) 越大,偏差越低但方差越高。
Step 3: 指数加权平均——GAE 的核心洞察
GAE 的关键思想:不选特定的 \(k\),而是对所有 \(k\) 步估计做**指数加权平均**,权重为 \(\lambda^{k-1}\)(归一化后):
为什么用指数加权?因为 \(k\) 越大,估计的方差越大,应该给予越低的权重。指数衰减 \(\lambda^{k-1}\) 恰好实现了这种"信任度随步数衰减"的效果。
展开计算(这是关键推导,每步都写清楚):
代入 \(\hat{A}_t^{(k)} = \sum_{l=0}^{k-1} \gamma^l \delta_{t+l}\):
收集 \(\delta_t\) 的系数:\((1-\lambda)(1 + \lambda + \lambda^2 + \cdots) = (1-\lambda) \cdot \frac{1}{1-\lambda} = 1\)
收集 \(\delta_{t+1}\) 的系数:\((1-\lambda) \cdot \gamma(\lambda + \lambda^2 + \cdots) = (1-\lambda) \cdot \frac{\gamma \lambda}{1-\lambda} = \gamma \lambda\)
收集 \(\delta_{t+l}\) 的系数:类推得 \((\gamma \lambda)^l\)
因此:
这是一个极其优雅的结果:GAE 就是 TD 残差的**指数衰减求和**,衰减因子为 \(\gamma \lambda\)。
\(\lambda\) 的物理含义:
| \(\lambda\) 值 | 效果 | 退化形式 | 适用场景 |
|---|---|---|---|
| \(\lambda = 0\) | \(\hat{A}_t = \delta_t^V\)(一步 TD) | 低方差,高偏差 | \(V\) 非常准确时 |
| \(\lambda = 1\) | \(\hat{A}_t = G_t - V(s_t)\)(MC) | 零偏差,高方差 | 需要无偏估计时 |
| \(\lambda = 0.95\) | 偏向低偏差的折中 | — | 腿足 RL 默认值 |
腿足 RL 选 \(\lambda = 0.95\) 的理由:腿足的 reward 信号密集(每步都有 tracking reward + 多个正则项),不像 Atari 那样奖励稀疏(玩数十步才得一分)。密集 reward 意味着方差本来就不大,所以可以用较高的 \(\lambda\) 降低偏差。
Step 4: rsl_rl 的递归实现
无穷级数在实际实现中用**从后往前的递推**:
这等价于无穷和(在有限 horizon \(T\) 内)。递推的好处:\(O(T)\) 时间复杂度,不需要存储所有 \(\delta\)。
# rsl_rl/algorithms/ppo.py 中 GAE 的实际实现
def compute_returns(self, last_values, dones):
"""从后往前递推计算 GAE advantage"""
advantage = 0
for step in reversed(range(self.num_transitions_per_env)):
if step == self.num_transitions_per_env - 1:
next_values = last_values # 最后一步用 Critic 估值
next_is_not_terminal = 1.0 - dones # done=1 时截断
else:
next_values = self.values[step + 1]
next_is_not_terminal = 1.0 - self.dones[step + 1]
# TD 残差: delta = r + gamma * V(s') - V(s)
delta = (self.rewards[step]
+ self.gamma * next_values * next_is_not_terminal
- self.values[step])
# GAE 递推: A_t = delta_t + gamma * lambda * A_{t+1}
advantage = delta + self.gamma * self.lam * next_is_not_terminal * advantage
self.returns[step] = advantage + self.values[step] # Return = A + V
注意 next_is_not_terminal:当 episode 结束(done=True)时,\(\hat{A}_{t+1}\) 不应跨越 episode 边界,必须截断为零。这在腿足训练中非常重要——机器人摔倒后 reset 的环境,其 advantage 不应该传播到下一个 episode。
Clipped Objective 的数学意义¶
PPO 要解决的问题: 在用旧策略 \(\pi_{\theta_{old}}\) 收集的数据上,更新到新策略 \(\pi_\theta\)。如果更新步长太大,\(\pi_\theta\) 可能偏离 \(\pi_{\theta_{old}}\) 太远,导致重要性采样比率失真,策略崩溃。
TRPO 的解: 加 KL 散度约束 \(D_{KL}(\pi_{\theta_{old}} \| \pi_\theta) \leq \delta\)。需要共轭梯度法求解约束优化,工程复杂度高。
PPO 的解: 用 clipping 近似 KL 约束——不需要任何二阶信息,只用一阶梯度,计算极其简单:
其中 \(r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}\) 是重要性采样比率(importance sampling ratio)。\(r_t = 1\) 意味着新旧策略对该动作的概率相同;\(r_t > 1\) 意味着新策略更倾向于该动作;\(r_t < 1\) 则相反。
分情况理解 clipping 的作用:
| 条件 | \(\hat{A}_t > 0\)(好动作) | \(\hat{A}_t < 0\)(坏动作) |
|---|---|---|
| \(r_t > 1+\epsilon\) | clip 生效,**阻止**过度增大概率 | 不 clip,允许减小概率 |
| \(r_t < 1-\epsilon\) | 不 clip,允许增大概率 | clip 生效,**阻止**过度减小概率 |
| \(1-\epsilon \leq r_t \leq 1+\epsilon\) | 正常梯度更新 | 正常梯度更新 |
直觉:clipping 是一个"安全带"——它不阻止你往正确方向更新,但**阻止你走得太远**。\(\epsilon = 0.2\)(腿足 RL 常用值)意味着每次更新最多让某个动作的概率变为原来的 0.8 到 1.2 倍。
💡 PPO 截断的直觉:悲观策略
PPO 的 \(\min(\text{ratio} \cdot A, \text{clip}(\text{ratio}) \cdot A)\) 本质上是一个**悲观策略**:当优势函数为正(好动作)时,限制策略往好的方向更新的幅度("少拿奖励");当优势函数为负(坏动作)时,不限制远离的速度("多担责任")。换言之:如果这一步更新让你赚翻了(提升太多),PPO 认为这可能是步长太大导致的估计误差,所以只给你算一点奖励(截断);如果这一步更新让你亏惨了(变差),PPO 认为这是真实的反馈,让你承担全部损失。这种"少拿奖励、多担责任"的不对称确保策略宁可"少学好的"也不会"突然变差",正是 PPO 能够防止策略在更新中突然崩溃(Collapse)的核心原因。
rsl_rl 的 PPO loss 实现:
# rsl_rl/algorithms/ppo.py — 核心 loss 计算
def update(self, batch):
# 1. 计算当前策略下动作的 log_prob
actions_log_prob, entropy = self.actor_critic.evaluate(
batch['obs'], batch['actions']
)
# 2. 重要性采样比率 r(theta)
ratio = torch.exp(actions_log_prob - batch['old_actions_log_prob'])
# 3. Surrogate loss (两项取 min — PPO-clip 核心)
surrogate1 = ratio * batch['advantages']
surrogate2 = (torch.clamp(ratio, 1.0 - self.clip_param, 1.0 + self.clip_param)
* batch['advantages'])
policy_loss = -torch.min(surrogate1, surrogate2).mean()
# 4. Value loss (Critic 更新)
value_loss = (batch['returns']
- self.actor_critic.evaluate_critic(batch['obs'])).pow(2).mean()
# 5. Entropy bonus (鼓励探索,防止策略过早收敛)
entropy_loss = -entropy.mean()
# 6. 总 loss = policy + value + entropy
loss = (policy_loss
+ self.value_loss_coef * value_loss
+ self.entropy_coef * entropy_loss)
return loss
训练超参数指南¶
| 超参数 | 腿足典型值 | 为什么选这个值 |
|---|---|---|
clip_param (\(\epsilon\)) |
0.2 | 太小(0.05)→学太慢;太大(0.5)→策略不稳定 |
num_epochs |
5 | 每批数据训 5 轮;更多轮会导致 overfitting 到旧数据 |
num_mini_batches |
4 | 98304 步切成 4 个 mini-batch,每个约 24k 样本 |
learning_rate |
1e-3 → 5e-4 | 通常用自适应 schedule,基于实际 KL 散度 |
gamma |
0.99 | 折扣因子;腿足不需要太长期规划(不像棋类) |
lam (\(\lambda\)) |
0.95 | GAE 参数;偏向低偏差(密集 reward) |
entropy_coef |
0.01 | 太大→动作过于随机;太小→过早收敛到次优 |
desired_kl |
0.01 | rsl_rl 特有:实际 KL 超标时自动降 lr |
⚠️ 常见陷阱¶
⚠️ 编程陷阱:GAE 计算时不处理 episode 边界 错误做法: 不用
donesmask 截断 advantage 传播。 后果: 一个 episode 的 reward 信号"泄漏"到另一个 episode——例如,摔倒后的负奖励影响到 reset 后新 episode 的优势估计,导致 Critic 估值混乱,训练不稳定。 正确做法: 在done=True的步骤上,next_is_not_terminal = 0,截断 GAE 递推。⚠️ 概念误区:认为 PPO 的 clipping 保证 KL 约束 新手想法: "clipping 等价于 KL 约束,所以 PPO 就是简化版 TRPO。" 实际情况: clipping 只是一个启发式近似。对于某些网络结构和数据分布,即使 clipping 了,实际 KL 散度可能仍然很大。rsl_rl 因此增加了
desired_kl的自适应机制——如果实际 KL 超过目标值的 1.5 倍,自动将学习率减半;如果 KL 低于目标值的 0.5 倍,自动将学习率加倍。 自检方法: 训练时监控approx_kl指标。如果它频繁超过 0.03-0.05,说明策略更新过于激进。⚠️ 思维陷阱:过度调优 PPO 超参数 新手想法: "训练效果不好,一定是 PPO 超参数没调好。" 实际情况: 腿足 RL 中,PPO 超参数的影响远小于**奖励设计**和 Domain Randomization。上表中的典型值已经被社区大量验证过,除非有特殊需求(如极长 horizon 的任务),否则不需要大幅调整。 正确方法: 先用默认超参数训练;如果效果不好,优先检查奖励函数和 DR 配置。
练习¶
练习 63.3.1: 手推 GAE 在 3 步 episode (\(T=3\)) 上的展开。给定 \(r_0=1, r_1=0, r_2=2\),\(V(s_0)=1, V(s_1)=1.5, V(s_2)=0.5, V(s_3)=0\),\(\gamma=0.99, \lambda=0.95\)。先计算 \(\delta_0, \delta_1, \delta_2\),再用递推公式计算 \(\hat{A}_2, \hat{A}_1, \hat{A}_0\)。
练习 63.3.2: 在 PPO 的 clipped objective 中,如果 \(\hat{A}_t > 0\) 且 \(r_t = 1.5\)(\(\epsilon = 0.2\)),clip 后的梯度与不 clip 时有何不同?画出 \(L^{CLIP}\) 关于 \(r_t\) 的函数图像(分 \(\hat{A}_t > 0\) 和 \(\hat{A}_t < 0\) 两种情况)。
练习 63.3.3: 阅读 rsl_rl 的 ppo.py 源码(约 200 行)。找到 desired_kl 自适应学习率的实现位置,解释它的完整逻辑:当实际 KL 超过目标值的几倍时降低 lr?降低到多少?
PPO 提供了策略优化的算法引擎,但"优化什么"取决于奖励函数的设计。下一节深入腿足 RL 中最难、最重要的工程问题——奖励设计。
63.4 奖励设计——腿足 RL 最难的工程问题 ⭐⭐⭐¶
动机:为什么奖励设计比算法更重要?¶
一个残酷的事实:腿足 RL 中,90% 的工程时间花在**奖励设计**上,而不是算法调优。PPO 的超参数基本固定(上节的表格),但奖励函数的设计空间是无限的。一个好的奖励函数让机器人优雅行走,一个差的奖励函数让机器人学会"作弊"。
Reward Hacking:RL 的创意远超你的想象¶
RL 代理是极致的"考试机器"——它会找到任何你没想到的方式来最大化奖励。这种现象称为 reward hacking(奖励黑客)。以下是腿足 RL 中真实出现过的 reward hacking 案例:
| 你的奖励设计 | 你的期望 | RL 学到的行为 | 原因 |
|---|---|---|---|
| 只奖励前进速度 \(v_x\) | 优雅地向前走 | 用头撞地面,反复跌倒翻滚前进 | 翻滚前进比走路更快 |
| 奖励脚触地次数 | 稳定步态 | 高频抖脚(>100 Hz) | 抖脚可以极大化触地次数 |
| 惩罚关节扭矩 \(\|\tau\|^2\) 权重太大 | 节能行走 | 几乎不动,微微颤抖 | 不动是扭矩最小化的全局最优 |
| 奖励活着(alive bonus)太大 | 存活越久越好 | 趴在地上不动 | 趴着=永远不摔=最大化 alive |
| 只惩罚非脚部碰撞 | 别用膝盖撞地 | 把膝盖磨在地面上滑行 | 碰撞检测范围定义不精确 |
这些案例说明:奖励设计不是"写几个数学公式"那么简单,而是一种**博弈**——你和 RL 代理之间的博弈。你定义规则,RL 找漏洞。
奖励设计的系统方法论¶
经过社区多年实践,腿足 RL 的奖励设计已经形成了系统的方法论。核心框架是**追踪奖励 + 正则项 + 终止条件**三层结构。
追踪奖励(Task Reward):驱动目标行为¶
追踪奖励告诉 RL "你应该做什么"。腿足行走的核心目标是跟踪速度命令:
为什么用 \(\exp(-\|\cdot\|^2 / \sigma^2)\) 而不是 \(-\|\cdot\|^2\)? 这是一个重要的设计选择,值得深入理解:
| 形式 | \(-\|v - v_{cmd}\|^2\) | \(\exp(-\|v - v_{cmd}\|^2 / \sigma^2)\) |
|---|---|---|
| 值域 | \((-\infty, 0]\) | \([0, 1]\) |
| 完美匹配时 | 0 | 1 |
| 微小偏差 | 接近 0(梯度小) | 接近 1(梯度小) |
| 大偏差 | 大负值(梯度大) | 接近 0(梯度趋零) |
| 问题 | 大偏差时梯度过大,可能不稳定 | 有界,不会梯度爆炸 |
\(\exp\) 形式的本质好处:奖励有界在 \([0, 1]\) 区间,与其他奖励项的权重容易平衡。\(\sigma\) 参数控制"容错范围"——\(\sigma\) 越大,对偏差越宽容。典型的 \(\sigma_{lin} = 0.25\) m/s,意味着误差在 0.25 m/s 以内时奖励仍大于 \(1/e \approx 0.37\)。
正则项(Regularization):限制不良行为¶
正则项告诉 RL "你不应该做什么"。每个正则项背后都有明确的物理动机。下面逐一解析:
class RewardTerms:
"""每个奖励项的物理动机和数学形式"""
# === 身体姿态正则 ===
# 1. 身体水平: r = -(roll^2 + pitch^2)
# 物理动机: 倾斜的身体重心偏离支撑多边形,容易摔倒
# 如果没有: 机器人可能学会侧倾行走(更快但不稳定)
# 与 Ch53 WBC 的联系: WBC 也有姿态保持 task,这里用 reward 替代
r_orientation = -(projected_gravity_xy.norm(dim=-1))**2
# 2. Z 方向线速度: r = -v_z^2
# 物理动机: 垂直方向速度意味着跳跃或正在摔倒
# 如果没有: 机器人可能学会"蹦跶着走"(跳跃前进)
r_lin_vel_z = -base_lin_vel[:, 2]**2
# 3. Roll/Pitch 角速度: r = -(w_x^2 + w_y^2)
# 物理动机: 绕非 yaw 轴的旋转意味着姿态不稳
r_ang_vel_xy = -base_ang_vel[:, :2].norm(dim=-1)**2
# === 能量效率正则 ===
# 4. 扭矩正则: r = -||tau||^2
# 物理动机: 真机电机功率有限,省电=续航更长
# 如果没有: 策略可能用过大的扭矩(仿真不在乎,真机烧电机)
# 关键: 权重必须很小(~1e-5),否则机器人不敢动
r_torques = -torch.sum(torques**2, dim=-1)
# 5. 关节加速度: r = -||ddq||^2
# 物理动机: 高加速度 -> 高冲击力 -> 机械磨损 + 噪声
r_dof_acc = -torch.sum(
((last_dof_vel - dof_vel) / dt)**2, dim=-1
)
# === 动作平滑正则 ===
# 6. 动作变化率: r = -||a_t - a_{t-1}||^2
# 物理动机: 动作剧烈变化 -> 关节指令跳变 -> 电机抖动
# 如果没有: 策略输出可能在相邻步之间剧烈波动
r_action_rate = -torch.sum((actions - last_actions)**2, dim=-1)
# === 接触正则 ===
# 7. 非脚部碰撞: r = -count(collisions)
# 物理动机: 膝盖、身体撞地 = 硬件损坏
r_collision = -torch.sum(
(contact_forces[:, non_feet_indices, :].norm(dim=-1) > 0.1).float(),
dim=-1
)
# 8. 脚部空气时间: r = f(air_time)
# 物理动机: 鼓励合理步频(不是一直贴地拖行)
# 形式: 只在脚着地瞬间给奖励,奖励与空气时间成正比
r_feet_air_time = torch.sum(
(feet_air_time - 0.5) * first_contact, dim=-1
)
权重平衡的艺术¶
奖励函数的最终形式:\(r_{total} = \sum_i w_i \cdot r_i\)。权重的设计原则:
- 追踪奖励的量级远大于正则项: 追踪 \(w \sim 1.0\),正则 \(w \sim 0.001-0.1\)。如果正则项太大,机器人"不敢动"(因为任何动作都会增加扭矩/加速度/碰撞等惩罚)
- 先追踪,再正则: 先只用追踪奖励训到能走,然后逐个加正则项,观察行为变化
- 用 TensorBoard 监控每个奖励项的贡献: 如果某个正则项的累积贡献超过追踪项,说明权重失衡
# legged_gym 的典型权重配置 (anymal_c_config.py)
class scales:
# 追踪(正,大权重)— 主导行为方向
tracking_lin_vel = 1.0 # 主目标:跟踪线速度
tracking_ang_vel = 0.5 # 次目标:跟踪角速度
# 正则(负,小权重)— 约束行为品质
lin_vel_z = -2.0 # 中等惩罚(跳跃对硬件冲击大)
ang_vel_xy = -0.05 # 轻微惩罚(轻微摇晃可接受)
orientation = -0.0 # 可选(某些配置不启用)
torques = -0.00001 # 极小(否则不动)
dof_vel = -0.0 # 可选
dof_acc = -2.5e-7 # 极小
action_rate = -0.01 # 中等惩罚(平滑性重要)
collision = -1.0 # 强惩罚(碰撞是硬约束)
dof_pos_limits = -10.0 # 极强惩罚(关节超限很危险)
奖励系数的课程学习¶
一个重要的实践经验是:如果从训练一开始就施加全部正则惩罚,智能体很可能学会"站着不动"——因为任何运动都会产生力矩、加速度和碰撞等惩罚,不动反而是全局最优。解决方案是对惩罚系数做课程学习:训练初期使用极小的惩罚系数,让策略先学会"如何动起来";随训练推进逐步增大系数,迫使策略优化动作品质。同时可以对域随机化的扰动难度(如质量偏移幅度、外力大小)进行线性递增。这种"先学会走,再学会走好"的策略比"一开始就要求完美"更容易收敛。
💡 训练技巧:奖励系数 curriculum
除了常见的**地形难度 curriculum**,惩罚项系数 curriculum 在实践中同样重要。正确做法:保留足够的任务奖励(速度跟踪等),训练初期将能耗、平滑度、冲击力等**惩罚项系数设低**,让策略先学会运动;随训练推进逐步加严惩罚,迫使策略优化动作品质。
如果初期惩罚过高,策略会发现"不动 = 零惩罚"是局部最优,退化为静止——这是腿足 RL 最常见的训练病灶。
额外的物理启发奖励项¶
除了上面列出的标准奖励集,以下几个来自具体工程实践的奖励项也值得考虑:
| 奖励项 | 公式 | 物理动机 |
|---|---|---|
| 机械功 | \(-\|\tau \odot (q_t - q_{t-1})\|_1\) | 惩罚逐关节绝对做功,避免正负功在关节间互相抵消 |
| 地面冲击 | \(-\|f_t - f_{t-1}\|^2\) | 惩罚足端接触力突变,保护硬件 |
| 足端打滑 | \(-\|\text{diag}(g_t) \cdot v_{f,t}\|^2\) | 触地脚不应有水平速度 |
| 关节机械功率 | $-\sum_i | \tau_i \cdot \dot{q}_i |
| 动作平滑度(二阶) | \(-\|a_t - 2a_{t-1} + a_{t-2}\|^2\) | 抑制动作的二阶变化(抖动) |
其中机械功和地面冲击奖励源于生物能量学的启发——自然界中动物的步态正是能量效率和冲击最小化的演化结果。在实践中,这些奖励对于在仿真中学到可直接迁移到真机的自然步态非常关键。注意不同论文对"work"的离散写法并不完全相同:有的对总功取绝对值,有的对每个关节的功逐项取绝对值再求和。后者更保守,可以避免一个关节正功被另一个关节负功抵消。
标志性论文的奖励函数设计¶
以下两篇标志性论文的完整奖励配置具有重要参考价值——它们提供了可直接复现的具体数值,而不仅仅是定性描述。
Miki et al. (2022) — Learning robust perceptive locomotion (Science Robotics)
该工作使用教师-学生架构训练 ANYmal 在阿尔卑斯山徒步,教师网络使用 7 个奖励项,总奖励为线性加权和:
| 奖励项 | 含义 | 公式/描述 | 权重 |
|---|---|---|---|
| \(r_{lv}\) | 线速度奖励 | 基座速度在目标方向上的投影 \(v_{pr}\);\(v_{pr}<0.6\) m/s 时指数逼近,\(v_{pr}\ge 0.6\) m/s 时饱和满分 | 0.05 |
| \(r_{av}\) | 角速度奖励 | 鼓励绕基座 z 轴按指令方向转动,与 \(r_{lv}\) 类似的分段/饱和设计 | 0.05 |
| \(r_b\) | 机身运动稳定 | 惩罚偏离目标方向的速度分量(横向/无效速度)与 roll/pitch 方向角速度 | 0.04 |
| \(r_{fc}\) | 摆动腿足部净空 | 摆动相位时鼓励足端高度高于周围地形高度,统计满足净空条件的比例 | 0.01 |
| \(r_{bc}\) | 机身碰撞惩罚 | 惩罚身体部件与地形的不期望接触(不包含足端正常接触) | 0.02 |
| \(r_s\) | 目标足端轨迹平滑 | 惩罚目标足端位置的二阶差分(离散"加速度/抖动"),抑制抖动 | 0.025 |
| \(r_{\tau}\) | 关节力矩惩罚 | 惩罚关节力矩绝对值之和,降低能耗并保护执行器 | \(2\times 10^{-5}\) |
注意力矩惩罚权重 \(2\times 10^{-5}\) 远小于追踪项——这是"先让机器人动起来,再优化能耗"思想的体现。
RMA (Kumar et al., 2021) — Rapid Motor Adaptation (RSS)
RMA 使用 10 个奖励项,受生物能量学启发,鼓励减少做功与地面冲击。记基座线速度为 \(v\),角速度为 \(\omega\),关节角度为 \(q\),关节速度为 \(\dot{q}\),关节力矩为 \(\tau\),足端接触力为 \(f\),足端速度为 \(v_f\),足端接触指示为 \(g\):
| 序号 | 奖励项 | 公式 | 缩放系数 |
|---|---|---|---|
| 1 | Forward(前进奖励) | \(\min(v_x^t,\; 0.35)\) | 20 |
| 2 | Lateral + Rotation(横向 + 旋转惩罚) | \(-\|v_y^t\|^2 - \|\omega_{yaw}^t\|^2\) | 21 |
| 3 | Work(机械功惩罚) | $-\left | \tau^{tT}(q^t - q^{t-1})\right |
| 4 | Ground Impact(地面冲击惩罚) | \(-\|f^t - f^{t-1}\|^2\) | 0.02 |
| 5 | Smoothness(平滑度惩罚) | \(-\|\tau^t - \tau^{t-1}\|^2\) | 0.001 |
| 6 | Action Magnitude(动作幅度惩罚) | \(-\|a^t\|^2\) | 0.07 |
| 7 | Joint Speed(关节速度惩罚) | \(-\|\dot{q}^t\|^2\) | 0.002 |
| 8 | Orientation(姿态稳定性惩罚) | \(-\|\theta_{roll, pitch}\|^2\) | 1.5 |
| 9 | Z Velocity(垂直速度惩罚) | \(-\|v_z^t\|^2\) | 2.0 |
| 10 | Foot Slip(足端打滑惩罚) | \(-\|\text{diag}(g^t) \cdot v_f^t\|^2\) | 0.8 |
RMA 的一个关键训练技巧:如果直接使用上述奖励训练,智能体会保持不动(因为关节运动相关的惩罚项压制运动)。因此训练初期使用很小的惩罚系数,用固定 curriculum 逐步增大;同时随训练推进线性增加质量、摩擦、电机强度等扰动难度。地形则从一开始就随机采样固定难度,不做 curriculum。
Margolis "Walk These Ways" 的奖励设计创新¶
Margolis 和 Agrawal (2022, CoRL) 提出了 Multiplicity of Behavior (MoB) 的概念。核心思想:不是训一个固定步态的策略,而是训一个能执行**多种步态**的策略,通过命令向量在运行时选择。把步态参数(步频、步幅、身体高度、姿态偏移等)作为**额外的命令输入**:
训练时随机采样这些参数,策略学会"给什么参数就做什么步态"。一个策略即可做:慢走、快跑、蹲行、跳跃、舞蹈等。这对奖励设计的启示是:奖励函数不应该 hard-code 一种"正确行为",而应该**参数化地**定义目标。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:奖励函数中的数值溢出 错误做法:
r = exp(-error)其中error可能是极大的正数(如 \(10^6\))。 后果:exp(-1e6)在 float32 下精确为 0.0,梯度为零,策略对这个奖励项失去学习信号。 根本原因: 训练早期,跟踪误差可能非常大(策略几乎是随机的)。 正确做法: 使用exp(-error / sigma**2)并确保sigma足够大(如sigma=0.25意味着误差需要到 ~1.0 才严重衰减),或者先 clip error 再取 exp。⚠️ 概念误区:认为所有正则项都应该同时打开 新手想法: "legged_gym 配置里有 20 个奖励项,全部打开肯定更好。" 实际情况: 过多的正则项会产生**冲突**。例如,
feet_air_time(鼓励抬脚)和dof_vel(限制关节速度)可能矛盾——抬脚需要关节速度。冲突的奖励项让 RL 找不到好的解,可能收敛到一个所有正则项的"妥协"——但这个妥协往往不是你想要的行为。 正确做法: 从最小集开始(追踪 + 3-5 个关键正则),逐步增加,每次 ablation 验证。⚠️ 思维陷阱:照搬论文的奖励权重到不同机器人 新手想法: "Miki 2022 的权重在 ANYmal 上效果好,直接用到 Go2 上。" 实际情况: ANYmal(~50 kg,SEA 驱动器,关节减速比高)和 Go2(~12 kg,QDD 电机,低减速比)的动力学特性完全不同。ANYmal 的扭矩惩罚权重对 Go2 来说太小(Go2 电机更小,扭矩范围不同)。 正确做法: 理解每个权重背后的**物理含义**,根据机器人特性(质量、电机型号、减速比)重新标定。Ch62 的电机动力学知识在这里非常有用。
练习¶
练习 63.4.1: 设计一个"让 Go2 后退行走"的奖励函数。需要修改哪些追踪项?需要添加哪些新的正则项?(提示:后退时机器人看不到身后,碰撞风险更高)
练习 63.4.2: 做一个 ablation 实验:以训好的 Go2 trot 策略为基线,分别(a) 删除 action_rate 奖励,(b) 将 torques 惩罚权重放大 100 倍,(c) 删除 lin_vel_z 惩罚。预测每个 ablation 的行为变化,然后训练 30 分钟验证。
练习 63.4.3: Margolis 的 Walk These Ways 引入了"步态参数命令"。如果你想让策略能在 trot 和 bound(两腿同步)之间切换,你需要添加哪些命令维度?对应的奖励项如何设计?
奖励函数告诉 RL "什么是好的行为",但仿真环境与真实世界的差距可能让这些行为在真机上失效。下一节深入 Domain Randomization——系统化地缩小 sim-to-real gap。
63.5 Domain Randomization:从贝叶斯视角理解鲁棒性 ⭐⭐⭐¶
动机:为什么仿真训好的策略在真机上会失败?¶
即使你的奖励设计完美,策略在仿真中表现优异,部署到真机时仍然可能失败。原因是 sim-to-real gap(仿真-真实差距):
| 差距来源 | 仿真中的值 | 真实世界 |
|---|---|---|
| 接触模型 | penalty-based(柔性穿透) | 刚性接触,更复杂的力学 |
| 摩擦 | 单一系数,恒定 | 各向异性,与速度/温度/磨损有关 |
| 电机 | 理想扭矩源 | 有带宽、延迟、齿槽效应(Ch62) |
| 传感器 | 完美数值 | 有噪声、偏置、延迟、漂移 |
| 质量/惯量 | CAD 标称值 | 制造公差,负载变化 |
| 通信延迟 | 0 或固定 | 1-5 ms,有抖动(Ch61) |
如果策略 overfit 到仿真的特定参数(如摩擦系数恰好是 0.8),那么在真实地面(摩擦系数可能是 0.5 或 1.2)上就会失败——策略的控制行为是针对 0.8 的摩擦"量身定制"的,换了摩擦就像换了鞋子一样不适应。
将 sim-to-real gap 的来源系统地展开,可以归纳为四大类:
动力学建模误差。真实物体的质量分布无法完全可知,惯性张量标定存在误差,关节摩擦通常呈非线性特性(包含库仑摩擦、粘滞摩擦和 Stribeck 效应),而仿真中往往用线性模型近似。地面摩擦系数在不同材料和工况下变化剧烈,接触力学被简化为 penalty-based 模型,无法精确表征形变和能量耗散。
物理参数偏差。重力加速度因地理位置和海拔略有差异(影响 IMU 校准),空气阻力在高速运动和轻型机器人上不可忽视但仿真通常不建模,材料弹性和阻尼系数影响碰撞响应但仿真参数难以匹配真实值。
感知层面差异。真实传感器的噪声远比仿真中假设的高斯模型复杂——IMU 存在偏置漂移和温漂,编码器存在量化误差,视觉传感器受光照、纹理和遮挡影响。传感器采样延迟和处理延迟在仿真中通常被假设为零或固定值,但真实系统中会出现抖动。数据丢帧和读取失败在仿真中几乎不考虑。
执行层面问题。电机从接收指令到输出力矩存在时间延迟,真实控制器的数字量化带来指令精度损失。电机存在力矩和速度饱和,其饱和曲线是高度非线性的——尤其当电机高速转动时,反电动势(Back-EMF)会显著降低有效力矩输出:
其中 \(K_t\) 为力矩常数,\(K_e\) 为反电动势常数,\(R\) 为线圈电阻。当转速 \(\dot{q}\) 增大时,可用于产生力矩的有效电压 \((V_{pwm} - K_e \dot{q})\) 减小,导致高速时力矩衰减。如果仿真中忽略这一特性,策略会在高速运动时因力矩不足而失控。Hwangbo et al. (2019, Science Robotics) 的工作表明,用神经网络或解析模型精确建模执行器动力学(包括反电动势、磁饱和、非线性摩擦)对 sim-to-real 成功至关重要,该方法已被 IsaacLab 的 ActuatorNetMLP 和 ActuatorNetLSTM 模块标准化。
理解这四类 gap 的实际意义在于:每一类都对应着不同的缓解策略——动力学误差靠 DR 和执行器建模来覆盖,物理参数偏差靠系统辨识和 DR 范围校准,感知差异靠观测噪声注入和历史堆叠,执行层面靠延迟建模和动作平滑正则化(参见 63.4 节的 action_rate 奖励项)。
上述四类 gap 按**缓解手段**分类。下表从**来源**角度重新整理,便于逐项检查:
Sim-to-Real Gap 的五大来源¶
在讨论如何缩小仿真-现实差距之前,需要先理解差距从何而来:
| Gap 类别 | 具体表现 | 影响程度 | 主要缓解手段 |
|---|---|---|---|
| 动力学模型 | 接触模型不准确(穿透/弹跳)、关节摩擦建模粗糙 | 高 | 系统辨识 + DR |
| 执行器特性 | 力矩-电流非线性、高速力矩衰减、传动间隙、延迟 | 高 | 执行器网络建模(Hwangbo2019) |
| 感知噪声 | IMU 漂移/偏置、编码器量化、相机延迟/遮挡 | 中 | 传感器噪声 DR + 观测滤波 |
| 接触特性 | 摩擦系数不确定、地形形状/刚度未知 | 中 | 摩擦/地形 DR |
| 环境复杂度 | 仿真场景过于简化(无风/无外部干扰/平坦地形) | 中 | 外力扰动 DR + 地形 curriculum |
💡 工程判断:执行器特性 Gap 往往比动力学模型 Gap 影响更大。精确建模 MuJoCo/Isaac 的刚体动力学后,残留的最大误差源通常是电机非理想特性——这也是为什么 Hwangbo et al. (2019) 的 Actuator Net 在 sim-to-real 领域如此重要。
Domain Randomization 的贝叶斯解释¶
Domain Randomization 好比**考试前做各种难度的模拟题**——如果你只刷一种难度的题目(固定仿真参数),上了考场遇到稍有变化的题型就会懵;但如果平时练习时随机混合简单题、中等题和难题(随机化质量、摩擦、延迟等参数),虽然每套模拟卷的分数可能不是最高,但真正考试时无论出什么难度都能稳定发挥。DR 的本质就是用训练时的"题型多样性"换取部署时的"鲁棒性"。
Domain Randomization (DR) 的操作很简单:训练时随机化环境参数。但**为什么这样做能提高鲁棒性?** 从贝叶斯推断的视角可以给出严格的理论基础。
形式化: 设环境参数为 \(\xi \in \Xi\)(\(\xi\) 是一个向量,包括质量、摩擦、延迟等)。标准训练目标:
仅在仿真参数 \(\xi_{sim}\) 下最优——如果 \(\xi_{real} \neq \xi_{sim}\),性能可能大幅下降。
DR 的训练目标:
即最大化在参数分布 \(p(\xi)\) 下的**期望**性能。
贝叶斯视角: 我们不知道真机的精确参数 \(\xi_{real}\),但有先验信念 \(p(\xi)\) 认为 \(\xi_{real}\) 落在某个范围内。DR 训练等价于在**先验分布**下做贝叶斯最优决策——策略在整个先验覆盖的参数空间上表现最好,而不是在某个特定点上最优。
DR 成功的必要条件: 如果 \(\xi_{real} \in \text{support}(p(\xi))\)——即先验分布覆盖了真实参数值——那么 DR 训练出的策略**有可能**在真机上工作。覆盖是必要条件,不是充分条件:最终效果还取决于分布权重、策略容量、优化质量和仿真模型结构误差。这给出了一个明确的工程指导:DR 范围必须覆盖真实参数的可能范围,但不能指望"范围足够大"自动解决所有 sim-to-real 问题。
与其他鲁棒方法的关系:
| 方法 | 目标 | 特点 |
|---|---|---|
| Robust Control (minimax) | \(\max_\theta \min_\xi J(\theta, \xi)\) | 对最坏情况鲁棒,过度保守 |
| Domain Randomization | \(\max_\theta \mathbb{E}_\xi [J(\theta, \xi)]\) | 对分布鲁棒,温和有效 |
| System Identification | 先辨识 \(\xi_{real}\),再针对优化 | 精确但费时,不泛化 |
DR 是这三者之间的甜蜜点:不像 minimax 那样过度保守(最坏情况可能永远不会发生),也不像 System ID 那样脆弱(辨识一次就固定了)。
腿足 DR 的典型配置¶
class DomainRandCfg:
"""Go2 的 Domain Randomization 配置"""
# === 动力学参数 ===
mass_randomization_range = [0.85, 1.15] # 质量 ±15%
friction_range = [0.5, 1.25] # 摩擦系数
restitution_range = [0.0, 0.5] # 弹性恢复
com_displacement_range = [-0.05, 0.05] # 质心偏移(m)
# === 电机参数 ===
motor_strength_range = [0.9, 1.1] # 电机强度 ±10%
joint_friction_range = [0.0, 0.05] # 关节摩擦(Nm)
# === 控制参数 ===
action_delay_range = [0, 2] # 步数(每步 10ms → 0~20ms)
kp_range = [0.8, 1.2] # PD 比例增益 ±20%
kd_range = [0.8, 1.2] # PD 微分增益 ±20%
# === 观测噪声 ===
imu_angular_velocity_noise = 0.2 # rad/s
imu_gravity_noise = 0.05
joint_pos_noise = 0.01 # rad
joint_vel_noise = 1.5 # rad/s
# === 外部扰动 ===
push_interval_s = [5, 15] # 每隔 5-15 秒推一次
push_velocity_range = [0, 1.0] # 推力效果(m/s)
域随机化参数速查表¶
下表汇总了腿足 RL 训练中常用的域随机化参数、典型取值范围及其物理依据,可直接用于配置训练环境。数值不是固定标准,应根据机器人质量、执行器带宽、传感器噪声和目标地形重新标定:
| 类别 | 参数 | 随机化范围(典型值) | 说明 |
|---|---|---|---|
| 质量/惯性 | 机器人总质量 | \(\pm 20\%\) | 覆盖制造误差和磨损导致的质量变化 |
| 各 link 质量 | \(\pm 15\%\) | 各部件的质量分布不确定性 | |
| 负载质量 | 0-5 kg | 模拟不同负载情况下的运动 | |
| 摩擦 | 地面摩擦系数 | 0.3-1.2 | 覆盖湿滑地面到粗糙地面的摩擦范围 |
| 关节摩擦力 | \(\pm 30\%\) | 考虑关节磨损和润滑状态变化 | |
| 滚动阻力 | \(\pm 50\%\) | 模拟不同地面材质的滚动阻力 | |
| 刚度/阻尼 | 关节刚度 | \(\pm 20\%\) | 考虑关节柔性和弹性变化 |
| 关节阻尼 | \(\pm 30\%\) | 不同阻尼特性对运动的影响 | |
| 地面刚度 | \(\pm 40\%\) | 覆盖软地面到硬地面的刚度范围 | |
| 传感器噪声 | IMU 噪声 | 高斯噪声 + 偏置漂移 | 模拟加速度计和陀螺仪的测量误差 |
| 编码器噪声 | \(\pm 0.01\) rad | 考虑关节角度测量的精度限制 | |
| 力传感器噪声 | \(\pm 5\%\) | 模拟接触力测量的不确定性 | |
| 执行器 | 电机响应延迟 | 0-50 ms | 电机从接收指令到输出力矩的延迟 |
| 控制指令延迟 | 0-20 ms | 控制器计算和通信延迟 | |
| 力矩限制 | \(\pm 10\%\) | 模拟电机力矩输出能力的变化 | |
| 环境参数 | 重力加速度 | \(\pm 0.2\) m/s\(^2\) | 人为鲁棒性扰动;真实地球表面重力变化远小于此量级,这里不是在模拟地理位置差异 |
| 空气密度 | \(\pm 10\%\) | 不同温度和湿度下的空气阻力 | |
| 温度影响 | -10\(^\circ\)C 到 40\(^\circ\)C | 温度对材料特性和电机性能的影响 |
更粗略的经验法则:物理参数(质量 \(\pm 30\%\)、摩擦系数 0.3-1.5、阻尼 \(\pm 50\%\)),动力学参数(延迟 0-50 ms、力矩噪声 \(\pm 10\%\)),视觉参数(光照、纹理、颜色、相机位置)。
💡 使用建议:调试阶段**可短暂关闭 DR 以排查 pipeline 问题(奖励函数、观测维度等)。但**正式训练应从一开始就开启 DR(参见下文"先训好策略再加 DR"陷阱)。如果训练不稳定,应缩小 DR 范围而非完全关闭。具体做法是在每个 Episode 开始时,从预定义的范围内随机采样物理参数和传感器噪声等,让策略在多样化的动力学条件下学习。
DR 强度的权衡¶
| DR 强度 | 仿真内表现 | 真机表现 | 策略特征 |
|---|---|---|---|
| 无 DR | 完美 | 几乎必定失败 | 激进,overfit 到仿真 |
| 适中 DR | 很好 | 大概率成功 | 自然,有一定保守性 |
| 过大 DR | 一般 | 能跑但保守 | 缓慢,过度小心 |
直觉:DR 越大,策略需要在更广的参数范围内"妥协",单点最优性能越低。关键是找到**刚好覆盖真实参数**的 DR 范围——不多不少。仿真环境和真实环境本质上是两个高维分布,DR 的目标是让仿真分布**包含**真实分布,从而使仿真下学到的策略在真实环境中仍然有效。
自适应 DR (ADR)¶
OpenAI 在 Rubik's Cube 项目中提出了 Automatic Domain Randomization (ADR),自动调整 DR 范围:
- 初始化一个小的 DR 范围 \([\xi_{min}^{(0)}, \xi_{max}^{(0)}]\)
- 训练策略到性能阈值
- 在边界参数 \(\xi_{min}\) 和 \(\xi_{max}\) 上评估策略
- 如果策略在边界表现好,自动扩大范围
- 如果表现差,缩小范围
- 重复直到收敛
ADR 避免了手动调 DR 范围的痛苦。但实现复杂度较高,目前腿足社区更多使用**手动 DR + 消融实验**的方式。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:质量随机化后不更新惯量矩阵 错误做法: 只随机化质量标量 \(m\),不更新 inertia tensor \(I\)。 后果: 质量和惯量不匹配——质量增大但惯量不变,意味着旋转太容易(仿真中出现非物理的动力学行为)。策略学到的行为在真机上不成立。 正确做法: 假设密度均匀变化,惯量按质量比例缩放:\(I_{new} = \frac{m_{new}}{m_{old}} I_{old}\)。
⚠️ 概念误区:认为 DR 能解决所有 sim-to-real gap 新手想法: "只要 DR 范围够大,真机一定能工作。" 实际情况: DR 只能解决**参数化的**差距——即那些可以用数值描述并在仿真中调整的差距(质量、摩擦等)。如果仿真器的物理模型与真实世界**结构性不同**(如 penalty-based 接触 vs 刚性接触),DR 无法弥补这种**模型误差**。 正确认知: DR 是 sim-to-real 的必要条件,但不充分。还需要 Teacher-Student(63.7)、Actuator Network(学习电机非理想特性)和真机微调(Ch64)。
💡 前沿范式:Real2Sim2Real 闭环
传统 sim-to-real 是单向的(仿真→现实),Real2Sim2Real 构成闭环: 1. Real→Sim:从真机数据(运动捕捉/力传感器)辨识系统参数,校准仿真模型 2. Sim(训练):在校准后的仿真中训练/改进策略 3. Sim→Real:部署到真机验证 4. Real→Sim(迭代):用新的真机数据进一步校准仿真
核心观念转变:仿真不是一次性建模,而是持续校准的闭环过程。每次真机测试的数据都是校准仿真的宝贵资源。
系统辨识的具体流程¶
Real→Sim 环节的核心是**系统辨识**(System Identification):用真机数据校准仿真参数。典型流程:
Step 1:数据采集 在真机上执行**标准化动作序列**(非 RL 策略,而是预设的关节运动): - 各关节独立正弦扫频(0.5-10 Hz),覆盖工作范围 - 阶跃响应(突然施加/撤除力矩) - 自由落体/着地冲击(标定接触参数)
记录三元组:\((q(t),\; \dot{q}(t),\; \tau_{cmd}(t))\) 以及 IMU 数据,采样率 ≥ 1 kHz。
Step 2:定义待辨识参数 典型参数集:
| 参数类别 | 具体参数 | 初始值来源 |
|---|---|---|
| 连杆惯性 | 质量、质心位置、惯性张量 | CAD 模型 |
| 关节摩擦 | 库仑摩擦、粘滞摩擦系数 | 经验值 |
| 执行器 | 力矩常数、反电动势常数、电阻 | 电机数据手册 |
| 接触 | 刚度、阻尼、摩擦系数 | 材料手册 |
Step 3:优化求解 定义代价函数为仿真轨迹与真机轨迹的差异: $\(\min_{\xi} \sum_{t} \|q_{sim}(t; \xi) - q_{real}(t)\|^2 + \|\dot{q}_{sim}(t; \xi) - \dot{q}_{real}(t)\|^2\)$
常用优化器: - CMA-ES(协方差矩阵自适应进化策略):无梯度、全局搜索,适合参数空间 < 50 维 - 贝叶斯优化:样本效率高,适合评估代价昂贵的情况 - 差分进化:简单鲁棒,常作为 baseline
Step 4:验证 用辨识后的参数在仿真中回放**未参与优化的**真机轨迹,检查泛化性。如果仅在优化数据上拟合好但新数据上偏差大,说明过拟合或模型结构不足。
⚠️ 工程陷阱:不要用 RL 策略的轨迹做系统辨识——RL 策略会主动适应仿真误差,掩盖了真实的参数偏差。应使用开环或简单闭环的标准化动作。
⚠️ 思维陷阱:先训好策略再加 DR 新手想法: "先在无 DR 环境训到满意的策略,然后加 DR fine-tune。" 实际情况: 从无 DR 到有 DR 的切换通常会让策略严重退化甚至崩溃——因为策略已经 overfit 到仿真的特定参数,面对随机化的参数很难立即适应。从头带 DR 训练效果远优于后期加 DR。 正确做法: 从一开始就用 DR 训练。如果训练困难,可以降低 DR 范围(但不去掉)。
练习¶
练习 63.5.1: 训练两个版本的 Go2 策略——Version A 无 DR,Version B 有 DR(使用上面的配置)。在仿真中分别测试:固定摩擦=0.8 vs 摩擦=0.3。哪个版本的性能下降更大?为什么?
练习 63.5.2: 从贝叶斯角度分析:如果你知道真机的摩擦系数在 \([0.6, 0.9]\) 范围内,但你的 DR 设置为 \([0.3, 1.5]\)。DR 范围过大有什么后果?(提示:策略需要在更大范围内"妥协",单点性能下降。)如何确定合适的 DR 范围?
DR 通过参数化地模拟环境不确定性来提高鲁棒性。但有些信息(如地形形状)无法简单地参数化。下一节介绍 Curriculum Learning——让训练从简单到复杂渐进。
63.6 Curriculum Learning:从简单到复杂的渐进训练 ⭐⭐¶
动机:为什么不能一开始就在最难的地形上训?¶
想象你在教一个孩子骑自行车。你不会一开始就带他去山路——你会先在平地上让他找到平衡,再去缓坡,最后才是复杂路况。Curriculum Learning 的思想完全一样。
如果一开始就在困难地形(高台阶、陡坡)上训练 RL 策略,策略大概率**学不到任何有用的东西**——因为在困难地形上,随机策略几乎立刻摔倒,收集不到有意义的正向 reward 信号。这在 RL 中叫做**探索困难**(exploration difficulty):奖励如此稀疏,以至于策略无法通过随机探索找到好的行为。
如果不用 Curriculum 会怎样¶
两种极端都有问题:
| 策略 | 后果 |
|---|---|
| 只在简单地形(平地)训 | 策略在平地完美,遇到台阶立刻摔倒 |
| 直接在最难地形训 | 策略长时间学不到东西(随机动作在难地形上立刻失败),训练效率极低 |
Curriculum 是两者的平衡:从简单开始建立基本技能,逐步增加难度。
可通行性度量与自适应地形生成¶
设计 curriculum 的核心问题是:如何量化"当前策略在某个难度的地形上的表现",以及如何据此决定是升级还是降级?
一种经过验证的方法是定义**可通行性(traversability)**度量:衡量策略在特定地形参数下的通过概率。具体做法是设定一个速度阈值(如 0.2 m/s,约为最大速度的 1/3),如果策略能以高于该阈值的速度沿指令方向持续前进,则认为该地形是"可通行"的。可通行性定义为多次采样的成功比例:
这里 \(\tau\) 表示策略 \(\pi\) 在地形参数 \(c_T\) 下生成的轨迹,不要与前面 Domain Randomization 中表示环境参数的 \(\xi\) 混用。符号分开后,含义更清楚:DR 的随机变量是"世界参数",Curriculum 的随机变量是"在某个世界参数下跑出来的轨迹"。
基于此,定义地形的"期望度"——一个地形的期望度高,当且仅当其可通行性处于**中等水平**(如 \([0.5, 0.9]\)):太容易(\(Tr > 0.9\))的地形学不到新东西,太难(\(Tr < 0.5\))的地形导致策略频繁失败而无法学习。训练过程中持续维护一个地形难度分布,使采样到的地形大部分落在"刚好能学"的难度区间内。
地形类型通常由参数化方式生成,包括:(1) Hills 地形——基于 Perlin 噪声的起伏面,由粗糙度、频率和振幅三个参数控制;(2) Steps 地形——随机方块台阶,由块宽度和高度上限控制,造成离散高度突变和陷足;(3) Stairs 地形——规则楼梯,考察周期性抬脚和前后腿协调。每个 episode 都用不同随机种子重新生成地形,防止策略记忆特定地图。
腿足 Curriculum 的多维度设计¶
Curriculum 不只是地形难度,腿足训练通常有多个 curriculum 维度同时推进:
维度 1:地形难度
Level 0: 完全平地
Level 1: 小幅随机高度扰动(±2 cm)
Level 2: 缓坡(坡度 < 15 度)
Level 3: 小台阶(高度 5 cm)
Level 4: 大台阶(高度 15-20 cm)
Level 5: 混合地形(台阶 + 坡度 + 间隙)
Level 6: 极端地形(大间隙、高台阶、碎石)
维度 2:速度命令范围
Level 0: 站立(v = 0)
Level 1: 慢速(|v| < 0.3 m/s)
Level 2: 中速(|v| < 0.8 m/s)
Level 3: 快速(|v| < 1.5 m/s)
Level 4: 极速(|v| < 2.5 m/s)
维度 3:外部扰动强度
Level 0: 无扰动
Level 1: 轻微推力(v_push < 0.3 m/s)
Level 2: 中等推力(v_push < 0.8 m/s)
Level 3: 强推力(v_push < 1.5 m/s)
legged_gym 的 Curriculum 实现¶
legged_gym 中,每个环境有独立的 terrain_level,根据 episode 成功率自动调整:
def update_terrain_curriculum(self, env_ids):
"""根据 episode 成功率调整地形难度"""
# 1. 计算每个环境的"距离走得够远" -> 成功
distance_walked = torch.norm(
self.root_states[env_ids, :2] - self.env_origins[env_ids, :2],
dim=1
)
success = distance_walked > self.cfg.terrain.curriculum_distance_threshold
# 2. 成功的环境升级(80%概率),失败的降级(20%概率)
move_up = success.float()
move_down = (~success).float()
self.terrain_levels[env_ids] += 1 * (torch.rand_like(move_up) < 0.8 * move_up)
self.terrain_levels[env_ids] -= 1 * (torch.rand_like(move_down) < 0.2 * move_down)
# 3. Clamp 到有效范围
self.terrain_levels[env_ids] = torch.clamp(
self.terrain_levels[env_ids], 0, self.cfg.terrain.num_levels - 1
)
关键设计思想:
- 独立进度: 4096 个环境中,有些在 Level 0,有些已经到 Level 5——保证了**数据多样性**
- 概率升降级: 不是"成功必定升级",而是概率性的——避免过拟合到特定难度的边界
- 保留低难度环境: 即使大部分环境在高难度,仍有少量留在低难度——防止策略"忘记"简单场景(catastrophic forgetting)
⚠️ 常见陷阱¶
⚠️ 概念误区:Curriculum 升级越快越好 新手想法: "成功率到 50% 就升级,加速训练。" 实际情况: 升级太快会导致策略在当前难度上**不够稳固**,到了下一难度直接崩溃,形成"学了忘,忘了学"的循环。 正确做法: 等成功率到 80% 再升级,确保策略已经 robust。宁慢勿快。
⚠️ 编程陷阱:所有环境同步升级 错误做法:
if avg_success_rate > 0.8: terrain_level += 1(全局统一升级)。 后果: 所有 4096 个环境同时跳到新难度,策略突然面对全部困难环境,reward 可能断崖式下降,训练不稳定。 正确做法: 每个环境独立进度,渐进过渡。
练习¶
练习 63.6.1: 设计一个三维 Curriculum(地形 \(\times\) 速度 \(\times\) 扰动)的状态机。当机器人在 Level (2, 1, 0)(中坡、慢速、无扰动)上成功率到 80% 时,下一步应该提升哪个维度?设计一个优先级策略并论证。
练习 63.6.2: legged_gym 的 curriculum 只考虑"距离走得够远"作为成功标准。设计一个更综合的成功标准,同时考虑能量效率(累积扭矩)和平滑度(动作变化率),写出数学表达式。
Curriculum 解决了"从简单学到复杂"的问题,但还有一个核心挑战:真机只有本体感知(IMU + 关节编码器),没有完美的地形信息。如何让策略在有限信息下也能表现好?这是 Teacher-Student 范式的动机。
63.7 Teacher-Student 范式:信息论视角 ⭐⭐⭐¶
动机:信息不对称的困境¶
真实机器人与仿真环境之间存在根本的**信息不对称**:
| 信息类型 | 仿真中可获取 | 真机可获取 |
|---|---|---|
| 关节位置/速度 | Yes | Yes (编码器) |
| IMU (角速度/加速度) | Yes | Yes |
| 接触力真值 | Yes | 困难(需要力传感器,Ch62) |
| 地形高度图 | Yes | 困难(需要深度相机+处理) |
| 摩擦系数 | Yes | No |
| 机器人精确质量 | Yes | 近似(标称值,有制造误差) |
| 外部扰动力 | Yes | No |
仿真中那些"额外可用"的信息被称为**特权信息**(privileged information)。直接用纯本体感知训练 RL 策略的困难:
- 信号太弱: 只有关节和 IMU 数据,无法直接"看到"前方地形
- 探索困难: 不知道地形,随机行动容易摔倒,难以收集好的经验
- 收敛慢: 策略需要**隐式地学会推断**那些看不到的信息(如从脚底反馈力推断地形硬度)——这比直接"看到"难得多
Teacher-Student 的两阶段训练¶
Miki et al. (2022, Science Robotics) 在 ANYmal 上提出的 Teacher-Student 范式优雅地解决了这个问题:
第一阶段:训练 Teacher(在仿真中)
Teacher 网络看到**所有信息**(包括特权信息):
用 PPO 训练 Teacher。因为信息完整,Teacher 收敛快、策略质量高。整个 Teacher-Student 范式好比驾校教学——教练(Teacher)坐在副驾驶位上能看到所有后视镜和仪表盘(特权信息),在这种全视野条件下先练出完美的驾驶技术;然后学员(Student)只靠前挡风玻璃的有限视野(本体感知),通过模仿教练的操作来学会同样的驾驶技能。
第二阶段:蒸馏到 Student(监督学习)
Student 网络只看**本体感知**(真机能拿到的):
注意 Student 的输入中有**历史动作和观测**——这让 Student 可以从"我做了什么动作,世界如何响应"来推断环境特性(类似系统辨识)。
蒸馏 loss:
即让 Student 的输出(动作)尽量匹配 Teacher 的输出。训练数据:在仿真中用 Teacher 策略 rollout,同时记录 \(o_{student}\) 和 Teacher 的动作。
这一步看起来像普通 Behavior Cloning,但腿足场景必须警惕两个经典失败模式:
| 失败模式 | 发生原因 | 工程后果 | 常见缓解 |
|---|---|---|---|
| 累计误差 | 训练数据来自 Teacher 的状态分布,部署时 Student 的小偏差会把系统带到未见过的状态 | 一开始动作只差一点,几步后姿态和接触相位完全偏离训练集 | 用 DAgger / on-policy 蒸馏收集 Student 自己访问到的状态,再由 Teacher 标注 |
| 多峰动作分布 | 同一观测下可能有多个合理动作,如左绕/右绕、抬左脚/抬右脚 | MSE 会学到多个专家动作的平均值,平均动作可能不可执行 | 分阶段任务拆解、混合密度策略、扩散策略,或用 GAIL/AMP 学风格奖励 |
因此 Teacher-Student 不是"把 Teacher rollout 存下来做一次监督学习"这么简单。真正可靠的做法是让 Student 或当前适应模块参与采样,暴露它自己会犯错后到达的状态,再用 Teacher/Privileged Encoder 给这些状态提供监督信号。Ch65 会从 MPC-Net 和 DAgger 角度展开"分布偏移如何累积";Ch70 会把 GAIL/AMP 放到更大的模仿学习和风格化运动框架里讨论。本章先建立接口:只要用了模仿或蒸馏,就必须问数据分布是否覆盖部署时状态,以及动作分布是否存在多峰。
信息论解释:为什么 Teacher-Student 有效?¶
从信息论角度,Teacher-Student 的本质是**信息压缩和传递**:
Teacher 阶段: Teacher 学到了一个映射 \(f: (o_{proprio}, o_{priv}) \mapsto a\)。这个映射隐式地包含了"在给定完整信息下的最优行为"。
Student 阶段: Student 学到了一个映射 \(g: o_{proprio} \mapsto a\)。由于 \(g\) 的输入比 \(f\) 少(缺少 \(o_{priv}\)),Student 必须通过**推断** \(o_{priv}\) 来近似 \(f\) 的输出。
信息论的视角:设 \(I(o_{proprio}; o_{priv})\) 是本体感知与特权信息之间的互信息(mutual information)。如果互信息为零(两者完全独立),Student 无法从本体感知推断任何特权信息,蒸馏必然失败。如果互信息大(如关节力矩包含地形硬度信息),Student 可以从本体感知中**推断**特权信息。
Teacher-Student 的隐式假设:本体感知与特权信息之间存在足够的互信息。在腿足场景中,这个假设通常成立:
- 脚底接触力 \(\leftrightarrow\) 关节电流(电机扭矩 \(\propto\) 接触力)
- 地形坡度 \(\leftrightarrow\) 身体倾斜(IMU 可测)
- 摩擦系数 \(\leftrightarrow\) 打滑时的速度变化(关节速度可测)
变种:RMA (Rapid Motor Adaptation)¶
Kumar et al. (2021, RSS) 提出了 RMA(Rapid Motor Adaptation)的变体。与 Miki 的整体蒸馏不同,RMA 在网络结构中显式地引入了"适应模块":
Teacher 架构:
Base Policy: o_proprio + e_priv --> action
e_priv = Encoder(o_priv) <-- 特权信息编码为向量
Student 架构(部署):
Base Policy: o_proprio + e_hat --> action (同一个 Base Policy)
e_hat = AdaptationModule(history) <-- 从历史推断特权编码
RMA 的关键创新:Base Policy 是**共享的**(Teacher 和 Student 使用同一个 Base Policy 网络)。区别只在于特权编码的来源——Teacher 用 Encoder 从真值编码,Student 用 Adaptation Module 从历史观测推断。这使蒸馏更聚焦:只需要训 Adaptation Module 去匹配编码后的 latent \(z_t = \mu(e_t)\)(而非原始特权参数 \(e_t\)),不需要蒸馏整个策略。
RMA 与 Miki 方法的核心区别值得强调:Miki 的方法是**动作蒸馏**——Student 同时模仿 Teacher 的动作输出和环境编码;而 RMA 只让 Adaptation Module 模仿环境编码 \(z_t\),将动作的产生完全留给 Base Policy。这种模块化设计带来了两个直接收益:(1) Adaptation Module 只负责"辨识/适应",Base Policy 只负责"控制",两者可以以不同频率运行(如 Adaptation Module 10 Hz,Base Policy 100 Hz);(2) 自适应模块不追求恢复真实物理参数(如精确的摩擦系数值),而是恢复一个"足够用"的控制表征——只要能指导正确的动作即可。
RMA 还有一个重要的训练技巧:在训练 Adaptation Module 时,使用**在线策略数据**(类似 DAgger 算法)——用当前(可能不完美的)Adaptation Module 产生的 \(\hat{z}_t\) 驱动 Base Policy 采集轨迹,然后用真实的 \(z_t\) 作为标签训练。这种做法确保 Adaptation Module 在训练时就见过因自身预测不准确而导致的"偏离最优轨迹"的数据,从而在部署时更加鲁棒。如果只用 Teacher 的完美轨迹数据训练 Adaptation Module,则 Student 在部署中一旦出现偏差就无法处理(分布偏移问题)。
💡 RMA 关键工程细节
预测 latent 而非物理参数:适应模块输出的是环境编码向量 \(\hat{z}_t\)(对应 Teacher Encoder 输出的 \(z_t\)),而非物理参数本身(质量、摩擦系数等)。这是因为不同的物理参数组合可能产生相同的行为效果——适应模块只需要编码"行为应如何改变",不需要精确辨识每个物理量。
异步运行频率:适应模块运行在 ~10 Hz(基于 50 步观测历史),策略网络运行在 ~100 Hz。适应模块不需要高频更新——环境特性(摩擦、质量)变化远慢于控制周期。
on-policy 数据采集:适应模块的训练数据必须由**当前策略**(而非旧策略)采集,否则观测历史的分布与部署时不匹配。这本质上是一个 DAgger 式的在线蒸馏过程。
💡 为什么 TCN 而非 MLP:时序编码器的必要性
Miki et al. (2022) 使用**时序卷积网络(TCN)**而非简单 MLP 来处理观测历史,原因在于:
- 因果卷积:TCN 的卷积核只看过去、不看未来,天然适合实时控制(MLP 拼接历史在数学上等价但参数效率低)
- 多尺度时序特征:通过扩张卷积(dilated convolution),TCN 用少量层数就能覆盖长时间窗口——底层捕捉快速接触事件(~10 ms),高层捕捉缓慢环境变化(~1 s)
- 参数共享:同一组卷积核在所有时间步上共享,比 MLP(为每个时间步分配独立权重)参数量小一个量级
TCN 从本体感知历史中推断的隐变量包括: - 当前接触状态(哪只脚触地)——通过力矩/速度突变模式 - 地面摩擦特性——通过打滑时的速度-力矩关系 - 负载/质量变化——通过同样动作下加速度的差异 - 地形坡度——通过重力分量在 IMU 中的投影变化
这些信息在单帧观测中不可见(或噪声过大),必须从时序模式中提取。
💡 "辅助轮骑自行车":参考轨迹 + 策略残差
Tan et al. (2018) 的开创性设计:先用运动捕捉或轨迹优化生成参考运动(辅助轮),RL 策略只需学习残差修正(骑自行车)。这大幅降低了探索难度——策略从"学会走路"简化为"学会微调已有的走路方式"。代价是参考轨迹限制了运动多样性,后续工作(如 RMA)通过去除参考轨迹、完全依赖奖励塑形来换取更大的行为空间。
Asymmetric Actor-Critic:更简单的替代方案¶
如果不想用两阶段训练,可以用 Asymmetric Actor-Critic(不对称演员-评论家):
- Actor: 输入 = 本体感知(部署时使用)
- Critic: 输入 = 本体感知 + 特权信息(仅训练时使用)
Critic 信息更完整,可以给出更准确的 value 估计,从而让 Actor 的 PPO 更新更稳定。部署时只用 Actor。
| 方法 | 训练复杂度 | 策略质量 | 部署简洁性 |
|---|---|---|---|
| 直接训(纯本体感知) | 低 | 较差 | 高 |
| Asymmetric Actor-Critic | 中(一阶段) | 中等 | 高(只部署 Actor) |
| Teacher-Student (Miki) | 高(两阶段) | 高 | 高(只部署 Student) |
| RMA (Kumar) | 高(两阶段) | 高 | 中(需要 Adaptation Module) |
补充:事后经验回放(HER)与稀疏奖励场景¶
在某些足式任务中(如精确的足端定位或物体操纵),奖励可能非常稀疏——只有完成目标才能获得正向信号。这类场景下标准 PPO 几乎无法收敛,因为随机探索极难触及目标状态。
事后经验回放(Hindsight Experience Replay, HER)是一种数据增强技术,核心思想是"从失败中学习":即使智能体未能达成既定目标 \(A\),它实际上也到达了某个位置 \(B\)。HER 通过事后将目标替换为 \(B\),把这次"失败"的轨迹重新标记为"成功",从而将稀疏奖励转化为稠密的学习信号。这种机制在早期的 sim-to-real 域随机化工作(如 OpenAI 的机械臂操纵)中被证明是训练成功的关键之一——在极端困难的随机化物理参数下,HER 保证了智能体仍能获得正向反馈。
对于腿足 RL 的主流任务(速度跟踪),奖励通常是稠密的,因此 HER 不是必需的。但在设计涉及精确目标到达或物体交互的高层策略时,HER 是值得了解的重要工具。
补充:真机直接训练与自动温度调节¶
尽管 sim-to-real 是腿足 RL 的主流范式,也有研究探索了直接在真实机器人上训练的路线。其中一个关键挑战是:on-policy 算法(如 PPO)的样本效率极低,而 off-policy 算法(如 SAC)虽然样本高效,但其温度参数 \(\alpha\) 对性能极为敏感——不同任务甚至同一任务的不同训练阶段,最优的 \(\alpha\) 值都不同,手动调参在真机上几乎不可行。
解决方案是将温度参数从超参数转变为可学习的对偶变量:将策略熵视为约束条件而非目标的一部分,即要求策略的期望熵不低于目标下界 \(\mathcal{H}\),然后通过拉格朗日对偶法自动调节 \(\alpha\)。这一技巧(Haarnoja et al., 2019)使 SAC 在真机上的部署门槛显著降低,代表了"不依赖仿真"的另一条技术路线。不过,这条路线受限于真机训练的数据采集速度和安全风险,目前工业界仍以仿真训练为主。
⚠️ 常见陷阱¶
⚠️ 概念误区:认为 Teacher-Student 只是"降级" 新手想法: "Student 信息更少,性能一定比 Teacher 差很多。" 实际情况: Student 的性能通常只比 Teacher 低 5-15%。原因:腿足行走的大部分决策不需要精确的地形信息——"感觉脚底变软了就小心走"这种粗略推断就够了。 类比: 人类闭眼走路不会立刻摔倒——因为从脚底触觉和身体平衡感就能推断很多。
⚠️ 编程陷阱:蒸馏时不冻结 Teacher 网络 错误做法: 蒸馏阶段同时更新 Teacher 和 Student。 后果: Teacher 的输出会变化,Student 追逐一个移动目标(moving target),训练不稳定。 正确做法: 蒸馏阶段 Teacher 完全冻结(
requires_grad_(False))。⚠️ 思维陷阱:所有任务都用 Teacher-Student 新手想法: "所有腿足 RL 都应该用 Teacher-Student。" 实际情况: 如果任务不需要感知(如平地 trot),直接用本体感知训练+Asymmetric AC 就够了。Teacher-Student 的价值在于**感知密集型任务**(崎岖地形、楼梯攀爬、复杂交互)。多一个阶段的训练意味着更多的调参工作和更长的开发周期。 判断标准: 如果 Asymmetric Actor-Critic 就能达到满意效果,就不需要两阶段蒸馏。
练习¶
练习 63.7.1: 设计一个简化的 Teacher-Student 实验。Teacher 输入:本体感知 + 摩擦系数(单个标量)。Student 输入:纯本体感知。训练后测试:在不同摩擦系数上,Student 的性能与 Teacher 差多少?
练习 63.7.2: RMA 的 Adaptation Module 输入是"历史观测和动作的滑动窗口"。窗口长度对推断精度有什么影响?太短(如 5 步)和太长(如 200 步)分别有什么问题?
练习 63.7.3: 从信息论角度分析:如果机器人的脚底没有力传感器(只有关节电流间接估计接触力,Ch62 讲过),Teacher-Student 的蒸馏效果会如何变化?互信息 \(I(o_{proprio}; o_{priv})\) 是增大还是减小?
前面七节覆盖了腿足 RL 训练栈的核心组件。下一节把 legged_gym 的代码结构做一次系统精读,将概念对应到具体实现。
63.8 legged_gym 代码精读与完整训练流程 ⭐⭐¶
动机:为什么要精读源码?¶
legged_gym 是腿足 RL 的"事实标准"代码库。虽然新项目建议使用 IsaacLab,但 legged_gym 的代码结构和设计思想至今仍是理解腿足 RL 工程的最佳教材——仅约 3000 行核心代码,但覆盖了环境、奖励、DR、Curriculum 的完整流水线。很多 IsaacLab 的设计直接继承自 legged_gym。
项目结构与模块分工¶
legged_gym/
├── envs/
│ ├── base/
│ │ ├── legged_robot.py # 通用腿足环境基类 (~1500 行)
│ │ │ ├── create_sim() # 创建 IsaacGym 仿真场景
│ │ │ ├── step() # 核心:并行执行 4096 环境
│ │ │ ├── compute_observations()
│ │ │ ├── compute_reward() # 向量化奖励计算
│ │ │ ├── reset_idx() # 部分环境 reset
│ │ │ └── _push_robots() # 随机推力(DR)
│ │ │
│ │ └── legged_robot_config.py # 配置类(所有参数集中管理)
│ │
│ ├── anymal_c/ # ANYmal 特化配置
│ └── go1/ # Go1 特化配置
│
├── scripts/
│ ├── train.py # 训练入口
│ └── play.py # 推理可视化
│
└── utils/
└── task_registry.py # 任务注册表
核心函数精读:step() 的并行执行¶
def step(self, actions):
"""在 GPU 上并行执行 4096 个环境的一步"""
# 1. 动作处理: clip + scale
# actions shape: (4096, 12) — 4096 个环境 x 12 个关节
clip_actions = self.cfg.normalization.clip_actions
self.actions = torch.clip(actions, -clip_actions, clip_actions)
# 2. PD 控制: target = default_pos + action * scale
# action 被解释为"关节位置偏移量",不是直接的扭矩
target = self.default_dof_pos + self.actions * self.cfg.control.action_scale
# 3. 物理仿真: 调用 IsaacGym 的 GPU 物理引擎
# decimation: 每个 RL 步包含多个物理步
# (如 RL 50Hz, 物理 200Hz -> decimation=4)
for _ in range(self.cfg.control.decimation):
torques = self._compute_torques(target) # PD -> 扭矩
self.gym.set_dof_actuation_force_tensor(
self.sim, gymtorch.unwrap_tensor(torques)
)
self.gym.simulate(self.sim) # GPU 物理仿真一步
self.gym.refresh_dof_state_tensor(self.sim)
# 4. 后处理: 计算观测、奖励、done
self.post_physics_step() # -> compute_reward() + compute_observations()
# 5. 部分 reset: 只 reset 摔倒/超时的环境,其他继续
env_ids = self.reset_buf.nonzero(as_tuple=False).flatten()
if len(env_ids) > 0:
self.reset_idx(env_ids)
return self.obs_buf, self.rew_buf, self.reset_buf, self.extras
关键设计点:
decimation: RL 频率(50 Hz)和物理频率(200 Hz)解耦。每个 RL 步内跑 4 个物理步,提高物理精度同时不增加 RL 的 sample 数量reset_idx: 只 reset 摔倒的环境,其他环境继续运行。4096 个环境中,每步可能只有 10-50 个需要 reset——这保证了高利用率- 全部操作在 GPU 上: 没有任何 CPU 调用,所有 tensor 操作都是批量的 GPU 运算
奖励计算的向量化¶
def compute_reward(self):
"""向量化计算所有奖励项——不是 for 循环,而是 tensor 运算"""
self.rew_buf[:] = 0. # 清零
for name, scale in self.reward_scales.items():
# 每个奖励项是一个函数,输入/输出都是 (4096,) 的 tensor
reward_fn = getattr(self, '_reward_' + name)
reward = reward_fn() * scale # (4096,) x scalar
self.rew_buf += reward # 累加到总奖励
# 记录每个奖励项的均值(用于 TensorBoard 监控)
self.episode_sums[name] += reward
核心思想:每个 _reward_xxx() 函数操作的都是 (4096, ...) 的 tensor,一次计算所有环境的奖励。没有 Python for 循环遍历环境,纯 GPU tensor 操作。
Asymmetric Actor-Critic 的实现¶
class ActorCritic(nn.Module):
"""rsl_rl 的 Actor-Critic 网络"""
def __init__(self, num_actor_obs, num_critic_obs, num_actions,
actor_hidden_dims=[256, 256, 256],
critic_hidden_dims=[256, 256, 256]):
super().__init__()
# Actor: 输入 = 本体感知 (如 45 维)
self.actor = build_mlp(num_actor_obs, actor_hidden_dims, num_actions)
# Critic: 输入 = 本体感知 + 特权信息 (如 45 + 73 = 118 维)
self.critic = build_mlp(num_critic_obs, critic_hidden_dims, 1)
# 动作标准差(可学习参数)
self.std = nn.Parameter(0.3 * torch.ones(num_actions))
def act(self, observations):
"""Actor 推理(训练和部署都用)"""
mean = self.actor(observations)
distribution = Normal(mean, self.std)
action = distribution.sample()
log_prob = distribution.log_prob(action).sum(dim=-1)
return action, log_prob
def evaluate(self, critic_observations):
"""Critic 估值(仅训练时用)"""
return self.critic(critic_observations)
关键:Actor 和 Critic 的**输入维度不同**——Actor 只有本体感知(45 维),Critic 有更完整的信息(118 维)。部署时只导出 Actor。
完整训练 Pipeline¶
完整训练流程
┌────────────────────────────────────────────┐
│ 1. 配置 │
│ 环境 config + RL config + 奖励权重 │
│ 4096 并行环境 + PPO 超参数 │
├────────────────────────────────────────────┤
│ 2. 训练循环 (4000-8000 iterations) │
│ ┌──────────────────────────────────┐ │
│ │ a. Rollout: 4096 env x 24 steps │ │
│ │ = 98,304 transitions/iter │ │
│ │ │ │
│ │ b. GAE 计算 advantage │ │
│ │ │ │
│ │ c. PPO 更新 (5 epochs x 4 mini) │ │
│ │ │ │
│ │ d. Curriculum 更新(地形难度) │ │
│ │ │ │
│ │ e. 日志: reward/KL/FPS/etc. │ │
│ └──────────────────────────────────┘ │
│ │
│ 3. Checkpoint 保存 │
│ model_XXXX.pt (每 200 iterations) │
├────────────────────────────────────────────┤
│ 4. 评估 │
│ play.py 加载 checkpoint, 可视化行为 │
│ 观察: 步态自然性/速度跟踪/地形适应 │
├────────────────────────────────────────────┤
│ 5. 迭代: 修改奖励权重/DR 配置 -> 回步骤 1 │
└────────────────────────────────────────────┘
TensorBoard 监控指标:
| 指标 | 含义 | 健康范围 |
|---|---|---|
mean_reward |
平均 episode 奖励 | 持续上升,最终收敛 |
mean_episode_length |
平均 episode 长度 | 从短到长(说明不再早期摔倒) |
approx_kl |
PPO 的近似 KL 散度 | 0.005 ~ 0.03 |
policy_loss |
Actor loss | 从大到小,接近 0 |
value_loss |
Critic loss | 持续下降 |
FPS |
每秒仿真帧数 | RTX 4090: 40k-80k |
curriculum/terrain_level |
平均地形难度 | 缓慢上升 |
💡 评价指标:不要只看 reward 曲线
仅用 reward 曲线评价腿足 RL 策略是不够的——reward 可以通过"抖动"等非物理行为获得高分但无法部署。建议补充以下量化指标:
指标 含义 为什么重要 CoT (Cost of Transport) 单位质量单位距离的能耗 反映运动效率,值越小越好 接触冲击力峰值 足端着地瞬间的力峰值 过大→硬件损伤/噪声大 命令跟踪误差 实际速度 vs 期望速度 核心控制性能 延迟鲁棒性 增加观测/执行延迟后的性能衰减 真机必然有延迟
⚠️ 常见陷阱¶
⚠️ 编程陷阱:修改 legged_gym 代码时用 Python for 循环遍历环境 错误做法:
for env_id in range(4096): rewards[env_id] = compute_single(env_id)后果: Python for 循环在 4096 个环境上极慢(每步 ~100 ms vs tensor 操作 ~0.1 ms),训练速度降低 1000 倍。 正确做法: 所有计算都用 PyTorch tensor 操作(广播、索引、归约),保持 GPU 并行性。⚠️ 编程陷阱:保存 checkpoint 时只存 Actor 错误做法: 只保存
actor.state_dict(),不保存 Critic 和 optimizer。 后果: 无法从 checkpoint 恢复训练(Critic 状态和 optimizer 动量丢失)。虽然部署只需要 Actor,但训练恢复需要完整状态。 正确做法:torch.save({'actor': ..., 'critic': ..., 'optimizer': ..., 'iter': ...}, path)。⚠️ 思维陷阱:训练时间越长越好 新手想法: "训 72 小时肯定比 24 小时好。" 实际情况: 腿足 RL 通常在 3000-6000 iterations 后收敛。过度训练可能导致策略 overfit 到训练地形的特定特征(如某种台阶高度的精确步法),泛化性反而下降。 判断方法: 监控
mean_reward曲线,如果连续 1000 iterations 不再上升,就可以停止。⚠️ 概念误区:认为 legged_gym 是唯一选择 新手想法: "所有腿足 RL 项目都必须 fork legged_gym。" 实际情况: legged_gym 基于 IsaacGym(已 legacy)。2025/2026 年的新项目应使用 IsaacLab 原生环境,或者使用 fan-ziqi 的
robot_lab(legged_gym 概念到 IsaacLab 的迁移封装)。 正确策略: 用 legged_gym 学习概念和架构,用 IsaacLab 做实际项目。
练习¶
练习 63.8.1: 精读 legged_robot.py 的 _reward_tracking_lin_vel() 函数。它的返回值 shape 是什么?为什么用 exp(-error / sigma**2) 而不是 -error**2?
练习 63.8.2: 精读 reset_idx() 函数。当一个环境 reset 时,哪些状态量被重置?观测 buffer 是否也被 reset?如果不 reset 观测 buffer,会导致什么问题?(提示:考虑 last_action 观测项)
练习 63.8.3: 配置并训练一个 Go2 flat terrain trot 策略(使用 IsaacLab 或 legged_gym)。记录完整的训练曲线(reward、episode length、KL),分析何时收敛。做 3 个奖励 ablation(删除 action_rate / 放大 torques / 删除 lin_vel_z),用 TensorBoard 对比训练曲线和行为差异。
常见故障与排查¶
腿足 RL 训练涉及仿真环境、奖励函数、网络优化和 sim-to-real 迁移等多个层面,调试往往比传统控制更难定位根因。以下是训练和部署中最常见的故障场景。
| 症状 | 可能原因 | 排查步骤 | 相关章节 |
|---|---|---|---|
| Reward 曲线不涨或持续下降 | 学习率过高/过低、奖励缩放失衡(正则项淹没追踪项)、GAE 参数不当 | 1. 打印各奖励分项均值,检查追踪 vs 正则量级比 2. 将学习率减半重试 3. 检查 \(\gamma\) 和 \(\lambda_{\text{GAE}}\) 是否在合理范围(0.99 / 0.95) | 63.3, 63.4 |
| Reward hacking(奖励高但行为异常,如原地抖动或单脚跳) | 奖励函数存在漏洞——智能体找到了高回报但非预期的策略 | 1. 渲染策略行为确认异常 2. 逐项检查哪个奖励分量贡献最大 3. 加入针对异常行为的惩罚项(如 action_rate、feet_air_time) 4. 用 \(\exp(-e^2/\sigma^2)\) 替代线性奖励限制饱和 |
63.4 |
| Sim-to-real gap 过大(仿真表现好,真机摔倒) | Domain Randomization 范围未覆盖真实参数、或观测延迟未建模 | 1. 测量真机摩擦系数/质量/电机延迟,确认在 DR 范围内 2. 加入 1-2 步动作延迟随机化 3. 在仿真中测试 DR 极端参数下策略是否仍存活 | 63.5 |
| 训练收敛但策略在复杂地形上失败 | Curriculum 升级过快或地形难度分布不均 | 1. 检查各地形类别成功率,确认是否有类别低于 50% 2. 降低升级阈值(如从 80% 改为 90%) 3. 增加困难地形比例或添加降级机制 | 63.6 |
| Teacher-Student 蒸馏后 Student 性能严重退化 | Student 观测空间信息不足以重建 Teacher 隐状态、或蒸馏损失权重不当 | 1. 打印 Teacher 和 Student 的动作 MSE 2. 检查 Student 观测历史长度是否足够(通常需 10-50 步) 3. 先冻结 Adaptation Module 单独训练 Student Policy,再联合微调 | 63.7 |
Reward 调优实战技巧——从 20+ 个项目中总结的经验 ⭐⭐⭐¶
技巧 1:先跑"纯追踪"基线
在加入任何正则化项之前,先只用速度追踪奖励训练一版策略。这个策略可能行为很丑(抖动、能耗大),但它建立了"追踪项的基准 reward 量级"。后续加入正则项时,确保正则项的量级不超过追踪项的 50%——否则 RL 会优先"不动"(正则项最优)而非"追踪命令"。
技巧 2:\(\sigma\) 参数是 reward shaping 最强的旋钮
对于 \(r = \exp(-\|e\|^2 / \sigma^2)\) 形式的奖励,\(\sigma\) 决定了"多大的误差仍能获得正回报"。\(\sigma\) 太小则只有完美追踪才有 reward(训练早期无信号),太大则粗糙追踪也有高 reward(训练后期不精细)。
| 奖励项 | 推荐 \(\sigma\) | 直觉 |
|---|---|---|
| 线速度追踪 | 0.25 m/s | 误差 0.25 m/s 时 reward 降到 \(1/e\) |
| 角速度追踪 | 0.25 rad/s | 允许约 14 度/秒偏差 |
| 躯体高度 | 0.05 m | 5cm 偏差即显著惩罚 |
| 关节加速度 | 按量级定,通常 1.0 | 正则化不宜太严 |
技巧 3:用 TensorBoard 的 reward 分项图诊断
训练时记录每个奖励分量的均值,不只是总 reward。当总 reward 卡住时,逐项检查:
- 如果追踪项在涨但正则项在跌 → 正则权重太大,降低
- 如果所有项都不变 → 学习率太小或 GAE 参数不当
- 如果某项突然跳变 → 策略发现了新行为模式,可能是 reward hacking
本章小结¶
| 知识点 | 核心内容 | 难度 | 关键公式/概念 |
|---|---|---|---|
| 63.1 腿足 RL 差异 | GPU 并行仿真、sim-to-real gap、专用训练栈 | ⭐ | — |
| 63.2 IsaacGym→Lab | GPU 原生仿真、Manager-Based 架构、性能数据 | ⭐⭐ | 4096 并行, 10 亿步/天 |
| 63.3 rsl_rl PPO | GAE 完整推导、clipped objective、训练超参数 | ⭐⭐ | \(\hat{A}_t = \sum (\gamma\lambda)^l \delta_{t+l}\) |
| 63.4 奖励设计 | 追踪+正则框架、reward hacking、权重平衡 | ⭐⭐⭐ | \(r = \exp(-\|e\|^2/\sigma^2)\) |
| 63.5 Domain Rand. | 贝叶斯解释、典型配置、ADR | ⭐⭐⭐ | \(\max_\theta \mathbb{E}_{\xi \sim p(\xi)}[J(\theta,\xi)]\) |
| 63.6 Curriculum | 多维 Curriculum、独立进度、升降级规则 | ⭐⭐ | 80% 成功率 \(\to\) 升级 |
| 63.7 Teacher-Student | 信息不对称、两阶段蒸馏、RMA、信息论 | ⭐⭐⭐ | \(L = \|\pi_S - \pi_T\|^2\) |
| 63.8 代码+流程 | legged_gym 精读、Asymmetric AC、训练 pipeline | ⭐⭐ | 98,304 步/iteration |
累积项目:本章新增模块¶
四足站立控制器进度更新:
Ch47: 加载 URDF + Pinocchio 建模 [完成]
Ch49: 正/逆运动学 + 接触 Jacobian [完成]
Ch50: QP 求解器集成 [完成]
Ch52: 摩擦锥约束 [完成]
Ch53: WBC 实现 (加权 QP) [完成]
Ch55: OCS2 MPC 集成 [完成]
Ch61: 实时部署框架 [完成]
Ch62: 硬件接口 + Unitree SDK [完成]
Ch63: RL 策略训练 <-- 本章新增
|-- IsaacLab Go2 环境配置
|-- rsl_rl PPO 训练脚本
|-- 自定义奖励函数(追踪 + 正则)
|-- DR 配置(质量/摩擦/延迟/噪声)
|-- Curriculum 地形配置
|-- TensorBoard 训练监控
|-- 导出 .pth checkpoint
本章新增的具体任务:
1. 在 IsaacLab 中配置 Go2 flat terrain 环境(4096 并行)
2. 使用 rsl_rl 的 PPO 训练 trot 策略(约 2-4 小时)
3. 做 3 个奖励 ablation 实验并记录行为差异
4. 配置 DR(摩擦 + 质量 + 推力),对比有/无 DR 的鲁棒性
5. 导出训好的 checkpoint(.pth),为 Ch64 部署做准备
延伸阅读¶
必读经典 ⭐⭐¶
| 资料 | 类型 | 说明 |
|---|---|---|
| Schulman et al. (2017) "Proximal Policy Optimization Algorithms", arXiv:1707.06347 | 论文 | PPO 原始论文 |
| Hwangbo J., Lee J., Dosovitskiy A. et al. (2019) "Learning agile and dynamic motor skills for legged robots", Science Robotics 4(26), eaau5872 | 论文 | ANYmal RL 奠基之作,首次在中型四足上实现 sim-to-real RL 部署 |
| Miki T. et al. (2022) "Learning robust perceptive locomotion for quadrupedal robots in the wild", Science Robotics 7(62), eabk2822 | 论文 | Teacher-Student + 感知,ANYmal 完成阿尔卑斯山徒步 |
核心工作 ⭐⭐⭐¶
| 资料 | 类型 | 说明 |
|---|---|---|
| Kumar A., Fu Z., Pathak D., Malik J. (2021) "RMA: Rapid Motor Adaptation for Legged Robots", RSS | 论文 | Adaptation Module 在线自适应,A1 真机验证 |
| Margolis G. B., Agrawal P. (2022) "Walk These Ways: Tuning Robot Control for Generalization with Multiplicity of Behavior", CoRL | 论文 | 行为多样性,一个策略多种步态,Go1 部署 |
| Rudin N., Hoeller D., Reist P., Hutter M. (2022) "Learning to Walk in Minutes Using Massively Parallel Deep Reinforcement Learning", CoRL | 论文 | legged_gym 训练框架,20 分钟训好 ANYmal |
| Mittal M. et al. (2023) "Orbit: A Unified Simulation Framework for Interactive Robot Learning Environments", RA-L | 论文 | IsaacLab 前身 |
前沿进展 ⭐⭐⭐⭐¶
| 资料 | 类型 | 说明 |
|---|---|---|
| Hoeller D. et al. (2024) "ANYmal parkour: Learning agile navigation for quadrupedal robots", Science Robotics | 论文 | 四足跑酷,感知+RL 的极致展示 |
| Zhuang Z. et al. (2023) "Robot Parkour Learning", CoRL | 论文 | 视觉 RL parkour |
rsl_rl GitHub (leggedrobotics/rsl_rl) |
代码 | ETH RSL 的 RL 训练库,多 GPU 支持 |
IsaacLab 官方文档 (developer.nvidia.com/isaac/lab) |
文档 | NVIDIA 官方,Isaac Lab 3.x/3.0 beta 需以当前文档为准 |
robot_lab (fan-ziqi/robot_lab) |
代码 | legged_gym 概念到 IsaacLab 的迁移封装 |
本章从概念到实现,系统构建了腿足 RL 训练栈的完整知识体系。训好的策略存在 PyTorch checkpoint 里——但这只是一个 Python 对象。要在真实机器人上以 1 kHz 运行,必须解决一个核心工程问题:如何把 Python 模型变成 C++ 可执行的实时推理引擎? 这正是 Ch64 的主题——RL 策略的 C++ 部署,从 TorchScript/LibTorch 到 ONNX Runtime,从零拷贝数据转换到安全降级设计。