做过强化学习的人大概都有过这种经历:奖励函数调了三天,loss 曲线看着还不错,但策略就是学不会。问题往往不在奖励本身,而在引入的时机。Microduck 的训练代码里有一套明确的课程编排规则——不是”什么时候加什么”,而是”什么时候才能加什么”。顺序搞反了,前面所有的功夫都可能白费。
课程编排的两条线,分开走
microduck_rl 的课程系统把训练进度分成两条独立的线。
第一条线是 reward_weight,管奖励权重的调度。这个调度不是一个平滑的插值曲线,而是一个阶跃函数——分阶段离散跳变。为什么不做插值?因为 PPO 是 on-policy 算法,策略对奖励 landscape 的感知是当前时刻的快照。如果权重在连续变化,策略每一步看到的 reward 信号都在漂移,梯度方向不稳定。阶跃跳变让策略在每个阶段内看到稳定的 reward 结构,有足够的时间收敛到当前条件下的局部最优,然后下一个阶段再施加新的约束。
第二条线是 params-curriculum,管命令和事件的采样范围。比如行走任务里,前进速度命令的采样范围从最初的 ±0.2 m/s 逐渐扩展到 ±0.8 m/s,转弯命令从 ±0.5 rad/s 扩展到 ±2.0 rad/s。这两条线各管各的,不要混在一起。
通过 manager 改配置,不是改 env.cfg
一个容易踩的坑:想在某一步修改某个奖励项的权重,直接写 env.cfg.events[...] = new_value。看起来合理,但不会生效。
原因在于 mjlab 的事件管理器在初始化时对配置做了一次深拷贝。运行时管理器读的是自己内部的那份拷贝,不是 env.cfg 上的原始引用。你改 env.cfg 等于改了一个没人读的变量。
正确的方式是通过管理器接口:env.event_manager.get_term_cfg("term_name") 拿到当前配置的引用,修改这个引用里的字段,管理器才会在下一步生效。这个坑也咬 eval 脚本——如果你在评估时强制设置 spawn 状态,同样的机制会吞掉你的修改。
阶段对齐:策略学到什么了再加什么
课程编排最核心的原则就一句话:每个阶段的开启时机要对齐策略当前的实际能力,不能超前。
具体来说有两层意思。第一,不要在策略还没学会基本技能时就引入新的惩罚项。CLAUDE.md 里写得很明确:action_rate(动作平滑度)这类正则化项,如果在一个困难技能还在探索阶段时就激活,”什么都不做”会变成最优策略——因为任何动作都比不动产生更大的 action_rate 惩罚。所以平滑度惩罚的权重应该从一个很小的值开始,甚至从零开始,等策略学会基本技能后再逐步拉高。
第二,不要在当前阶段的策略还没巩固时就加硬下一个阶段。判断标准是看 wandb 上的关键指标:如果某个指标在课程阶段切换的边界上突然下降,说明节奏太快了——策略还没准备好就被推到了更难的条件下。解法是把当前阶段拉长,或者把新内容的引入时间点往后挪,永远不要往前挪。
反向课程:治”学会了开头学不会结尾”
有些任务天然有一个”最后一公里”问题。比如 Microduck 的翻滚动作(Roulade task):策略很快学会了起势——低头、收肩、开始翻——但最后落地站稳这一步怎么都学不好。原因不是奖励函数不对,而是 on-policy 数据的分布问题。正常的前向课程里,策略大部分时间都在练起势,因为起势容易成功、有奖励。到了落地阶段,策略已经偏离了正确轨迹,拿不到奖励,也就没有梯度信号去改进最后这一段。
解法是反向课程生成(reverse-curriculum spawns)。不是让策略从起始姿态开始跑完整流程,而是让一部分环境从接近完成的状态开始——比如从翻滚到一半的位置启动。这样策略能频繁遇到”最后一公里”的场景,拿到落地阶段的梯度信号。随着策略改进,启动点逐渐前移,最终覆盖整个动作。
CLAUDE.md 把这个方法标注为”解决学到开头学不会结尾的可靠方案”。关键是:前向课程的 spawn 比例也要跟着调——如果策略在当前 spawn mix 下已经稳定,不要急着加更难的 spawn 比例,否则策略的前半段能力会退化。
命令采样里的陷阱
课程编排不只是奖励权重和 spawn 状态,命令采样范围也有讲究。
一个容易被忽略的点:零命令行为必须显式训练。如果前进速度命令是均匀采样 [-0.8, 0.8] m/s,那精确的零命令(鸭子站住不动)出现的概率几乎为零。但部署时鸭子最常处于什么状态?站着不动。一个从未见过零命令的策略,部署后会轻微漂移——因为”不动”对它来说是一个没训练过的 out-of-distribution 输入。
解法是 zero_command_prob 式的采样:一定比例的样本精确设为零命令,其余样本从连续分布里采。这个比例可以随课程推进——早期不需要零命令行为时比例低一些,后期部署前拉高。
另一个陷阱是稀有命令区域。原地转弯(turn in place)在均匀采样下只占约 2% 的数据量,根本不够学。解法是设立独立的采样桶(buckets),给稀有命令区域分配更高的采样概率。这不是什么高级技巧,但不做的话策略会在这些场景下明显表现差。
先测再训:5 迭代烟雾测试
CLAUDE.md 里反复强调一个数字:5 迭代、64 环境、成本几毛钱的烟雾测试。在启动任何长时间训练之前,先跑一轮短的。
这个烟雾测试检查什么?配置能否正确构建、环境能否 step 而不产生 NaN、观测维度是不是 61、每个奖励项能不能正常计算、ONNX 能不能导出。这些问题如果在 4000 环境的长时间训练里才暴露,浪费的不仅是 GPU 时间,还有你排队等结果的时间。
烟雾测试通过不代表训练一定成功,但 CLAUDE.md 说它挡住了约 95% 的配置错误。剩下 5% 是需要实际训练才能发现的——比如奖励项之间的比例不对、课程节奏太快。这些就留给正式训练时通过 wandb 监控来发现。
预算估算:什么任务要训多久
不同类型的任务需要的训练量差别很大。
简单的一次性技能(踢球、鞠躬)大约 1000 迭代就能出可用策略。这指的是 4096 个并行环境、每迭代 24 步的配置下——也就是每迭代约 10 万个环境步。
步态和恢复类任务需要 4000-6000 迭代。这类任务之所以更久,是因为课程更复杂——要练前进、后退、转弯、原地站、摔倒恢复,每个子行为都要足够的 on-policy 数据才能收敛。
这里的”迭代”是环境步数的概念:iteration × 24,其中 NUM_STEPS_PER_ENV = 24。这个数字是 mjlab 框架的默认值,不是 Microduck 特有的。4096 个环境乘以 24 步再乘以迭代数,就是策略见过的总状态转移数。
读完 wandb 的正确姿势
训练跑起来后,wandb 上该看什么?
第一,总奖励在上升。但这个不够——总奖励上升可能纯粹是因为正则化项在减少(比如 action_rate 下降),而真正的任务奖励一直为零。所以要同时看 Episode_Reward/<主任务项>,确认它确实在涨。
第二,每个惩罚项的值必须 ≤ 0。CLAUDE.md 里有一条硬规则:如果某个惩罚项的 Episode_Reward 出现正值,说明权重符号搞反了——一个自否定的惩罚函数(返回 ≤ 0 的值)配上负权重,双重否定变成奖励,策略会去”犯规”来刷分。
第三,回合长度要符合任务预期。行走任务的回合长度应该逐步增长——如果回合在变短,可能是终止条件被触发得太频繁。翻滚任务的回合长度应该接近预设的 duration_s——如果远短于此,说明策略在翻滚中途就摔了。
“先量再猜”:别急着改奖励
训练”失败”时最直觉的反应是改奖励函数。CLAUDE.md 给了一个不同的建议:先做 headless 评估。
跑一个无头评估脚本,按 spawn 类型分组统计——每种起始姿态下的成功率、结束状态聚类、角速度分布。过去的”失败”案例里,有几个最后发现根本不是策略的问题:一个是用了早期 checkpoint(还没训够)、一个是成功标准把一个行为聚类硬切成了两半、一个是速度上限在和物理规律打架。
仿真指标可以通过的同时视频看着不行——这种情况要同时看数据和视频。哪个 geom 碰到地面了?哪个轴的角度超了?这些具体问题比”它不 work”有用得多。
—
数据来源:pollen-robotics/microduck_rl 仓库 CLAUDE.md(课程编排规则、奖励设计时序、烟雾测试流程、训练预算估算、wandb 监控规则、反向课程生成方法),README.md(任务列表、环境配置、迭代步数定义),microduck_rl 训练框架文档。
