做过强化学习的人都知道一个痛苦的循环:奖励函数改了三天,loss 曲线看着不错,策略学到的行为却完全不是你想要的。问题往往不在奖励本身合不合理,而在于 RL 会精确优化你写的每一个字——包括你没想清楚的那些。Microduck 的 CLAUDE.md 里记录了十条奖励设计铁律,每一条都对应一次真实的 sim2real 失败。今天逐条拆开看。
符号写反,策略就钻空子
Microduck 的 mdp.py 里有两种惩罚函数风格。mjlab 基础库的成本函数返回值大于等于零,配负权重。Microduck 自己写的惩罚函数(*_penalty、*_l1)返回值小于等于零,配正权重。
听起来只是约定问题?搞反了就是灾难。一个返回负值的自否定惩罚配上负权重,双重否定变成正奖励——策略会主动去触发这个惩罚。具体表现就是鸭子用屁股弹跳着前进,或者故意摔倒再爬起来,因为这些行为在数学上最大化了”惩罚”。
Pollen Robotics 给出了一个不可错的检查方法:每次训练,wandb 上每个 Episode_Reward/<penalty> 必须小于等于零。如果看到一个惩罚项的 episode reward 是正数,立刻停——符号反了。
RL 优化的是字面意思,不是你的意图
这条可能是所有铁律里最核心的一条。每一个你没明确约束的自由度都会被利用。
Microduck 训练中出现的具体案例:你想让鸭子做一个前滚翻,策略学到的是用鞭打式弹射把整个身体甩过去,而不是真正的翻滚。你想让鸭子站立,策略发现把头当成第三个支撑点(头 tripod)比用脚平衡省力得多。你想让鸭子走路,策略发现侧身横着滑比正着走更省能量。
解决方案不是加更多惩罚微调——那只会让奖励 landscape 变复杂,新的空子又冒出来。正确的做法是用硬性的状态门控(state-based gates)定义什么才算这个动作:支撑接触检测、姿态轴检查、状态锁存。惩罚告诉策略”别做什么”,门控告诉策略”什么才算做对了”。两者缺一不可。
别给 jackpot
“到达目标 X 就给奖励”听起来合理,但如果到达后每一步都持续给这个奖励,策略会尽快冲到目标状态然后原地不动领钱。Microduck 把这种叫 jackpot——提前到达一个持续付费的目标状态,策略就有动机用任何暴力手段快速到达,包括不安全的动作。
对于需要切换状态的命令任务(比如坐到站),Microduck 用一个斜坡跟踪内部目标(constant-rate blend)。策略超前于斜坡不加分,只有跟斜坡同步才有最优回报。这样”慢慢做”就是最优解,快速冲到终点不再划算。
光靠速度上限惩罚不够。速度惩罚积分为有界代价,策略算一算发现快速冲到目标后持续领奖的收益远大于速度惩罚的代价,照样冲。
别在坏状态上开门
如果”摔倒时给正奖励”来鼓励恢复,策略会发现摔倒后保持趴着是最便宜的合格姿态。Microduck 的设计原则是:永远不要把正奖励的门控条件设在坏状态上(摔倒、低位)。
替代方案是用 potential-based shaping:奖励 Δprogress 而不是 progress 本身。比如用 Δcos(tilt):姿态变好的那一刻给正奖励,保持不变给零,策略没法通过趴着不动来 farm 奖励。
对于休息类任务,要逐个检查每个正奖励项在所有可能的趴姿(仰面、俯卧、侧卧)下的表现。如果趴着能保留大部分奖励栈,策略一定会趴着。
着陆任务别用关键帧轨迹
你想让鸭子从某个姿态到达目标姿态,自然会想到设一串中间关键帧。Microduck 的教训是:策略会在关键帧之间找到最便宜的路径,然后停在关键帧上 farm 奖励。
正确做法是单一固定目标 + 高斯奖励 + L1 惩罚 + 着地冲击惩罚 + 两层直立检查。轨迹是 RL 应该自己发现的东西,不是你该规定的。一旦你规定了轨迹,策略的优化空间就变成了”怎么最便宜地走完关键帧”,而不是”怎么做好这个动作”。
两种正则化,别混着用
Microduck 把正则化分两类。
运动阻断型(body_ang_vel、angular_momentum、pose std)惩罚的是动态动作物理上需要的东西。前滚翻需要身体角速度,你惩罚角速度,策略就不翻了。这类正则化在动态任务里要低权重。
平滑型(action_rate、joint_torque_rate)阻尼抖动但不阻碍大慢动作。安全加权,但必须在技能被发现之后才引入(课程从接近零开始)。如果在策略还在探索阶段就加平滑惩罚,”什么都不做”就变成了最优解——不动最平滑。
比较奖励质量不是权重
从行走环境复制正则化项到新环境时,不能直接照搬权重。PPO 看到的是相对优势:同一个 action_rate 权重,在正奖励总量大 4 倍的环境里就弱了 4 倍。
正确做法是比较奖励在总回报中的占比(reward mass),而不是权重数值。一个权重 0.05 的正则化项在总回报 2.0 的环境里占比 2.5%,到了总回报 8.0 的环境里只占 0.6%——可能完全不起作用。
高斯标准差选错了梯度就没了
跟踪奖励通常用高斯函数:误差越小奖励越高,标准差控制”多严”。直觉是标准差设为你能接受的最大误差。但这个直觉是错的。
标准差应该设在你仍然关心的误差附近——太松的话小误差区域没有梯度,策略停在”差不多”就不改了。但收紧之前要先问:这个误差是策略能消除的,还是行为本身固有的?
Microduck 的具体案例:鸭子的头占全身质量的 38%。走路时头一定会晃。如果你把头部跟踪的高斯标准差设得太紧,走路时头部晃动持续被扣分,策略发现站着不动头部最稳——于是不走了。解决方案是用 1 秒 EMA 上的 L1 惩罚,只惩罚直流偏置,让交流晃动自己抵消掉。
乘积比加法好
当策略到达目标状态时,加法奖励栈会出现妥协盆地:每个项都拿到 80%,总奖励不错,但鸭子的姿态是歪的——每个维度都差一点但没差到被惩罚。
乘法高斯积要求每个维度都达标。任何一个维度严重偏离,乘积就崩到零。但这要求标准差够宽,让当前策略能看到梯度——太紧的乘积在初始策略下处处为零,梯度消失,什么都不学。
关节停在硬限位上
XL330 舵机的控制范围很宽,这是设计意图——低 kp 舵机需要超调量。但策略会发现把某些关节推到硬限位上最省力。MuJoCo 自带的 dof_pos_limits 只在最后 7.5% 的范围内生效,命令侧的惩罚又因为控制范围宽而无效。
Microduck 的修复是加一个 qpos 侧的限位接近惩罚,专门针对违规关节。惩罚基于实际关节位置(qpos)而不是命令位置(cmd),因为命令范围宽是故意的,真正的问题在于实际位置到了极限。
这些规则怎么串在一起
Microduck 的训练流程是:先用 64 个环境跑 5 轮迭代做烟雾测试(成本几美分),确认配置没硬伤。然后 4096 个环境正式训练,预期经历 2 到 5 轮的”打地鼠”阶段——策略找到新的空子,你堵住它,再找下一个。这是正常的。
课程编排上,奖励权重调度用阶跃函数而不是平滑插值。PPO 是 on-policy 算法,如果权重在连续变化,策略每一步看到的奖励信号都在漂移,梯度方向不稳定。阶跃跳变让策略在每个阶段看到稳定的奖励结构,充分收敛后再施加新约束。
观察每个训练迭代时,核心指标是:平均奖励上升且回合长度符合任务预期;每个惩罚项的 episode reward 小于等于零;主要任务项实际在增长(总奖励上升可能只是正则化项在变化,跟技能有没有学会无关)。
这十条铁律不是理论推导出来的,是 Microduck 团队在 sim2real 迁移中一次次看着策略学出荒谬行为后总结的。每一条背后都有一个鸭子做奇怪动作的视频。
