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

Microduck 的域随机化:为什么训练时要故意把鸭子搞坏

Build in Public admin 45分钟前 3次浏览 已收录 扫描二维码

Microduck 的域随机化:为什么训练时要故意把鸭子搞”坏”

做过 sim2real 的人都知道一个反直觉的事:你在仿真里把环境调得越完美,策略上了真机摔得越惨。原因很简单——仿真永远跟真实有偏差,策略如果只在一种”完美”环境里训练过,它学到的是过拟合的解,不是泛化的解。域随机化(Domain Randomization,DR)就是故意把仿真参数搞乱,让策略见过足够多的”坏”情况,到了真机上反而觉得一切都在预期之内。

Microduck 的训练仓库把 DR 当成 sim2real 的核心支柱之一,跟 BAM 执行器模型、齿隙仿真并列。今天拆开看它具体随机化了什么、怎么实现、以及一个让团队踩了几个月坑的累积 bug。

随机化什么:四个维度

Microduck 的 DR 不是泛泛地”加噪声”,它针对的是 XL330 舵机和 Microduck 这个特定量级的机器人最敏感的物理参数。从 CLAUDE.md 和训练仓库的代码看,主要有四个维度。

电池电压随机化

XL330 是电压驱动的舵机。BAM 模型里有完整的电压控制律——给一个目标角度,模型先算出需要的电压,再通过电机方程算力矩。电池电压不是恒定的:满电 5V,亏电可能掉到 3.7V。电压低了,舵机能输出的最大力矩就小了,动态响应就慢了。

训练时每个环境的电池电压被独立随机化。这意味着 4096 个并行环境里,每个鸭子的”电池电量”都不一样。策略必须学会在电压充足时大力迈步、电压不足时保守行动。上了真机不管电池什么状态,策略都见过。

电压跌落

这跟电池电压不一样。电压跌落说的是负载突变时的瞬态压降——你让所有舵机同时发力,瞬间电流拉满,电池电压会短时跌落再恢复。这个现象在真实机器人上很常见,尤其是一人公司用的小型锂电池组。

BAM 模型里有一个 voltage_sag_under_load 参数,模拟的就是这个。随机化它意味着策略不能假设”我给指令就能立刻得到力矩”——有时候力矩会延迟一点点才到,因为电压先跌了再恢复。

指令延迟

从策略输出到舵机实际执行之间有延迟。ONNX 推理是亚毫秒级的,但 Dynamixel 总线的通信延迟、舵机内部 PD 控制器的响应延迟加起来可能有几个毫秒。训练时把这个延迟随机化,策略就学会了提前量——不是”现在该出力”,而是”再过几毫秒该出力,所以现在就发指令”。

摩擦系数

BAM 模型已经把摩擦建模到了电压级——库仑摩擦、Stribeck 效应、负载相关摩擦都进了执行器模型。DR 在这上面再加一层:摩擦的幅度本身也被随机化。每个环境的摩擦系数在基准值附近波动。

这里有个 CLAUDE.md 特别强调的坑:dof_frictionloss 在 BAM 模式下被置零了,因为摩擦已经由执行器模型内部计算。如果你去随机化 dof_frictionloss,它是个 silent no-op——代码不报错,但什么也没做。正确的做法是随机化执行器的 friction_scale 参数。这个坑能让你浪费几天训练时间找不到问题。

FrictionDRBamActuator:DR 跟 BAM 的耦合

Microduck 的 DR 实现不是简单地往 MuJoCo 的 dof_frictionloss 里塞随机数。它通过一个叫 FrictionDRBamActuator 的自定义执行器类来实现,这个类继承自 BAM 执行器,在每个环境的 reset 时重新设置摩擦参数。

为什么要这么做?因为 BAM 执行器内部维护着摩擦模型的状态——Stribeck 曲线的过渡点、负载相关摩擦的插值表。如果你从外部改 MuJoCo 的摩擦参数,BAM 内部的状态不会跟着更新,两层摩擦模型就打架了。把 DR 集成到执行器类里,确保摩擦参数的变更通过正确的路径传播到整个执行器模型。

这个设计对复刻者有一个直接启示:如果你的执行器模型是自定义的(不是 MuJoCo 自带的 PD),DR 参数必须通过执行器模型的接口去设置,不能直接改 MuJoCo 的物理参数。否则你随机化的东西跟策略实际体验的东西是两层皮。

累积 DR Bug:一个让团队踩了几个月坑的案例

CLAUDE.md 里记录了一个特别值得注意的 bug,我把它单独拎出来讲。

域随机化的标准做法是:每个环境 reset 时,随机化参数从基准分布里重新采样。这意味着每个 episode 开始时参数被重置到一个新的随机值,episode 内参数保持不变。

但 mjlab 的 dr.* 操作在早期版本里有一个问题:operation="add"/"scale" 模式会在 reset 时把随机化值叠加到当前值上,而不是替换。举个例子,摩擦基准值是 0.1,第一个 episode 随机化 +0.02 变成 0.12,第二个 episode 再随机化 +0.03 变成 0.15——每次 reset 都在累积。

这种 bug 极其隐蔽。短期训练看不出问题——几十个 episode 的累积还在合理范围内。但长时间训练(几万个 episode)后,参数漂移到完全不合理的位置。一只训练了 5000 万步的鸭子,它的摩擦系数可能已经漂到了基准值的 10 倍,策略在仿真里适应了这个荒谬的参数,上了真机当然摔。

CLAUDE.md 原话是”an accumulating CoM randomizer once degraded every long run for months”——一个累积的质心随机化器让几个月的长训练全部退化。质心随机化本意是让策略学会适应质量分布的微小偏差(舵机位置、线材走线导致的重心偏移),但累积后质心可能漂到了机器人外面。

mjlab 1.3.0 修复了这个问题:dr.* 操作在 operation="add"/"scale" 模式下会在每次 reset 时重新读取编译时的默认值再施加随机化,而不是在当前值上叠加。但 CLAUDE.md 还是加了一条铁律:自定义 DR 函数必须 restore-then-apply——先把参数恢复到基准值,再施加随机化。

自定义 DR 的正确写法

如果你要写一个自定义的 DR 函数(比如随机化质心位置),正确模式是这样的:

  1. 在环境的 reset 钩子里,先把质心恢复到 MJCF 里定义的默认值
  2. 从分布里采样一个随机偏移
  3. 把偏移加到默认值上,设置到仿真器里

错误模式是直接在当前值上加偏移而不恢复默认值。这种写法在短训练里跟正确模式表现一样,长训练才会分叉。你不会在 5 迭代的 smoke test 里发现它,可能在 5000 万步的训练跑到一半时才发现策略行为异常。

CLAUDE.md 建议的 smoke test 方法是 64 个环境、5 轮迭代,成本几美分。这个 smoke test 能抓住 95% 的配置错误,但累积 DR bug 属于那 5%——它需要长时间运行才能暴露。对这种 bug,定期检查训练中的参数实际值是最靠谱的调试方法。

DR 跟策略鲁棒性的关系

域随机化的核心假设是:如果仿真里的变异范围覆盖了真实环境的参数分布,那么策略在真实环境里的表现不会比训练里最差的情况更差。

这个假设有一个前提:你的 BAM 执行器模型本身是准确的。DR 是在 BAM 的基础上加扰动——如果 BAM 本身建模错了(比如摩擦曲线形状不对),DR 随机化再大的范围也没用,因为你在错误的基准上加噪声。Pollen Robotics 把 BAM 和 DR 放在一起是有道理的:先确保执行器模型能准确复现真实舵机行为,再通过 DR 覆盖剩余的不确定性。

这也解释了为什么 Microduck 的 DR 范围不是特别大。电池电压在 3.7V 到 5V 之间波动,不是一个很宽的范围。摩擦系数的随机化幅度也没有特别夸张。因为 BAM 已经消除了大部分 sim2real gap,DR 只需要覆盖 BAM 没建模到的那些残余不确定性。如果你的执行器模型是 MuJoCo 自带的 PD,你会需要大得多的 DR 范围来补偿,而大范围 DR 会让策略学到的行为过于保守——它要在所有极端情况下都不摔,结果在正常情况下也放不开手脚。

对复刻者的实践建议

先搞准执行器模型,再加 DR。 如果你的舵机模型是理想 PD,先花时间把摩擦、反电动势这些加进去(用 BAM 或者自己写)。在此基础上再加 DR。在错误的执行器模型上加 DR 是用更大的噪声掩盖更深的偏差。

DR 参数要从真实测量出发。 电池电压范围用万用表测满电和亏电状态。摩擦偏差通过让舵机做恒速运动、测量实际电流跟模型预测的偏差来估计。不要凭感觉设范围——太大策略变保守,太小覆盖不了真实变异。

自定义 DR 函数一定要 restore-then-apply。 这个不用多说了,上面那个 bug 的代价是几个月的训练时间。即使你用的框架声称已经修复了累积问题,自己写的 DR 函数还是要显式恢复默认值再施加随机化。防御式编程在这一行不是多余的。

长训练要定期 dump 参数实际值。 在训练过程中每隔 N 步把几个环境的实际 DR 参数打印出来,检查它们是否在合理范围内。如果看到参数单调漂移,立刻停——你的 DR 在累积。

数据来源

  • GitHub: pollen-robotics/microduck_rl — CLAUDE.md(DR 不变量、累积 bug 记录、smoke test 方法、符号约定、BAM 与 DR 的耦合关系)
  • GitHub: pollen-robotics/microduck_rl — README.md(BAM 执行器模型、FrictionDRBamActuator、域随机化参数列表、训练命令)
  • GitHub: pollen-robotics/microduck_rl — src/mjlab_microduck/actuator/friction_dr_bam.py(BAM + 摩擦 DR + 齿隙编码器实现)
  • GitHub: pollen-robotics/microduck — docs/design/architecture.md(七守护进程架构、部署环境描述)
喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。