从零训练MicroDuck:MuJoCo+PPO实现双足机器人稳定行走全攻略
2026/9/18 8:10:15 网站建设 项目流程

去年有个朋友问我,说想用强化学习做机器人控制,但手头没有实体设备,也没接触过 MuJoCo 这类仿真器,不知道怎么入门。我当时就推荐了一个特别适合练手的项目:MicroDuck。简单说,它是一个基于 MuJoCo 物理引擎的鸭子形态双足仿真模型,你可以让智能体从零开始,通过强化学习学会“站立”和“行走”。这篇内容我把自己完整跑通的流程写下来,包括环境配置、训练命令、效果展示,以及我踩过的坑,给同样想入门的同学做参考。

整个项目的核心链路其实不复杂:准备仿真环境,搭好强化学习训练框架,设计合适的奖励函数,然后训练模型并在 MuJoCo viewer 里回放效果。但实际做下来,环境版本不兼容、奖励不收敛、viewer 回放黑屏这类问题特别劝退新手。所以下面我尽量把每一步为什么这么做、参数怎么调、报错怎么排查都讲清楚,希望能帮你少走点弯路。

1. 项目整体设计与思路拆解

1.1 MicroDuck 是什么,为什么适合练手

MicroDuck 是一个用 MuJoCo 建模的仿鸭形双足机器人环境,模型体积不大,状态空间和动作空间都属于“比 CartPole 难、比 Humanoid 简单”的中间档位。它不像 Gym 自带的 HalfCheetah 那样偏跑,也不像 Humanoid 那样高维到让人崩溃,关节数量适中,正好能让你感受到“连续控制任务”的核心难点:如何让一个多关节物体稳定地保持姿态并向前移动。

我选它还有一个现实原因:实体机器人成本高,而且调试策略的时候动辄摔坏硬件,仿真环境可以随便跑。MicroDuck 因为物理模型简单,单次仿真速度很快,普通笔记本的 CPU 就能跑,不需要 GPU 也能完成“从不会走到会走”的训练过程。对于第一次接触强化学习 + 机器人控制的人来说,这个反馈周期足够短,能很快建立正反馈。

提示:如果你的环境是纯 CPU 训练的,建议把仿真频率调低一点,或者用n_steps较小的配置,否则前期探索阶段会非常耗时。

1.2 为什么用强化学习而不是写死步态

一个很自然的疑问是:鸭子走路有固定模式,为什么不让工程师手写一套步态控制器?传统方法当然可以,但问题在于,真实环境中有大量难以建模的干扰,比如地面摩擦变化、电机响应延迟、自身传感器噪声。你手写一套规则,换个地形可能就站不稳了。

强化学习的思路是让智能体通过在环境中反复试错,自己发现一套鲁棒性更强的策略。它不需要你告诉机器人“膝关节在哪个角度发力”,只需要你告诉它“往前走有奖励,摔倒扣奖励”,剩下全靠策略网络自己去学。我用学骑自行车类比过很多次:你没法用语言告诉一个新手每一块肌肉的发力时机,但摔倒几次之后他就学会了。强化学习本质上就是这个过程。

MicroDuck 的环境里,我还会在动作上叠加一些高斯噪声,模拟真实执行器的不确定性。这个细节很重要,加了噪声之后学出来的策略,放到另一个没加噪声的评估环境里,稳定性会明显更好。

1.3 算法选型:PPO 为什么是第一选择

能做连续控制的算法不少,DDPG、TD3、SAC 都是常见选项。但我的建议是第一次跑项目优先选 PPO,原因很朴素:它对超参不那么敏感,训练过程相对稳定,而且社区资料最多,出问题好排查。

PPO 的核心思路是“在每次更新时,不要让新策略比旧策略偏离太远”。它通过一个 clipped surrogate objective 来限制策略更新的幅度,相当于每次只小心翼翼迈一小步。这种保守策略在仿真环境里很管用,不会因为某次更新太激进导致策略崩塌。

还有一个实际好处:stable-baselines3 里直接封装好了 PPO,不需要自己实现 actor-critic 网络。你只要关注环境接口、奖励函数和超参调整,特别适合用来理解“整个训练链路是什么样的”。

2. 环境配置全过程

2.1 软件栈总览与版本选择

先说一下我最终跑通的组合:

组件版本说明
操作系统Ubuntu 22.04也可以 Windows/WSL,但 Linux 最省心
Python3.10兼容性最稳
MuJoCo2.3.7免费开源的最后一个完整版本
gymnasium0.29.1新版 Gym API
stable-baselines32.3.0训练框架
tensorboard2.16.2训练日志可视化

