做过机器人踢球的人都知道,最难的不是踢得准,是踢得到。球在脚前 5 厘米还是 2 厘米,对真机来说差别巨大。但 Microduck 的 BallKick 任务偏偏选了一条反直觉的路:策略完全看不见球。
不是忘了加感知,是故意不加。今天拆开看这个设计。
球在哪:70 毫米 15 克,脚尖前 9 厘米
BallKick 任务的物理设置很简单。一个 70 毫米直径、15 克重的球,放在踢球脚的正前方。以机器人 HOME 姿态为基准,球心在 x=0.09 米(前方),y 方向跟踢球脚对齐——右脚踢就 y=-0.042,左脚踢就 y=+0.042。
球的位置不是固定的。每次 reset 时,x 和 y 方向各加 ±1.5 厘米的均匀噪声。这个噪声是整个盲踢策略能工作的核心——策略在训练时见过足够多的球位偏移,学到的是一个”大范围扫腿”动作,而不是精确瞄准某个点。
为什么不需要球感知?因为真机上 Microduck 没有球检测能力。操作员把鸭子对着球摆好,按一下手柄按钮触发踢球策略,鸭子就踢。踢中不踢中,全看摆得准不准和策略够不够鲁棒。±1.5 厘米的训练噪声就是对着这个”操作员瞄准误差”建模的。
盲演员,明评论家:非对称 Actor-Critic
这是整个 BallKick 设计里最精巧的一笔。
策略网络(actor)的输入是 61 维观测,全部是本体感知:48 维关节状态加 13 维指令。球的位置和速度不在这个观测里。actor 不知道球在哪,只能根据自身状态决定怎么动腿。
但价值网络(critic)不一样。critic 能看到球的位置和速度。这意味着价值函数可以预判”这一脚能不能踢到球、球会滚多快”,从而给 actor 正确的梯度信号。
这个非对称设计的好处很直接:训练时策略能学到”什么样的腿部动作能踢飞球”,但部署时不需要任何球感知硬件。actor 只管踢,critic 在训练时帮它学,部署时 critic 根本不存在——ONNX 导出的只有 actor。
奖励函数:线性不饱和,踢得越狠越好
踢球奖励用的是球的前向速度的线性函数,clamp 在 5 m/s。不是 tanh 饱和函数。
这个区别很重要。tanh 类的饱和奖励在球速接近上限时梯度趋近于零,策略没有动力踢得更用力。线性奖励在 0 到 5 m/s 之间每增加一点速度都有满额梯度——”as hard as possible” 需要的就是这个。
实际效果怎样?第一版训练出来的策略踢得太猛了。代码注释里写得明白:训练完的策略踢出的球远超需要。所以后来加了一个 BALL_TARGET_SPEED = 1.0 m/s 的限制,把踢球奖励的权重按 3/target 重新缩放,让目标速度下每步收益大概是 +3。这样既保持了线性奖励的梯度优势,又把踢球力度控制在一个温和的范围内。
支撑脚约束:左脚不离地
右脚踢球,左脚就得站着。听起来像废话,但 PPO 不懂这个常识。
如果没有约束,策略完全可能学到”双脚同时起跳、用右脚扫球”——因为起跳能获得更大的腿部摆动空间,瞬间踢球速度更高。但起跳的鸭子落地时大概率摔,而 BallKick 任务要求踢完球后保持站立。
所以代码里有一个 always-on 的支撑脚接地奖励。右脚踢时,左脚必须保持与地面接触。每一步如果左脚离开地面,扣分。这个奖励不参与课程编排,从 t=0 就全量开启,因为它是安全约束不是性能优化。
与之配套的是一个 contact sensor,专门检测支撑脚是否接触地面。这个传感器的数据只进奖励函数,不进 actor 观测——又是那个”critic 知道但 actor 不知道”的设计。
单边训练:对称性必须关掉
Microduck 的大多数任务开了对称性训练(symmetry),因为行走、站立这些动作左右对称。BallKick 不行。
踢球天然是单边动作。右脚踢和左脚踢是两个不同的策略,训练成两个独立的 run。代码里 ENABLE_SYMMETRY = False,KICK_FOOT = “right” 或 “left” 二选一。
为什么不能一个策略学两边?因为单边踢球时身体的重心转移、支撑腿的加载模式都是不对称的。强行对称化会让策略在”左脚踢”和”右脚踢”之间折中,两边都踢不好。分开训练反而简单直接。
部署:2 秒踢完就换回来
BallKick 策略在真机上的部署方式跟其他策略不一样。
行走策略是常驻的——鸭子一直在走。BallKick 是触发的:操作员按手柄按钮,运行时把行走策略的 ONNX 热切换成 BallKick 策略,鸭子踢一脚,大约 2 秒后自动切回行走策略。
这个热切换能成立,靠的是所有策略共享 61 维观测契约。行走策略和踢球策略的 actor 输入维度完全一样,运行时只需要换一个 ONNX 文件,不需要重启或重新初始化观测缓冲区。BallKick 任务里没用到的指令槽(twist、head_pose、body_pose)被零填充,不占用额外维度。
5 秒的 episode 长度足够覆盖完整的踢球序列:准备、抬腿、摆腿、触球、收腿、回到站立。训练时这个 5 秒窗口内策略可以持续收集球滚动奖励,所以它会尽快踢——越早踢,球滚的时间越长,累计奖励越高。不需要额外的”踢球时机”指令,奖励结构本身就驱动了即时踢球。
仿真配置:全碰撞模型加球
BallKick 用的是 robot_allcollisions.xml 模型,不是行走任务的 robot_walk.xml。原因是球可能碰到腿的任何部位,不只是脚底。如果像行走任务那样剥离躯干和腿部碰撞体,球就会穿过鸭子。
仿真配置里额外加了 50 的接触点上限(nconmax=50),给球和地形、球和机器人之间的接触留足预算。球作为一个独立的场景实体加入,有自己的 MJCF 配置。
设计启示
BallKick 任务的核心思路其实是一种”训练时作弊,部署时诚实”的策略。critic 在训练时偷看球的位置帮 actor 学,actor 部署时两眼一抹黑全靠肌肉记忆。这不是什么新颖的 RL 技巧,但在机器人部署中特别实用——真机上的感知永远比仿真里差,与其给策略一个不靠谱的球位置观测,不如不给。
Microduck 的整套任务设计里到处是这种务实选择。行走策略不碰碰撞体是为了训练速度,踢球策略不用球感知是为了部署可行性。每个设计决策都对应一个具体的物理约束或工程限制,不是理论上的最优解,是工程上的最可行解。
