XgoDuck RL

代码实现、数学原理与训练过程分析
MuJoCo Warp PPO · rsl_rl BAM 执行器 ONNX 部署 14-DOF 双足

目录

  1. 系统概览
  2. 机器人模型数学原理
  3. MDP 定义
  4. 奖励函数数学设计
  5. 域随机化(Sim2Real)
  6. 训练过程
  7. 部署流程
  8. 核心设计理念总结
  9. 大白话版本:模型架构、输入输出与训练

1系统概览

XgoDuck 是一个小型双足机器人(几何/惯量不同于上游 MicroDuck),基于 mjlab(MuJoCo Warp GPU 并行物理引擎)构建强化学习环境,使用 PPO 算法训练运动技能策略,导出为 ONNX 部署到真机。

物理仿真

MuJoCo(mujoco-warp 1.12.0),GPU 并行 4096 环境,5ms 物理步长 × 5 decimation = 50Hz 控制频率

RL 框架

rsl_rl PPO 实现,Actor-Critic 架构,自适应学习率,GAE 优势估计

执行器模型

BAM(Better Actuator Models),HLS1910/STS3215 舵机电磁-摩擦辨识,非理想 PD

关节构型

14 自由度(每腿 5 + 头部 4),位置控制,2 个被动下颌连杆排除

端到端流程

🤖
XgoDuck 真机
14-DOF 双足
→
📐
MJCF 建模
XML + 网格
→
⚙️
BAM 执行器
电磁+摩擦
→
🎯
RL 环境
4096 envs
→
🧠
PPO 训练
50K iters
→
📦
ONNX 导出
归一化烘焙
→
🚀
真机部署
50Hz 推理

2机器人模型数学原理

2.1 运动学构型

XgoDuck 共 14 个可控关节(另有 2 个被动下颌连杆 passive_* 排除在观测外):

序号关节序号关节
0–4左腿: hip_yaw, hip_roll, hip_pitch, knee, ankle9–13右腿: hip_yaw, hip_roll, hip_pitch, knee, ankle
5–8头部: neck_pitch, head_pitch, head_yaw, head_roll——

初始姿态 (HOME)

髋俯仰/踝关节 ±22°(Walk 变体 ±24°),髋翻滚 ±5°,颈部/头部俯仰各 20°,使脚底恰好高于地面 1mm。站立躯干高度 XGODUCK_STAND_Z = 0.139158 m。

碰撞分组策略

碰撞体contypeconaffinitycondim说明
*_foot_collision103仅与地面接触,带摩擦锥
trunk_collision / head_collision321与地面和彼此碰撞
其他 *_collision101仅地面,无机器人间碰撞

Walk 模型仅脚部有碰撞体;Standup 模型全身有碰撞网格(用于起立/翻滚等全身接触场景)。

2.2 BAM 执行器数学模型

BAM 是 Rhoban 开发的执行器物理模型,将真实舵机的电磁学和摩擦学嵌入仿真,而非简单使用 PD 控制器。

HLS1910 辨识参数

参数含义值
kt电机转矩常数0.624 N·m/A
R绕组电阻4.91 Ω
armature转子惯量0.00178 kg·m²
friction_base库仑摩擦基值0.0158
friction_stribeckStribeck 摩擦系数0.0096
friction_viscous粘性摩擦0.00031
load_friction_*负载相关摩擦(线性/二次)多项
max_velocity100(禁用固件目标限速)—
command_delay6ms(由 mjlab delay buffer 覆盖)—

核心方程(BAM compute() 流程)

步骤 1:电压计算(PWM 占空比 → 端电压 → 减反电动势)
\[ V_{emf} = k_t \cdot \dot{\theta}, \quad I = \frac{V - V_{emf}}{R} \]
步骤 2:电流 → 转矩
\[ \tau_{motor} = k_t \cdot I = k_t \cdot \frac{V - k_t \dot{\theta}}{R} \]
步骤 3:摩擦预算(_compute_friction_budget)
\[ F_{friction} = \underbrace{F_{coulomb}}_{\text{库仑}} + \underbrace{F_{stribeck}(v)}_{\text{Stribeck}} + \underbrace{F_{load}(\tau_{ext})}_{\text{负载相关}} + \underbrace{F_{viscous} \cdot v}_{\text{粘性}} \]

其中 Stribeck 项随速度衰减,模拟静动摩擦转换;负载相关项捕捉齿轮箱变形带来的摩擦。load_friction_motor_quad 和 load_friction_external_quad 提供二次项精度。

步骤 4:固件 PD 闭环
\[ \tau_{cmd} = k_{p,fw} \cdot (q_{target} - q_{measured}) \]

kp_fw=5,读取编码器位置作反馈,通过 q_target_smooth 实现目标限速(max_velocity=100 禁用此限速,速度由电压+反电动势决定)。

Sim2Real 关键扩展

FrictionDRBamActuator

在 _compute_friction_budget 中乘以每环境 friction_scale,实现摩擦量级的域随机化。因为 BAM 下 MuJoCo 的 dof_frictionloss 被置零,标准 DR 失效——这是 BAM 原生路径。

BacklashEncoderBamActuator

编码器穿过齿隙读取——固件 PD 反馈的是 qpos[servo] + qpos[backlash],仿真真实舵机编码器在齿隙输出侧的行为。串联齿隙铰链模型 passive_*_backlash。

3MDP 定义

3.1 观测空间(61 维 Actor)

观测向量按项插入顺序拼接,Actor 与 Critic 信息不对称:

