杂技机器人分阶段奖励塑形:约束多目标强化学习方法 最近在某个顶级机器人学术会议下文统一用 R-Conf 2025 代称上看到一篇很有意思的论文标题是《面向杂技机器人的分阶段奖励塑形一种约束多目标强化学习方法》。第一眼看到杂技机器人几个字我整个人就精神了——这两年人形机器人做跑酷、空翻、跳跃的视频满天飞看起来确实炫酷但真正在仿真里训练过这类技能的人都知道这玩意儿对强化学习来说就是一座难爬的山。奖励函数稍微设计得糙一点智能体要么学出一个四仰八叉的诡异姿势要么直接原地躺平。这篇论文的思路很对我胃口它没有试图搞一个万能奖励而是把后空翻这种复杂任务拆成阶段每个阶段单独塑形同时把力矩限制、身体姿态、落地安全等硬约束塞进多目标优化的框架里。整篇文章读下来感觉不是那种纯秀数学的堆砌而是一套能真正落地的工程方法论。这篇文章我就结合自己的复现经验和理解给大家拆解一下这套方法的核心逻辑、实现要点以及我踩过的几个坑。1. 先搞清楚杂技机器人到底难在哪1.1 高动态、欠驱动、弱耦合带来的控制难题杂技动作和普通的行走、抓取不一样它属于典型的高动态运动。机器人要在极短的时间窗口内完成大幅度的姿态变化比如后空翻时从蹬地发力到腾空旋转再到落地缓冲整个过程通常不到一秒。在这种时间尺度下传统的基于模型的控制器往往力不从心因为动力学模型在高速高加速度区间会严重失准摩擦力、柔顺性、驱动延迟这些因素都会放大控制误差。更麻烦的是像双足机器人这类系统本身就是欠驱动的。什么叫欠驱动就是机器人的可控输入自由度少于其本身的运动自由度。生活化一点理解你骑自行车的时候能控制的是车把和踏板但整辆车的倾斜、侧滑这些状态不是你直接就能推出来的你得靠身体重心去哄它。双足机器人也一样脚踝和髋关节的力矩输出是有限的但身体的摆动、旋转这些状态本质上是被动演化的。你要在欠驱动条件下精确哄出一个空翻动作本质上是一个边界值问题对初始条件极其敏感。稍微差一点整个轨迹就垮了。1.2 稀疏奖励会让智能体原地打转如果直接拿是否成功完成空翻作为奖励信号那就是一个极端的稀疏奖励问题。假设每个 episode 随机探索几千步在动作空间那么大、成功率极低的情况下智能体根本不知道往哪个方向使劲。就好比让一个从没见过地图的人在迷宫里找唯一一枚金币他每一步都是瞎走走了很久也碰不到金币于是没有任何梯度信息可以更新策略。这种情况下的强化学习基本就是纯随机搜索效率低到令人绝望。传统的解决办法是人工设计稠密奖励比如给高度的上升、角速度的累积、姿态的朝向这些中间量都配上奖励。但这里有个隐蔽的坑一旦奖励项多了它们之间的量纲和权重就会互相打架。你给跳到足够高设了奖励智能体可能学会故意倒地然后把身体弹起来像弹簧一样来刷高度你给旋转速度设了奖励它可能直接原地转圈刷分。这就是奖励塑形不当引发的奖励黑客reward hacking现象在杂技动作训练里尤其常见因为动作本身是流畅连续的很容易被某些局部奖励钻空子。1.3 多目标与安全约束的双重压力杂技任务天然就不是单一目标既要跳得高、翻得完整又要落地时人体姿态足够稳定、关节力矩不超过硬件极限。更关键的是这些目标之间往往互相冲突——想要翻得快可能就需要更猛烈的蹬腿但猛烈蹬腿会导致关节冲击太大影响落地缓冲想要落得稳就得在腾空阶段主动调整姿态但姿态调整又会消耗额外的角动量影响旋转圈数。这就引出一个核心矛盾我们既要最大化任务表现高度、圈数、速度又要严格满足安全约束关节力矩、速度极限、姿态极限。在强化学习里这属于约束多目标优化问题。之前很多工作要么把约束变成一个大权重惩罚项往奖励里塞要么只优化单一目标。前者的坏处是惩罚系数很难调调大了策略变得畏手畏脚调小了约束形同虚设后者的坏处是你没法保证机器人落地稳稳当当。这篇论文给出的答案很干脆把约束当成硬约束放进优化目标里而不是简单加在奖励后面同时用分阶段奖励塑形来引导探索。这就是标题里约束多目标强化学习的意思。2. 核心思路拆解分阶段奖励塑形为什么能成2.1 把后空翻拆成有先后顺序的阶段我做行走控制的时候有一个很深的体会对复杂动作与其在性能指标上死磕不如先给任务排个序。这篇论文的做法就是把一个连续动作分解成若干阶段比如蹲起蓄力、蹬地起跳、腾空旋转、落地缓冲每个阶段有自己明确的状态子目标和奖励函数。这样的好处是智能体不再需要一步到位地理解整个任务而是先在当前阶段把动作学像然后再通过阶段间的衔接过渡到下一步。这种思路和课程学习curriculum learning很像但更细致。课程学习通常是把难度从低到高排列比如先学平地站再学原地跳最后学空翻。而分阶段塑形则是把同一个动作按生理时间线切开给出一连串通往成功的里程碑。里程碑式的奖励即使稀疏也是一个个单独的小稀疏远比最终结果的大稀疏好解决。每个阶段的学习进度可以被单独监控——如果某个阶段老是完不成你就能精准定位是起跳力度没学对还是腾空姿态没控制好。这里有一个容易被忽略的设计点阶段之间不是靠时间硬切而是靠状态触发。比如只有当质心高度超过某个阈值且垂直速度超过某个阈值时才会从起跳阶段切换到腾空阶段。这么做保证了切换与物理状态对齐而不是傻傻地按时间切——否则策略学到一半可能因为执行节奏略慢被强行切到下一个阶段导致奖励断层。2.2 和传统单一奖励塑形的本质区别传统的奖励塑形比如基于势函数的奖励塑形potential-based shaping是为了解决稀疏奖励问题在不改变最优策略的前提下给探索提供梯度。它的理论保证很好但实际操作中势函数的设计还是高度依赖人对动作结构的理解而且它终究是在原奖励塑形奖励这个单一框架下工作。对于一个多阶段任务单一势函数很难同时刻画蹲起—起跳—腾空—落地这么复杂的动态特性。分阶段塑形的关键突破在于它允许不同阶段采用不同的回报尺度、不同侧重点的奖励项甚至允许阶段之间的奖励在数值上不可比。比如在蹲起阶段你更关心的是膝盖弯曲的角度和臀部下沉的深度而在腾空阶段你关心的是躯干角速度和整体角动量。你说这两者的奖励怎么统一到一个势函数里硬凑的话必然要在中间某处产生巨大的梯度残差。所以分阶段奖励塑形本质上是把一个复杂的长期稀疏奖励问题拆成多个相对简单的短期稠密奖励问题的组合。每个阶段内部智能体面对的是一个更容易收敛的子任务阶段切换时由于有状态触发机制策略不会面临剧烈的目标跳变而是平滑地把上一阶段的输出当作下一阶段的初始状态。我在做某跨平台系统时也用过分治思想对这种拆解带来的稳定性提升印象深刻。2.3 约束多目标强化学习的数学表达与优化逻辑约束多目标问题在数学上可以写成一个带约束的多目标优化形式[ \max_{\pi} \quad J_i(\pi) \mathbb{E}{\pi}\left[\sum{t} \gamma^t r_i(s_t,a_t)\right],\quad i1,\dots,m ] [ \text{s.t.} \quad C_j(\pi) \mathbb{E}{\pi}\left[\sum{t} \gamma^t c_j(s_t,a_t)\right] \le d_j, \quad j1,\dots,p ]其中 (r_i) 表示第 (i) 个奖励项高度、旋转、姿态稳定等(c_j) 表示第 (j) 个约束代价例如力矩超过极限的次数、脚踝过度倾斜幅度等(d_j) 是约束的阈值。多目标的情况下我们通常会把多个 (J_i) 用权重组合成一个标量目标(J \sum_i w_i J_i)但约束条件必须原样保留。在强化学习中处理硬约束的标准思路是拉格朗日松弛。把约束项放到目标函数后面形成拉格朗日函数[ L(\pi, \lambda) J(\pi) - \sum_j \lambda_j \left(C_j(\pi) - d_j\right) ]这里 (\lambda_j) 是拉格朗日乘子可以在训练过程中根据当前约束违反程度自适应调节。如果某个约束老是被违反对应的 (\lambda_j) 会变大导致策略更倾向于把该约束代价降下来如果约束一直满足(\lambda_j) 就会变小把优化重点重新放回任务表现上。这个思路和带约束的轨迹优化很像但把约束推到了策略优化层面。另外为了保证阶段性目标仍然优先于整体目标论文还在拉格朗日函数外面加了一层阶段权重。也就是说不同阶段使用不同组的 (\lambda)这样每个阶段自己有一条约束放松的曲线——初期约束可以松一点让策略充分探索后期约束必须拉紧以保证动作安全。这种动态松弛约束的做法是我在复现时觉得特别实用的点后面我会详细讲。3. 实操要点怎么把纸面设计变成能跑的代码3.1 阶段划分的粒度怎么定阶段划分是这套方法的地基。以最常见的后空翻为例我看了很多运动学数据之后会把动作分为四个阶段蹲起蓄力、蹬地起跳、腾空翻转、落地缓冲。每个阶段的定义方式和触发条件各不相同具体如下表所示阶段名状态特征定义进入条件阶段内奖励侧重点蹲起蓄力髋关节下降双膝弯曲质心压低初始即开始持续到垂直速度为正前膝盖角度、质心高度降低幅度、躯干姿态竖直度蹬地起跳垂直速度为正地面反作用力上升质心垂直速度 0.2 m/s 且双脚离地垂直速度增长率、蹬地阶段时间最短化腾空翻转双脚离地处于空中支撑脚接触力为零或传感器阈值躯干旋转角速度、躯干朝向与目标角度差落地缓冲首次接触地面至姿态稳定任意脚接触力 阈值落地速度垂直分量、脚踝屈曲缓冲、全身姿态偏差这个划分不是拍脑袋定的而是基于运动捕捉或仿真中参考轨迹的关键姿态点。为什么建议这四个阶段因为每个阶段都有明确的力学标志物离地、腾空、着地都是状态突变点用它们做切分策略才能清楚地知道自己当前在任务中的角色。如果切得太细比如把腾空再切成前半段和后半段阶段间切换过于频繁容易引入噪声如果切得太粗比如把蹲起和起跳合并起跳发力就不容易学出来。需要注意的是阶段状态变量比如双脚离地在仿真中要用真实传感器模拟量的阈值判断而不是用 ground truth 直接硬切。否则你把策略搬到真实机器人上时因为感知误差阶段切换的瞬间就会和训练不一致。建议给每个判断加上滞回hysteresis比如离地判定采用接触力小于5N持续超过两帧而不是瞬时值避免噪声导致反复横跳。3.2 奖励函数设计示范我们以一个简化版本为例每个阶段我给出关键的奖励项和系数。实际项目中这些系数都要通过搜索或经验调整但下面这组是相对稳定的起点。蹲起蓄力阶段[ r_{\text{squat}} w_1 \cdot \exp(-\alpha | q_{\text{knee}} - q_{\text{target}} |) w_2 \cdot \exp(-\beta |h_{\text{CoM}} - h_{\text{low}}|) w_3 \cdot \exp(-\gamma \theta_{\text{lean}}) ](q_{\text{knee}}) 是膝盖弯曲角度(q_{\text{target}}) 是目标弯曲角比如 120 度(h_{\text{CoM}}) 是质心高度(h_{\text{low}}) 是设定的下限高度(\theta_{\text{lean}}) 是躯干相对于竖直的倾斜角这三项分别奖励蹲得够低膝盖弯曲到预期躯干保持竖直。(w_i) 是权重(\alpha,\beta,\gamma) 是控制奖励衰减速度的系数。这些衰减系数很重要它们决定了奖励的锐度如果太小奖励变化不明显如果太大策略容易陷入局部最优比如死磕弯曲角度而忽略其他项。蹬地起跳阶段[ r_{\text{jump}} v_{\text{vert}} 0.3 , v_{\text{vert}}^2 \cdot \mathbb{1}[v_{\text{vert}} 0] ]这里直接用垂直速度正比奖励再加上一个加速项奖励鼓励爆发式起跳。注意我没有加跳跃高度的直接奖励因为高度本质上由垂直速度积分决定直接算高度会把时间因素也掺和进去容易让策略学到慢吞吞地飘高而不是果断蹬地。这个细节是我在调参时踩过坑的地方——最开始用了高度奖励策略学出来的动作像个老太太慢慢起身极不流畅。腾空翻转阶段[ r_{\text{flip}} w_4 \cdot \exp(-\delta | \omega_{\text{pitch}} - \omega_{\text{target}} |) w_5 \cdot \exp(-\epsilon |\theta_{\text{roll}}|) ](\omega_{\text{pitch}}) 是俯仰角速度对应前空翻/后空翻的旋转速度(\omega_{\text{target}}) 是目标角速度(\theta_{\text{roll}}) 是侧倾角用来防止翻转过程中偏航歪斜落地缓冲阶段[ r_{\text{land}} -w_6 \cdot (|v_{\text{vert,land}}| | \omega_{\text{land}} |) w_7 \cdot \exp(-\zeta|\theta_{\text{total}}|) ]落地阶段主要是惩罚性奖励垂直速度和角速度越大惩罚越大同时给一个姿态奖励要求落地后身体尽量正。注意落地阶段不鼓励任何主动加力因为缓冲是耗散过程控制策略应该尽量让身体自然吸收冲击而不是继续输出力矩。3.3 约束条件怎么塞进优化目标约束条件的设置需要区分硬约束和软约束。对于硬件安全边界比如关节力矩不能超过最大扭矩的 95%这类必须作为硬约束对于姿态自然度、侧倾幅度可以做成软约束并给惩罚权重。但在约束多目标框架里硬约束也不是直接限制策略网络输出而是通过拉格朗日乘子法在损失函数里加项。具体实现时可以用一个函数去度量约束代价。例如力矩约束代价[ c_{\text{torque}} \sum_i \max(0, \frac{|t_i| - t_{\max,i}}{t_{\max,i}})^2 ]这个代价在力矩超出限制时为正值超出越多平方值越大平滑且可微对强化学习不是必须但对价值函数估计更友好。然后把这个代价和阈值 (d_{\text{torque}})比如 0.05一起塞进拉格朗日函数。伪代码如下for episode in range(total_episodes): # 阶段触发判断发生在环境step内部 while not done: action pi(obs) next_obs, reward, done, info env.step(action) phase info[phase] stage_reward compute_stage_reward(reward, phase) # 取得当前阶段的奖励 constraints compute_constraint_cost(info) # 返回每个约束的代价列表 store_transition(obs, action, stage_reward, constraints) # 更新策略 # 目标函数阶段奖励 多目标权重 - 拉格朗日项lambda * (约束代价 - 阈值) for idx in range(num_updates): obs_batch, action_batch, reward_batch, cost_batch sample_batch() weights get_phase_weights(phase_batch) objective (weights * reward_batch).sum() - sum( lambdas[j] * (cost_batch[j] - d[j]) for j in range(num_constraints) ) loss policy_loss value_loss - objective # 具体取决于使用PPO还是其他算法 optimizer.zero_grad() loss.backward() optimizer.step() # 自适应更新lambda for j in range(num_constraints): lambdas[j] max(0, lambdas[j] lr_lambda * (mean_cost[j] - d[j]))这里最关键的是 phase_batch 的存储。每个 transition 必须记录当前阶段这样你在计算每条样本的 objective 时才能乘上当前阶段对应的权重和不同的 lambda 组。如果你的 replay buffer 不记录阶段信息后面训练时根本没法做分阶段加权。3.4 训练配置示例与随机化我用的仿真环境是开源的物理引擎基于 Bullet 或 MuJoCo 的机器人模型具体品牌不重要。为了增强迁移性必须做 domain randomization——随机化地面摩擦系数、机器人质量、电机最大力矩、控制延迟等。参考常见做法我给出一组起点参数摩擦系数范围0.6 ~ 1.4质量变化比例±10%关节力矩上限变化±5%控制频率30Hz 与 60Hz 之间随机选取初始化随机状态给初始关节角加 ±0.05 rad 的噪声训练算法上直接用 PPOProximal Policy Optimization就能跑不需要特别高级的算法。PPO 对超参数相对鲁棒而且它的 clip 机制可以防止复杂多阶段奖励造成的策略更新过猛。Critic 网络要观察阶段信息我的做法是把 phase 作为 one-hot 向量拼接进观测这样价值函数能够区分不同阶段的价值差异训练会更稳定。关于 batch size如果每个阶段样本不均衡会导致阶段权重失衡。建议对 transition 进行重采样保证每个阶段的样本量大致相等。我刚开始没做这一步蹲起蓄力阶段特别长占了整个 episode 的大半时间导致起跳阶段的样本在训练中被稀释起跳质量迟迟提不上去。后来对每个阶段按比例采样表现立刻上来了。4. 常见问题与排查技巧实录4.1 阶段切换时奖励发生跳变策略在边界震荡这是我复现时遇到的第一个大问题蹲起阶段切换成起跳阶段的那一帧奖励突然从鼓励蹲起跳变成鼓励垂直速度数值上可能出现明显跳变。PPO 的 GAE 在计算优势函数时遇到这种大跳变会导致值函数误差增大继而策略更新方向混乱。解决办法有两个方向。一是让阶段切换的奖励过渡平滑化。比如在阶段边界附近用两个阶段奖励的加权平均作为过渡权重根据距离边界的时间或状态距离线性过渡。二是调整阶段切换阈值让切换点落在大概率不会来回横跳的区域。我给离地检测加了滞回之后这个问题很大程度缓解但即便这样阶段边界仍然会有一定幅度的奖励差。我后来选择把阶段奖励本身离散化每个阶段的奖励量级都归一化到接近的数值范围这样切换时奖励跳变幅度可控策略训练稳定很多。4.2 多个任务目标之间权重失衡策略只刷单项目标当你先给高度奖励又给速度奖励时如果高度奖励的系数大策略可能更乐意跳高而不是翻得快。这类问题在分阶段框架里会被局部放大因为每个阶段只有一个主奖励这反而是好事——你只需在每个阶段内解决一个小规模的权重冲突。但如果在一个阶段里还有多个子目标比如腾空阶段既要转得快又要姿态正肯定也会有权重冲突。我的排查方法是对每个子奖励输出单独记录它们的累计值曲线。如果发现某个子奖励涨得很猛但另一个完全没涨基本就是权重失衡。这时候不用盲目加大权重先检查两个奖励的数值尺度是否在同一量级。我经常用 reward normalization每个奖励项除以它在过去 N 个 episode 的均方根或者最大绝对值再做权重组合。归一化之后权重调节变得非常直观。4.3 拉格朗日乘子更新太快约束惩罚过重策略变得畏手畏脚拉格朗日乘子如果初始值过大或者学习率 (lr_{\lambda}) 太大策略很快就会完全被安全约束压死——它宁可躺着不动也不尝试任何高风险动作因为动作越猛违反约束的可能性越大而约束惩罚远远大于任务奖励。这种情况下你看训练曲线会发现约束满足但任务表现几乎零增长。要解决这点我的经验是给 lambda 学习率设得非常小大约是策略学习率的 1/10 甚至更小。同时给 (d_j) 设一个初始的宽松值让策略在早期有大量探索余量等任务奖励上升到一定阈值后再把 (d_j) 慢慢收紧。这个思想其实和课程学习一致先学无用后学精确。另外可以给 lambda 更新加一个只在约束连续违反超过 K 个 episode 时才增大的韧性机制避免单个随机坏 episode 导致 lambda 大幅抖动。4.4 奖励项量纲差异导致梯度噪声在一个阶段奖励里角速度的单位是 rad/s质心高度的单位是 m膝盖角度的单位是 rad这些数值范围可能差好几个数量级。如果你直接把原始值加在一起那么量纲大的项会主导梯度其他项基本学不动。这个坑在腾空阶段特别明显角速度往往在 5~20 rad/s 之间而姿态偏差可能在 0.1~1 rad 之间两者的平方指数项数值会差得离谱。我在实现里会强制对每个子奖励项做类似 min-max 归一化用经验上下界把奖励映射到大概 [0.3, 1.0] 的范围。这样做不是为了好看而是为了稳定不同训练阶段的奖励尺度。需要注意归一化用的上下界要在线自适应更新但更新频率要慢否则奖励分布一变策略刚学到的价值函数又要重新适应。我习惯使用滑动平均的上下界窗口取 1000 个 episode。4.5 策略学会钻空子玩文字游戏强化学习最讽刺的地方就是它会想尽办法利用你定义的奖励函数而不是完成你的本意。比如我早期在蹲起阶段给了一个膝盖弯曲角度接近目标的奖励策略很快发现只要直接跌坐到地上双脚不动身体倒下膝盖角度就能瞬间接近目标还能因为这个刷到一波奖励。结果整个动作变成了摔倒大法。这种问题只能靠混合约束来防。我给蹲起阶段加了一个质心高度不得低于下限的硬约束摔倒必然触碰这个约束于是拉格朗日乘子迅速把它拉回来。另一个技巧是对每个阶段奖励设置上限一旦超过某个阈值就不再累加防止智能体过度刷同一行为。上限的设定要依据参考动作中该指标的峰值来确定调起来有点麻烦但对防止钻空子很有效。5. 一些从实际操作中沉淀下来的心得这套分阶段奖励塑形 约束多目标强化学习的方法我最喜欢的地方是它把工程上那些暗中摸索的操作给体系化了。以往我调奖励全靠瞎试哪个涨了就留下哪个不涨就删掉完全依赖直觉和运气。而分阶段框架逼着我把任务先拆清楚再去定义每个阶段的奖励和约束整个调试过程变得可解释、可追踪。实际动手时我强烈建议先在一个简单的平面跳跃任务上复现这套框架不要一上来就挑战后空翻。因为后空翻本身对仿真环境的物理精度要求极高一旦失败你很难分清是奖励塑形的问题还是仿真参数的问题。简单任务能帮你验证阶段划分、lambda 自适应更新、奖励归一化这些组件是否正常工作。还有一个很微妙但很重要的点阶段的定义不应该只依赖当前状态还应该包含历史状态的信息。比如落地阶段怎么区分正常落地和摔倒后弹起你只能通过连续多帧的接触力模式和速度变化来判断。所以做阶段识别时我经常会在观测里拼上最近几帧的状态差时间差分特征而不是只用单帧感测值。这个细节在真实机器人上尤其关键因为真实传感器噪声会让单帧判断完全不可靠。最后给想复现这篇文章的同学们一个忠告论文里给出的奖励权重和约束阈值是作者在他们仿真环境下调出来的千万不要直接搬。你必须先拿到自己环境里的基准量纲按比例缩放后再调。我在复现时花了整整一周去折腾权重后来想通了直接把所有奖励做成可配置的配置文件每次调参只需跑个短脚本看梯度和奖励曲线效率高得多。希望这篇文章的拆解和踩坑记录能让大家少走一点弯路。