拆一只开源鸭子(四):自己训一遍走路和爬起

“拆一只开源鸭子”系列:(一)硬件、软件和部署 · (二)训练原理 · (三)仿真里鸭子为什么不走路 · (四)自己训一遍

前三篇都是读别人的代码。这一篇是自己动手:用官方仓库先训一个走路教师策略(teacher policy),再复现 velstand(走、站、摔倒后爬起一体的策略),最后修掉它慢速前进不走的问题。用到的概念在第二篇《训练原理》里都有解释。

这次训练用的参数

代码是 microduck_rl 的 commit bae39f5。除了最后一行的修复,其余参数都是官方配置,没有改。

项目(item) 配置项(config key) 取值(value)
并行环境(parallel environments) num_envs 4096 个
仿真(simulation)步长 / 控制频率 timestep、decimation 5 ms / 50 Hz(每 4 个仿真步做一次决策)
每轮采样 num_steps_per_env 每个环境 24 步,共 98,304 步
每回合最长 episode_length_s 20 秒(1000 步)
网络 hidden_dims、activation、obs_normalization Actor 和 Critic 都是三层全连接(fully connected) 512-256-128,ELU 激活,输入先归一化(normalization)
PPO learning_rate、schedule: adaptive、desired_kl、gamma、lam、clip_param、entropy_coef、num_learning_epochs、num_mini_batches 学习率(learning rate) 1e-3,按 KL 散度(KL divergence)自动调整(目标 0.01);γ 0.99,λ 0.95;裁剪 0.2;熵系数 0.01;每轮数据过 5 遍,分 4 个小批量(mini-batch)
随机种子 seed 42
保存间隔 save_interval 每 250 轮
走路教师策略 max_iterations 任务 Mjlab-Velocity-Flat-MicroDuck,从零训练 4000 轮
velstand max_iterations、MICRODUCK_WARM_START=1、--init-checkpoint 任务 Mjlab-VelStand-Rough-Backlash-MicroDuck,3000 轮。网络初始参数直接从官方走路锚点(anchor)拷过来,课程(curriculum)从最后一档开始(MICRODUCK_WARM_START=1),这叫热启动(warm start)
velstand 的行为克隆(behavior cloning, BC) bc_cfg:hf_checkpoint、gate_tilt_deg、anchor_hf_checkpoint、anchor_tilt_deg、coef、anchor_coef、learning_rate 站立教师策略 microduck_stand/69u48n8l_9750,教倾斜超过 35° 的帧;走路锚点 velocity2/441tzs6d_3750,教倾斜小于 25° 的帧。两个系数都是 1.0,单独一个 Adam 优化器(optimizer),学习率 3e-4。两个教师策略都从官方公开的 Hugging Face 仓库下载
修慢速的继继续训练练(fine-tune) anchor_lin_remap(补丁新增)、奖励(reward) track_lin_fine(std、weight) 从第 2999 轮再训 1000 轮。小指令时给走路锚点喂放大的速度(0.10 → 0.22,0.15 → 0.25 m/s),新增一项窄的前进速度跟踪奖励(std 0.12,权重 2.0)

验证都在电脑上的 CPU 仿真里做:官方的 infer_policy.py,BAM 舵机(servo)模型,带背隙(backlash)的场景。

第一步:训一个走路教师策略

我用官方仓库原样训了一遍走路任务(Mjlab-Velocity-Flat-MicroDuck),4096 个并行环境,4000 轮。

训练曲线

复现训练的奖励、每回合长度、摔倒次数和 loss 曲线
  • 前 150 轮最关键。奖励从 0 冲到 80 左右,每回合长度从 36 步涨到 800 多步(一回合最长 1000 步,即 20 秒),摔倒次数从每次统计 100 多次降到 1 次左右。到这里它已经学会不摔了。
  • 之后的奖励曲线有几处台阶和回落,都对得上课程表:第 500 到 1500 轮,动作平滑惩罚分五档从 −0.1 加到 −1.0;第 2000 轮,头部转动范围放到最大,同时四分之一的环境改练原地站着。每加一次难度,奖励掉一点,再慢慢爬回来。
  • 价值函数(value function)的 loss 在第 70 轮左右冲到 0.35,随后回落到 0.1 上下。前期回报变化太快,评价网络要跟着重学,这一段是正常的。策略(policy)的 surrogate loss 一直贴近 0。

结果

训完导出 ONNX,放回电脑上的 CPU 仿真(官方的 infer_policy.py,BAM 舵机模型)里跑,每种指令 12 秒:

指令(command) 实际表现(result)
站着不动 稳稳站住
前进 0.15 m/s 站着不动,没有往前走
前进 0.3 m/s 往前走,沿身体朝向约 0.13 m/s,但同时以约 0.24 rad/s 往一边偏,走成一段弧线
原地转 0.8 rad/s 转起来,约 0.65 rad/s

所有情况下都没摔。前进速度只有指令的四成左右,还会偏,小指令下干脆不走。拿官方发布的走路策略 alpha_walking.onnx 在同一套回放里对照,表现差不多:0.15 m/s 不走,0.3 m/s 时向前约 0.09 m/s,也往一侧漂。所以我这次算是复现了官方的走路教师策略,包括它的毛病。官方机器人上实际跑的是 velstand,它是在这个走路教师策略的基础上,再用行为克隆和更多课程一代代练出来的。

验证视频

指令 0.3 m/s。8 秒里走过的路程是 0.82 米,能看出路线是弯的。
原地转向,指令 0.8 rad/s。

第二步:复现 velstand,顺手修慢速前进

走路教师策略复现完,我接着复现官方机器人上实际跑的 velstand。它把走、站、摔倒后爬起放进同一个策略里。官方的 v8 是分 5 次、在不同分支上一代代训出来的。我简化成一次:从官方公开的走路锚点(Hugging Face 上的 velocity2/441tzs6d_3750)热启动,任务 Mjlab-VelStand-Rough-Backlash-MicroDuck,3000 轮。

和官方 v8 比

两个策略放进同一套 CPU 仿真(带背隙的场景,BAM 舵机模型)里对照:

测试(test) 官方 v8 我的(第 2999 轮)
前进 0.3 m/s,实际速度 0.118 m/s 0.123 m/s
原地转 1.0 rad/s,实际转速 0.99 rad/s 1.02 rad/s
仰面躺着,爬起用时 0.62 s 0.52 s
趴着,爬起用时 0.36 s 0.36 s
爬起和推搡(push),9 类各 40 次,失败次数 4 4

9 类包括仰面、趴着、侧躺,每种再分关节在默认姿势和关节随机两种;还有随机朝向从 12 厘米高处丢下,以及站着和走着时被横向踢一下(0.8 到 1.2 m/s)。两个策略各有 4 次卡住爬不起来,有一个随机朝向的开局两个都过不去。

只有一项明显比官方差:指令 0.15 m/s 时,我的策略站着不动。

为什么慢速不走

我把四个策略在 0.05 到 0.40 m/s 之间扫了一遍,发现它们都有一个死区(dead zone):指令落在死区里就站着,一出死区就直接走到 0.07 到 0.09 m/s,中间没有过渡。我的 velstand 死区边界在 0.15 和 0.20 之间,和官方走路教师策略 alpha_walking 完全一样。

原因有两层。第一层是奖励太宽:前进速度跟踪奖励是 2·exp(−误差²/0.1),指令 0.15 时站着不动也能拿 1.60 分,以 0.08 m/s 起步走是 1.90 分,只多 0.3 分,还要付动作变化、身体晃动、脚打滑的惩罚。第二层是继承:velstand 训练时,倾斜小于 25° 的帧都用行为克隆贴向走路锚点,锚点在 0.15 时的动作就是站着,学生照着学。

Pollen 在转向上踩过同一个坑,他们的修法是给锚点喂放大后的转向指令,再加一项更窄的转向跟踪奖励。前进方向没有做。我照着补上:小指令时喂给锚点放大的速度(0.10 喂 0.22,0.15 喂 0.25),加一项 std 0.12 的窄跟踪奖励,再从第 2999 轮继续训练 1000 轮。

修完的结果

每 250 轮存一个版本。走路每个速度测 10 个随机开局,每次 20 秒;爬起和推搡还是 360 次。

版本(version) 0.15 m/s 能走吗(walks at 0.15 m/s?) 0.3 m/s 直走偏航(yaw) 爬起和推搡失败(get-up & push failures)
官方 v8 10 次里 4 次不走 +0.02 rad/s 4 / 360
修之前(第 2999 轮) 10 次里 9 次不走 −0.04 rad/s 4 / 360
第 3750 轮 10 次都走,约 0.07 m/s −0.26 rad/s 0 / 360
第 3998 轮 10 次都走,约 0.07 m/s +0.02 rad/s 12 / 360
同一个指令 0.15 m/s,左边是修之前(第 2999 轮),10 秒后离起点只有 1 厘米;右边是修之后(第 3750 轮),10 秒后离起点 0.67 米。

0.15 m/s 修好了,比官方还稳。麻烦的是没有一个版本各项都好:第 3998 轮走得直,但有 12 次彻底爬不起来(我用 20 秒窗口重跑过,不是慢,是卡住);第 3750 轮 360 次全爬起来了,但 0.2 到 0.3 m/s 直走时稳定地往右偏,每秒十几度。继续训练过程中,爬起能力在各个版本之间来回晃,看上去慢走和爬起在争同一部分网络。

我选了第 3750 轮。它的偏航很固定,10 个开局的波动只有 ±0.01 rad/s,给一个反向转向指令(0.3 m/s 时加 +0.25 rad/s)就走直了,速度不变。真机换了舵机和背隙,本来就会有自己的偏航,控制层迟早要用 IMU 朝向做闭环修正,所以这个代价我愿意付。爬不起来就没有办法在外面补了。

第 3750 轮,指令都是站着。左边从仰面躺着开始,右边从趴着开始。

还剩一个没解决:指令 0.10 m/s 时,所有版本都还是站着。锚点已经被喂了 0.22,原因我还没查清。

训练出的权重(选定的第 3750 轮,加上修之前、第 3998 轮和走路教师策略三个对照版本)、修慢速的补丁和评估脚本,我都放到了 Hugging Face 上:mindthink/microduck-policies-repro。

几点体会

强化学习(reinforcement learning, RL)策略对环境非常挑剔。观测(observation)维度、执行器(actuator)模型、场景里的一个背隙,任何一处跟训练时对不上,策略都可能站得住但不迈步,而且不报任何错。所以排查时,第一步应该是翻出策略的 manifest 和训练任务名对一遍,而不是先猜。我那个 BAM 的猜测(见第三篇)是被对照实验推翻的;要是装上 BAM 碰巧走起来了,我大概就停在一个错误的结论上。

Microduck 的工程质量整体很高,最让我佩服的是”软件和策略分开发布”以及处处可回滚的部署设计,还有那些把”为什么”写清楚的设计文档。仿真里的问题出在一个写死的文件名上:策略集升级了,脚本没跟着改。只要有写死的值,迟早会和数据对不上。

如果你也想动手:一张支持 CUDA 的显卡就能训练,没有机器人也能在仿真里把整套守护进程跑起来。

请我喝杯咖啡