资讯动态

分层强化学习实现四足机器人多步态切换:从端到端困境到真机部署

发布时间:2026/10/9 12:51:27 来源:尧图企业网站定制
简介这份资源面向机器人运动控制方向的研究者与开发者聚焦用分层强化学习训练四足机器人掌握多种步态解决复杂运动策略学习中状态与动作空间过大、训练效率低的问题。压缩包共50个文件约3.77MB以24个Python脚本为核心配合9个hpp与3个cpp头文件与源文件搭建仿真环境另有4个yaml参数配置、4个gif步态演示及md说明文档覆盖算法、环境与配置各环节。项目将决策拆分为高层方向速度规划与低层腿足协调控制借助PPO与stable_baselines3实现训练并给出trot、bound、pace等多种步态的可视化结果便于对照复现。目前已有230人学习下载适合希望深入马尔可夫决策过程、策略梯度与分层框架实践的中高级读者可据此快速搭建实验、调参优化并拓展至爬楼梯等复杂动作研究。1. 四足机器人步态学习为什么分层强化学习是绕不开的那条路四足机器人步态学习这件事单靠一个端到端策略网络硬训能走但走不好。我最早做四足控制的时候用 PPO 直接输出十二个关节的目标角度训练了三天机器人在平地上能小碎步挪动一上斜坡就翻切换速度更是灾难——慢走和快跑之间没有过渡策略在两种步态之间反复横跳关节抖得像筛糠。后来换成分层强化学习上层策略负责选步态类型和切换时机下层策略负责在当前步态下输出关节动作训练稳定性和步态多样性同时上来了。这个项目标题讲的就是这件事用分层强化学习让四足机器人学会多种步态而不是只会一种。适合谁看做足式机器人控制的、想从单一步态扩展到多步态切换的、以及被端到端训练折磨过的从业者。下面我把这套方案的选型理由、实现路径、参数设置和踩坑记录拆开讲。2. 分层强化学习做步态学习上层选步态下层控关节2.1 为什么端到端策略学不会多种步态端到端策略的问题不在于网络容量不够而在于奖励信号冲突。四足机器人走平地和爬斜坡最优关节轨迹差异很大如果用一个策略同时拟合这两种模式梯度更新会互相拉扯。更麻烦的是步态切换——从 trot 切到 bound中间需要一个过渡过程端到端策略没有显式的步态表示切换点完全靠隐状态碰运气。我试过在观测里加一个步态标签作为输入让策略自己学切换结果策略学会了偷懒不管什么地形都输出同一个标签因为切换带来的奖励波动太大策略宁愿保守。分层强化学习的思路是把这个问题拆开。上层策略的观测是机器人本体状态加地形特征动作空间是离散的步态选择下层策略的观测是本体状态加当前步态编码动作空间是连续的关节目标角度。两层策略分开训练上层学切换逻辑下层学步态执行。这样做的好处是每层策略的优化目标更清晰下层只需要在给定步态下把动作做稳上层只需要判断什么时候该换步态。2.2 分层策略的网络结构与观测设计上层策略我一般用一个小型 MLP三层每层 128 个单元输出离散动作的 logits。观测包括机身姿态四元数、机身线速度、角速度、四个足端的接触状态、地形坡度估计值。地形坡度估计值可以用机身姿态和足端高度差算出来不需要额外的感知模块。下层策略用两层 MLP每层 256 个单元输出十二维关节目标角度再经过一个 PD 控制器转成力矩。步态编码我用的是 one-hot 向量维度等于步态数量。比如 trot、walk、bound、pace 四种步态编码就是四维 one-hot。下层策略的输入是本体观测拼接步态编码这样同一个下层网络可以处理所有步态不需要为每种步态单独训一个网络。如果步态数量多可以用 embedding 层把 one-hot 压成低维向量减少输入维度。import torch import torch.nn as nn class UpperPolicy(nn.Module): 上层策略输出步态选择的离散动作分布 def __init__(self, obs_dim, num_gaits): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, num_gaits) # 输出每个步态的 logit ) def forward(self, obs): logits self.net(obs) return torch.distributions.Categorical(logitslogits) class LowerPolicy(nn.Module): 下层策略给定步态编码输出关节目标角度 def __init__(self, obs_dim, gait_embed_dim, action_dim): super().__init__() self.gait_embed nn.Linear(gait_embed_dim, 32) # 步态编码嵌入 self.net nn.Sequential( nn.Linear(obs_dim 32, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, action_dim) # 十二维关节目标角度 ) def forward(self, obs, gait_onehot): gait_feat torch.relu(self.gait_embed(gait_onehot)) x torch.cat([obs, gait_feat], dim-1) return self.net(x)上层策略的输出是离散分布训练时用 REINFORCE 或者 PPO 的离散版本。下层策略输出连续动作用 PPO 或者 SAC 都行。我一般用 PPO因为实现简单超参不敏感。步态编码的嵌入层是可选的如果步态数量少于六种直接用 one-hot 拼接就够了加嵌入层反而增加调参负担。2.3 训练流程两阶段训练与联合微调训练分两个阶段。第一阶段单独训练下层策略固定步态编码让下层学会在每种步态下稳定行走。具体做法是每次 rollout 随机选一个步态编码保持整个 episode 不变下层策略只在这个步态下优化。这样训练出来的下层策略在每种步态下都有基本的行走能力。第二阶段训练上层策略固定下层策略的参数上层策略根据地形和本体状态选择步态下层执行。上层策略的奖励包括前进速度、姿态稳定、步态切换频率惩罚。两阶段训练完之后可以做一轮联合微调上下层一起更新但学习率要调小否则下层策略会遗忘之前学到的步态。联合微调的时候上层策略的探索噪声要降低避免频繁切换步态导致下层策略来不及适应。# 第一阶段训练下层策略 for gait_id in range(num_gaits): gait_onehot torch.zeros(num_gaits) gait_onehot[gait_id] 1.0 for episode in range(num_episodes_per_gait): obs env.reset() for step in range(max_steps): action lower_policy(obs, gait_onehot) next_obs, reward, done, info env.step(action) # 只优化下层策略步态编码保持不变 lower_ppo.update(obs, action, reward, next_obs, done) obs next_obs if done: break # 第二阶段训练上层策略 for episode in range(num_upper_episodes): obs env.reset() gait_onehot upper_policy(obs).sample() # 上层选择步态 for step in range(max_steps): action lower_policy(obs, gait_onehot) # 下层执行 next_obs, reward, done, info env.step(action) # 上层策略根据奖励更新下层策略冻结 upper_ppo.update(obs, gait_onehot, reward, next_obs, done) obs next_obs if done: break第一阶段的 episode 数量取决于步态复杂度trot 和 walk 一般 500 到 1000 个 episode 就能走稳bound 和 pace 需要 1500 个 episode 左右。第二阶段上层策略的训练量大概是下层总训练量的三分之一因为上层只需要学切换逻辑不需要学关节控制。联合微调阶段学习率降到初始值的十分之一跑 200 到 300 个 episode 就够了。3. 步态切换的奖励设计与地形观测处理3.1 奖励函数拆解前进、稳定、切换代价奖励函数是分层强化学习里最容易翻车的地方。我见过有人把前进速度奖励设得很大结果机器人学会了用前腿刨地往前蹭姿态完全不管。也有人把姿态惩罚设得太重机器人站在原地不敢动因为动起来就有姿态误差。我的经验是分三块前进奖励、稳定奖励、切换代价。前进奖励用机身线速度在前进方向的分量乘以一个系数。系数一般设 1.0 到 1.5太小机器人走得慢太大机器人会冲刺导致失稳。稳定奖励包括机身高度维持在目标高度附近、机身俯仰角和滚转角接近零、足端接触力不要突变。机身高度奖励用指数函数偏差越大奖励越小。姿态奖励用余弦相似度机身朝向和目标朝向的夹角越小奖励越大。切换代价是上层策略每切换一次步态给一个小的负奖励防止策略频繁切换。切换代价的绝对值不能太大否则上层策略会一直不切换退化成单一步态。def compute_reward(obs, action, gait_changed): # 前进奖励线速度在前进方向的分量 forward_vel obs[linear_vel][0] # 假设 x 轴是前进方向 reward_forward 1.2 * forward_vel # 稳定奖励机身高度和姿态 height_error abs(obs[height] - target_height) reward_height 0.5 * np.exp(-5.0 * height_error) roll, pitch obs[roll_pitch] reward_attitude 0.3 * (np.cos(roll) np.cos(pitch)) / 2.0 # 切换代价每次切换给 -0.1 reward_switch -0.1 if gait_changed else 0.0 total_reward reward_forward reward_height reward_attitude reward_switch return total_reward前进奖励的系数需要根据机器人的最大速度调。如果机器人最大速度是 1.5 米每秒系数设 1.2 意味着全速前进时前进奖励是 1.8加上稳定奖励大概 0.8总奖励 2.6 左右。切换代价 -0.1 相对于总奖励来说很小但累积起来能抑制频繁切换。如果发现上层策略还是频繁切换可以把切换代价调到 -0.3或者加一个切换冷却时间切换后若干步内不能再切。3.2 地形观测用本体感受估计坡度与粗糙度分层强化学习做地形适应不一定需要外部感知。本体感受就够用。坡度估计可以用四个足端的高度差算前足和后足的高度差除以轴距得到俯仰方向的坡度左足和右足的高度差除以轮距得到滚转方向的坡度。粗糙度可以用足端接触力的方差来估计方差大说明地面不平。地形观测的维度不用太高坡度两个维度、粗糙度一个维度加上机身姿态和速度总共十几个维度就够了。观测维度太高会增加上层策略的训练难度而且容易过拟合。我试过把地形观测加到三十维结果上层策略在训练集上表现很好换一个地形就翻车因为策略记住了特定地形的观测模式没有学到泛化的切换逻辑。def estimate_terrain(obs): # 足端高度四个足端的 z 坐标 foot_heights obs[foot_heights] # [fl, fr, rl, rr] wheelbase 0.4 # 轴距单位米 track_width 0.3 # 轮距单位米 # 俯仰方向坡度前足平均高度减后足平均高度除以轴距 front_height (foot_heights[0] foot_heights[1]) / 2.0 rear_height (foot_heights[2] foot_heights[3]) / 2.0 pitch_slope (front_height - rear_height) / wheelbase # 滚转方向坡度左足平均高度减右足平均高度除以轮距 left_height (foot_heights[0] foot_heights[2]) / 2.0 right_height (foot_heights[1] foot_heights[3]) / 2.0 roll_slope (left_height - right_height) / track_width # 粗糙度足端接触力的方差 contact_forces obs[contact_forces] roughness np.var(contact_forces) return np.array([pitch_slope, roll_slope, roughness])坡度估计的精度取决于足端高度测量的噪声。真实机器人上足端高度用关节编码器算会有累积误差。我一般会在估计值上做一个低通滤波截止频率设 5 赫兹左右滤掉高频噪声。粗糙度用接触力方差接触力用足端力传感器测没有力传感器的话可以用关节电流估计精度差一些但能用。3.3 步态编码与切换时机上层策略的观测窗口上层策略的观测窗口很重要。如果只看当前时刻的观测上层策略无法判断地形变化趋势切换时机会滞后。我一般给上层策略一个观测窗口包含过去 10 到 20 步的观测用一维卷积或者 LSTM 提取时序特征。一维卷积比 LSTM 训练快而且不容易梯度爆炸。卷积核大小设 3 到 5层数两层就够了。切换时机的判断逻辑是当地形坡度超过阈值或者粗糙度超过阈值上层策略切换到更适合的步态。比如平地上用 trot上斜坡切到 walk下斜坡切到 bound。阈值不需要手动设让上层策略自己学。但可以在奖励函数里加一个引导项如果地形坡度和当前步态不匹配给一个小的负奖励。引导项的系数要小否则会限制上层策略的探索。class UpperPolicyWithHistory(nn.Module): 带观测窗口的上层策略 def __init__(self, obs_dim, num_gaits, window_size15): super().__init__() self.conv nn.Sequential( nn.Conv1d(obs_dim, 64, kernel_size3, padding1), nn.ReLU(), nn.Conv1d(64, 64, kernel_size3, padding1), nn.ReLU(), ) self.fc nn.Sequential( nn.Linear(64 * window_size, 128), nn.ReLU(), nn.Linear(128, num_gaits) ) def forward(self, obs_history): # obs_history: [batch, window_size, obs_dim] x obs_history.transpose(1, 2) # 转成 [batch, obs_dim, window_size] x self.conv(x) x x.flatten(start_dim1) logits self.fc(x) return torch.distributions.Categorical(logitslogits)观测窗口的大小需要根据控制频率调。如果控制频率是 50 赫兹窗口 15 步对应 0.3 秒足够判断地形变化趋势。如果控制频率是 100 赫兹窗口 15 步只有 0.15 秒可能不够需要加到 30 步。窗口太大也会有问题卷积的计算量增加而且早期观测对当前决策的影响被稀释。我一般控制在 0.2 到 0.5 秒的时间窗口内。4. 避坑与排查分层强化学习步态学习的五个血泪教训4.1 下层策略遗忘联合微调时学习率没降现象两阶段训练完之后联合微调跑了 50 个 episode机器人突然不会走 trot 了只会用 bound 蹦。原因联合微调时下层策略的学习率没有降上层策略切换步态的频率又高下层策略在多个步态之间反复更新把之前学到的 trot 策略覆盖了。解决联合微调时下层策略的学习率降到初始值的十分之一上层策略的学习率保持不变。如果还是遗忘可以冻结下层策略的前几层只微调输出层。4.2 上层策略摆烂切换代价设得太大现象上层策略训练完之后不管什么地形都输出同一个步态从来不切换。原因切换代价设得太大比如 -1.0上层策略发现不切换的累积奖励更高因为切换带来的前进奖励提升抵不过切换代价。解决切换代价从 -0.1 开始调观察上层策略的切换频率。如果切换频率太低减小切换代价如果切换频率太高增大切换代价。我一般把切换代价设在 -0.05 到 -0.2 之间。4.3 地形观测噪声坡度估计值抖动导致误切换现象机器人在平地上走上层策略频繁切换步态关节动作抖动。原因坡度估计值有噪声足端高度测量的误差导致坡度估计在零附近抖动上层策略误以为地形在变化。解决在坡度估计值上加低通滤波截止频率 5 赫兹。如果还是抖动可以在上层策略的观测里加一个坡度变化率的阈值只有坡度变化超过阈值才触发切换。4.4 步态编码维度不匹配下层策略输入维度对不上现象训练下层策略的时候正常加载上层策略做联合推理时报维度错误。原因下层策略的步态编码维度是训练时定的上层策略输出的步态数量如果和下层不一致拼接的时候维度对不上。解决把步态数量和步态编码维度写成配置文件上下层策略都从配置文件读。如果步态数量变了下层策略的嵌入层需要重新训练不能直接复用。4.5 仿真到真机迁移关节摩擦和延迟没建模现象仿真里步态切换很流畅真机上切换时关节抖动甚至触发过流保护。原因仿真里的关节摩擦模型太理想真机上的摩擦非线性而且控制指令有延迟。解决在仿真里加关节摩擦模型用库仑摩擦加粘性摩擦的组合。控制延迟可以在仿真里加一个延迟缓冲把动作指令延迟几毫秒再执行。迁移到真机时先用手动模式测试每种步态的关节轨迹确认没有过流风险再跑上层策略。5. 从仿真到真机步态切换的验证方法与调参技巧真机验证分层强化学习步态切换我一般分三步走。第一步固定步态编码手动切换验证下层策略在每种步态下的关节轨迹是否安全。具体做法是把机器人吊起来足端悬空手动给下层策略输入步态编码观察关节动作是否平滑有没有突变。如果关节动作有突变检查下层策略的输出是否经过了平滑滤波我一般会在输出层加一个一阶低通滤波截止频率 20 赫兹。第二步让上层策略在平地上跑观察切换频率和切换时的姿态变化。平地上上层策略应该很少切换如果频繁切换说明地形观测有噪声或者切换代价太小。切换时的姿态变化用机身俯仰角和滚转角的峰值来衡量峰值超过 15 度说明切换太剧烈需要在下层策略的奖励里加一个切换时的姿态惩罚。第三步在斜坡和粗糙地面上跑验证上层策略的切换逻辑是否符合预期。我一般会记录切换时的地形坡度和粗糙度画成散点图看切换点是否集中在地形特征变化的区域。如果切换点分散说明上层策略没有学到有效的地形特征需要增加地形观测的维度或者调整观测窗口的大小。调参方面下层策略的 PPO 裁剪系数我一般设 0.2学习率 3e-4batch size 4096。上层策略的 PPO 裁剪系数设 0.1学习率 1e-4batch size 2048。上层策略的学习率比下层小因为上层策略的观测维度低容易过拟合。折扣因子下层设 0.99上层设 0.95上层更关注近期奖励因为步态切换的收益是短期的。参数下层策略上层策略学习率3e-41e-4PPO 裁剪系数0.20.1batch size40962048折扣因子0.990.95网络层数22每层单元数256128验证步态切换是否成功我一般看三个指标切换成功率、切换时的姿态误差峰值、切换后的速度恢复时间。切换成功率是上层策略发出切换指令后下层策略在 0.5 秒内达到目标步态的速度范围。姿态误差峰值是切换过程中机身俯仰角和滚转角的最大偏差。速度恢复时间是切换后速度回到目标速度正负 10% 以内的时间。这三个指标在仿真里调好之后真机上一般会有 10% 到 20% 的衰减属于正常范围。真机调参有个玄学同样的参数早上跑和下午跑效果不一样。后来发现是电池电压的影响电压低的时候关节力矩输出不足步态切换会变慢。解决办法是在观测里加电池电压让下层策略自己适应电压变化。这个改动很小但效果很明显电压从满电到低电步态切换的成功率从 60% 提到了 85%。最后说一个我自己的习惯每次改奖励函数或者网络结构先跑 100 个 episode 看趋势不要等跑完几千个 episode 再看。如果 100 个 episode 奖励曲线没有上升趋势说明改动有问题赶紧回滚。这个习惯帮我省了很多时间也避免了很多次翻车。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