这里最需要注意的是 MuJoCo 版本。2.3.7 之后 DeepMind 改成了mujoco包直接安装,不需要再单独下载mjpro150或者设置MUJOCO_KEY,省去很多麻烦。Gymnasium 则要选 0.29 左右的新版本,因为它和旧版gym的 API 差异不小,如果你拿 2020 年的教程来看,代码基本跑不通。

注意:不要贪新直接用 Python 3.12,当前不少强化学习依赖库的编译版本还不完全兼容,LightGBM、shimmy 这些间接依赖可能会出问题。Python 3.10 是最稳的选择。

2.2 创建 conda 环境并安装依赖

我习惯用 Miniconda 管理 Python 环境。命令行操作如下:

conda create -n microduck python=3.10 -y conda activate microduck

然后安装核心依赖:

pip install mujoco==2.3.7 pip install gymnasium==0.29.1 pip install stable-baselines3==2.3.0 pip install tensorboard pip install shimmy

如果你的网络比较慢,可以临时切换到国内镜像源,比如用清华 PyPI 镜像:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple mujoco==2.3.7

装完 MuJoCo 之后,建议随手写几行代码确认它能正常加载物理模型:

import mujoco import gymnasium as gym env = gym.make("MicroDuck-v0", render_mode="human") print("观察空间:", env.observation_space) print("动作空间:", env.action_space) env.close()

如果这步能正常打印出空间维度,说明环境本身已经接进来了。我见过很多同学卡在这一步,报错基本都是libGL.so.1缺失或者KeyError: 'MicroDuck-v0',这俩问题后面第五节统一说。

2.3 环境注册与接口梳理

MicroDuck 这个环境大概率不是 Gym 官方自带的,你需要确认它的注册方式。我用的时候是把环境代码放在项目根目录,然后在脚本里显式注册:

from gymnasium.envs.registration import register register( id="MicroDuck-v0", entry_point="microduck_env:MicroDuckEnv", max_episode_steps=1000, )

这里特别提醒一下:max_episode_steps会直接影响训练效果。默认 1000 步太长,鸭子摔倒后会在原地挣扎很久,浪费探索机会;太短又来不及学会平衡。我实测下来 500 到 1000 是一个合理区间,建议从 500 开始。

环境接入之后,你还需要看它的状态和动作定义。MicroDuck 的状态通常包含身体躯干的角度、角速度、各个关节的角度和速度,动作则是各个关节的目标力矩或目标位置。这部分你需要打开环境源码确认,不要凭感觉写奖励函数,否则后面训练一定会后悔。

3. 训练命令与核心参数

3.1 训练脚本怎么写

我的训练脚本结构很简单,主要分三块:创建环境、定义模型、开始训练。下面这个版本是我实际跑通过的,可以直接抄:

import gymnasium as gym from stable_baselines3 import PPO from stable_baselines3.common.vec_env import DummyVecEnv, VecNormalize from stable_baselines3.common.callbacks import CheckpointCallback # 注册环境 from gymnasium.envs.registration import register register( id="MicroDuck-v0", entry_point="microduck_env:MicroDuckEnv", max_episode_steps=500, ) def make_env(): env = gym.make("MicroDuck-v0", render_mode="rgb_array") return env env = DummyVecEnv([make_env]) env = VecNormalize(env, norm_obs=True, norm_reward=True, clip_obs=10.0) model = PPO( "MlpPolicy", env, n_steps=2048, batch_size=512, n_epochs=10, gamma=0.99, gae_lambda=0.95, clip_range=0.2, ent_coef=0.0, learning_rate=3e-4, verbose=1, tensorboard_log="./tensorboard_logs/", ) checkpoint_callback = CheckpointCallback( save_freq=50000, save_path="./models/", name_prefix="microduck_ppo" ) model.learn( total_timesteps=2_000_000, tb_log_name="microduck_run1", callback=checkpoint_callback, ) model.save("./models/microduck_ppo_final") env.save("./models/vecnormalize_final.pkl")

终端运行:

conda activate microduck python train_microduck.py

训练开始后,你会看到控制台每隔一段时间打印一条评估日志,包含ep_rew_meanep_len_mean这些指标。

3.2 关键超参解析

很多人照着网上的代码跑完,发现模型不学或者学得很慢,十有八九是超参没理解就乱调了。我把自己用下来的推荐参数列成表格,方便对照:

参数推荐值作用调参方向
n_steps2048每次更新收集的交互步数步态任务不需要太大,过大会拖慢更新频率
batch_size512每次梯度更新的样本量一般保持n_steps / 4左右
n_epochs10每个 batch 重复训练轮数调大容易过拟合,调小学不动
gamma0.99折扣因子任务需要长期规划时调高
gae_lambda0.95优势估计平滑程度0.9 更激进,0.98 更保守
clip_range0.2策略更新幅度限制不收敛时可暂时调到 0.1
learning_rate3e-4网络更新步长最需要关注的参数,过高会 NaN
ent_coef0.0熵奖励系数早期探索不够时调到 0.01

