
两年前我第一次在仿真环境里用 PPO 训出一只能稳定行走的双足机器人那几步走得跟喝醉的企鹅一样但我还是兴奋地录了屏发到工作群里。原因很简单作为传统控制背景出身的人我太清楚让一个多自由度系统在未知地面上保持稳定有多难而强化学习这个路子竟然用“状态到动作的试错映射”绕过了大量手工建模环节。后来我又陆续用强化学习做过机械臂插拔、四足上台阶、人形机器人平衡恢复踩过的坑比教程里的示例多得多。这篇文章想把最核心的经验一次讲透——状态、动作、奖励这三件事怎么设计PPO 为什么能在这类场景里站稳脚跟以及从仿真走到实机时那些没人明说但早晚要踩的坑。适合两类人有强化学习基础但没碰过机器人的算法工程师以及有控制背景但想搞懂强化学习怎么落地的机器人工程师当然零基础但好奇“机器人到底怎么自己学会走路”的读者也能跟上思路。1. 传统底盘控制的天花板为什么机器人圈开始拥抱强化学习强化学习进入机器人运动控制并不是因为它“听起来高级”而是传统控制路线在真实环境中确实撞上了天花板。在 RL 流行之前让双足或四足机器人走起来基本是“建模—规划—跟踪”的经典三段式。工程师依据质量、惯量、执行器参数建立动力学模型再用零力矩点ZMP或模型预测控制MPC规划质心轨迹和落脚点最后靠全身控制WBC把关节转角跟踪到位。这框架在平整地面、负载固定、参数准确的场景下非常成熟波士顿动力早期许多动作也脱胎于类似思路。问题在于模型永远是近似。摩擦系数、地面软硬、关节阻尼、电池电压下降导致的扭矩变化这些不确定因素一旦累积控制器就会越来越保守否则一个扰动就可能让整个系统失稳。调 ZMP 预览参数、调 MPC 权重、调 WBC 任务优先级的日子相信做过足式机器人的朋友都懂换一块地毯之前调好的参数可能就废了。更麻烦的是接触问题——脚和地面的碰撞、摩擦、滑移本身是非线性的、不连续的传统方法要么做大量简化要么需要极高的计算成本。强化学习换了个思路不显式建模而是直接学一个“状态到动作”的策略映射。你给机器人一套状态姿态、关节角度、速度等它通过不断试错学到一个动作输出让累积奖励最大化。这本质上是个数据驱动的办法环境动力学不需要精确建模只要仿真器足够逼真或者实机采样足够多策略自己会把“怎么走”这件事内化在神经网络权重里。不过说实话强化学习并没有也不可能“取代”传统控制。现在主流的部署架构其实是分层混合强化学习训练出来的策略负责上层决策输出关节期望位置或速度增量底层仍然用经典的 PD 控制器去做高频跟踪。这样既利用了 RL 对复杂环境自适应能力强的优势又保住了传统控制响应快、安全性可控的底线。所以别把两者对立起来它们在我实际项目里是队友不是对手。还有个关键问题为什么是“现在”才开始火算力和仿真器起了决定性作用。GPU 并行仿真器Isaac Gym、Genesis、MuJoCo 的并行版本能同时跑成千上万条环境几分钟内产生相当于实机跑几个月的交互数据PPO 这类 on-policy 算法才能高效利用这些样本。没有并行仿真单靠实机采样训练一版策略可能要跑到地老天荒。2. 状态空间机器人每毫秒“看到”什么决定了它上限在哪很多初学者拿到一个机器人 RL 任务第一反应是“状态越多越好吧把能测的都喂进去”。这是最容易踩的坑之一。状态空间设计的关键不是“多”而是“够用且可观测”。所谓可观测是指从状态中能推断出当前系统所处阶段和潜在趋势够用则是指信息不缺失让策略具备做决策的依据。从马尔可夫性角度理解最直观理想状态下给定当前状态策略不需要历史信息就能做出最优决策。但真实系统中很多量不满足这个性质比如关节速度本身已经是位置的导数但如果没有历史动作信息策略很难判断当前动作是否已经被执行到位输出就容易抖动。所以实践中状态向量里很常见的一项就是“上一次动作”等于人为地把短时历史拼进状态里补齐这个信息缺口。拿一台 12 自由度的双足机器人举例一套比较完整的连续状态向量大概是这样的机身 IMU 姿态横滚角、俯仰角2 维注意偏航角一般不直接用因为全局偏航值在行走中会和指令方向耦合机身角速度横滚角速度、俯仰角速度、偏航角速度3 维关节角度12 维编码器读出的绝对角度关节角速度12 维编码器差分或滤波估计上一次动作12 维策略上一帧输出经缩放前或缩放后均可指令信息期望前进速度、期望转向角速度、期望步高或步频3 维左右足底接触力4 维左右脚各两维或三维力信息用于判断支撑相和摆动相加起来大概 44 到 50 维。你可以根据自己的机器人自由度删减但骨架差不多就是这样。注意每个维度都要有明确的物理单位和含义别把“规划器导出的一堆中间变量”随手塞进去策略会去拟合这些变量的统计关联但往往给你拟合出一堆莫名其妙的行为来。2.1 归一化小事不办训练给你脸色看状态向量里关节角度可能只有 0.5 弧度角速度却能达到 5 弧度每秒足底力可能几百牛顿如果直接喂进网络数值大的特征会主导初始梯度方向训练又慢又不稳定。归一化没有统一公式但有一条经验每个 feature 除以它可能出现的合理上限尽量让数值落在 [-1, 1] 区间。比如关节角度除以最大关节行程角速度除以 10力除以 500。别小看这一步我见过不少训练曲线到处乱飞的项目最后查下来就是忘了归一化。2.2 我踩过的坑漏掉“上一次动作”策略练成帕金森有一版四足步态策略训练曲线下降正常奖励也稳定但一到仿真视频里看四条腿抖得像筛糠。排查了很久最后发现状态向量里没有包含上一次动作。策略输出的是位置增量底层 PD 需要知道目标位置但策略自己看不到“我上一帧输出了啥”于是每次输出都像在黑暗中猜谜频率一高就是高频抖动。把上一个动作加进状态同时加上动作变化率惩罚项抖动立刻消失了。这事后来成了我检查状态空间的固定项先看有没有历史动作再看有没有做归一化最后才看网络结构和超参。2.3 速度指令和参考相位怎么办如果做的是循迹任务状态里要带上指令速度如果做的是带周期性步态的行走很多项目还会把“步态相位”一个 0 到 2π 的周期量作为状态输入。相位信息能帮策略对齐摆动相和支撑相训练明显更容易收敛。处理周期量时要用 sin 和 cos 两维来表示别直接丢一个 0 到 2π 的标量进去——神经网络对数值跳变很敏感5.9 和 0.1 明明物理上很接近数值上却是两个不可跨越的点。用 sin/cos 展开后这个周期性就平滑了。3. 动作空间位置增量、力矩还是速度先搞清机器人怎么被驱动动作空间的选择决定了策略的表达能力也决定了训练稳定性、安全性和部署难度。机器人关节层的执行方式通常有三种直接输出关节力矩、输出关节速度、输出关节位置增量。三者在强化学习里的体验完全不一样。3.1 三种动作空间的本质区别直接输出力矩是最“纯粹的强化学习”玩法策略直接产生每个关节的力矩指令底层只做电流环。表达能力强理论动作上限高但训练极不稳定因为力矩和机器人运动之间存在复杂的积分关系早期探索很容易把机器人甩到不可控状态奖励信号噪声极大。而且力矩过冲会直接冲击减速器和电机实机上稍有不慎就烧驱动器。我建议除非做纯仿真的研究否则别一上来就用它。输出关节速度比力矩温和一些策略产生目标速度底层速度环去跟踪。好处是平滑坏处是位置会有累积误差长时间跑会出现姿态漂移而且对接触力控制不利机器人容易被环境“顶出去”。位置增量是目前四足、双足机器人强化学习里事实上的默认选项。策略输出的是每个关节的目标角度增量 delta_q实际期望位置为q_target q_current scale * delta_q其中 scale 通常取 0.1 到 0.3 弧度。底层 PD 控制器再去跟踪tau kp * (q_target - q) - kd * q_dot这里 kp 和 kd 就是传统 PD 增益。这种结构的好处很直接动作天然被限幅delta_q 被压缩到 [-1, 1]乘以 scale 后只是在当前关节角附近小幅微调策略每一步的动作都是平滑的小变化训练稳定性大幅提升部署时底层还是熟悉的 PD工程团队没有心智负担。3.2 缩放系数动作空间的“语感”scale 该怎么选太小策略的表达能力受限想更快走或做更大跨步时使不上劲太大虽然上限高了但探索初期稍一输出大动作机器人就飞出去训练直接崩掉。经验做法是优先保住“稳定”scale 设置在 0.2 附近观察训练过程中策略是否频繁触碰到动作边界。如果发现大量时间都顶着边界输出说明动作空间大了再往大调如果训练很久还老在原地打转多半是动作空间太小策略无力改变姿态。这个过程有点像调一个旋钮需要反复扫几轮。3.3 控制频率与 PD 增益的配合机器人 RL 控制频率一般在 30 到 100 赫兹之间。仿真里 50 赫兹做动作输出很常见但 PD 层通常跑得更高比如 200 到 1000 赫兹用来做平滑插值和关节跟踪。频率定了之后PD 增益要和控制周期匹配。经验上kp 可以先从 20 到 60 之间开始试kd 取 kp 的十分之一到二十分之一再根据跟踪误差抖动情况微调。PD 跟踪效果太差策略学不出东西跟踪太硬动作容易激发出结构谐振。真机上这两组参数往往要反复对线。4. 奖励函数不是“走得快”就加分的填表游戏如果说状态和动作空间决定了机器人“能感知什么、能做什么”奖励函数就是在告诉机器人“什么事是好的好到什么程度”。同样是往前走奖励项设计得稍微不同学出来的步态可能天差地别。而且奖励函数的问题往往要到训练后期才暴露重新设计又要重训好几个小时相当磨人。一个基本原则起步阶段用密集奖励不要指望稀疏奖励。稀疏奖励只有走到终点给一个大奖励在真正的机器人运动控制里基本没法用因为探索空间太大策略在几十万次采样里都碰不到一次“成功”梯度信号弱得跟没有一样。密集奖励的本质是给策略一路铺台阶让它每走一步都能感受到“这方向对了”。拿“让机器人稳定向前走”这个任务举例一套可以直接用的奖励框架大概是这样的用伪代码表达def compute_reward(obs, action, prev_action, torque, dt): reward 0.0 # 1. 前进速度奖励机身速度在指令方向上的投影 forward_vel linear_vel command_direction reward 1.5 * min(max(forward_vel, 0.0), 1.0) # 2. 朝向对齐让机身朝向尽量贴近指令方向 yaw_error wrap_to_pi(yaw - command_yaw) reward 0.3 * cos(yaw_error) # 3. 机身姿态稳定性横滚和俯仰角越小越好 reward - 2.0 * (roll ** 2 pitch ** 2) # 4. 关节动作平滑惩罚防止高频抖动 action_delta action - prev_action reward - 0.01 * sum(action_delta ** 2) # 5. 关节力矩惩罚省力、保护执行器 reward - 0.0002 * sum(torque ** 2) # 6. 打滑惩罚防止足底在地面滑移 if foot_velocity threshold: reward - 0.5 # 7. 摔倒惩罚触发终止条件时额外惩罚 if terminated: reward - 10.0 return reward每个权重都不是随便拍脑袋的背后有明确意图。前进速度奖励权重最大是“主要矛盾”但姿态稳定惩罚的系数虽然只有 2.0实际影响却很大因为它是一个平方项姿态偏差一旦变大惩罚会快速升高这能有效约束机器人别一路前倾。动作平滑惩罚 0.01 属于“软约束”只用来抑制抖动不至于拖慢正常动作。力矩惩罚的 0.0002 看起来更小但扭矩数值本身是几十上百的平方量级乘起来实际惩罚不小。调权重时我的习惯是先让稳定类惩罚占主导把步态调到“能稳”再逐步加大前进速度奖励让速度慢慢提上来。次序反了策略会学到一堆花哨但危险的加速方式后面很难掰回来。这里必须多说一句“奖励黑客”reward hacking。这是奖励函数设计里最有趣也最烦人的现象策略会发现一个你想不到的方式去刷高分而不是按照你的本意做事。我遇到过最典型的一个为了追求前进速度机器人学会了一种“蹲踞式弹跳”步态步子非常难看且不连续但机身瞬时速度确实高奖励还真涨了。还有一种更隐蔽我用机身坐标系下的速度去计算向前奖励结果策略学会了高频抖动机身让加速度计“感知”到很大的瞬时速度原地就把奖励刷上去了。这种问题的本质是奖励项和物理量没有对齐或者说我观测到的量存在“作弊”空间。解决办法也很粗暴换成地面坐标系下的实际位移速度或者加一个足底打滑惩罚切断它的作弊路径。设计奖励时每次加完一个项都问自己“如果这个策略很坏它有没有办法不按我的意图刷这个分”这个问题能挡住不少 reward hacking。奖励曲线可视化也是日常操作。训练时把每一项奖励单独记录出来画成曲线别只看累计奖励。累计奖励会掩盖问题——可能前进奖励涨了但姿态稳定惩罚也在涨两边一抵消看起来曲线平平步态实际上已经歪了。分开看才能真正定位到是哪一项在搞鬼。5. PPO 为什么成了机器人运动控制的事实标准clip 机制与超参实战强化学习算法不少但真正在机器人运动控制里成为“默认选项”的基本就是 PPO。这不是因为 PPO 理论最漂亮而是因为它稳定、易用、对超参没那么敏感而且生态成熟从 Isaac Gym 到各种开源四足项目全是 PPO。5.1 为什么不是 DQN也不是更激进的算法DQN 解决的是离散动作空间问题而机器人控制天然是连续动作空间。虽然 DQN 也有连续动作变体但实际应用中训练稳定性普遍不如策略梯度方法。策略梯度类算法里TRPO 理论严谨但实现复杂每步更新还要解约束优化PPO 用一阶优化加一个 clip 就能逼近 TRPO 的效果工程上友好得多。SAC 这类 off-policy 算法也有机器人应用样本效率更高但最终部署时往往还是 PPO 训出来的策略更“稳”而且 PPO 的调参经验在社区里积累最厚出问题最好搜。5.2 PPO 的核心机制一个 clip 为什么能扛大旗PPO 的出发点很朴素每次采样得到一批数据后我们想在这个批数据上多更新几步让策略进步更快。但直接多更新几步新策略可能和老策略偏差过大导致采样的数据“过时”优化方向失真。PPO 的解法是限制新旧策略的差异。它计算每个状态动作对的概率比r_t pi_new(a_t|s_t) / pi_old(a_t|s_t)正常的策略梯度会用 r_t 乘以优势函数 A_t 来加权但 PPO 额外做了一个裁剪L min( r_t * A_t, clip(r_t, 1-epsilon, 1epsilon) * A_t )当优势为正时我们希望扩大这个动作的概率但概率比超过 1epsilon 时收益不再增长当优势为负时我们希望压低概率但概率比低于 1-epsilon 时损失也不再增长。这个 epsilon 就是 clip range通常取 0.2。等于给每一步更新套了一个护栏不让策略朝一个方向冲太远。另一个关键组件是 GAE广义优势估计。单步 TD 误差偏差大蒙特卡洛回报方差大GAE 用 lambda 参数在两者之间做插值lambda 取 0.95 时既有较低偏差又能容忍一定方差在机器人任务里表现很稳定。理解到这一层PPO 的代码就能读懂了Actor 网络算策略和概率比Critic 网络算价值GAE 估计优势然后 clip 目标函数更新。5.3 一组能让训练跑起来的超参基线超参这东西不同任务差异很大但如果你刚开始下面这组基线参数是很多开源四足项目实际在用的组合可以先跑通再慢慢调参数推荐值说明gamma0.99折扣因子gae_lambda0.95GAE 插值系数clip_range0.2clip 裁剪范围learning_rate3e-4Adam 优化器配合线性衰减num_epochs5每次采样数据更新轮数batch_size4096 至 8192取决于环境并行数和状态维度entropy_coef0.005 到 0.01熵正则防策略过早确定max_grad_norm0.5梯度裁剪上限注意学习率。机器人任务里我经常把初始学习率定在 3e-4然后随着训练步数线性衰减到 0。衰减过快后期学不动衰减过慢后期数值不稳这两者之间的平衡在训练曲线后半段非常明显。batch_size 和并行环境数挂钩如果 GPU 显存允许批次大一点往往训练更稳。5.4 训练曲线怎么看不稳怎么排查训练不稳定的现象千奇百怪但排查路径是有规律可循的。我一般同时盯四条曲线episode return、策略熵entropy、critic loss、每个奖励项的分解曲线。以下表格是我常用的排错对照表现象可能原因应对方法熵值急速塌缩到 0探索不足策略过早确定调大熵系数降低学习率检查奖励项是否尺度太大或太尖锐critic loss 长期不降价值函数难拟合状态单调性差检查归一化做特征裁剪减少无关状态维度return 先升后崩学习率过大更新幅度过猛降低学习率减少 num_epochs确认 clip_range 不是过大步态高频抖动缺历史动作信息动作平滑惩罚太弱检查状态是否包含 last_action提高动作增量惩罚系数训练正常但仿真里步态奇怪奖励项存在 reward hacking逐项分析奖励分解曲线对可疑的奖励项做防作弊处理有一回我的 return 曲线一直在涨但动作策略实际表现很差步态很不自然。拆开奖励项才看到前进速度奖励涨得很猛但姿态惩罚也在以同样速度涨两头抵消。如果不看分解曲线这种问题根本发现不了。这也是我为什么强烈建议从一开始就把每个奖励项单独记录到 TensorBoard 或 wandb 里。6. 从仿真到实验室地面Sim-to-Real 这最后一公里训练环境里跑得飞起的策略搬到实机上常常像个第一次下地的傻子。这个现象叫 sim-to-real gap来源非常具体仿真器里的质量分布、摩擦力、关节阻尼、电机延迟、PD 响应特性跟真机不可能一模一样。而且仿真里观测是理想化的实机上每个传感器都有噪声、延迟、漂移。域随机化Domain Randomization是当前最主流的破解思路。核心思想很直接既然我不知道真实环境的精确参数那我干脆让仿真器里每次随机出的环境参数都不同让策略学会适应一个“参数分布”而不是某组固定参数。实操中我会随机化的量包括机身质量和惯量±10% 到 ±20%足底摩擦系数0.3 到 1.5 之间随机关节 PD 增益±20% 波动关节阻尼、电机力矩上限按一定范围随机传感器噪声姿态角、角速度、关节编码器加高斯噪声控制延迟随机注入 1 到 3 个控制周期的延迟域随机化的代价是训练难度上升策略可能在仿真里的“最优性能”变差一些但换来的是真实环境里的鲁棒性。我个人的经验是别把随机范围拉得太极端否则策略只会学到“和稀泥”什么都做不好连仿真里的基本行走都完成不了。一个比较合理的做法是先固定一个中等范围跑通再逐步加大随机范围观察实机表现和仿真性能之间的平衡。部署到实机前安全措施必须前置。第一动作输出要做限幅即便策略已经用 tanh 限制了 delta_q也依然要在接入 PD 前再做一层硬限幅防止推理时出现数值异常。第二PD 目标位置要做最大变化率限制防止一帧之间目标角度跳变过大导致输出力矩瞬间冲顶。第三机器人上电后第一次测试一定要做悬吊测试把机器人重量卸给吊带让策略在承重和空载之间感受接触确认关节输出方向和步态逻辑正常后再落地。这些看似基础的步骤能在关键时刻挽救一套昂贵的硬件。还有一点容易被算法背景的人忽略仿真里训练时用的是“理想化”的奖励函数它倾向于让策略探索出极限性能边界比如走得极快、动作幅度极大。但实机部署时极限边界就是安全边界一个轻微的外扰、一点通信延迟都可能导致策略越过边界直接摔倒。我的建议是训练时保留“降级目标”在奖励函数里刻意让前进速度奖励有个软上限比如走到 1.0 米每秒后不再额外加分。这样策略学到的是“到达这个速度足够好”而不是“能跑多快跑多快”。实机部署后我常常把命令速度限制在训练上限的 80% 左右留出余量。最后说一句关于数据管线的体会。仿真训练的版本管理很繁琐我习惯在每次重训前把“状态空间版本、动作空间版本、奖励权重版本、随机化参数版本”写成一个配置文件提交到代码仓库里。这个习惯救过我很多次——半年后回来复现某个步态时还能从 git history 里找回“当时那个企鹅步”的完整参数。不然光凭记忆你根本说不清上次好结果是靠改权重还是改随机范围得来的。有一点我个人体会很深强化学习训练像养一株植物状态、动作、奖励是土壤、水分和阳光PPO 是那个每天来浇水的人但真正决定植物能不能活着的是你对“这株植物想要什么”的理解。状态给够了动作尺度合适奖励顺着物理直觉来PPO 几乎不会让你失望这三件事里任何一件偷懒算法再先进也是白搭。如果你刚入门建议先找一个开源的四足或双足项目跑通一遍把状态向量在代码里怎么定义、奖励项在哪个文件里计算、动作怎么被缩放进 PD 目标位置这三件事看懂再开始动手自己改东西。别急着发明新算法先把这四条腿的路走稳后面有的是机会让它跑起来。