维度内容ActorCritic说明
[0:3]base_ang_vel✓✓机体角速度 (roll, pitch, yaw),IMU 体坐标
[3:6]projected_gravity✓✓投影重力 (gx, gy, gz),体坐标
[6:20]joint_pos_rel✓✓14 关节位置(相对默认姿态),含编码器偏置
[20:34]joint_vel_rel✓✓14 关节速度,1 步延迟(模拟 Dynamixel 移动平均)
[34:48]last_action✓✓14 维上一步动作
[48:51]twist command✓✓速度指令 (lin_x, lin_y, ang_z)
[51:55]head command✓✓头部姿态指令 4D
[55:61]body command✓✓躯干位姿指令 6D
—base_lin_vel✗特权真实线速度(策略不可见)
—foot_contact 等✗特权脚步接触力/离地高度传感器数据

观测处理关键设计

设计机制目的
IMU 安装误差 DR每环境固定旋转 ≤6°(零中心随机轴),旋转角速度和投影重力模拟 IMU 装配误差
编码器偏置 DR每环境 ±0.86° 固定偏置,仅 Actor 可见模拟编码器校准误差
观测噪声角速度 ±0.03, 重力 ±0.01, 关节位置 ±0.001, 速度 ±0.25传感器噪声建模
延迟模拟角速度/重力 0–1 步延迟(≈20ms),关节速度 1 步延迟匹配真实通信延迟

3.2 动作空间(14 维)

JointPositionActionCfg,scale=1.0:策略输出 14 维关节位置偏移量,叠加到默认姿态偏移上。StandUp 任务使用 FreezeThenJointPositionActionCfg——前 2 秒冻结动作(让机器人在掉落后稳定),然后放开控制。

3.3 指令系统

① 速度指令 twist

均匀采样:\(v_x \in [-0.4, 0.4]\) m/s, \(v_y \in [-0.3, 0.3]\) m/s, \(\omega_z \in [-1.0, 1.0]\) rad/s

  • 2% → 25% 站立环境(零指令),课程渐进
  • 15% 原地转弯(\(v=0, |\omega| \in [0.4, 1.0]\))
  • 15% 后退环境(\(v_x \in [-0.2, -0.1]\))

② 头部姿态指令 head_pose

4D 从 HOME 的偏移量 [neck_pitch, head_pitch, head_yaw, head_roll],2–5s 重采样。课程分 5 阶段从 ±5% 扩展到各关节机械极限:

  • neck_pitch / head_pitch: ±1.10 rad
  • head_yaw: ±1.40 rad
  • head_roll: ±0.31 rad

③ 躯干位姿指令 body_pose

6D 偏移量 [x, y, z, roll, pitch, yaw]。在 Velocity 任务中权重为 0(保持输入神经元活跃但不主导),Standup 任务提升权重。

4奖励函数数学设计

Velocity 任务的总奖励为多项加权求和,采用高斯核形式 \(\exp(-e^2/\sigma^2)\) 保证梯度始终非零。

4.1 速度跟踪(核心目标)

\[ r_{track} = w_1 \cdot \exp\!\left(-\frac{\|\mathbf{v}_{cmd} - \mathbf{v}_{actual}\|^2}{\sigma_{lin}^2}\right) + w_2 \cdot \exp\!\left(-\frac{(\omega_{cmd} - \omega_{actual})^2}{\sigma_{ang}^2}\right) \]
项权重σ说明
线速度跟踪w₁ = 3.0 (XgoDuck)√0.25误差为 σ 时奖励 = 1/e ≈ 0.37
角速度跟踪w₂ = 2.0√0.5角速度容差更大(转弯难度高)

4.2 姿态保持

\[ r_{pose} = \sum_{j \in legs} \exp\!\left(-\frac{(q_j - q_j^{default})^2}{\sigma_j^2}\right) \cdot \text{speed\_gate}(\mathbf{v}) \]

按速度分级 σ(standing / walking / running),低速时更紧、高速时更松。XgoDuck 调高权重至 1.3,并收紧 hip_yaw 的 σ(standing 0.05, walking 0.15, running 0.25)。

4.3 头部姿态跟踪

均值形式(非平方和)保证单关节偏差不杀死整体梯度
\[ r_{head} = \frac{1}{4}\sum_{j=1}^{4} \exp\!\left(-\frac{(e_j)^2}{\sigma^2}\right), \quad \sigma = 0.5, \quad w = 2.0 \]

4.4 头部 DC 偏置惩罚(关键设计)

设计原理

280g 头部占机器人 38% 质量,行走时不可避免振荡。瞬时紧容差会永久惩罚行走(不可逃避),导致策略放弃走路。但 DC 偏置(重力下垂)是可逃避的——策略可偏置颈部指令抵消重力。EMA 滤波让振荡抵消,只惩罚偏置。

\[ r_{bias} = -w \cdot \frac{1}{4}\sum_j \left|\text{EMA}(e_j,\; \tau = 1\text{s})\right| \]

课程从 iter 600 开始:w: 0 → 1.0 → 2.0 → 3.0。在 weight 3.0 时,15° 残余偏置每步代价 0.79,2° 偏置代价 0.10——精确地价格可改善的误差。

4.5 其他奖励项总览

奖励权重公式/说明
upright+2.0\(\exp(-\theta_{tilt}^2 / 0.05)\),强惩罚躯干倾斜
air_time+3.0摆动相时长在 [0.125, 0.3]s 窗口内奖励
foot_swing_height-5.0L1 惩罚摆动脚高偏离目标 0.02m
foot_clearance—惩罚拖步
foot_slip-0.1弱惩罚脚底滑动(弱因转弯需枢轴转动)
body_ang_vel-0.05惩罚躯干旋转
angular_momentum-0.02惩罚全身角动量(稳定性)
action_rate_l2课程 -0.1→-1.0动作平滑度,逐步收紧
self_collisions-1.0腿撞躯干的惩罚
hip_yaw_home-1.0低速时髋偏航保持 HOME(允许转弯)