关于VecNormalize,我建议一定开。MicroDuck 的观察量里,关节角速度和躯干角速度的量纲差别很大,不归一化的话,神经网络在训练初期很容易被大数值特征带偏。它做的是在线估计均值方差,不需要像图像任务那样额外做标准化。

3.3 训练过程监控

训练途中我会同时开一个终端跑 TensorBoard:

tensorboard --logdir ./tensorboard_logs --port 6006

浏览器访问http://localhost:6006,主要看两张曲线:rollout/ep_rew_meanrollout/ep_len_mean

ep_rew_mean不会是一条光滑上升曲线,前期会有比较大的震荡,这很正常。真正需要注意的是它是否在持续抬高,以及是否出现突然跳水然后永远回不来。后者通常是奖励函数里某一项权重过大,导致策略往错误方向猛冲。ep_len_mean则能反映“存活时间”,如果这个值一直在涨,说明机器人在学会保持不摔倒,这是好的信号。

我在一次 200 万步的训练里,大约前 30 万步鸭子基本在原地摔倒,40 万步之后能看到它踉跄迈出第一步,70 万步左右能连续走两三米,最后 150 万步以后才稳定形成比较自然的步态。所以训练的时候千万别着急,前 20% 的曲线难看是正常的。

4. 效果展示与回放可视化

4.1 从日志判断训练效果

训练完不要急着做视频,先看一眼训练日志。如果ep_rew_mean到后段还在稳步上升,说明策略还没完全收敛,可以加大总步数继续训练。如果已经进入平台期,并出现小幅震荡,说明基本到顶了,再训练提升有限。

我一般还会计算一个“平均前进速度”指标。在评估脚本里记录每个 episode 结束后的位移除以时间步数,如果速度长期趋近于 0,说明鸭子只是站稳了但没真正学会走,这时候需要检查奖励函数里的前进项是否权重太低。

4.2 MuJoCo viewer 重新播放操作

很多人问“microduck mujoco viewer 重新播放”具体怎么操作。简单说有两种方式:实时渲染和轨迹回放。

实时渲染是最直观的,加载训练好的模型,直接在human渲染模式下跑一步看一步:

import gymnasium as gym from stable_baselines3 import PPO env = gym.make("MicroDuck-v0", render_mode="human") model = PPO.load("./models/microduck_ppo_final.zip") obs, _ = env.reset() for _ in range(2000): action, _ = model.predict(obs, deterministic=True) obs, reward, terminated, truncated, _ = env.step(action) if terminated or truncated: obs, _ = env.reset() env.close()

但如果只想反复观察一个特定片段,或者想排查某一步为什么摔倒,我更推荐先把动作序列保存下来,再用 viewer 重放。具体做法是:

import numpy as np traj = [] obs, _ = env.reset() for _ in range(2000): action, _ = model.predict(obs, deterministic=True) obs, reward, terminated, truncated, _ = env.step(action) traj.append(action) if terminated or truncated: break np.save("trajectory.npy", np.array(traj))

重放时不需要加载模型,只用动作序列驱动环境:

traj = np.load("trajectory.npy") env = gym.make("MicroDuck-v0", render_mode="human") obs, _ = env.reset() for action in traj: obs, _, terminated, truncated, _ = env.step(action) if terminated or truncated: obs, _ = env.reset() env.close()

这样每次重放结果一致,适合用来比较不同超参下策略行为的差异。

提示:如果 viewer 窗口黑屏,先确认有没有设置MUJOCO_GL环境变量。Linux 下可以试试export MUJOCO_GL=eglexport MUJOCO_GL=osmesa,前者需要显卡支持,后者是纯软件渲染,速度慢但不会黑屏。

4.3 把演示过程保存成 MP4

human模式是在屏幕上实时渲染,做不了录制,而大多数人的目标是“把训练效果做成视频发出去”。我的做法是改用rgb_array模式,配合 Gymnasium 自带的RecordVideowrapper:

import gymnasium as gym from stable_baselines3 import PPO env = gym.make("MicroDuck-v0", render_mode="rgb_array") env = gym.wrappers.RecordVideo( env, video_folder="./video", episode_trigger=lambda e: True, fps=30, ) model = PPO.load("./models/microduck_ppo_final.zip") obs, _ = env.reset() for i in range(800): action, _ = model.predict(obs, deterministic=True) obs, _, terminated, truncated, _ = env.step(action) if terminated or truncated: obs, _ = env.reset() env.close()

运行完会在./video目录下生成.mp4文件。如果没装 ffmpeg,需要先执行:

sudo apt install ffmpeg

