2. 强化学习基础

说实话,很多搞强化学习的朋友,理论背得滚瓜烂熟,一上手就卡在环境搭建上。MuJoCo这玩意儿,说难不难,但坑确实不少。我当年第一次装的时候,光一个Python接口就折腾了大半天。

这一章,咱们就把MuJoCo从安装到跑通第一个模型,整个流程捋一遍。你跟着我的步骤走,应该半小时内就能看到倒立摆动起来。

2.1 为什么选MuJoCo?

市面上机器人仿真器不少,Gazebo、PyBullet、MuJoCo……为什么我推荐MuJoCo?

快。 MuJoCo的计算效率在同类工具里是顶尖的。它用了一种叫「软接触」的模型,碰撞检测算得特别快。我在做四足机器人步态训练时,同样一个策略,MuJoCo比PyBullet快了将近3倍。

准。 它的物理引擎基于广义坐标,关节约束处理得很干净。力矩控制、位置控制、速度控制,切换起来非常自然。

生态好。 DeepMind收购后,MuJoCo直接开源了,还给了官方的Python绑定。现在很多强化学习框架(比如Stable-Baselines3)都原生支持MuJoCo。

一句话总结: MuJoCo = 快速 + 精确 + 好上手。做机器人RL,它是目前最省心的选择。

2.2 安装MuJoCo与Python接口

安装分两步:先装MuJoCo本体,再装Python绑定。我建议你直接用pip,省事。

2.2.1 安装MuJoCo引擎

从MuJoCo 2.1.0开始,官方提供了预编译的二进制包。你只需要一行命令:

pip install mujoco

嗯,就这么简单。它会自动下载对应你系统的二进制文件。如果你用的是Linux,可能需要装几个依赖库:

sudo apt-get install libgl1-mesa-dev libgl1-mesa-glx libosmesa6-dev patchelf

我个人习惯用conda环境隔离项目,所以会先建一个干净的环境:

conda create -n mujoco_rl python=3.9
conda activate mujoco_rl
pip install mujoco

小技巧: 安装完成后,在Python里执行 import mujoco,如果不报错,就说明装好了。我第一次装的时候忘了装OpenGL依赖,import倒是没报错,但一渲染就崩……

2.2.2 验证安装

装好了,咱们跑个简单的测试。MuJoCo自带了一个查看器,可以加载模型文件:

python -m mujoco.viewer

如果看到弹出一个灰色窗口,恭喜你,环境搭好了。如果报错说找不到显示器,别慌——你大概率是在远程服务器上跑的。这时候需要设置虚拟显示器:

export DISPLAY=:0
# 或者用 xvfb 跑离屏渲染
xvfb-run python -m mujoco.viewer

我曾经在AWS的GPU实例上折腾这个,搞了快两个小时才意识到是DISPLAY变量没设……

2.3 加载并运行倒立摆模型

倒立摆(Inverted Pendulum)是RL界的「Hello World」。一个杆子立在滑块上,你要控制滑块左右移动,让杆子不倒。

MuJoCo的模型文件是XML格式,后缀是.xml。倒立摆的模型在MuJoCo的示例里就有。咱们直接加载它:

import mujoco

# 加载倒立摆模型
xml_path = mujoco.utils.get_model_path("inverted_pendulum.xml")
model = mujoco.MjModel.from_xml_path(xml_path)
data = mujoco.MjData(model)

print("模型加载成功!")
print(f"关节数量: {model.nq}")
print(f"自由度: {model.nv}")

你会看到输出:

模型加载成功!
关节数量: 2
自由度: 2

嗯,倒立摆有2个关节:一个是滑块的水平移动,一个是杆子的旋转。自由度也是2,说明没有冗余约束。

2.3.1 理解模型结构

MuJoCo的模型结构,说白了就是三样东西:

  • MJCF(MuJoCo XML格式):描述机器人的几何、关节、执行器、传感器
  • MjModel:编译后的模型,包含所有静态信息(质量、惯性、关节限位等)
  • MjData:运行时数据,包含状态、控制输入、传感器读数

我刚开始学的时候,总搞混Model和Data。后来记住一句话:Model是「设计图」,Data是「当前状态」。设计图不会变,但状态每帧都在更新。

概念 类比 特点
MJCF 建筑设计图纸 可读的XML,描述机器人结构
MjModel 建筑结构模型 编译后的只读数据,包含所有物理参数
MjData 建筑当前状态 每帧更新的位置、速度、力、传感器值

2.4 仿真循环与数据流

仿真循环,说白了就是一个死循环,每步做三件事:

  1. 设置控制输入(比如给关节施加力矩)
  2. 调用物理引擎前进一步
  3. 读取传感器反馈(位置、速度、接触力等)

咱们写一个最简单的仿真循环,让倒立摆自由落体:

import mujoco
import numpy as np

# 加载模型
xml_path = mujoco.utils.get_model_path("inverted_pendulum.xml")
model = mujoco.MjModel.from_xml_path(xml_path)
data = mujoco.MjData(model)