5域随机化(Sim2Real)

这是 sim2real 迁移的核心,每 episode reset 重新采样(非累积):

随机化项范围机制频率
躯干质心偏移±3mm → ±15mm(课程)dr.body_ipos addreset
头部质心偏移±3mm → ±10mm同上,逐 bodyreset
质量+惯量±5%\(\alpha\) 缩放 \(m\) 和 \(I\) 同时 \(e^{2\alpha}\),CoM 不变startup
关节摩擦±10%FrictionDRBamActuator.friction_scalereset
电枢(转子惯量)±10%dr.joint_armature scalereset
脚底摩擦0.4 – 1.5几何属性reset
IMU 安装误差≤6°观测级旋转startup
编码器偏置±0.86°观测级,固定/envstartup
速度推力±0.3 m/s踢基底速度每 3–6s
电压跌落vin 7.4–8.0V, drop 0–0.2BAM 参数reset
命令延迟Walk 3–6步 (15–30ms),其他 1步mjlab delay buffer固定
质心范围审计

CoM 随机化上限从 ±30mm 降至 ±15mm:脚后跟距踝仅 20mm,±30mm 可使 CoM 完全在支撑多边形外,迫使超反应步态,使后退平衡不可训练。

6训练过程

6.1 训练命令总览

所有训练通过 mjlab 的 train 命令启动,使用 uv run 在项目虚拟环境中执行。命令格式:

uv run train <TASK_ID> [--env.<path>.<value> ...] [--agent.<path>.<value> ...] [其他全局选项]

列出所有可用任务

uv run list-envs

打印每个已注册的任务 ID。XgoDuck 相关任务包括:

任务 ID地形日志目录说明
Mjlab-Velocity-Flat-XgoDuck平地logs/rsl_rl/xgoduck_velocity/主行走任务
Mjlab-Velocity-Rough-XgoDuck崎岖logs/rsl_rl/xgoduck_velocity/粗糙地形行走
Mjlab-Velocity-Swizzle-XgoDuck平地logs/rsl_rl/xgoduck_velocity_swizzle/滑冰式交替蹬地
Mjlab-StandUp-Flat-XgoDuck平地logs/rsl_rl/xgoduck_standup/从倒地起立
Mjlab-StandUp-Rough-XgoDuck崎岖logs/rsl_rl/xgoduck_standup/粗糙地形起立
Mjlab-GroundPick-Flat-XgoDuck平地logs/rsl_rl/xgoduck_ground_pick/蹲下拾取
Mjlab-GroundPick-Rough-XgoDuck崎岖logs/rsl_rl/xgoduck_ground_pick/粗糙地形拾取
Mjlab-SitStand-Flat-XgoDuck平地logs/rsl_rl/xgoduck_sitstand/坐↔站转换
Mjlab-SitStand-Rough-XgoDuck崎岖logs/rsl_rl/xgoduck_sitstand/粗糙地形坐站
Mjlab-BallKick-Flat-XgoDuck平地logs/rsl_rl/xgoduck_ball_kick_right/右脚踢球
Mjlab-BallKick-Left-Flat-XgoDuck平地logs/rsl_rl/xgoduck_ball_kick_left/左脚踢球
Mjlab-Roulade-Flat-XgoDuck平地logs/rsl_rl/xgoduck_roulade/前滚翻
Mjlab-Roulade-Angle-Flat-XgoDuck平地logs/rsl_rl/xgoduck_roulade_angle/带角度观测的前滚翻

6.2 基本训练命令示例

标准训练(4096 并行环境)

uv run train Mjlab-Velocity-Flat-XgoDuck --env.scene.num-envs 4096

崎岖地形训练

uv run train Mjlab-Velocity-Rough-XgoDuck --env.scene.num-envs 4096

Swizzle(滑冰)训练

uv run train Mjlab-Velocity-Swizzle-XgoDuck --env.scene.num-envs 4096

减少环境数(显存不足时)

uv run train Mjlab-Velocity-Flat-XgoDuck --env.scene.num-envs 2048

冒烟测试(快速验证代码路径,仅 5 个迭代)

uv run train Mjlab-Velocity-Flat-XgoDuck --agent.max-iterations 5 --env.scene.num-envs 64

6.3 CLI 参数详解

mjlab 使用 tyro CLI 框架解析命令行参数,支持通过点分路径覆盖环境配置 (--env.*) 和 RL 配置 (--agent.*) 中的任意字段。

环境配置参数 (--env.*)

参数默认值说明
--env.scene.num-envs4096并行环境数。越大采样越快,但显存消耗线性增长。2048 适合 8GB GPU
--env.sim.mujoco.timestep0.005 (Walk) / 0.004 (其他)物理仿真步长(秒)。Walk 用 5ms,StandUp 等用 4ms
--env.decimation5控制频率 = 1 / (timestep × decimation)。5 × 4ms = 50Hz
--env.sim.nconmax35 (平地) / 200 (崎岖)最大接触数。崎岖地形需增大防止接触溢出→NaN
--env.sim.mujoco.iterations10 (平地) / 30 (崎岖)MuJoCo 约束求解器迭代次数
--env.sim.mujoco.ls-iterations20 (平地) / 50 (崎岖)线搜索迭代次数
--env.episode-length-s任务特定单 episode 时长(秒)。Velocity 通常 20s,StandUp 8s
--env.scene.terrain.terrain-type"plane" / "generator"地形类型。平地="plane",崎岖="generator"

RL/Agent 配置参数 (--agent.*)

参数默认值说明
--agent.max-iterations50,000 (Vel) / 15,000 (StandUp)最大训练迭代数。每个迭代 = num_envs × num_steps_per_env 步
--agent.num-steps-per-env24每次 rollout 每环境采集步数。4096×24 = 98,304 样本/迭代
--agent.save-interval250每多少迭代保存一次 checkpoint
--agent.learning-rate1e-3初始学习率(adaptive 模式下基于 KL 自适应调整)
--agent.clip-param0.2PPO 裁剪比率 ε
--agent.entropy-coef0.01熵正则化系数(鼓励探索)
--agent.gamma0.99折扣因子 γ
--agent.lam0.95GAE 参数 λ
--agent.desired-kl0.01目标 KL 散度,自适应 LR 的锚点
--agent.max-grad-norm1.0梯度裁剪范数
--agent.num-learning-epochs5每批数据 PPO 训练轮数
--agent.num-mini-batches4每轮切分的 minibatch 数
--agent.actor.hidden-dims(512, 256, 128)Actor MLP 隐藏层维度
--agent.critic.hidden-dims(512, 256, 128)Critic MLP 隐藏层维度
--agent.experiment-name"xgoduck_velocity" 等TensorBoard/checkpoint 目录名
--agent.run-name"xgoduck"wandb 中的运行名

全局选项

选项说明
--gpu-ids <ids>指定使用的 GPU(如 --gpu-ids 0 或 --gpu-ids 0,1)
--checkpoint-file <path>从指定 checkpoint 恢复训练
--hf-jobs提交训练到 HuggingFace Jobs 远程执行(见 6.6 节)
--headless无头模式(无可视化窗口,适合服务器)
-h / --help打印完整参数帮助(列出所有可覆盖字段)
参数发现技巧

运行 uv run train Mjlab-Velocity-Flat-XgoDuck --help 可列出所有可通过 CLI 覆盖的参数,包括嵌套的环境配置和 RL 超参数。tyro 会自动从 dataclass 生成完整的参数文档。

6.4 PPO 超参数完整表

参数Velocity 值StandUp 值说明
Actor 网络(512, 256, 128) ELU(512, 256, 128) ELU观测归一化(EmpiricalNormalization 内嵌)
Critic 网络(512, 256, 128) ELU(512, 256, 128) ELU额外特权信息输入
分布Gaussian, scalar stdGaussian, scalar std初始 std = 1.0
clip_param0.20.2PPO 裁剪比率
entropy_coef0.010.01探索激励
num_learning_epochs55每批数据训练轮数
num_mini_batches44每轮切分数
learning_rate1e-31e-3adaptive:基于 KL 自适应
gamma0.990.99折扣因子
λ (GAE)0.950.95广义优势估计
desired_kl0.010.01KL 目标,自适应 LR 锚点
max_grad_norm1.01.0梯度裁剪
num_steps_per_env2424每次 rollout 环境步数
num_envs40964096并行环境数
max_iterations50,00015,000最大训练迭代
save_interval250250checkpoint 保存间隔
loggertensorboardtensorboard日志后端
upload_modelFalseFalse是否上传到 wandb

对称性增强(已实现,当前关闭)

代码实现了完整的左右对称镜像增强(61 维排列 + 符号表),镜像规则:

  • 左腿关节 (0–4) ↔ 右腿关节 (9–13) 交换,中线关节 (5–8) 不变
  • hip_yaw/hip_roll: 符号取反(反射轴反向)
  • hip_pitch/knee/ankle: 符号取反(左右 HOME 姿态符号相反)
  • base_ang_vel: 取反 roll [0] 和 yaw [2]
  • projected_gravity: 取反 gy [4]

当前 ENABLE_SYMMETRY = False,镜像损失系数 0.5。可在代码中开启以加速收敛。

6.5 多阶段训练(Shell 脚本)

项目提供两个 Shell 脚本实现顺序多阶段训练——每个阶段独立从头训练,不共享 checkpoint。

脚本 1:起立 → 行走

scripts/train_xgoduck_getup_then_velocity.sh

# 默认:4096 环境,平地
bash scripts/train_xgoduck_getup_then_velocity.sh

# 崎岖地形
bash scripts/train_xgoduck_getup_then_velocity.sh --rough

# 跳过起立阶段,仅训练行走
bash scripts/train_xgoduck_getup_then_velocity.sh --skip-getup

# 自定义环境数 + 指定 GPU
NUM_ENVS=2048 bash scripts/train_xgoduck_getup_then_velocity.sh --gpu-ids 0

# 转发额外参数给 train 命令(如冒烟测试)
bash scripts/train_xgoduck_getup_then_velocity.sh --agent.max-iterations 5

流程:① Mjlab-StandUp-Flat-XgoDuck → ② Mjlab-Velocity-Flat-XgoDuck

脚本 2:拾取 → 起立 → 行走

scripts/train_xgoduck_pick_standup_velocity.sh

# 完整三阶段
bash scripts/train_xgoduck_pick_standup_velocity.sh

# 跳过拾取和起立,仅训练行走
bash scripts/train_xgoduck_pick_standup_velocity.sh --skip-pick --skip-standup

# 崎岖地形全部三阶段
bash scripts/train_xgoduck_pick_standup_velocity.sh --rough

流程:① Mjlab-GroundPick-Flat-XgoDuck → ② Mjlab-StandUp-Flat-XgoDuck → ③ Mjlab-Velocity-Flat-XgoDuck

脚本选项汇总
选项说明
--skip-getup / --skip-standup跳过起立阶段
--skip-pick跳过拾取阶段
--rough使用崎岖地形版本的任务
NUM_ENVS=<N>环境变量,覆盖默认 4096
--agent.max-iterations <N>转发给 train 的参数(冒烟测试用)
--分隔符:之后的参数全部转发给 train

6.6 HuggingFace Jobs 远程训练

添加 --hf-jobs 标志可将训练提交到 HuggingFace Jobs 远程 GPU 节点执行,无需本地 GPU。

提交远程训练

uv run train Mjlab-Velocity-Flat-XgoDuck \
    --env.scene.num-envs 4096 \
    --agent.max-iterations 4000 \
    --hf-jobs

此命令会在导入阶段被 train_hook.py 拦截,将源码打包上传到 HF,然后在远程容器内执行 uv run train。

HF Jobs 专用参数

参数默认值说明
--flavorl4x1GPU 硬件规格(NVIDIA L4 × 1)
--imagepytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtimeDocker 镜像
--timeout12h任务最大运行时长
--namespace交互选择HF 命名空间(个人账号或组织)
--run-nametask+timestamp运行标签
--detachFalse提交后立即返回(不流式传输日志)
--dry-runFalse打印任务规格但不提交
--no-wandbFalse不转发 wandb API key
--uv-cacheFalse挂载持久化 uv 缓存桶(默认关闭,FUSE 挂载不支持 hardlink)
远程训练自动行为
  • 源码打包:git HEAD + 未提交的跟踪文件打包为 tar.gz 上传到 HF dataset repo
  • Checkpoint 上传:scripts/hf/uploader.py 后台监控 logs/rsl_rl/,自动推送到 HF model repo
  • 自动 ONNX 导出:训练成功后自动导出最终 checkpoint 为 ONNX 并上传(AUTO_EXPORT=1)
  • wandb 转发:自动从 $WANDB_API_KEY 或 ~/.netrc 获取 key 并注入容器

6.7 课程学习

多维度渐进式课程,分阶段开启难度。以下以 Velocity 任务为例:

课程阶段iter 范围变化
action_rate 平滑度10-0.1
2500-0.2
3750-0.4
41000-0.6
51250-0.8
61500-1.0
站立环境比例102%
25005%
375010%
4100015%
5150020%
6200025%
头部姿态指令范围105%
250015%
3100035%
4150065%
52000100%
头部偏置惩罚10–6000 (关闭)
26001.0
310002.0
415003.0
躯干质心随机化10±3mm
2500±5mm
31000±10mm
41500±15mm

StandUp 任务特有课程

课程说明
ground_state_mix初始姿态混合:iter 0 以坐/站为主 → iter 2500 以面朝下/面朝上为主(逆课程,先学简单再学难恢复)
arrival_damping_weight到达阻尼:iter 0–3000 关闭 → iter 3000 引入 → iter 4000 达 -0.05(技能发现后再精调)
head_pose_bias_weight头部偏置:iter 0–1000 关闭 → iter 4000 达 3.0
torque_rate_weight力矩变化率:iter 0–3000 关闭 → iter 3000 引入 -1e-3(防抖)
body_pose_tracking_weight躯干跟踪:iter 0–2500 关闭 → iter 4000 达 4.0(恢复技能掌握后开启身体控制)
push_magnitude推力幅度:iter 0 零推力 → iter 1000 达 ±0.3 m/s(渐进增加鲁棒性)

6.8 训练循环内部流程

uv run train Mjlab-Velocity-Flat-XgoDuck --env.scene.num-envs 4096
1️⃣
入口
train_cli → mjlab 插件加载
→
2️⃣
环境构造
_xgoduck_velocity_cfg() 替换 XML + BAM
→
3️⃣
Rollout
4096 envs × 24 steps = 98,304 样本
→
4️⃣
PPO 更新
5 epochs × 4 minibatches = 20 次梯度更新
→
5️⃣
自适应 LR
KL > 0.01 → 降 LR;KL < 0.01 → 升 LR
→
6️⃣
课程推进
按 iter 检查课程阶段并更新参数
→
7️⃣
Checkpoint
每 250 iter 保存 model_XXXX.pt
指标VelocityStandUp说明
每迭代样本数98,30498,3044096 × 24
每迭代梯度更新20205 epochs × 4 minibatches
物理步长5ms4msWalk 用 5ms,其他用 4ms
控制频率50Hz50Hz1 / (timestep × decimation)
最大迭代50,00015,000Vel 训练约 8–12 小时
总交互步数~4.9B~1.5Bmax_iter × num_envs × steps_per_env

6.9 监控与可视化

TensorBoard

tensorboard --logdir logs/rsl_rl/

监控奖励曲线、学习率、KL 散度、episode 长度等。XgoDuck 默认使用 TensorBoard(logger="tensorboard")。

Play 回放

uv run play Mjlab-Velocity-Flat-XgoDuck
uv run play Mjlab-Velocity-Swizzle-XgoDuck

无 checkpoint 标志时自动加载最新 model_*.pt。用 --checkpoint-file 指定特定 checkpoint。

wandb 工具

uv run python scripts/wandb_utils.py

查找最近的 wandb 运行,打印奖励分解、学习率、checkpoint 列表等。

观察对比可视化

uv run python scripts/plot_observations_comparison_plotly.py

对比不同策略的观察值分布,使用 Plotly 交互式图表。

关键监控指标

指标含义健康信号
Train/mean_reward平均 episode 奖励稳步上升
Episode_Reward/track_linear_velocity线速度跟踪奖励接近 1.0(满分)
Episode_Reward/track_angular_velocity角速度跟踪奖励接近 1.0
Episode_Reward/air_time摆动相时长奖励接近 1.0(步态稳定)
Episode_Reward/head_pose_tracking头部姿态跟踪接近 1.0
Episode_Termination/nan_stateNaN 终止次数≈0(物理稳定)
Loss/learning_rate当前学习率adaptive 模式下随 KL 波动
Loss/mean KL新旧策略 KL 散度接近 desired_kl=0.01
Loss/entropy策略熵缓慢下降(探索→收敛)
Episode/length平均 episode 长度接近 episode_length_s / dt

6.10 Checkpoint 恢复与选择

从特定 checkpoint 恢复训练

uv run train Mjlab-Velocity-Flat-XgoDuck \
    --checkpoint-file logs/rsl_rl/xgoduck_velocity/<run>/model_3000.pt

Play 时自动选择最新 checkpoint

# 自动找到 logs/rsl_rl/xgoduck_velocity/ 下最新的 model_*.pt
uv run play Mjlab-Velocity-Flat-XgoDuck

Play 指定 checkpoint

uv run play Mjlab-Velocity-Flat-XgoDuck \
    --checkpoint-file logs/rsl_rl/xgoduck_velocity/<run>/model_5000.pt

从 wandb 下载 checkpoint

uv run play Mjlab-Velocity-Flat-XgoDuck \
    --wandb-run-path pollen-robotics/mjlab_microduck/<run_id>

6.11 多任务体系

任务目标物理模型max_iter
Velocity行走 + 转向 + 头部控制Walk(仅脚碰撞)50,000
StandUp从倒地起立全身碰撞,冻结+掉落起启15,000
SitStand坐 ↔ 站转换全身碰撞15,000
GroundPick蹲下嘴触地 → 起身全身碰撞,相位化姿态跟踪15,000
BallKick踢球(左/右脚)Walk + 球15,000
Roulade前滚翻全身碰撞(排除重叠关节)15,000
Swizzle滑冰式交替蹬地被动轮模型50,000
Backlash 变体全部任务的齿隙版本串联齿隙铰链 + 编码器穿过齿隙同基础任务
训练前环境准备
  • 硬件要求:CUDA GPU,Python 3.12,uv 包管理器
  • 安装:cd xgoduck_rl && uv sync
  • ARM 机器(DGX Spark / GB10 / Jetson):首次 uv sync 设置 UV_HTTP_TIMEOUT=600(CUDA wheel 较大)
  • 日志位置:checkpoint 和 TensorBoard 日志在 logs/rsl_rl/<experiment_name>/<run>/

7部署流程

🏋️
训练完成
model_XXXX.pt
→
📤
export.py
加载 checkpoint
→
🔧
ONNX 导出
归一化烘焙进图
→
📋
元数据附加
关节顺序/范围
→
🤖
真机推理
61 维 → 14 维 @ 50Hz

ONNX 导出关键

  • 观测归一化(EmpiricalNormalization)作为 MLPModel 的子模块,自动烘焙进 ONNX 计算图:actor(normalizer(obs))
  • 元数据包含关节顺序、动作范围等部署信息
  • 推理输入 61 维观测,输出 14 维关节位置偏移

8核心设计理念总结

① 物理保真优先

BAM 执行器模型精确模拟电磁-摩擦耦合,而非理想 PD——这是 sim2real 成功的关键。摩擦预算包含库仑、Stribeck、负载相关、粘性四项。

② 可逃避 vs 不可逃避惩罚

头部偏置惩罚用 EMA 滤波只惩罚 DC 分量——"只惩罚策略能改善的误差"。行走振荡不可逃避(不罚),重力下垂可逃避(罚)。

③ 课程渐进

从简单到困难:先建立步态(低 action_rate 惩罚),再逐步施加精度要求(头部跟踪)、鲁棒性要求(CoM 随机化)和稳定性(偏置惩罚)。

④ Actor-Critic 信息不对称

策略只看传感器级信息(模拟真机):带噪声、带延迟、带偏置。Critic 看特权信息(真实线速度、接触力)加速训练收敛。

⑤ 全身域随机化

从质心到摩擦到 IMU 装配误差,覆盖所有真实不确定性来源。每 episode reset 重新采样,非累积。

⑥ 高斯核奖励设计

\(\exp(-e^2/\sigma^2)\) 保证梯度始终非零,误差为 σ 时奖励 1/e。均值形式(非平方和)保证单维度偏差不杀死整体梯度。

9大白话版本:模型架构、输入输出与训练

前面 8 章全是公式和术语,这章我们用最通俗的大白话把整个事情从头讲一遍。不用懂强化学习,不用懂数学,看完你就知道这机器人的"大脑"长什么样、吃什么吐什么、怎么学会走路的。

模型架构 3D 可视化 — 鼠标拖拽旋转 · 滚轮缩放
输入层 (61 维) 隐藏层 1 (512) 隐藏层 2 (256) 隐藏层 3 (128) 输出层 (14 维)

黄色光点表示数据正向传播:传感器输入 → 三层全连接 → 14 个关节动作输出

9.1 一句话:这机器人在干什么

