“奖励设计得越细,机器人学得越好”——这句话在大部分任务里是成立的,但放到杂技机器人身上,我第一次按这个思路尝试时,得到的不是优美的后空翻,而是一台在仿真环境里疯狂转圈、最后把自己“甩”出去的失控体。后来复盘才明白:杂技动作的时序极短、动力学耦合极强,传统奖励塑形要么让策略陷入局部最优,要么干脆被智能体钻了空子。折腾了几个月,真正把问题解决掉的思路,就是这篇要聊的“分阶段奖励塑形+约束多目标强化学习”。这篇文章我会把整个方法的动机、设计框架、实现细节和真机迁移教训完整拆开,适合正在做机器人强化学习、尤其是做高动态控制任务的工程师和研究者参考。
1. 为什么“奖励越细越好”在杂技任务里不成立
1.1 杂技动作的三个硬特征
杂技机器人,比如做后空翻的足式机器人、做垂直筋斗穿越动作的固定翼无人机,和普通抓取、导航任务有本质区别。我把它总结成三个硬特征。
第一是极短时序。整个动作往往只有几百毫秒到一两秒。后空翻从起跳到落地,真正关键的姿态调整窗口可能只有零点几秒;无人机做筋斗动作,机翼的姿态角速度可以达到每秒几百度的量级。这意味着策略必须在极短时间内做出一系列高精度决策,任何一步延迟或误差都会直接导致任务失败。
第二是强动力学耦合。机器人的身体是一个多刚体系统,腿部发力会影响躯干角速度,躯干姿态又决定了落地的受力点。你很难把动作拆成“先把姿态调好,再做下一个动作”——因为每一步都在同时影响多个状态量。这也让奖励设计变得非常棘手:你奖励某个状态,往往意味着隐式惩罚了另一个状态。
第三是高动态范围。杂技动作中机器人会经历接近失重、超重、高速旋转等极端工况,控制器必须从“低速精细调节”切换到“大力矩爆发输出”再切回“着陆缓冲”,这种大范围的切换让单一策略很难自然涌现。
这三个特征叠加在一起,恰恰就是稀疏奖励和传统密集奖励都不好使的根本原因。
1.2 稀疏奖励学不动,手调密集奖励学歪了
在杂技任务里,稀疏奖励的本质问题是“概率太低”。比如四足机器人后空翻,如果你只在“完成翻转且落地稳定”时给+1,那么随机探索过程中能碰到这个状态的概率几乎为零。策略梯度方法的方差本来就大,在这种环境下训练,基本上是随机游走,几百个epoch下来奖励曲线纹丝不动。
于是大家自然想到手调密集奖励。我最早也是这么做的:给姿态误差一个负项,给身体高度一个正项,给落地速度一个负项……听起来很合理,实际跑起来却遇到了更隐蔽的问题——奖励欺骗(reward hacking)。智能体发现只要拼命往下蹲再弹起,就能骗到“高度上升”的奖励,结果学出来的动作是一个高频弹跳,完全不是后空翻。
这背后的原因在于:手调奖励函数本质上是人为定义了一个“最优路径”。但我们并不知道后空翻过程中的最优中间状态长什么样,也不知道各项误差该以什么权重组合。一旦权重不合理,智能体就会沿着奖励梯度找到一条“看起来分数高、实际上不是人类想要的”路径。
1.3 单一标量奖励表达不了“又快又稳又安全”
再进一步想,杂技任务天然就是一个多目标优化问题:希望动作完成度高(比如翻转角度精确到位)、希望姿态偏差小、希望落地冲击小、还希望时刻不超出机械安全边界。这些目标之间是矛盾的。
举个直接例子:翻转角度精确到位,往往需要更高的角速度,这会让落地冲击变大;落地冲击小,需要预留缓冲空间,但会牺牲动作的利落度。如果我强行把这些矛盾目标加权求和成一个标量奖励,等于强迫系统找出一个固定权重妥协。而且这个妥协在动作的不同阶段应该是不一样的——起跳阶段姿态误差可以放宽,落地阶段却必须严格约束。
因此思路必须转变:不把“安全”“稳定”放进奖励里让智能体“权衡”,而是把关键的安全条件单独抽出来当作约束,强化学习只负责在满足约束的可行域内最大化那几个真正要优化的目标。这就是约束多目标强化学习的核心动机。
2. 分阶段奖励塑形的设计框架:把后空翻拆成五步来学
2.1 阶段划分的逻辑:不是按时间切,而是按“控制意图”切
提到“分阶段”,很多人直觉上是按时间切片,比如前0.2秒一个阶段,0.2到0.4秒一个阶段。我在实践中发现这是错的,时间切片的边界对策略极不友好,因为机器人状态并不是恰好按时间对齐的。
正确的切法是按“控制意图”划分。以后空翻为例,完整的动作可以拆成五个阶段:
- 下蹲蓄力阶段:重心降低、关节储存弹性势能;
- 蹬地起跳阶段:腿部爆发伸展,身体获得向上的线速度和翻滚角速度;
- 空中收腿翻转阶段:身体收紧、减少转动惯量,实现快速翻转;
- 伸展探测阶段:通过前馈和反馈判断是否接近落地姿态;
- 缓冲落地阶段:腿部主动柔顺控制,吸收冲击并恢复平衡。
每一个阶段的“控制意图”完全不同:蓄力阶段要的是慢、稳、不提前触发;起跳阶段要的是猛、准、力矩足够大;空中阶段要的是姿态控制的主权转换;落地阶段要的是柔顺和安全。
2.2 各阶段奖励函数的具体写法
划分好阶段之后,每一阶段单独设计奖励函数。注意:这些奖励函数不需要骗智能体“一步到位完成整个动作”,只需要引导它完成本阶段的目标。
蓄力阶段:主要奖励重心下沉量,惩罚提前起跳和姿态偏移。例如 r = α·(z_target - z_current) - β·||roll, pitch||,其中 z_target 是预定的下蹲深度,z_current 是当前高度变化量,roll/pitch 是躯干侧倾和俯仰。这里的 α、β 不需要很复杂,关键是让智能体学到“蹲到位了再走下一步”。
起跳阶段:核心奖励是离地速度和目标俯仰角速度的匹配程度。可以写成 r = -k1·(v_z_target - v_z)² - k2·(θ_pitch_rate_target - θ_pitch_rate)²。同时设置一个阈值,只有起跳高度超过某个值才给额外加成,鼓励干脆利落的爆发。
空中阶段:这个阶段是整个任务最难的。我建议不要直接奖励翻转角度,而是设置“翻转窗口奖励”——只要翻转角度从90°到180°之间平滑推进,就给稳定的正向奖励,同时严厉惩罚角速度饱和(也就是电机力矩达到上限且持续卡住)。
伸展探测阶段:奖励基于角速度和预期落地时间的匹配。你可以估算剩余翻转角度和当前角速度,得到一个预测落地时间,奖励策略让预测时间落在合理区间。
缓冲落地阶段:奖励落地时刻的冲击力小、关节速度小、机身姿态接近水平。这里甚至可以引入后空翻任务里少有的“速度惩罚”——惩罚落地的接触线速度,引导柔顺控制策略涌现。
每个阶段奖励函数的量级不要偏差太大,因为阶段切换时奖励跳变过大会让价值函数估计不稳定。我在实际代码里会在阶段切换边界附近做 5~10 步的小范围线性混合过渡,效果比硬切换稳定得多。
2.3 阶段切换与阶段分类器
阶段划分好之后,训练时如何知道当前处于哪个阶段?最简单方案是用自定义状态机判断:高度、角速度、接触力等阈值触发切换。比如触地传感器一旦激活,就从“空中阶段”强制切换到“缓冲落地阶段”。
但阈值状态机在仿真和真机之间迁移会遇到问题:真机的接触力噪声、高度估计算法延迟都会导致误触发。更稳的方案是训练一个轻量级分类器,输入最近几十步的状态序列(关节角、角速度、接触信号),输出当前阶段概率,再结合阈值状态机做仲裁。我用的是一个两层LSTM加一个全连接头的小网络,训练数据直接用仿真中已学会的 rollout 自动标注就好。
分类器输出和阈值状态机仲裁的逻辑是:只有两者一致时才切换;不一致时沿用上一阶段,同时给分类器标记一个异常样本,下一轮继续修正。这个机制虽然不复杂,但极大减少了真机弹跳时“阶段逻辑反复横跳”的问题。
2.4 为什么阶段化比全过程课程学习更稳
有人会问:这跟课程学习(curriculum learning)有什么区别?课程学习是让任务难度渐进,但奖励函数里只有一个全局目标。比如先学慢翻、再学快翻、最后学带启动的准备动作——注意,这还是用一个跨全过程的奖励在学。
阶段化真正的优势是:它改变了信用分配的结构。在一个阶段化的MDP里,每个阶段的策略只需要对“本阶段的回报”负责,每个阶段的价值函数都更局部、方差更低、收敛更快。你可以想象,与其让一个agent从一堆杂乱的状态序列里学习“从下蹲到落地的整个过程哪个动作该负责”,不如清晰告诉它“你现在阶段1,只优化阶段1的奖励,阶段2的入口条件会由阶段分类器把握”。
从这个视角看,阶段化本质上是一种“时间方向的任务分解”,它让奖励信号不再需要穿越整个杂技动作的长时程延迟,每个阶段内部的延迟窗口非常短。这是它能稳定训练的最根本原因。
3. 约束多目标强化学习的核心机制:安全必须写进约束里
3.1 CMDP框架:把“目标”和“安全”彻底分开
传统的强化学习建模成马尔可夫决策过程(MDP):最大化长期奖励期望。而约束马尔可夫决策过程(CMDP)多了一组约束条件:在最大化奖励的同时,确保某个累积代价不超过上限。
我在实现时,对每个阶段都会额外维护一个或多个代价函数 c(s, a)。代价函数表达“安全边界被违反的程度”,它不参与奖励优化,只用来约束策略。比如:
- 关节力矩接近极限的程度;
- 质心离地面支撑多边形边界的距离;
- 落地时的垂直冲击速度;
- 机器人躯干过度倾斜的时间占比。
CMDP问题可以用拉格朗日方法求解:目标函数变成 max π E[Σγ^t r(s,a)] - λ·(E[Σγ^t c(s,a)] - d),其中 λ 是拉格朗日乘子,在学习过程中动态调整。这个λ学习率要比策略学习率慢,否则策略还没收敛约束先崩了。
我遇到过的一个典型问题是:λ更新太快导致策略为了满足约束而“缩”在安全状态里不动,动作幅度越来越小,最后完全学不出来。解决办法是约束预算 d 不要一开始就设到最终严格值,而是先放松 20% 左右,等策略能力涨上来之后逐渐收紧。
3.2 多目标冲突怎么处理:阶段内加权、阶段外约束
多目标优化最常见的是加权求和法,但在CMDP框架下,我对“目标”做了更细致拆分:真正进入奖励函数求和的,通常是两到三个“相性较好”的目标(比如动作完成度和能量效率),而把抖动幅度、安全边界这些“相性差”的目标放进约束。
以起跳阶段为例,奖励函数里只有两项:离地速度匹配项和俯仰角速度匹配项。而“力矩不超过限幅”“质心不提前离地”完全放进约束条件。这样做的好处是,奖励面的梯度方向很清晰,只是向“更快更准”的方向推;代价函数则负责划一条底线。
跨阶段看,还有一个全局约束:整个过程的累积代价总和不能超限。我一般会设每个阶段内的预算和全局总预算两层。阶段内预算给了智能体灵活分配的空间:比如空中阶段可以稍微多用一点力矩,但落地阶段必须预留足够的缓冲能力。
3.3 约束的松弛与惩罚:如何避免可行域太小
CMDP最大的坑是可行域(feasible region)太窄导致初始化策略无法产生任何有意义的探索。特别是杂技动作起步阶段,智能体随机策略几乎必然触碰安全约束,如果约束设得过硬,所有轨迹都被砍掉,策略梯度直接消失。
我的经验是采用“双层约束机制”:第一层是软约束,用小的二次惩罚项引导;第二层是硬约束,用CMDP的拉格朗日项强制。软约束只在早期阶段启用,当策略训练到一定程度(可以以阶段完成率超过某个阈值为标志)之后,软约束逐渐退场,由硬约束接管。
举个例子,空中阶段我最初允许关节角速度超过安全限幅的惩罚项存在,但超过幅度会被乘以一个较大的系数。这样agent早期可以稍微越界探索,但越界代价很高,不至于去走极端路线。等到它能稳定完成翻转后,再把越界行为彻底设为硬约束。
4. 训练流程与工程实现细节
4.1 状态空间、动作空间与网络结构
我的状态空间设计原则是“把能测到的都放进去,但区分优先级”。以足式机器人后空翻为例,状态向量包括:机身姿态四元数、机身角速度、关节位置、关节角速度、上一时刻的关节力矩指令、足端接触信号。如果是无人机筋斗场景,还会加上风速估计值。
这里有一个容易被忽略的点:奖励函数里用到的量,如果状态里没有直接对应,策略就会学得磕磕绊绊。比如你想奖励“离地速度”,那状态里最好有质心速度的估计量,而不是让网络从关节速度和姿态变化里自行反推。虽然理论上它能学出来,但多一层反推意味着多一层方差。
动作空间方面,我建议用关节力矩/加速度指令,不用位置指令。原因在于杂技动作需要的是力控级别的响应速度,位置环的延迟和饱和特性会让高动态动作严重失真。网络结构我用的是带残差连接的MLP,隐藏层宽度512,三层。实测在这个任务规模下,没有用到Transformer的必要,MLP足够,而且训练速度更快、真机部署更简单。
4.2 算法选择与超参数:为什么用PPO-Lagrangian
我把PPO作为基础算法,再套上拉格朗日约束项,也就是PPO-Lagrangian。选PPO的原因很实际:实现成熟、超参数文档丰富、对超参数不敏感,而且在持续训练过程中足够稳定。SAC虽然样本效率更高,但高维连续控制任务里它的熵系数调节极易影响约束满足度,调试成本太高。
关键超参数我列一个经验表:
| 参数 | 值 | 备注 |
|---|---|---|
| 阶段内奖励折扣 γ | 0.99 | 阶段窗口短,不需要更低 |
| GAE λ | 0.95 | 减少方差、保留偏差 |
| PPO clip ε | 0.2 | 前后期保持一致 |
| 拉格朗日乘子学习率 | 0.005 | 必须远小于策略学习率 |
| 约束预算 d(初期) | 目标值的120% | 逐步降到100% |
| 阶段切换混合步数 | 5~10 步 | 线性混合 |
| 每阶段并行环境数 | 512 | CPU仿真即可 |
| 每阶段训练时长 | 约400万step | 视任务复杂度浮动 |
训练顺序上,我从第一阶段开始逐个训练:先固定后续阶段的“演示引导奖励”不可用,只让策略学第一阶段;第一阶段学好之后固定其参数,再开第二阶段。这里要注意,每个阶段开始时都要重置拉格朗日乘子,不然乘子会延续上一阶段的状态,导致新阶段约束判定过松或过紧。
4.3 训练中常见的失败模式:奖励崩塌、阶段反复、约束震荡
训练阶段,我总结出三种最常见失败模式,也给一下判断信号和应对方法。
奖励崩塌(reward collapse)的表现是奖励曲线前期快速上升,然后突然断崖式下降。原因通常是智能体找到了某个高奖励捷径,随后陷入局部最优出不来了。判断方法:如果把某一步的轨迹可视化,会发现动作在特定帧序列上完全锁死。应对方法:对奖励函数的稀疏度做微调,减少“单帧高回报”的出现概率,让奖励“平滑”而不是“尖峰”。
阶段反复(phase oscillation)的表现是阶段分类器在两个阶段之间高频切换,甚至一帧内来回跳。这通常说明阶段特征区分不够明显,需要调整阶段划分边界条件或增强特征输入。如果分类器和状态机的仲裁逻辑已经启用仍然震荡,我会考虑干脆放宽切换条件让状态机优先,分类器只作慢速校正。
约束震荡(constraint oscillation)的表现是拉格朗日乘子呈现周期性大起大落。这说明乘子学习率相对策略学习率太快了。乘子本来的作用是平滑地调整策略以满足约束,如果它波动太快,策略会被反复推来推去。解决办法一方面是调低乘子学习率,另一方面是给乘子更新加一个低通滤波,用指数滑动平均。
5. 从仿真到真机迁移的几个关键坎
5.1 物理引擎里学出来的动作,为什么到真机就失灵
这是所有做这块的人都绕不开的问题。仿真和真机的差异可以归结为:动力学参数不准(质量分布、摩擦力、关节阻尼)、执行器延迟、感知噪声和延迟、以及机械结构的柔性形变。
杂技任务对这些问题格外敏感,因为动作本身是在极限边缘试探。一个关节阻尼误差 5% 可能在地面行走任务里无所谓,但在空中翻转的毫秒级时序里就会导致姿态过冲或不足。解决思路有两个主流方向:域随机化(randomize dynamics parameters during training)和系统辨识+仿真校准。
我的做法是两者结合:在训练期间对质量、惯量、摩擦系数、关节摩擦力做均匀分布的随机化,范围约 ±10%;同时真机前用一小段系统辨识实验(比如扫频激励)校准关键惯性参数。不要一开始就追求模型完全精确,因为随机化本身已经给了策略一定的鲁棒性。
5.2 仿真到真机迁移的具体操作顺序
我第一次迁移时直接拿仿真策略上真机,结果起跳就翻车,还把护栏撞弯了。后来整理出一套稳妥的操作流程:
- 先在纯仿真里用固定参数跑通完整动作,生成“仿真参考轨迹”;
- 用系统辨识实验校准动力学参数,回填到仿真环境重新训练一轮;
- 做额外的随机化训练,把感知延迟也加进随机化范围(比如增加30~50ms的随机观测延迟);
- 真机测试时,先只测第一阶段(比如下蹲蓄力),确认执行器响应和状态估计精度达标;
- 逐阶段串联测试,每串联一个新阶段,都要回归验证之前阶段的成功率没有退化。
步骤4和5容易被跳过,但恰恰是避免真机损坏的关键。我有一次在足式机器人上串联起跳+空中翻转时发现,起跳阶段的力矩输出在真机上比仿真低了约15%,导致空中翻转的角速度不够。症结在电池电压降带来的力矩衰减,解决办法不是改奖励,而是在训练时就把“最大输出力矩×电压系数”这个变量纳入随机化范围。
5.3 真机数据如何回流修正阶段奖励
真机测试会遇到一个尴尬问题:阶段分类器在仿真里准确率很高,到了真机却频繁误判。原因是真机接触传感器噪声形态和仿真很不一样。
我的做法是收集真机状态序列,回放到仿真器的“状态记录器”里做离线分析,看阶段分类器在每个时刻的输出概率分布,找出错误模式。然后通过两类修正:一是给分类器增加真机样本做微调(迁移学习,用一个真实域的domain adapter);二是调整阶段切换阈值,比如把“触地判断”从单帧触发改成连续3帧持续触发。这一步很枯燥,但每修正一个误判,真机串联的成功率都会有肉眼可见的提升。
6. 复现与实践中的一些心得
6.1 先花一周把“失败”定义清楚
如果有人要复现这个方案,我第一个建议不是去抠奖励函数参数,而是先把“失败”定义清楚。每个阶段都要写清楚:什么条件下算成功、什么条件下算失败、失败后是否重置本阶段还是回退到上一阶段。
这个定义直接影响回放机制(experience replay)的效率和课程进度。如果“失败”的定义过于严格,正常探索中被中断的轨迹过多,策略几乎没有完整体验;如果定义过松,智能体混过多次“半成功”轨迹,学出来的动作边界模糊。我最终在每个阶段加了“成功区间”和“严格失败区间”两个阈值,中间区域不计入课程推进统计。
6.2 可视化优先级:先看关节力矩+能量曲线
训练过程中最值钱的可视化不是奖励曲线,而是关节力矩曲线和系统能量曲线。奖励曲线只能告诉你“分数涨了”,但力矩曲线能告诉你“策略是不是在用力过猛硬掰”,能量曲线能告诉你“动作是否自然”。
我习惯每个阶段训练时同时绘制以下三个图:各关节力矩的平均值和最大值随时间变化(看是否长期贴着限幅)、系统总能量和动能/势能拆分(看起跳时机是否合理)、阶段切换时间分布直方图(看策略是不是在刻意拖延或加速通过某个阶段)。如果这三个图表现正常,说明策略基本走上正轨;如果异常,那多半不是调参能解决的,而是奖励函数结构本身需要调整。
6.3 这类方法还能扩展到什么方向
分阶段奖励塑形+约束多目标强化学习的组合,并不只适用于后空翻或筋斗。我认为它最值钱的地方在于:把“长程、高动态、强安全要求”的任务从“一个不可分的黑盒优化”变成“一串可解释、可调试的分步优化”。
像乒乓球机器人的人机对拉、机器狗在废墟上的连续跳跃翻越、人形机器人的跌倒自恢复,这几个场景都有同样的结构:动作时间短、状态变化剧烈、安全边界不可妥协。分阶段奖励把每个子动作的“好坏”信号拉近,约束多目标框架把“安全底线”和“优化目标”分离,二者组合起来,可以让训练稳定性和后期真机迁移调试的颗粒度都大大提升。
如果你正要动手做这类项目,我的建议是从一个足够简单的杂技动作(比如单次前空翻)开始,先把两个机制在仿真里跑通,再逐步加复杂度。第一版不要追求动作花哨,先把阶段切换的边界和约束预算的中后期收紧策略跑稳,后面的扩展会顺利很多。