# 仿真参数
duration = 5.0          # 仿真时长(秒)
dt = model.opt.timestep # 仿真步长(默认0.005秒)
steps = int(duration / dt)

print(f"仿真步长: {dt:.4f}s, 总步数: {steps}")

# 仿真循环
for i in range(steps):
    # 1. 设置控制输入(这里给零力矩,让摆自由摆动)
    data.ctrl[0] = 0.0
    
    # 2. 前进一步
    mujoco.mj_step(model, data)
    
    # 3. 每100步打印一次状态
    if i % 100 == 0:
        cart_pos = data.qpos[0]    # 滑块位置
        pole_angle = data.qpos[1]  # 杆子角度(弧度)
        cart_vel = data.qvel[0]    # 滑块速度
        pole_vel = data.qvel[1]    # 杆子角速度
        
        print(f"步{i:5d} | 滑块位置: {cart_pos:6.3f} | 杆子角度: {pole_angle:6.3f}")

运行这段代码,你会看到杆子角度从0开始逐渐变大——因为重力在拉着它往下倒。这就是一个最简单的「什么都不做」的仿真。

注意: data.qpos 和 data.qvel 的索引顺序,取决于模型文件中关节的定义顺序。倒立摆模型里,qpos[0]是滑块位置,qpos[1]是杆子角度。不同模型可能不一样,一定要先打印出来看看。

2.4.1 数据流全景

为了让你更直观地理解仿真中的数据流动,我画了一张图:

MuJoCo仿真循环与数据流 用户策略 (神经网络/控制器) 控制输入 data.ctrl 物理引擎 mj_step() 状态更新 data.qpos / data.qvel 传感器反馈 data.sensordata 奖励计算 (强化学习用) 动作 a_t 力矩/位置 积分 观测 o_t 状态 奖励 r_t 关键数据流说明 1. 策略输出动作 a_t → 写入 data.ctrl 2. mj_step() 执行物理积分 → 更新 data.qpos / data.qvel 3. 从 data.sensordata 读取观测 o_t → 计算奖励 r_t 4. 观测和奖励送入策略 → 输出下一动作 a_{t+1} 5. 循环往复,直到仿真结束

这张图把整个仿真循环串起来了。你想想看,强化学习的「智能体-环境交互」范式,在MuJoCo里就是这张图的循环:策略输出动作,物理引擎更新状态,传感器反馈观测,然后策略再根据观测做决策。

2.5 可视化与交互控制

光看数字没意思,咱们让倒立摆动起来。MuJoCo自带了一个基于OpenGL的查看器,可以实时渲染:

import mujoco
import mujoco.viewer

# 加载模型
xml_path = mujoco.utils.get_model_path("inverted_pendulum.xml")
model = mujoco.MjModel.from_xml_path(xml_path)
data = mujoco.MjData(model)

# 启动查看器
with mujoco.viewer.launch_passive(model, data) as viewer:
    # 设置相机视角
    viewer.cam.distance = 3.0
    viewer.cam.azimuth = 90
    viewer.cam.elevation = -10
    
    # 仿真循环
    while viewer.is_running():
        # 给一个小的正弦扰动,让摆晃动
        data.ctrl[0] = 0.5 * np.sin(data.time * 2.0)
        
        # 前进一步
        mujoco.mj_step(model, data)
        
        # 同步查看器
        viewer.sync()

运行这段代码,你会看到一个3D窗口,滑块在左右移动,杆子跟着晃动。你可以用鼠标拖拽视角,从各个角度观察。

交互技巧: 在查看器窗口中,按住鼠标左键拖拽可以旋转视角,滚轮可以缩放。按 F1 可以查看所有快捷键。我调试机器人模型时,经常用这个功能检查关节运动范围是否合理。

2.6 常见坑与避坑指南

环境搭建这块,我踩过的坑比走过的路还多。挑几个最常见的说说:

  • 模型文件路径问题: get_model_path() 只能加载MuJoCo自带的示例模型。如果你用自己的模型文件,直接用路径或相对路径:mujoco.MjModel.from_xml_path("./my_robot.xml")。
  • 仿真速度太慢: 默认步长0.005秒,如果你不需要那么高的精度,可以调大步长。但注意,步长超过0.01秒,物理精度会明显下降。
  • 查看器卡死: 如果你在Jupyter Notebook里运行查看器,可能会卡住。我建议把仿真代码写成独立的Python脚本,在终端里运行。
  • 力矩控制没反应: 检查一下模型文件里执行器的类型。如果是位置控制模式,你设力矩是没用的。需要把 actuator 的 gaintype 改成力矩模式。

曾经我犯过一个低级错误: 在仿真循环里忘了调用 viewer.sync(),结果查看器窗口一直黑屏。我以为是显卡驱动的问题,重装了三次驱动才发现……嗯,这种错误犯一次就够了。

2.7 本章小结

这一章,咱们把MuJoCo从安装到跑通第一个仿真,整个流程走了一遍。你现在应该能:

  • 用pip安装MuJoCo和Python接口
  • 加载一个机器人模型(倒立摆)
  • 理解仿真循环中Model和Data的角色
  • 编写一个完整的仿真循环,并实时可视化