一句话总结
给一个小型双足机器人装一个"AI 大脑"(神经网络),让它在仿真器里反复摔跤几万次,自己学会怎么走路、怎么站起来、怎么保持头部稳定,最后把学好的大脑导出成一个文件,塞进真机里就能用。

9.2 模型架构:这个"大脑"长什么样

它就是一个多层全连接神经网络

别被"PPO""Actor-Critic"这些词吓到。本质上,模型就是一个三层全连接神经网络(MLP),跟你手写的那个没什么区别:

输入(61 个数字)
  │
  ▼
┌──────────────┐
│  全连接层 1   │  512 个神经元,激活函数 ELU
├──────────────┤
│  全连接层 2   │  256 个神经元,激活函数 ELU
├──────────────┤
│  全连接层 3   │  128 个神经元,激活函数 ELU
├──────────────┤
│  输出层       │  14 个数字(对应 14 个关节)
└──────────────┘

没有 Transformer,没有注意力机制,没有卷积。就是最朴素的 Linear → ELU → Linear → ELU → Linear → ELU → Linear。三层隐藏层,规模从大到小(512→256→128),像一个漏斗,把信息逐步压缩。

为什么用 ELU 而不是 ReLU?

ReLU 在负数区域输出恒为 0("死掉"),机器人控制中很多信号是有正有负的(比如关节角度偏差左偏右偏)。ELU 在负数区域也有输出(平滑的负值),梯度不会断,训练更稳。

Actor 和 Critic 是两个大脑

严格说,这不是一个网络,而是两个网络共享前三层:

Actor(演员)— 干活的

看完传感器数据,直接输出 14 个关节该怎么动。这就是最终部署到真机上的那个大脑。它看到的信息和真机一模一样:带噪声、带延迟、带偏置。

Critic(评委)— 打分的

看完同样的情况,输出一个数字:当前这个状态值多少分?它比 Actor 多看一些特权信息(真实速度、真实接触力等真机测不到的东西)。它只在训练时用,帮 Actor 更快学习,部署时不要它。

为什么 Critic 要看特权信息?
打个比方:你学开车,教练坐在旁边能看到仪表盘上所有数据(真实车速、发动机转速),但你只能凭感觉。教练用完整信息给你打分反馈,你慢慢就学会凭感觉也开得好。训练完,教练走了,你自己也能开。

9.3 输入输出:吃什么、吐什么

输入:61 个数字(机器人"感觉"到的一切)

每 0.02 秒(50Hz),机器人的大脑会收到 61 个数字,这就是它的全部"感官"。来源包括:

感官维度通俗解释
本体姿态指令~3命令你"往前走 / 转弯 / 停下",别人给的遥控指令
关节角度偏差~14每个关节"现在在哪"和"应该在哪"差多少,本体感觉
关节速度~14每个关节转得多快,类似你感觉自己手臂挥动的速度
上一帧动作~14你上次让关节动了多少,记住自己刚做了什么
身体姿态(IMU)~6身体朝哪倾斜、转了多少,类似人的前庭系统(管平衡的)
身体角速度~3身体正在往哪边倒的速度
线速度(估计)~3整体往前/往后/往侧面飘的速度
足部接触~2左右脚是否踩在地上,类似脚底的触觉
其他(定时器等)~2辅助信息,比如原地转弯的时间到了没
注意:Actor 看到的不是"真相"
这 61 个数字不是物理引擎里的精确值。代码会故意往里面加噪声、加延迟、加偏置,模拟真机传感器的不完美。这样训练出来的大脑到真机上才不会"水土不服"。Critic 才看到精确值。

输出:14 个数字(让 14 个关节动)

大脑吐出 14 个数字,每个数字在 −1 到 +1 之间(经过 tanh 压缩)。这 14 个数字分别对应机器人 14 个关节的目标角度偏移量:

输出 = [关节1目标偏移, 关节2目标偏移, ..., 关节14目标偏移]
         ↑                    ↑
         范围 [-1, 1]          乘以缩放系数后加到"默认站姿"上

也就是说,不是让关节直接去某个绝对位置,而是在默认姿势基础上微调。比如默认是站直的,输出 [0.1, -0.05, ...] 就意味着"比站直稍微抬一下左腿、收一点右腿"。这种设计让网络不用从零学起,学习效率高很多。

14 个关节是哪些?

每条腿 5 个(髋关节 3 个:俯仰/滚转/偏航,膝关节 1 个,踝关节 1 个),两条腿共 10 个。头部 4 个(颈部俯仰/偏航 + 头部俯仰/偏航),用来控制嘴巴和头的朝向。

输出之后发生了什么?

这 14 个目标角度不会直接发给电机。中间还隔着一层 BAM 执行器模型——它模拟真实舵机的电磁力、摩擦、延迟,把"目标角度"变成"实际转到的角度"。所以仿真里的机器人和真机一样"笨拙"。

9.4 怎么训练:摔几万次就学会了

核心思路:让机器人自己试,做对了奖励,做错了惩罚

训练的本质就是试错。没有人类示范,没有预编程动作,机器人完全从零开始,靠奖励信号自己摸索。流程是这样的:

1
同时开 4096 个机器人
GPU 上并行跑 4096 个一模一样的机器人
→
2
每个都随机试试怎么动
神经网络输出动作,机器人动了 24 步
→
3
给每个打分
走得好加分,摔了扣分,头歪了扣分
→
4
总结经验
哪些动作导致高分?下次多做
→
5
更新大脑
微调神经网络参数,变得更聪明
↻
6
重复几万轮
直到走得又稳又好

奖励:做对了给糖,做错了挨揍