fps我建议设 30,太低了视频看起来一顿一顿的,太高了文件体积偏大。另外注意episode_trigger这个参数,它控制哪些 episode 会被录下来,我这里设置为每次都录,方便一次性截取满意的片段。

4.4 效果达标的标准是什么

判断“训练成功”不能光看视频里鸭子会不会走。我习惯用三个客观指标来评估:

指标参考标准怎么算
平均步态周期无明显拖拽,左右腿交替从回放里肉眼判断
平均前进速度大于 0.5 m/s记录位移除以步数
连续行走步数至少 30 步不摔倒统计 episode 长度

前两个指标都要在“确定性策略”下评估,也就是model.predict时设置deterministic=True。如果带着探索噪声去评估,效果会比真实水平差不少。

5. 常见问题与避坑实录

5.1 环境配置期高频报错

我总结了一下,环境配置期的报错几乎集中在这几个地方:

  • 报错ImportError: libGL.so.1: cannot open shared object file这是 Ubuntu 系统缺少 OpenGL 库,执行sudo apt install libgl1 libglib2.0-0就能解决。Windows 下一般不出现这个问题,但 WSL 里出现的概率很高。

  • 报错KeyError: 'MicroDuck-v0'说明环境没有注册成功。检查你写的register()是否在被调用的代码之前执行,或者entry_point的类路径是否写错。

  • pip install mujoco之后import mujoco崩溃这类问题通常和 GCC 版本或者 NumPy 版本有关。可以用pip install --upgrade numpy试一下,再不行就换个 Python 3.10 的全新环境重装。

  • stable-baselines3 和 gymnasium API 对不上如果你用的 SB3 是 2.3.0,但环境类还是旧版gym.Env,会出现接口不兼容。统一装新版库,并确认你的环境类继承的是gymnasium.Env

5.2 训练不收敛或一直摔倒

这是我见过最多的问题。很多人第一反应是加训练步数,但很多时候是奖励设计有毛病。MicroDuck 这种行走任务,我的奖励函数拆成四个部分:

奖励项表达式权重
前进速度奖励v_x2.0
存活奖励1.00.5
姿态稳定性奖励-abs(roll) - abs(pitch)0.8
能量惩罚-sum(action^2)0.01

关键是速度奖励的权重必须够大,否则鸭子在原地站立也能拿到不错的回报,学出来只会站不会走。能量惩罚权重则要很小,太大会让智能体学会“躺平”,因为什么都不做反而惩罚最小。

如果训练过程出现 loss 曲线突然变 NaN,先查学习率。PPO 默认3e-4在网络结构简单、奖励量级正常的情况下是安全的,但如果你把奖励里某个系数调得特别大,梯度会爆炸。此时可以先不调学习率,把奖励缩小一个数量级,通常就能稳住。

我还踩过一个坑:环境重置后初始状态完全固定,导致策略过拟合到单一初始位置。解决办法是在 reset 函数里给初始角度加一个小范围随机噪声,比如躯干倾斜角加 ±0.05 弧度。这样学出来的策略泛化性好很多,丢到不同初始条件下也不容易立刻倒。

5.3 回放与调整的实用技巧

回放阶段常见的坑,一个是 MuJoCo 版本不一致导致轨迹重放对不上。比如你用 2.3.7 训练,又用新版本安装的 viewer 去播放,碰撞判定和关节限位可能有细微差异。建议训练和回放都保持同一个 MuJoCo 版本。

另一个问题是想调节录制视角。MuJoCo 的默认视角往往不是最佳展示角度,我的做法是在环境代码里设置mujoco.viewer的相机参数,或者直接用 env 渲染接口传camera_id。对于展示视频来说,侧面低角度视角通常比默认俯视视角好看得多。

最后一个特别实用的调整技巧:如果你发现步态特别僵硬或者频率不对,可以试试在奖励函数里加一个“周期性步频奖励”,奖励量不大,但能鼓励智能体产生节律行为。这个思路对很多双足/四足仿真任务都有效,我自己在 MicroDuck 上试过,加了之后步态明显自然很多。

写在最后

从环境配置到看到 MicroDuck 真正走出第一步,整个过程最磨人的不是写代码,而是等训练曲线爬坡时的那个阶段。我会盯着 TensorBoard 里那条 reward 曲线,前半小时可能纹丝不动,然后突然某个 checkpoint 之后它就开始往上走,那种感觉还是很奇妙的。如果你也有一个强化学习练手项目,我建议别急着上特别复杂的算法,先用 PPO 跑出一个能稳定行走出去的策略,再慢慢改奖励、加随机化、调结构。这个过程里积累的“孬”操作经验,往往比算法本身更有价值。最后再分享一个小技巧,训练中断之后别从零开始,用checkpoint_callback保存的模型接续训练,能省掉大量重复计算时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询