• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

Microduck 的热启动训练:为什么让鸭子跑着开始反而更快学会走路

Build in Public admin 1天前 24次浏览 已收录 扫描二维码

Microduck 的热启动训练:为什么让鸭子跑着开始反而更快学会走路

做过强化学习的人有个直觉:训练要从零开始,让策略自己探索。Microduck 的训练代码里有一个反直觉的设计——环境重置时,一部分鸭子不是从静止站好的姿态开始,而是被赋予一个向前的初速度,一出生就在跑。这个技巧叫 warm-start velocity reset,写在 mdp.py 的 reset_with_forward_velocity 函数里。今天拆开看它为什么有效,以及实现里有哪些坑。

从”站着开始学走”到”跑着感受惯性”

Microduck 的主训练任务是 Mjlab-Velocity-Flat-MicroDuck——平地速度跟踪。每次 episode 重置时,鸭子回到初始姿态,收到一个新的速度命令,然后尝试跟踪。

问题在于从静止开始学习走路,策略要同时发现两件事:怎么保持平衡,怎么产生向前的位移。这两件事耦合在一起,搜索空间巨大。PPO 的前几次迭代往往找到”站着不动不扣分最多”这个局部最优,因为任何尝试迈步都会先导致不稳定。

warm-start 的思路是先让策略体验”已经在移动”是什么感觉。重置时,一部分环境的鸭子被赋予 0.3 到 0.8 m/s 的向前初速度,关节也被设到接近行走的姿态。策略不需要自己发现”向前移动是什么”——它一上来就在向前移动,第一个要学的只是”怎么不摔倒”。

代码里的三个关键细节

reset_with_forward_velocity 的实现有几个容易被忽略但很关键的细节。

初速度的衰减是阶跃式的。函数接受一个 fraction_stages 参数,指定在不同训练步数下使用多少比例的环境做热启动。比如配置 step:0 fraction:0.8 表示训练开始时 80% 的重置环境带初速度,到 step:48000 降到 0%。这个衰减是阶跃的不是平滑插值。原因和课程编排一样:PPO 是 on-policy 算法,如果热启动比例在连续变化,每一步策略看到的初始状态分布都在漂移,梯度方向不稳定。阶跃跳变让策略在每个阶段看到稳定的初始状态分布,充分收敛后再进入下一阶段。

航向角从 qpos 读不从 xquat 读。函数里有一段注释专门说了这个问题。重置时写入新的 yaw 到 qpos 后,root_link_quat_w 底层读的是 xquat,还是旧 episode 的值,因为 sim.forward 还没来得及执行。直接从 qpos 读四元数是安全的,因为 write_root_pose 已经更新了 qpos。

这个 bug 如果不踩永远不会发现——只有在重置后立刻读朝向、且朝向被修改过的情况下才会触发。但一旦触发,热启动的鸭子会被推向错误的方向,而且看起来跟正常的鸭子没什么区别。训练日志里什么都看不出来,只是策略收敛慢了一点。

轮子要同步旋转。对于 roller 变体任务(鸭子脚底下有被动轮子),光给身体一个向前速度还不够。如果不把轮子的角速度也设成匹配值,仿真中的接触模型会判定为瞬间急刹车——轮子从静止突然要跟上身体速度,产生巨大的摩擦力脉冲。函数里算了 omega = vx / WHEEL_RADIUS,轮半径 0.0175 米,把所有轮子的关节速度设为这个值。正方向是前进,这是用 test_wheel_direction.py 验证过的。

死权重:命令槽位不采样就是废的

跟热启动相关的一个问题:如果某个命令槽位从来不采样非零值,策略网络里对应的输入神经元就是死权重。

Microduck 的观测是 61 维:48 维本体感知加 13 维命令块。命令块里包含 3 维 twist(线速度和角速度命令)、4 维 head_pose(头部姿态命令)、6 维 body_pose(身体姿态命令)。不是所有任务都用到所有命令——比如纯行走任务不关心 head_pose。但 CLAUDE.md 里有一条硬规则:每个命令槽位从一开始就保持一个小的非零采样范围,哪怕对应的奖励权重是零。

原因在神经网络的训练动力学。如果一个输入神经元永远收到零,它的权重在反向传播中永远收不到梯度。梯度是损失对权重的偏导,等于损失对输出的偏导乘以输入值。输入恒为零意味着梯度恒为零。权重卡在初始随机值上,既不小也不大,但完全没用。

等到后面的课程阶段你想激活这个命令槽位时,权重得从随机值开始学,跟从头训练没区别。但如果从一开始就采样小的非零值,权重会慢慢调整到合理范围,到激活时已经差不多对了,只需要微调。

CLAUDE.md 里还提到一个相关的点:零命令行为本身也需要显式训练。均匀采样几乎不会产生全零命令,但实际部署时用户可能不发任何命令——策略得知道没命令就站着。所以会有一个 zero_command_prob 参数专门采样精确的全零命令。

动作历史重置:不是重置关节是重置缓存

reset_action_history 函数处理的是另一个容易被忽略的问题。PPO 的奖励函数里有 action_rate 惩罚——惩罚相邻两步动作变化太大——和 action_acceleration 惩罚——惩罚动作加速度。这些惩罚需要缓存上一帧甚至上上帧的动作。

环境重置时如果不重置这些缓存,新 episode 的第一步会用到旧 episode 最后几帧的动作来计算 action_rate。旧 episode 的最后一个动作可能是摔倒时的紧急姿态,跟新 episode 的初始姿态差了十万八千里。action_rate 惩罚会爆出一个巨大的值,策略会学到第一步千万别动。

函数里逐个重置了 _prev_leg_actions、_prev_neck_actions、_prev_leg_actions_for_acc、_prev_prev_leg_actions_for_acc 这些缓存。不是重置到零,而是重置到当前动作——这样第一步的 action_rate 就是零,没有虚假的惩罚信号。这个区别很重要:重置到零会让策略以为”上一帧所有关节都在零位”,跟实际状态不符,产生相反方向的虚假梯度。

接触频率和力平滑也要重置

除了动作缓存,函数还重置了两个跟踪用的状态:_contact_change_count 和 _prev_contacts_for_freq。这两个跟足部接触检测有关——用来计算接触频率(单位时间内脚着地/离地的次数)。如果不重置,新 episode 的接触频率会带着旧 episode 的尾巴,影响接触相关的奖励计算。

_ prev_foor_force_smoothness 之类的力平滑缓存也在重置列表里。Microduck 足部有力传感,力的变化率会被用来评估步态质量。重置到当前力值,跟动作缓存一个道理。

这些细节加起来意味着什么

单独看每个细节都很小——初速度衰减用阶跃、航向角从 qpos 读、轮子同步旋转、命令槽位保持非零采样、动作缓存重置到当前值。但 RL 训练的特点就是一百个小细节每个差一点,合起来就是策略学不会和学得会的区别。

Microduck 的 CLAUDE.md 里有一句话概括这个理念:sim2real transfer is the whole point,every convention exists because breaking it produced a policy that worked in the viewer and failed on hardware。这些 reset 细节不是在仿真器里看出来的——仿真器里策略跑得很好看。问题出在真机上:初速度方向错了鸭子往侧面跑,轮子没同步起步打滑,命令槽位是死权重头部控制没响应。每个 bug 都是一个在仿真里隐藏、在硬件上暴露的问题。

warm-start 本身的核心思想值得一记:不要让策略从零开始发现一切。先给它体验目标状态的感受,让它学的是”维持”而不是”发现”,然后逐步撤掉这个辅助。跟教小孩骑车先扶着跑两圈再放手是一个道理。

喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。