机器人怎么知道自己做得好不好?靠一堆奖励项加减。每个奖励项就是一个"规则",符合规则加分,违反扣分。主要的奖励项用大白话说:

奖励项权重大白话
跟踪线速度3.0让你往前走,走对方向就给糖(最重要的一项)
跟踪角速度1.5让你按指令转弯,转对了给糖
跟踪站立姿态2.0没让你走的时候就站好,别乱动
头部姿态跟踪0.5头别歪,眼睛看前方
动作平滑惩罚-0.05别抽搐,动作别太剧烈(扣分项)
动作变化率惩罚-0.005别一帧一帧地抖,动作要连贯(扣分项)
足部交替惩罚-0.5别两只脚同时离地,也别拖着脚走
滑步惩罚-0.1脚踩在地上就别滑,要真走不要溜冰
躯干朝向惩罚-0.2身体别歪,保持直立
超限惩罚-0.05关节别转到极限,留余地

每一项的权重决定了机器人更在乎什么。比如线速度权重 3.0 最高——最重要的就是把路走对。动作变化率惩罚只有 −0.005——轻微提醒别抖,但不会为了不抖而牺牲走路能力。

课程学习:先易后难

如果一上来就把所有要求都加上,机器人啥也不会,每项都扣分,总分永远负的,根本不知道该怎么改。所以用课程学习——一开始要求很低,慢慢加难度:

第 0 轮:   只要别摔就行,动作怎么抽搐都无所谓
           ↓ 慢慢加难度
第 5000 轮: 要走对方向,动作开始要求平滑
           ↓ 继续加难度
第 15000 轮:头要稳,身体要直,脚不能滑
           ↓ 最后阶段
第 40000 轮:全身随机化都打开,连质心位置都随机变
           目标:在什么烂条件下都能走稳

具体哪些难度是慢慢加的:

课程维度开始结束大白话
动作平滑要求几乎不罚正常罚先让你随便动,再要求动作好看
站立比例10%30%先多练走路,后面加站立训练
头部精度要求松紧先不管头,后面要求头也要稳
质心随机化无±2cm先在理想条件下练,后面加不确定性
偏置惩罚关闭开启先不管姿态偏置,后期才罚

PPO:不是瞎试,是有策略地试

纯随机试错效率太低。PPO(Proximal Policy Optimization)的核心思想是:

  1. 用当前大脑试一批:4096 个机器人各自用当前策略走 24 步。
  2. 算出每步好不好:跟"评委"估的分比,走得好于预期就是正,差就是负。
  3. 只小幅调整:关键来了——PPO 限制每次更新幅度(clip=0.2),不让大脑一次变太多。变太多容易"走火入魔",把学过的忘掉。
  4. 重复:每批更新完,用新大脑再试一批,循环几万次。
PPO 的 clip 是什么意思?
假设当前大脑觉得"遇到这个情况,60% 概率抬左腿"。如果这批试下来抬左腿效果不错,你想让它更倾向抬左腿。PPO 说:最多调到 72%(60% × 1.2),不能再多了。防止一次调太多翻车。这就是 clip=0.2 的含义——每次最多调整 20%。

训练要多久

任务训练轮数大概含义
站立起身(StandUp)15,000 轮学会从摔倒状态爬起来
地面拾取(GroundPick)15,000 轮学会蹲下捡东西再站起来
行走(Velocity)50,000 轮学会往前走、转弯、站立

在一张 GPU 上,行走任务大约跑 3-5 小时。4096 个机器人同时练,效率很高。

训练完怎么用

1
训练完
得到 .pt 文件(PyTorch 模型)
→
2
导出
转成 .onnx 文件
→
3
部署
塞进真机控制器
→
4
运行
真机每 20ms 读传感器→喂给模型→输出14个关节动作

导出时,输入归一化(减均值除标准差)会被烘焙进 ONNX 文件。也就是说,真机拿到的是 61 个原始数字,ONNX 内部自己先做归一化再推理,真机代码不用管归一化的事,直接喂原始数据就行。

9.5 一图总结全部

┌─────────────────────────────────────────────────────────┐
│                    训练阶段(仿真器里)                    │
│                                                         │
│  4096 个机器人同时跑                                     │
│       │                                                 │
│       ▼                                                 │
│  每个机器人感知到 61 个数字(带噪声/延迟/偏置)            │
│       │                                                 │
│       ▼                                                 │
│  Actor 神经网络(512→256→128)输出 14 个关节动作           │
│       │                                                 │
│       ▼                                                 │
│  BAM 执行器模拟真实舵机 → 关节实际转动                    │
│       │                                                 │
│       ▼                                                 │
│  物理引擎算出机器人怎么动了 → 算奖励(走对了+分,摔了-分)  │
│       │                                                 │
│       ▼                                                 │
│  Critic(看特权信息)打分 → PPO 小幅更新 Actor 参数       │
│       │                                                 │
│       ↺ 重复 5 万轮                                      │
│                                                         │
├─────────────────────────────────────────────────────────┤
│                    部署阶段(真机上)                     │
│                                                         │
│  传感器读数(61 个数字)→ ONNX 模型 → 14 个关节动作 → 电机  │
│  每 20ms 循环一次,没有 Critic,没有奖励,纯推理           │
│                                                         │
└─────────────────────────────────────────────────────────┘
最后用一句话总结
一个三层神经网络,吃 61 个传感器数字,吐 14 个关节动作,在仿真器里摔 5 万轮学会走路,导出成 ONNX 塞进真机就能跑。前面 8 章的所有公式和细节,都是为了让这个"摔"的过程更高效、更接近真机、最终能真正迁移到现实世界。