分层强化学习驱动的四足机器人步态学习与仿真训练实践
简介面向四足机器人步态控制研究者的实战项目包采用分层强化学习方案解决如何让机器人习得行走、跑步、爬楼梯等多种步态的问题。项目将复杂决策拆分为高层方向与速度规划、低层腿足协调等子任务覆盖马尔可夫决策过程、策略梯度、价值函数估计等核心概念并附带完整可复现的实验与仿真代码。压缩包共50个文件大小约3.77MB构成上以24个Python脚本为主配合9个C头文件、3个源文件与4个YAML配置并附有多张步态演示GIF和说明文档。Python脚本承载算法训练与仿真流程C文件用于环境底层封装YAML则便于调整训练参数。已有230人学习下载源码目录结构清晰包含分层控制器示意图与PPO实现脚本便于研究者快速复现多种步态实验也可作为机器人运动控制方向课题的优质参考。1. 四足机器人步态学习为什么分层强化学习比端到端更早落地四足机器人步态学习这个项目给的方案是分层强化学习。核心思路很直白把“该往哪走”和“腿怎么迈”拆成两层决策。高层只输出目标速度、转向角速度和步态模式低层在给定步态下负责具体的腿足协调、足端力量分配。换来的好处是状态空间和动作空间都缩小了训练难度直线下降。相比端到端硬学分层框架还有一个优势在于可解释性——机器人行为异常时你能一眼看出是高层选错了步态还是低层没跟上指令。适合做四足机器人运动控制的学生、想把仿真策略搬上真机的工程师也适合刚接触强化学习但想直接玩真实机器人仿真的开发者。2. 分层强化学习框架为什么拆两层高层和低层各自学什么2.1 端到端方案撑不住上百维观测、稀疏奖励和奖励冲突先回答最核心的问题为什么非得分层。四足机器人如果做端到端强化学习从imu、关节编码器、足端力传感器的原始读数直接映射到12个关节力矩观测维度很容易超过100维。Anymal这样的机器人有12个主动关节加上机身姿态、角速度、线速度一个状态向量就是百维浮点数组。PPO在这类连续高维空间里探索前期大部分样本都是随机抖动能碰巧走出一步稳定步态的概率很低奖励信号来得稀疏训练周期经常以周为单位。更麻烦的是奖励冲突。端到端训练时前进速度、机身姿态、足端触地、能量消耗这些目标全部压在一个奖励函数里。速度项想让机器人跑快姿态项想让机身水平能量项想省力三项的梯度和量级不一致。经常出现的情况是速度项主导了梯度方向姿态项被淹没结果就是训练曲线里前进奖励刷得很高打开视频一看机器人歪着身子斜着冲出去。分层之后这个问题被结构性化解。高层只负责任务层指标比如是否到达目标点、速度是否符合预期低层只负责执行层指标比如是否跟上了高层给的指令、机身姿态是否平稳。两个网络各自优化各自的目标不再出现多目标在同一个网络里互相打架的情况。这也是项目摘要里强调“高层决策方向和速度、低层关注腿足协调和足端力量分配”的原因。从实现角度看分层还带来了一个工程上的隐性好处训练可以分阶段进行。低层的步态技能可以先单独训好再固定住给高层用高层策略训练时不需要从头探索腿该怎么迈。这种迁移方式省下的时间不止一半。我实际跑下来同样一个trot步态从零端到端至少要训一整天分层之后半天多就能收敛到能看的程度。2.2 层次控制器设计hierarchical_controller.png 里的分工项目里有一张hierarchical_controller.png把整个控制架构画得很清楚。结构上大致分三层任务规划层、步态管理层、关节执行层。强化学习的部分主要落在前两层关节执行层通常用PD控制器把低层输出的期望角度转成力矩。任务规划层对应高层策略。它运行在一个较慢的周期上比如每0.5秒更新一次决策输出的内容是期望的机身速度vx、vy、转向角速度wz以及当前应该采用哪种步态模式。步态管理层就是低层策略它把高层给的步态模式展开成具体的腿部摆动规律同时接收机身姿态、关节状态作为输入输出到12个关节的期望位置或力矩指令。三种步态的特性差异很大项目视频里已经直观展示了实际效果。我在复现的时候也用这个表做过对比步态着地相位特征速度区间训练难点trot对角腿左前右后同步摆动中低速对角协调防止侧倾bound前后腿分组轮流着地中高速腾空相控制落地缓冲pace同侧前后腿同步摆动低速横向平衡重心转移trot是对角小跑稳定性最好适合平地持续行走也是我建议你第一个训的步态。bound是弹跳步前后腿分组着地存在明显的腾空相速度潜力大但落地冲击也大如果奖励里不加冲击惩罚训练出来的动作会非常生硬。pace是踱步同侧腿同摆横向摆动明显适合低速精细移动训练时如果没有横向重心转移的引导低层策略很容易退化成四腿乱蹬。高层和低层的网络结构也不一样。高层因为输入维度低、动作维度低一个两层的MLP就够了隐层64到128个神经元没什么压力。低层要处理姿态和关节状态网络需要稍微宽一点我一般用两个128维隐层。动作头是高斯分布采样均值网络输出12维的关节位置增量或力矩log标准差可以固定也可以学习项目里默认是学习的训练后期步态会更稳。2.3 状态空间、动作空间和奖励两个MDP怎么定义强化学习绕不开马尔可夫决策过程。分层之后MDP的定义也随之一分为二。高层建立的是一个粗粒度的MDP状态S_high取朝向偏差、速度偏差、当前步态模式、地形类别这样的低维特征动作A_high是目标速度向量加gait_mode奖励R_high是到达奖励、时间惩罚和姿态惩罚的组合。低层的MDP则完全围绕“执行”来定义。状态S_low包含高层指令、机身姿态、关节角度、关节角速度、足端触地信号动作A_low就是12个关节的位置增量或力矩指令奖励R_low由跟踪高层指令的精度、足端滑移惩罚、关节力矩限制、机身姿态稳定项组成。这个结构在学术上对应option框架每个步态模式就是一个option高层负责选option低层负责在option内部完成动作生成。低层的奖励设计是步态质量的关键。拿trot来说跟踪指令的奖励用高斯误差函数比线性误差效果好很多。线性误差在误差较大时梯度恒定策略不知道怎么权衡高斯误差在误差接近零时梯度趋近于零能让策略更精细地逼近目标值。另一个经验是低层的控制频率要稳定。高层0.5秒出一个指令低层要以30到50Hz运行如果两个频率不匹配低层会感受到频繁的指令突变训练方差变大。3. 把 RaisimGym 环境跑起来四足机器人仿真环境搭建3.1 工程目录与核心文件先认清每个模块管什么拿到源码第一步先读目录结构不然很容易在编译阶段就卡住。这个项目文件组织比较规整大致分四块helper工具层、env仿真层、algo算法层、stable_baselines3适配层。我整理了一张表文件/目录职责helper/utils.py归一化、坐标系转换、统计工具helper/raisim_gym_helper.py环境辅助配置读取、日志初始化env/RaisimGymVecEnv.pyPython端环境封装兼容Gym接口env/VectorizedEnvironment.hppC底层向量化环境多环境并行仿真env/RaisimGymEnv.hpp单仿真环境的C基类env/Yaml.hpp、envs/Yaml.cpp配置解析读取机器人模型和奖励参数envs/Reward.hpp奖励函数定义三种步态各自的奖励项algo/ppo项目自带PPO实现含网络和更新逻辑stable_baselines3/RaisimSbGymVecEnv.py把RaisimGym环境适配成SB3格式的中间层stable_baselines3/anymal.pyAnymal机器人在SB3体系里的环境定义bin/debug_app.cpp调试入口加载权重可视化运行video/训练结果回放含trot.gif、bound.gif等这里最容易忽略的是Yaml类。Raisim的奖励配置、机器人模型路径、控制频率都通过Yaml文件传进来。很多人拿到代码后直接在Reward.hpp里改奖励改完发现没生效其实是被Yaml里的开关盖掉了。我一般先改Yaml确认开关打开再动Reward.hpp顺序反了容易白调半天。3.2 环境封装RaisimGymVecEnv 做了什么RaisimGymVecEnv.py是Python和C之间的桥。Raisim仿真器本身是C写的跑得快但直接操作不方便。这个类把底层的VectorizedEnvironment.hpp封装成OpenAI Gym风格的接口reset、step、close一一对应上层训练脚本不需要关心仿真器的内部实现。核心逻辑在step方法里。输入是一个二维数组形状是(num_envs, action_dim)每一行对应一个并行环境中要执行的12维动作。底层把动作分发到C仿真器推进一个或几个控制周期返回新的观测、奖励、done标志和额外信息# env/RaisimGymVecEnv.py 核心流程示意 class RaisimGymVecEnv: def __init__(self, config_file, num_envs, robot_class): self.envs robot_class(config_file, num_envs) self.num_envs num_envs self.action_dim self.envs.get_action_dim() self.obs_dim self.envs.get_obs_dim() def reset(self): obs, ext_info self.envs.reset() return obs, ext_info def step(self, actions): # actions: (num_envs, action_dim) 的numpy数组 obs, rewards, dones, ext_info self.envs.step(actions) return obs, rewards, dones, ext_info这段代码的关键点是robot_class参数。Anymal、Aliengo或者其它四足模型通过这个参数注入环境代码完全复用。get_obs_dim()和get_action_dim()必须在初始化阶段就返回确定值因为PPO的网络输入输出维度依赖它。如果机器人模型里加了一个传感器但忘了改这两个接口训练会在维度断言上直接崩掉。3.3 机器人类anymal.py 里定义了哪些观测和动作anymal.py是Anymal机器人的具体定义。它做的事情包括加载机器人URDF、设置初始姿态、实现观测归一化、把动作映射到关节指令、累计训练日志。观测空间一般包含机身线速度、角速度、姿态四元数、12个关节角度、12个关节角速度以及上一帧的动作缓冲# stable_baselines3/anymal.py 中观测与动作定义示例 import numpy as np class AnymalEnv: def get_obs_dim(self): # 姿态4 角速度3 线速度3 关节角度12 关节角速度12 34 return 34 def get_action_dim(self): # 四条腿各3个关节的期望位置增量 return 12 def normalize_obs(self, obs): # 关节角度除以关节限位速度除以经验最大值避免量纲差异 obs[4:16] obs[4:16] / self.joint_pos_limit return obs观测维度34维不是固定的如果加装足端力传感器或者把上一帧动作拼进去维度会涨。动作维度12是固定的对应四腿各3个关节。动作语义上低层输出的是关节位置增量还是力矩指令直接影响训练难度。位置增量模式更稳、容易收敛力矩模式上限高但前期探索容易翻车。这个项目默认走位置增量路线用PD控制器转成力矩工程上比较稳妥。3.4 编译与启动链路从raisim_gym.cpp到debug_app.cpp项目要跑起来先得编译Raisim那套C代码。Raisim依赖Eigen和RaiSim引擎库安装完依赖后走CMake流程# 编译RaisimGym环境的典型流程 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DPYTHON_EXECUTABLE$(which python3) make -j$(nproc)编译产物是一个Python可调用的.so文件RaisimGymVecEnv在import时通过pybind11加载。必须确认so路径在PYTHONPATH里否则报ModuleNotFoundError。调试入口在bin/debug_app.cpp作用是加载权重、初始化可视化窗口、循环跑推理# 可视化调试运行 ./anymal_debug --weight checkpoints/ppo_trot_5000.zip --cfg cfg/trot.yaml这里有个容易踩的细节--cfg和--weight必须配套。不同步态训练出来的权重对应不同奖励配置如果你拿trot权重配bound的配置文件加载网络输入输出维度对不上是小事更隐蔽的是底层奖励计算与训练时不一致步态跑出来完全是乱的。还有一个常见问题是CMake找不到RaiSim库这种情况检查RAISIM_DIR环境变量是否指向引擎安装路径export一下再重新cmake就好。4. 用 PPO 训出多种步态参数配置与训练流程4.1 PPO 在这个项目里的角色分层强化学习的两个层底层和高层都用了PPO作为策略更新算法。项目里既有自带的algo/ppo实现也有stable_baselines3适配层。用SB3的好处是代码量和调试成本低网络结构、GAE、clip这些细节都封装好了适合快速验证自带实现的好处是能精确控制每一步的细节比如在低层网络里接入步态相位信号。实际项目里我建议先用SB3跑通全流程再用自带实现去调细节。用SB3写的训练入口大概长这样# train_multi_gait.py from stable_baselines3 import PPO from stable_baselines3.ppo import MlpPolicy from raisim_gym.env import RaisimGymVecEnv cfg cfg/anymal_trot.yaml env RaisimGymVecEnv(cfg, num_envs64) model PPO( MlpPolicy, env, learning_rate3e-4, # 前期收敛速度与稳定性的平衡点 n_steps2048, # 每次策略更新采样的总步数 batch_size256, # min-batch大小影响梯度稳定性 gamma0.99, # 折扣因子步态任务中长期回报占比高 gae_lambda0.95, # GAE偏差方差折中偏低更稳偏高更快 clip_range0.2, # PPO裁剪范围太大容易破坏老策略 ent_coef0.005, # 熵系数保持探索防止步态模式固定太早 max_grad_norm0.5, # 防止梯度爆炸四足任务里很常见 verbose1, ) model.learn(total_timesteps10_000_000) model.save(checkpoints/anymal_multi_gait)网络用MlpPolicy因为观测是低维向量不需要CNN。learning_rate取3e-4是SB3在连续控制任务里的经验值四足任务如果奖励还没调顺可以先降到1.5e-4训得慢但不容易发散。n_steps取2048、num_envs取64时一次更新收集的轨迹量是2048×64步batch_size取256意味着一次更新做很多组小批量梯度更新数据利用效率会好很多。4.2 关键参数调试哪些参数对步态质量影响最大参数这块是玄学最集中的地方但有几个参数确实被反复验证过。第一个是clip_range它控制新旧策略允许偏离多远。四足机器人动作空间12维如果clip_range调到0.3以上策略更新步长过大经常出现的情况是连续几百步跑得挺好然后突然倒地起不来奖励曲线断崖式下跌。0.15到0.2之间是稳妥区间。第二个是ent_coef。四足步态很容易在训练中期坍缩成一种低效率动作比如原地高频小碎步。熵系数太小探索不足步态翻不出新花样熵系数太大策略不稳定动作随机性过强。我一般从0.01往下调看到步态开始重复就把熵拉高一点。第三个是gamma步态任务里机器人需要“先抬腿、后迈步、再站稳”这种跨多个控制周期的动作序列短期奖励不足以支撑技能形成。gamma取0.99让模型考虑几十步以后的收益能把“跌倒前的姿态偏差”和“之后的跌倒惩罚”关联起来。gamma取0.95也能训但步态会出现明显的短视只顾当前一步、不管后续平衡。参数推荐范围风险信号learning_rate1e-4 ~ 5e-4超过5e-4大概率NaNclip_range0.15 ~ 0.2超过0.3策略崩塌ent_coef0.001 ~ 0.01步态模式单调重复时调高gamma0.97 ~ 0.995低于0.95动作短视max_grad_norm0.5 ~ 1.0超过1.0梯度爆炸提示每次只改一个参数改完跑至少50万步再下结论。四足步态的收敛周期比普通连续控制任务长很多同时改两个参数出了问题你根本分不清是谁的责任。4.3 三种步态的奖励差异与训练顺序trot、bound、pace三种步态底层网络结构完全一样差别主要在奖励项和训练策略上。trot最基础奖励里机身高度项要放在优先位置建议用高斯型误差机身高度接近目标高度时奖励趋近1偏离时平滑下降这样策略学出来的姿态更稳不会出现高度震荡# envs/Reward.hpp 中trot步态奖励核心项示意 double target_height 0.54; # 目标机身高度单位米 double height_err base_height - target_height; rewards[height] exp(-height_err * height_err / 0.04); # 高斯误差奖励 rewards[forward_vel] clip(lin_vel[0], 0.0, 1.0); # 前进速度奖励 rewards[energy] -0.05 * mean(join_torque * join_vel); # 能量惩罚项bound步态有腾空相奖励函数里不能只盯前进速度落地冲击开始变得重要。建议加足端力峰值惩罚否则训练出来的bound动作是四腿硬砸地面仿真里看着还行真机上会把减速箱震坏。pace步态则要单独加横向重心转移引导项否则低层策略找不到同侧腿协调的局部最优。训练顺序上强烈建议先trot、再bound、最后pace。三个步态对平衡能力的要求递增trot学到的姿态控制特征可以被bound和pace的初始化借用。具体操作上把trot训练好的低层网络参数作为bound的初始权重收敛速度能快一半以上。这就是分层框架带来的额外红利步态技能之间可以迁移而不是每次都从随机初始化开始。5. 避坑与排查四足步态训练里我踩过的五个地雷5.1 训练中途NaN奖励断崖现象训练跑到几十万步时loss突然变NaN日志里只剩inf之前累积的奖励曲线全部作废。原因最常见的是学习率过高叠加奖励量级过大。Reward.hpp里某项奖励数值动辄上百其它项只有个位数梯度被这一项主导更新步长冲出合理范围。另一种可能是归一化环节除零比如关节速度的归一化除数取经验最大值如果一组异常数据把最大值刷到极大后面所有观测都会被压缩到接近0策略直接崩掉。解决先在tensorboard里看每一步的reward组件找到量级最大的项除以10或20再试。同时把learning_rate降一档从3e-4降到1e-4。如果NaN还出现检查归一化的最大最小值在save与load之间是否一致——训练中断后重载环境obs统计值常常没有同步这个坑特别隐蔽。5.2 步态退化成小碎步原地高频率倒脚现象训练能跑完机器人不跌不倒但步态退化成了高频小碎步速度上不去姿势像在原地踮脚。原因动作频率和能量惩罚不够。低层策略发现高频小幅摆动可以骗过稳定奖励——机身没倒就行那就怎么省力怎么来。更深层的原因是动作空间用位置增量模式时网络输出每帧的关节位置变化量高频小幅输出比低频大幅输出更容易在PD控制器下保持稳定。解决奖励里加动作频率惩罚对前后帧动作差做L2惩罚rewards[action_smooth] -0.1 * mean((action_now - action_last)^2)。这个惩罚会把高频倒脚抵消掉逼策略学出真正的周期步态。如果加上平滑惩罚以后步态幅度过大、身体晃动变强把惩罚系数从0.1调到0.05再观察。5.3 仿真里行真机上不行现象策略在仿真里跑得丝滑步态、速度、姿态全达标上真机后走了两步就摔或者动作频率明显跟不上。原因四足仿真和真机的差距主要是物理参数不匹配。仿真里摩擦系数、连杆质量、电机力矩上限都是固定理想值真机的地面摩擦随材质变化电池电压变化影响实际输出力矩控制频率也有差异。仿真里低层能跑到50Hz指令更新真机电机通信周期可能只有25Hz同一套权重换到慢频率上步态周期直接对不上。解决治本是domain randomization训练时把摩擦系数、重心位置、质量、力矩增益都加随机扰动强制策略学出鲁棒动作。项目源码如果没带随机化接口常见做法是在Raisim里注册物理属性扰动或者直接改Yaml里的物理参数每隔若干步随机重采样一组。治标的办法是降频训练把仿真控制频率压到真机水平步态质量会掉一些但至少迁移过去能走。5.4 步态模式切换瞬间姿态崩溃现象trot、bound分别训都很好部署multiple_gait策略时高层一旦从trot切到bound机器人先踉跄一下甚至摔倒不是平滑过渡。原因两个步态的低层策略在状态空间里是两个不同吸引域。切换gait_mode瞬间低层网络输入里的onehot标志突变而批归一化层的统计量还是按trot的数据积累的分布突变后策略输出剧烈跳变相当于给机器人一个瞬时动作扰动。解决一种做法是步态插值切换时给gait_mode的onehot做线性过渡比如0.3秒从trot向量滑到bound向量给低层缓冲时间。另一种是训练时做步态课程一开始让高层固定输出单一gait千步之后开始随机切换切换频率越往后越高让低层适应分布变化。项目里multiple_gait.gif演示效果好的权重多半是走了步态课程这条路。5.5 stable_baselines3 版本坑现象按照README装上依赖后一import就报错有的报ppo模块找不到有的报MlpPolicy参数对不上。原因项目里stable_baselines3对应的版本标注是1.15但SB3后来做过API调整新版部分内部接口改名。这个项目基于老API开发直接用新版SB3跑旧代码适配层RaisimSbGymVecEnv.py里的调用逻辑就断了。解决用虚拟环境锁版本创建环境时强制指定对应版本号不要装最新版。venv里激活后再跑训练脚本能省掉一晚上的排查时间。如果已经装了新版先卸载再装指定版本不建议在代码里做兼容时间成本不划算。6. 验证与进阶从训练曲线到部署前值得做的事6.1 怎么判断步态真的学对了只看总奖励曲线不够它总会骗你。我习惯把Reward.hpp里的每个奖励项单独写进TensorBoard不只累加。步态质量的关键指标看三个机身高度曲线是否平稳、足端触地信号是否有稳定周期、动作平滑惩罚是否收敛到低值。这三个同时达标步态才算真学会。单看episode reward刷得高打开视频十有八九是歪姿势。6.2 进阶方向奖励改造与域随机化源码基础上的两个高性价比改进一是给bound加落地冲击惩罚二是把摩擦系数和连杆质量做成随机采样。前者改善步态美观度后者直接决定能不能上真机。项目里自带的多种步态gif已经是训练后的参考标准拿它们做基准对比自己改出来的版本比对着数字调参直观得多。我自己第一次跑通这个项目时也栽过“速度项权重压过姿态项”的跟头。后来每调一次奖励就强制把所有组件单独输出一次才把trot和bound都稳定复现出来。从那以后每次训练四足步态我都强制走一遍“先单组件log、再合起来看总奖励”的流程。希望帮到你。本文还有配套的精品资源点击获取