
去年有个朋友问我说想用强化学习做机器人控制但手头没有实体设备也没接触过 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 APIstable-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 python3.10 -y conda activate microduck然后安装核心依赖pip install mujoco2.3.7 pip install gymnasium0.29.1 pip install stable-baselines32.3.0 pip install tensorboard pip install shimmy如果你的网络比较慢可以临时切换到国内镜像源比如用清华 PyPI 镜像pip install -i https://pypi.tuna.tsinghua.edu.cn/simple mujoco2.3.7装完 MuJoCo 之后建议随手写几行代码确认它能正常加载物理模型import mujoco import gymnasium as gym env gym.make(MicroDuck-v0, render_modehuman) 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( idMicroDuck-v0, entry_pointmicroduck_env:MicroDuckEnv, max_episode_steps1000, )这里特别提醒一下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( idMicroDuck-v0, entry_pointmicroduck_env:MicroDuckEnv, max_episode_steps500, ) def make_env(): env gym.make(MicroDuck-v0, render_modergb_array) return env env DummyVecEnv([make_env]) env VecNormalize(env, norm_obsTrue, norm_rewardTrue, clip_obs10.0) model PPO( MlpPolicy, env, n_steps2048, batch_size512, n_epochs10, gamma0.99, gae_lambda0.95, clip_range0.2, ent_coef0.0, learning_rate3e-4, verbose1, tensorboard_log./tensorboard_logs/, ) checkpoint_callback CheckpointCallback( save_freq50000, save_path./models/, name_prefixmicroduck_ppo ) model.learn( total_timesteps2_000_000, tb_log_namemicroduck_run1, callbackcheckpoint_callback, ) model.save(./models/microduck_ppo_final) env.save(./models/vecnormalize_final.pkl)终端运行conda activate microduck python train_microduck.py训练开始后你会看到控制台每隔一段时间打印一条评估日志包含ep_rew_mean、ep_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.1learning_rate3e-4网络更新步长最需要关注的参数过高会 NaNent_coef0.0熵奖励系数早期探索不够时调到 0.01关于VecNormalize我建议一定开。MicroDuck 的观察量里关节角速度和躯干角速度的量纲差别很大不归一化的话神经网络在训练初期很容易被大数值特征带偏。它做的是在线估计均值方差不需要像图像任务那样额外做标准化。3.3 训练过程监控训练途中我会同时开一个终端跑 TensorBoardtensorboard --logdir ./tensorboard_logs --port 6006浏览器访问http://localhost:6006主要看两张曲线rollout/ep_rew_mean和rollout/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_modehuman) model PPO.load(./models/microduck_ppo_final.zip) obs, _ env.reset() for _ in range(2000): action, _ model.predict(obs, deterministicTrue) 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, deterministicTrue) 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_modehuman) 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_GLegl或export MUJOCO_GLosmesa前者需要显卡支持后者是纯软件渲染速度慢但不会黑屏。4.3 把演示过程保存成 MP4human模式是在屏幕上实时渲染做不了录制而大多数人的目标是“把训练效果做成视频发出去”。我的做法是改用rgb_array模式配合 Gymnasium 自带的RecordVideowrapperimport gymnasium as gym from stable_baselines3 import PPO env gym.make(MicroDuck-v0, render_modergb_array) env gym.wrappers.RecordVideo( env, video_folder./video, episode_triggerlambda e: True, fps30, ) model PPO.load(./models/microduck_ppo_final.zip) obs, _ env.reset() for i in range(800): action, _ model.predict(obs, deterministicTrue) obs, _, terminated, truncated, _ env.step(action) if terminated or truncated: obs, _ env.reset() env.close()运行完会在./video目录下生成.mp4文件。如果没装 ffmpeg需要先执行sudo apt install ffmpegfps我建议设 30太低了视频看起来一顿一顿的太高了文件体积偏大。另外注意episode_trigger这个参数它控制哪些 episode 会被录下来我这里设置为每次都录方便一次性截取满意的片段。4.4 效果达标的标准是什么判断“训练成功”不能光看视频里鸭子会不会走。我习惯用三个客观指标来评估指标参考标准怎么算平均步态周期无明显拖拽左右腿交替从回放里肉眼判断平均前进速度大于 0.5 m/s记录位移除以步数连续行走步数至少 30 步不摔倒统计 episode 长度前两个指标都要在“确定性策略”下评估也就是model.predict时设置deterministicTrue。如果带着探索噪声去评估效果会比真实水平差不少。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保存的模型接续训练能省掉大量重复计算时间。