Gymnasium MuJoCo 连续控制实战:从跑通到调优
Gymnasium MuJoCo 连续控制实战从跑通到调优【免费下载链接】GymnasiumA standard API for single-agent reinforcement learning environments, with popular reference environments and related utilities (formerly Gym)项目地址: https://gitcode.com/GitHub_Trending/gy/Gymnasium随机策略接上 HalfCheetah-v5 跑 1000 步小猫趴在地板上原地摩擦奖励从 -5 左右一路漂到 -3。你以为算法不行其实你连这 17 维观测里哪一维是腿都没搞清楚。这种翻车在 Gymnasium 基于 MuJoCo 的连续控制强化学习环境里极其常见。本文就从这个现场开始。读完你可以做三件事拆解把 HalfCheetah-v5 的 17 维观测向量和奖励函数逐段拆明白知道每一维的物理含义跑通用最短路径得到一条make → reset → step → close的可运行流水线并配好三种渲染场景决策在归一化、向量化、奖励诊断、收敛判断这四个训练岔路口上做出有依据的选择拆一台机器人MuJoCo 环境的观测向量与奖励函数先把概念说透。MuJoCo 里每个机器人都有两组状态量广义坐标qpos每个部件此刻在哪里。类比一群人站在操场上qpos 就是每个人的坐标广义速度qvel每个部件正朝哪个方向、以多快的速度挪。类比每个人正以多快的速度往哪走机器人是刚体串起来的链条关节角度加上躯干位置就是 qpos角速度和线速度就是 qvel。HalfCheetah-v5 的 17 维观测向量观测空间Observation Space是Box(-inf, inf, (17,), float64)。这 17 维长这样维度数量含义01前梢高度rootz11前梢姿态角rooty2-76六条腿关节角后腿 3 个 前腿 3 个8-92前梢的 x、z 方向速度10-167前梢姿态角速度 六个关节角速度前 8 维是 qpos后 9 维是 qvel中间那条分界线就是位置和速度的交界。反直觉的点水平位置rootx被故意删掉了。这不是 bug而是归纳偏置——不告诉智能体你在哪逼它学出与位置无关的跑法。坐标没丢info[x_position]里随时可查。奖励函数每项都有设计意图源码在 gymnasium/envs/mujoco/half_cheetah_v5.py奖励只有一行核心逻辑reward forward_reward - ctrl_costforward_reward 前进速度。跑得快给分倒着跑扣分ctrl_cost 0.1 × 动作平方和。控制量越大扣分越狠为什么惩罚控制量因为不惩罚的话最优解是六条腿以最大力矩疯狂抖动——机器人确实动了但物理上不可行搬到真实硬件上直接炸关节。惩罚项把抖动从解空间里排除掉。去掉它试试总奖励会涨画面却是抽搐。再看一个对照。Humanoid-v5 的奖励是healthy_reward forward_reward - ctrl_cost - contact_cost站稳healthy躯干高度在 1.0~2.0 米之间每一步给 5 分比跑得快更优先。这决定了训练早期智能体先学会站再学会走。观测也从 17 维膨胀到 348 维——除了 qpos/qvel还拼了惯量cinert、质心速度cvel、执行器力qfrc_actuator、接触外力cfrc_ext四段。任务复杂了喂给智能体的信息也得跟着加厚。动作到物理执行器的映射动作空间Action Space是Box(-1, 1, (6,), float32)对应六个关节电机。一句话讲清映射你的[-1, 1]是挡位XML 里的gear是传动比实际输出力矩 动作值 × gear。HalfCheetah 的六个 gear 分别是 120、90、60、120、60、30所以动作给 1.0 时大腿电机最多输出 120 N·m脚掌电机只有 30 N·m。其余环境Ant、Swimmer、Walker2d、Hopper套路完全一致区别只在自由度数量和奖励权重源码都在 gymnasium/envs/mujoco/ 下。从 import 到出画面最小可运行工作流整条流水线五步走完make → reset → 采样 → step 循环 → close。第一步创建环境。import gymnasium as gym env gym.make(HalfCheetah-v5, render_modeNone) # 不开窗口采样最快 print(env.observation_space, env.action_space)你会看到Box(-inf, inf, (17,), float64)和Box(-1, 1, (6,), float32)与上面拆解的一致。第二步重置环境。obs, info env.reset(seed42) # 固定种子否则两次 run 结果不可比 print(obs.shape, info[x_position])你会看到(17,)和接近 0 的初始 x 坐标。第三步采样动作。action env.action_space.sample() # 随机 6 维向量每维落在 [-1, 1]你会看到 shape 为(6,)的 float32 数组。第四步跑完一个回合。total, terminated, truncated 0.0, False, False while not (terminated or truncated): obs, reward, terminated, truncated, info env.step(action) total reward action env.action_space.sample() print(f单回合总奖励: {total:.2f})你会看到回合在 1000 步处被截断TimeLimit包装器干的HalfCheetah 永不主动terminated随机策略总奖励通常在 -200 上下。第五步关闭环境。env.close() # 释放模拟资源有窗口时同时关窗三种渲染场景的最优配置场景render_mode要点调试human开实时窗口采样明显变慢批量训练None无窗口速度最高录屏rgb_array出帧缓冲交给 wrapper 存盘# 调试开窗看机器人怎么跑 env gym.make(HalfCheetah-v5, render_modehuman, width800, height600) # 批量训练什么都不加就是最快 env gym.make(HalfCheetah-v5) # 录屏rgb_array RecordVideo每回合存一个 mp4 from gymnasium.wrappers import RecordVideo env gym.make(HalfCheetah-v5, render_modergb_array) env RecordVideo(env, video_folder./videos)服务器无图形界面时渲染后端用环境变量指定只记这一行export MUJOCO_GLegl # 无头 GPU 渲染服务器首选纯 CPU 机器可换 osmesa训练路上的四个岔路口岔路口一观测要不要归一化现象前几百步 loss 巨大甚至 NaN或者训练稳定但前 10 万步学东西极慢。怎么判断打印观测的 min/max。HalfCheetah 各维基本在 ±2 以内网络天然吃得动不归一化也没问题。但 Humanoid 的cfrc_ext接触外力动辄上百比姿态角大了两三个数量级——这种就要归一化。怎么选from gymnasium.wrappers import NormalizeObservation env NormalizeObservation(env, min_obs1.0, max_obs1e4) # 滑动窗口估计均值方差选错的代价该归一化却没做critic 输出会被大尺度维度拖走价值函数训不稳不该做却做了多算几次窗口统计代价很小。判断依据是维度间量级差不是大家都做。岔路口二单环境还是 SyncVectorEnv现象PPO 单环境采 50 万步8 核 CPU 上跑了大半天瓶颈在采样不在更新。怎么判断先测单环境 steps/sec。MuJoCo 环境单核跑 HalfCheetah 大约 1000 步/秒量级而网络更新通常只需 100 步/秒的吞吐——采样是瓶颈并行才有意义。怎么选from gymnasium.vector import SyncVectorEnv env SyncVectorEnv([lambda: gym.make(HalfCheetah-v5)] * 8) # 8 个并行 obs, infos env.reset(seed0) # obs 形状变为 (8, 17)8 核 CPU 实测约 6× 加速100 个环境时接近线性。容易忽略的坑SyncVectorEnv是线程并行不是进程。速度上得来全靠 MuJoCo 物理仿真时释放 GIL——换成纯 Python 开销的环境线程并行几乎没收益。选错的代价给 CartPole 这种廉价环境开 8 线程调度开销比省下的时间还多。岔路口三奖励信号太弱、太噪、尺度不对现象奖励曲线一条直线或方差大得无法判断趋势。怎么判断按这个顺序排查信号被淹没把info[reward_forward]和info[reward_ctrl]分开打印。forward 恒等于 0 说明机器人根本没动问题在动作通路不在奖励尺度不对单步总奖励在 1e-3 或 1e3 量级学习率就得跟着陪绑。用gym.make参数直接调权重组比如forward_reward_weight2.0符号打架ctrl_cost_weight开太大时原地不动比跑更划算机器人会学会躺平。这是奖励设计问题不是算法问题怎么选优先调环境自带权重而不是魔改网络实在尺度混乱时再上NormalizeRewardwrapper。选错的代价躺平型机器人是最隐蔽的翻车——总奖励不低曲线也收敛了只是机器人没在跑。岔路口四什么时候算收敛别只说看曲线。给你三个可观测的标准评估回报的滑动均值走平每 5 万步跑 10 个评估回合取均值连续 10 万步斜率接近 0多种子一致3 个以上随机种子各自评估回报差在 10% 以内确定性策略稳定关掉探索噪声mean代替sample后回报不再依赖具体轨迹一条典型的连续控制训练曲线长这样前期贴着 0 爬中段陡升后段进入平台期。下面是 PPO 的最小骨架关键位置都留了替换点——骨架是通用容器算法部分换成 A2C、SAC 都行import torch, gymnasium as gym from torch.distributions import Normal class ActorCritic(torch.nn.Module): def __init__(self, obs_dim, act_dim): super().__init__() self.actor torch.nn.Sequential( torch.nn.Linear(obs_dim, 128), torch.nn.Tanh(), torch.nn.Linear(128, 128), torch.nn.Tanh(), torch.nn.Linear(128, act_dim)) self.critic torch.nn.Sequential( torch.nn.Linear(obs_dim, 128), torch.nn.Tanh(), torch.nn.Linear(128, 128), torch.nn.Tanh(), torch.nn.Linear(128, 1)) self.log_std torch.nn.Parameter(torch.zeros(act_dim)) def act(self, obs): mean, std self.actor(obs), self.log_std.exp() a Normal(mean, std).sample() return a, Normal(mean, std).log_prob(a).sum(-1) def train(env_id, total400_000): env gym.vector.SyncVectorEnv([lambda: gym.make(env_id)] * 4) m ActorCritic(17, 6) opt torch.optim.Adam(m.parameters(), lr3e-4) obs torch.tensor(env.reset()[0], dtypetorch.float32) for t in range(0, total, 2048): for _ in range(2048): # ← 这里换成你算法的 rollout 长度 a, _ m.act(obs) obs, r, term, trunc, _ env.step(a.numpy()) # ← 这里换成你熟悉的算法PPO 裁剪目标 / A2C / SAC 更新 opt.step() env.close()把更新逻辑补上后这就是一个能出曲线的最小训练器。翻车排查手册三个高频现场现场一奖励纹丝不动你遇到了什么训了 20 万步奖励曲线水平如湖连噪声都懒得给你。按从常见到罕见排查动作根本没传到机器人。打印action的 shape 和幅值。高频错误是把 Humanoid 的动作当 ±1 用——它的合法范围是 ±0.4超界部分会被静默裁剪你以为给了满力矩实际只有一半机器人冻住了。查info[x_position]是否随步数变化。不变则查初始姿态和观测是否合理策略坍缩成常数。观测没归一化时第一步梯度就能把网络参数打飞之后永远输出同一个动作第一反应动作每 100 步打印一次obs.min(), obs.max(), reward三行数字能排除一半问题。现场二第 N 步数值炸了你遇到了什么训练到某个步数loss 或观测里蹦出 inf/NaN之后再也回不来。原因链按此顺序核对梯度先炸打印参数范数若某次更新后暴增先加梯度裁剪torch.nn.utils.clip_grad_norm_(m.parameters(), 0.5)观测尺度失控某个维度突然跳到异常值——常见于接触力尖峰cfrc_ext。检查np.abs(obs).max()的历史找到起飞的那一维物理参数错位改过frame_skip或 XML 里的timestepdt frame_skip × 0.01dt 变了forward_reward 的量级和速度估计全跟着变第一反应动作print(np.isfinite(obs).all(), np.abs(obs).max())先定位是观测炸还是梯度炸。现场三跑不出论文里的数你遇到了什么照着别人的配置训回报差一截怎么调都对不齐。三大陷阱各占一个案发现场版本v3/v4/v5 奖励定义不同。v4 的 Humanoidcontact_cost恒为 0healthy_reward在倒地时还给分v5 才修掉——跨版本数字不可直接比较种子reset 带噪声不同种子的回报差 5%~10% 是正常波动。评估回合数太少时噪声比版本差异还大frame_skip默认 5dt 是 0.05 秒。改成 1 后 forward_reward 直接放大 5 倍奖励看起来变差其实是单位变了第一反应动作print(env.spec)核对环境参数再逐项对齐论文里的版本号和种子数量。把环境改成你的自定义模型与环境最小 XML 片段整个机器人可以缩到 10 行 XML结构就三层世界体放刚体关节开自由度执行器接控制mujoco modelmy_robot option timestep0.01 gravity0 0 -9.81/ worldbody body nametorso pos0 0 1 joint nameroot typefree/ body nameleg joint namehip typehinge axis1 0 0/ geom typecapsule size0.2 0.1/ /body /body /worldbody actuatormotor jointhip gear100//actuator !-- 改这行 gear关节就变硬/变软 -- /mujocogear就是前面说的传动比。调到 500同样的动作输出 5 倍力矩机器人从绵软变钢性——调试机器人手感时这一行是调得最多的。自定义环境类继承MujocoEnv后你必须自己实现三个方法其余渲染、种子、step主循环基类都给了import numpy as np, gymnasium as gym from gymnasium.envs.mujoco import MujocoEnv from gymnasium.utils import EzPickle class MyRobotEnv(MujocoEnv, EzPickle): metadata {render_modes: [human, rgb_array]} def __init__(self): EzPickle.__init__(self) super().__init__(my_robot.xml, frame_skip5, observation_spaceNone) self.observation_space gym.spaces.Box( -np.inf, np.inf, (self.data.qpos.size self.data.qvel.size,), np.float64) # ① 必须实现怎么初始化 qpos/qvel def reset_model(self): self.set_state(self.init_qpos, self.init_qvel) return self._get_obs() # ② 必须实现奖励函数任务设计的核心 def _get_rew(self, x_velocity, action): return x_velocity * 1.25, {reward_forward: x_velocity} # ③ 必须实现回合终止条件 def is_healthy(self): return 1.0 self.data.qpos[2] 2.0写好后用gym.register(idMyRobot-v0, entry_pointmy_module:MyRobotEnv)注册gym.make就认识它了。往哪儿扩展Sim2Real在reset里对关节摩擦、延迟加噪声逼策略学出对扰动鲁棒的跑法再上真机多智能体单环境本身是单智能体的扩展通常借助 gymnasium-robotics 生态或自行封装不在核心包内异步采样AsyncVectorEnv用子进程跑环境适合step里含重 Python 逻辑的场景纯 MuJoCo 场景收益有限JAX 加速gymnasium.experimental.functional提供函数式 API环境逻辑跑在 GPU 上适合超大规模并行试验合上电脑前先做这 3 件事用Walker2d-v5跑 50 步随机策略每步打印observation找到对应躯干高度rootz的那一维看它随动作起伏在HalfCheetah-v5上完整跑一回合把info[reward_forward]和info[reward_ctrl]分开累加验证两项之和等于总奖励用SyncVectorEnv开 4 个 worker 和单环境各跑 1000 步随机策略记录两边秒数亲眼确认约 6× 的差距把ctrl_cost_weight设为 0 再跑 10 个回合对比总奖励变化并录一段视频看看躺平惩罚消失后机器人变成什么样这四个动作做下来观测、奖励、并行、参数四条线你都亲手摸过一遍——下次再遇到奖励不动的曲线你心里应该已经有答案了。【免费下载链接】GymnasiumA standard API for single-agent reinforcement learning environments, with popular reference environments and related utilities (formerly Gym)项目地址: https://gitcode.com/GitHub_Trending/gy/Gymnasium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →