Unitree_RL_Gym项目(1): Legged Gym 项目深度技术解析
Unitree_RL_Gym项目(1): Legged Gym 项目深度技术解析
本文是对宇树科技(Unitree)开源足式机器人强化学习训练框架
legged_gym的全面技术拆解,涵盖项目架构、核心算法、数学公式、奖励工程与使用实践。
一、项目概述
1.1 背景与定位
legged_gym 是宇树科技基于 NVIDIA Isaac Gym GPU 并行物理仿真引擎构建的足式机器人强化学习(RL)训练框架。它专为四足与双足机器人的端到端步态控制策略训练而设计,采用 PPO(Proximal Policy Optimization) 算法,在单个 GPU 上可同时并行仿真数千个环境(默认 4096 个),极大地加速了策略收敛。
1.2 核心特性
| 特性 | 说明 |
|---|---|
| 多机器人支持 | GO2(四足 12DoF)、G1(双足 12DoF)、H1(双足 10DoF)、H1_2(双足 12DoF) |
| GPU 并行仿真 | 基于 Isaac Gym,4096 环境并行,物理计算完全在 GPU 上执行 |
| 非对称 Actor-Critic | Actor 仅接收本体可观测信息;Critic 接收包含本体线速度的特权观测,提升样本效率 |
| LSTM 循环策略 | G1/H1/H1_2 使用时序策略网络,利用历史观测信息 |
| 域随机化(Domain Randomization) | 摩擦力、基座质量、随机推力扰动,增强策略泛化能力 |
| 观测噪声注入 | 模拟真实传感器噪声,提升部署鲁棒性 |
| 程序化地形生成 | 支持斜坡、楼梯、离散障碍、踏步石、缝隙、坑洞等 7 种地形,支持课程学习 |
二、项目目录结构
legged_gym/
├── __init__.py # 包初始化,定义根目录常量
├── envs/
│ ├── __init__.py # 环境注册(注册所有机器人任务)
│ ├── base/
│ │ ├── base_config.py # 配置基类(递归实例化机制)
│ │ ├── base_task.py # 任务基类(仿真生命周期管理)
│ │ ├── legged_robot.py # 足式机器人核心环境(奖励/观测/控制)
│ │ └── legged_robot_config.py # 足式机器人默认配置(含 PPO 配置)
│ ├── g1/
│ │ ├── g1_config.py # G1 双足机器人配置
│ │ └── g1_env.py # G1 双足机器人环境(步态相位 + 足部追踪)
│ ├── go2/
│ │ └── go2_config.py # GO2 四足机器人配置(使用通用环境类)
│ ├── h1/
│ │ ├── h1_config.py # H1 双足机器人配置
│ │ └── h1_env.py # H1 双足机器人环境
│ └── h1_2/
│ ├── h1_2_config.py # H1_2 双足机器人配置
│ └── h1_2_env.py # H1_2 双足机器人环境
├── scripts/
│ ├── train.py # 训练入口脚本
│ └── play.py # 推理/测试入口脚本
└── utils/
├── __init__.py # 工具模块导出
├── helpers.py # 通用辅助函数(配置转换/参数解析/JIT 导出)
├── isaacgym_utils.py # Isaac Gym 工具(四元数→欧拉角)
├── logger.py # 训练日志记录器
├── math.py # 数学工具(角度归一化/偏航旋转)
├── task_registry.py # 任务注册表(环境创建/算法创建)
└── terrain.py # 程序化地形生成器
三、核心功能模块解析
3.1 配置系统(base_config.py)
设计理念:声明式配置
传统配置需要为每个类手写 __init__ 方法。BaseConfig 采用递归实例化机制:子类只需以嵌套 class 方式声明参数,__init__ 自动将所有类类型属性转换为实例对象。
class MyCfg(BaseConfig):
class env:
num_envs = 4096
class control:
class stiffness:
joint_a = 10.0
cfg = MyCfg() # 自动得到 cfg.env.num_envs, cfg.control.stiffness.joint_a
核心算法:递归实例化
def init_member_classes(obj):
for key in dir(obj):
if key == "__class__": continue
var = getattr(obj, key)
if inspect.isclass(var):
i_var = var() # 实例化
setattr(obj, key, i_var)
init_member_classes(i_var) # 递归处理嵌套
3.2 任务基类(base_task.py)
BaseTask 封装 Isaac Gym 仿真的核心生命周期:
- 设备解析:根据
sim_device(如cuda:0)和use_gpu_pipeline决定张量存放位置 - 缓冲区分配:
obs_buf、rew_buf、reset_buf、episode_length_buf等核心张量 - 仿真创建:调用
create_sim()→_create_ground_plane()→_create_envs() - 渲染管理:查看器创建、键盘事件订阅(ESC 退出、V 键切换同步)
子类必须实现的抽象接口:
reset_idx(env_ids):重置指定环境step(actions):执行一步仿真并返回(obs, privileged_obs, reward, done, info)
3.3 足式机器人核心环境(legged_robot.py)
这是项目最核心、代码量最大的文件(约 600 行),包含完整的仿真 - 训练循环。
3.3.1 仿真步进流程
策略网络输出 actions
↓
[step()] 动作裁剪到 [-clip_actions, clip_actions]
↓
循环 decimation 次(默认 4 次):
├─ _compute_torques(actions) → PD 控制器计算关节力矩
├─ gym.set_dof_actuation_force_tensor() → 施加力矩
├─ gym.simulate() → 推进物理仿真一步
└─ gym.refresh_dof_state_tensor() → 刷新关节状态
↓
[post_physics_step()] 后处理:
├─ 刷新根状态和接触力张量
├─ 提取基座位置/姿态/速度(世界坐标→机体坐标)
├─ 计算重力投影向量(用于姿态感知)
├─ 检测终止条件(碰撞/倾覆/超时)
├─ 计算奖励
├─ 重置已终止环境
├─ 域随机化:随机推力
└─ 构建观测向量
3.3.2 PD 控制器
支持三种控制模式:
P 模式(位置控制,默认):
τ=Kp⋅(θtarget−θ)−Kd⋅θ˙ \tau = K_p \cdot (\theta_{target} - \theta) - K_d \cdot \dot{\theta} τ=Kp⋅(θtarget−θ)−Kd⋅θ˙
其中:
- θtarget=action_scale⋅a+θdefault\theta_{target} = \text{action\_scale} \cdot a + \theta_{default}θtarget=action_scale⋅a+θdefault
- KpK_pKp:刚度(stiffness)
- KdK_dKd:阻尼(damping)
V 模式(速度控制):
τ=Kp⋅(θ˙target−θ˙)−Kd⋅Δθ˙Δt \tau = K_p \cdot (\dot{\theta}_{target} - \dot{\theta}) - K_d \cdot \frac{\Delta \dot{\theta}}{\Delta t} τ=Kp⋅(θ˙target−θ˙)−Kd⋅ΔtΔθ˙
T 模式(力矩控制):
τ=action_scale⋅a \tau = \text{action\_scale} \cdot a τ=action_scale⋅a
最终力矩裁剪到关节力矩限制范围内:
τ=clip(τ,−τlimit,τlimit) \tau = \text{clip}(\tau, -\tau_{limit}, \tau_{limit}) τ=clip(τ,−τlimit,τlimit)
3.3.3 观测空间构建
默认观测向量(以 GO2 为例,48 维):
o=[vbodysv,ωbodysω,gproj,csc,(q−qdefault)sq,q˙sq˙,aprev] o = \left[ \frac{v_{body}}{s_v}, \frac{\omega_{body}}{s_{\omega}}, g_{proj}, \frac{c}{s_c}, \frac{(q - q_{default})}{s_q}, \frac{\dot{q}}{s_{\dot{q}}}, a_{prev} \right] o=[svvbody,sωωbody,gproj,scc,sq(q−qdefault),sq˙q˙,aprev]
其中:
- vbody∈R3v_{body} \in \mathbb{R}^3vbody∈R3:机体坐标系线速度
- ωbody∈R3\omega_{body} \in \mathbb{R}^3ωbody∈R3:机体坐标系角速度
- gproj∈R3g_{proj} \in \mathbb{R}^3gproj∈R3:重力向量在机体坐标系的投影
- c∈R3c \in \mathbb{R}^3c∈R3:速度指令(x 线速度,y 线速度,yaw 角速度)
- q∈RNdofq \in \mathbb{R}^{N_{dof}}q∈RNdof:关节位置
- q˙∈RNdof\dot{q} \in \mathbb{R}^{N_{dof}}q˙∈RNdof:关节速度
- aprev∈RNacta_{prev} \in \mathbb{R}^{N_{act}}aprev∈RNact:上一步动作
- sss 为各维度的观测缩放系数
若启用噪声:
onoisy=o+(2⋅U(0,1)−1)⊙σ o_{noisy} = o + (2 \cdot U(0,1) - 1) \odot \sigma onoisy=o+(2⋅U(0,1)−1)⊙σ
其中 U(0,1)U(0,1)U(0,1) 为均匀分布,σ\sigmaσ 为噪声缩放向量,⊙\odot⊙ 为逐元素乘法。
非对称观测(G1/H1/H1_2):
- Actor 观测:不含线速度 vbodyv_{body}vbody(部署时无法直接测量)
- Critic 特权观测:包含线速度 vbodyv_{body}vbody,用于更准确地估计价值函数
3.3.4 终止条件
环境在以下任一条件满足时终止:
- 碰撞终止:终止碰撞体(如躯干)接触力 >1 N> 1\,\text{N}>1N
- 倾覆终止:∣pitch∣>1.0 rad|\text{pitch}| > 1.0\,\text{rad}∣pitch∣>1.0rad 或 ∣roll∣>0.8 rad|\text{roll}| > 0.8\,\text{rad}∣roll∣>0.8rad
- 超时终止:回合步数超过最大长度 Nmax=⌈Tepisode/Δt⌉N_{max} = \lceil T_{episode} / \Delta t \rceilNmax=⌈Tepisode/Δt⌉
超时终止与碰撞终止的区别:
- 碰撞终止:机器人倒地,给予负奖励(惩罚)
- 超时终止:正常完成回合,不给予终止惩罚
3.4 奖励工程详解
奖励设计是足式机器人 RL 训练的核心。本项目采用多目标加权奖励体系,共 17 个奖励/惩罚项。
3.4.1 速度跟踪奖励(高斯核)
线速度跟踪:
rlin=exp(−∥cxy−vxy∥2σ2) r_{lin} = \exp\left(-\frac{\|c_{xy} - v_{xy}\|^2}{\sigma^2}\right) rlin=exp(−σ2∥cxy−vxy∥2)
角速度跟踪:
rang=exp(−(cyaw−ωz)2σ2) r_{ang} = \exp\left(-\frac{(c_{yaw} - \omega_z)^2}{\sigma^2}\right) rang=exp(−σ2(cyaw−ωz)2)
其中 σ=0.25\sigma = 0.25σ=0.25 为高斯核宽度。误差越小,奖励越接近 1;误差越大,奖励趋近 0。
3.4.2 姿态与运动惩罚
| 奖励项 | 公式 | 物理含义 |
|---|---|---|
| Z 轴线速度惩罚 | r=−vz2r = -v_z^2r=−vz2 | 防止跳跃/下蹲 |
| XY 轴角速度惩罚 | r=−(ωx2+ωy2)r = -(\omega_x^2 + \omega_y^2)r=−(ωx2+ωy2) | 防止翻滚 |
| 姿态倾斜惩罚 | r=−(gproj,x2+gproj,y2)r = -(g_{proj,x}^2 + g_{proj,y}^2)r=−(gproj,x2+gproj,y2) | 保持基座水平 |
| 基座高度惩罚 | r=−(z−ztarget)2r = -(z - z_{target})^2r=−(z−ztarget)2 | 维持目标行走高度 |
3.4.3 关节相关惩罚
| 奖励项 | 公式 | 物理含义 |
|---|---|---|
| 力矩惩罚 | r=−∑iτi2r = -\sum_i \tau_i^2r=−∑iτi2 | 节能,减少电机磨损 |
| 关节速度惩罚 | r=−∑iq˙i2r = -\sum_i \dot{q}_i^2r=−∑iq˙i2 | 平滑运动 |
| 关节加速度惩罚 | r=−∑i(Δq˙iΔt)2r = -\sum_i \left(\frac{\Delta \dot{q}_i}{\Delta t}\right)^2r=−∑i(ΔtΔq˙i)2 | 减少抖动 |
| 动作变化率惩罚 | r=−∑i(ait−ait−1)2r = -\sum_i (a_i^{t} - a_i^{t-1})^2r=−∑i(ait−ait−1)2 | 动作连贯性 |
3.4.4 接触与步态奖励
足部腾空时间奖励:
rair=∑i∈feet(tair,i−0.5)⋅1[first_contacti]⋅1[∥cxy∥>0.1] r_{air} = \sum_{i \in feet} (t_{air,i} - 0.5) \cdot \mathbb{1}[first\_contact_i] \cdot \mathbb{1}[\|c_{xy}\| > 0.1] rair=i∈feet∑(tair,i−0.5)⋅1[first_contacti]⋅1[∥cxy∥>0.1]
其中:
- tair,it_{air,i}tair,i:第 iii 只脚的腾空时间
- 仅在首次触地时计算奖励
- 零指令时不奖励(避免原地踏步获得奖励)
碰撞惩罚:
rcollision=−∑j∈penalized1[∥fcontact,j∥>0.1] r_{collision} = -\sum_{j \in penalized} \mathbb{1}[\|f_{contact,j}\| > 0.1] rcollision=−j∈penalized∑1[∥fcontact,j∥>0.1]
终止惩罚:
rterm=1[reset]⋅1[¬timeout] r_{term} = \mathbb{1}[reset] \cdot \mathbb{1}[\neg timeout] rterm=1[reset]⋅1[¬timeout]
仅对非超时的终止(即碰撞倒地)施加惩罚。
3.4.5 软限制惩罚
为防止关节到达物理硬极限,引入软限制:
qsoft,lower=m−0.5⋅r⋅α q_{soft,lower} = m - 0.5 \cdot r \cdot \alpha qsoft,lower=m−0.5⋅r⋅α
qsoft,upper=m+0.5⋅r⋅α q_{soft,upper} = m + 0.5 \cdot r \cdot \alpha qsoft,upper=m+0.5⋅r⋅α
其中 mmm 为限制中点,rrr 为限制范围,α∈[0,1]\alpha \in [0,1]α∈[0,1] 为软限制系数(默认 0.9)。
当关节超出软限制时:
rpos_limit=−∑i[(qi−qsoft,lower)−+(qi−qsoft,upper)+] r_{pos\_limit} = -\sum_i \left[(q_i - q_{soft,lower})_- + (q_i - q_{soft,upper})_+\right] rpos_limit=−i∑[(qi−qsoft,lower)−+(qi−qsoft,upper)+]
其中 (x)−=min(x,0)(x)_- = \min(x, 0)(x)−=min(x,0),(x)+=max(x,0)(x)_+ = \max(x, 0)(x)+=max(x,0)。
3.4.6 奖励裁剪策略
若启用 only_positive_rewards:
rtotal=max(∑iwi⋅ri,0)+wterm⋅rterm r_{total} = \max\left(\sum_i w_i \cdot r_i, 0\right) + w_{term} \cdot r_{term} rtotal=max(i∑wi⋅ri,0)+wterm⋅rterm
终止奖励在正奖励裁剪后单独添加,确保终止惩罚始终生效。
3.5 域随机化(Domain Randomization)
域随机化通过在训练时随机化物理参数,使策略对参数不确定性具有鲁棒性。
3.5.1 摩擦力随机化
预生成 64 个摩擦系数桶:
μk∼U(μmin,μmax),k=1,...,64 \mu_k \sim U(\mu_{min}, \mu_{max}), \quad k = 1, ..., 64 μk∼U(μmin,μmax),k=1,...,64
每个环境随机分配一个桶:
μenv=μbucket[randint(0,63)] \mu_{env} = \mu_{bucket[randint(0, 63)]} μenv=μbucket[randint(0,63)]
3.5.2 基座质量随机化
mbase=mbase,original+Δm,Δm∼U(−1,3) kg m_{base} = m_{base,original} + \Delta m, \quad \Delta m \sim U(-1, 3)\,\text{kg} mbase=mbase,original+Δm,Δm∼U(−1,3)kg
3.5.3 随机推力扰动
按固定间隔(默认 15 秒)为所有环境设置随机的基座水平速度:
vpush∼U(−vmax,vmax),vmax=1.0 m/s v_{push} \sim U(-v_{max}, v_{max}), \quad v_{max} = 1.0\,\text{m/s} vpush∼U(−vmax,vmax),vmax=1.0m/s
3.6 步态相位控制(G1/H1/H1_2)
双足机器人需要显式的步态相位控制以实现交替行走。
3.6.1 相位计算
设步态周期 T=0.8 sT = 0.8\,\text{s}T=0.8s,则当前相位:
ϕ=(t⋅Δt)mod TT∈[0,1) \phi = \frac{(t \cdot \Delta t) \mod T}{T} \in [0, 1) ϕ=T(t⋅Δt)modT∈[0,1)
左右脚相位偏移 0.5(半周期,交替步态):
ϕleft=ϕ \phi_{left} = \phi ϕleft=ϕ
ϕright=(ϕ+0.5)mod 1 \phi_{right} = (\phi + 0.5) \mod 1 ϕright=(ϕ+0.5)mod1
3.6.2 相位编码
将相位以 sin/cos 编码注入观测:
ophase=[sin(2πϕ),cos(2πϕ)] o_{phase} = [\sin(2\pi\phi), \cos(2\pi\phi)] ophase=[sin(2πϕ),cos(2πϕ)]
这种连续编码比原始相位值更利于神经网络学习周期性模式。
3.6.3 接触一致性奖励
检查每只脚的接触状态是否与步态相位一致:
is_stance=ϕleg<0.55 is\_stance = \phi_{leg} < 0.55 is_stance=ϕleg<0.55
contact=fz,foot>1 N contact = f_{z,foot} > 1\,\text{N} contact=fz,foot>1N
consistent=¬(contact⊕is_stance) consistent = \neg(contact \oplus is\_stance) consistent=¬(contact⊕is_stance)
其中 ⊕\oplus⊕ 为异或(XOR),¬\neg¬ 为取反。stance 阶段应接触,swing 阶段应离地。
四、数学工具详解
4.1 四元数到欧拉角(ZYX 内旋)
给定四元数 q=[qx,qy,qz,qw]q = [q_x, q_y, q_z, q_w]q=[qx,qy,qz,qw]:
Roll(X 轴旋转):
ϕ=arctan2(2(qwqx+qyqz),qw2−qx2−qy2+qz2) \phi = \arctan2(2(q_w q_x + q_y q_z), q_w^2 - q_x^2 - q_y^2 + q_z^2) ϕ=arctan2(2(qwqx+qyqz),qw2−qx2−qy2+qz2)
Pitch(Y 轴旋转):
θ=arcsin(clip(2(qwqy−qzqx),−1,1)) \theta = \arcsin(\text{clip}(2(q_w q_y - q_z q_x), -1, 1)) θ=arcsin(clip(2(qwqy−qzqx),−1,1))
Yaw(Z 轴旋转):
ψ=arctan2(2(qwqz+qxqy),qw2+qx2−qy2−qz2) \psi = \arctan2(2(q_w q_z + q_x q_y), q_w^2 + q_x^2 - q_y^2 - q_z^2) ψ=arctan2(2(qwqz+qxqy),qw2+qx2−qy2−qz2)
4.2 仅偏航旋转的四元数变换
将四元数的 roll 和 pitch 分量置零,只保留 yaw:
qyaw=normalize([0,0,qz,qw]) q_{yaw} = \text{normalize}([0, 0, q_z, q_w]) qyaw=normalize([0,0,qz,qw])
然后对向量施加旋转:
v′=qyaw⊗v⊗qyaw∗ v' = q_{yaw} \otimes v \otimes q_{yaw}^* v′=qyaw⊗v⊗qyaw∗
4.3 角度归一化到 [−π,π][-\pi, \pi][−π,π]
θnorm=((θmod 2π)+2π)mod 2π \theta_{norm} = ((\theta \mod 2\pi) + 2\pi) \mod 2\pi θnorm=((θmod2π)+2π)mod2π
θnorm={θnorm−2πif θnorm>πθnormotherwise \theta_{norm} = \begin{cases} \theta_{norm} - 2\pi & \text{if } \theta_{norm} > \pi \\ \theta_{norm} & \text{otherwise} \end{cases} θnorm={θnorm−2πθnormif θnorm>πotherwise
4.4 平方根分布随机数
生成概率密度向中间集中的随机数:
r=sign(u)⋅∣u∣,u∼U(−1,1) r = \text{sign}(u) \cdot \sqrt{|u|}, \quad u \sim U(-1, 1) r=sign(u)⋅∣u∣,u∼U(−1,1)
rnorm=r+12∈[0,1] r_{norm} = \frac{r + 1}{2} \in [0, 1] rnorm=2r+1∈[0,1]
x=(upper−lower)⋅rnorm+lower x = (upper - lower) \cdot r_{norm} + lower x=(upper−lower)⋅rnorm+lower
相比均匀分布,减少了极端值的出现概率。
五、PPO 算法解析
本项目使用 rsl_rl 库实现的 PPO 算法进行策略训练。
5.1 PPO 核心思想
PPO 通过裁剪替代目标(Clipped Surrogate Objective)限制策略更新幅度,避免传统策略梯度方法中步长过大导致策略崩溃的问题。
5.2 关键公式
5.2.1 策略目标
LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)] L^{CLIP}(\theta) = \hat{\mathbb{E}}_t \left[ \min\left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right] LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)]
其中:
- rt(θ)=πθ(at∣st)πθold(at∣st)r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}rt(θ)=πθold(at∣st)πθ(at∣st):概率比
- A^t\hat{A}_tA^t:广义优势估计(GAE)
- ϵ=0.2\epsilon = 0.2ϵ=0.2:裁剪参数
5.2.2 广义优势估计(GAE)
A^tGAE(γ,λ)=∑l=0∞(γλ)lδt+lV \hat{A}_t^{GAE(\gamma,\lambda)} = \sum_{l=0}^{\infty} (\gamma\lambda)^l \delta_{t+l}^V A^tGAE(γ,λ)=l=0∑∞(γλ)lδt+lV
其中:
δtV=rt+γV(st+1)−V(st) \delta_t^V = r_t + \gamma V(s_{t+1}) - V(s_t) δtV=rt+γV(st+1)−V(st)
- γ=0.99\gamma = 0.99γ=0.99:折扣因子
- λ=0.95\lambda = 0.95λ=0.95:GAE 参数
5.2.3 价值函数损失
LVF(θ)=E^t[(Vθ(st)−Vttarget)2] L^{VF}(\theta) = \hat{\mathbb{E}}_t \left[ (V_\theta(s_t) - V_t^{target})^2 \right] LVF(θ)=E^t[(Vθ(st)−Vttarget)2]
若启用裁剪价值损失:
LVF,clip(θ)=E^t[max((Vθ−Vtarget)2,(Vθold+clip(Vθ−Vθold,−ϵ,ϵ)−Vtarget)2)] L^{VF,clip}(\theta) = \hat{\mathbb{E}}_t \left[ \max\left( (V_\theta - V^{target})^2, (V_{\theta_{old}} + \text{clip}(V_\theta - V_{\theta_{old}}, -\epsilon, \epsilon) - V^{target})^2 \right) \right] LVF,clip(θ)=E^t[max((Vθ−Vtarget)2,(Vθold+clip(Vθ−Vθold,−ϵ,ϵ)−Vtarget)2)]
5.2.4 总损失
LTOTAL(θ)=−LCLIP(θ)+c1LVF(θ)−c2⋅Entropy(πθ) L^{TOTAL}(\theta) = -L^{CLIP}(\theta) + c_1 L^{VF}(\theta) - c_2 \cdot \text{Entropy}(\pi_\theta) LTOTAL(θ)=−LCLIP(θ)+c1LVF(θ)−c2⋅Entropy(πθ)
其中:
- c1=1.0c_1 = 1.0c1=1.0:价值损失系数
- c2=0.01c_2 = 0.01c2=0.01:熵正则化系数
5.2.5 自适应学习率
若学习率调度为 adaptive:
- 当 KL 散度 <0.5×target_KL< 0.5 \times target\_KL<0.5×target_KL 时:学习率 ×1.5\times 1.5×1.5
- 当 KL 散度 >1.5×target_KL> 1.5 \times target\_KL>1.5×target_KL 时:学习率 ×0.5\times 0.5×0.5
- 目标 KL 散度:0.010.010.01
5.3 网络架构
5.3.1 标准 Actor-Critic(GO2)
Actor:
Input: obs (48-dim)
→ Linear(48, 512) + ELU
→ Linear(512, 256) + ELU
→ Linear(256, 128) + ELU
→ Linear(128, 12) # 输出动作均值
→ 可学习噪声标准差
Critic:
Input: privileged_obs (48-dim)
→ Linear(48, 512) + ELU
→ Linear(512, 256) + ELU
→ Linear(256, 128) + ELU
→ Linear(128, 1) # 输出状态价值
5.3.2 LSTM Actor-Critic(G1/H1/H1_2)
Actor:
Input: obs (47-dim or 41-dim)
→ Linear(input_dim, 32) + ELU
→ LSTM(32, 64, num_layers=1)
→ Linear(64, num_actions)
Critic:
Input: privileged_obs (50-dim or 44-dim)
→ Linear(input_dim, 32) + ELU
→ LSTM(32, 64, num_layers=1)
→ Linear(64, 1)
LSTM 隐状态在每个回合开始时重置为零。
六、技术架构设计
6.1 类继承关系
BaseConfig
└── LeggedRobotCfg
├── GO2RoughCfg
├── H1RoughCfg
├── H1_2RoughCfg
└── G1RoughCfg
BaseConfig
└── LeggedRobotCfgPPO
├── GO2RoughCfgPPO
├── H1RoughCfgPPO
├── H1_2RoughCfgPPO
└── G1RoughCfgPPO
BaseTask
└── LeggedRobot
├── (GO2 直接使用 LeggedRobot)
├── H1Robot
├── H1_2Robot
└── G1Robot
6.2 训练数据流
┌─────────────┐ actions ┌──────────────┐ torques ┌──────────────┐
│ PPO 算法 │ ──────────────→ │ LeggedRobot │ ──────────────→ │ Isaac Gym │
│ (rsl_rl) │ │ _compute_ │ │ 物理仿真 │
│ │ ←────────────── │ torques() │ ←────────────── │ │
│ │ obs, rew, done │ step() │ 状态更新 │ │
└─────────────┘ └──────────────┘ └──────────────┘
↑ ↑
│ │
策略网络更新 奖励计算/终止检测/域随机化
6.3 控制频率链
仿真频率:1/dt (默认 200Hz, H1_2 为 400Hz)
│
│ ÷ decimation (默认 4, H1_2 为 8)
↓
策略频率:50Hz (所有机器人统一)
6.4 观测空间对比
| 机器人 | Actor 观测维度 | 特权观测维度 | 动作维度 | 策略类型 |
|---|---|---|---|---|
| GO2 | 48 | None | 12 | MLP |
| G1 | 47 | 50 | 12 | LSTM |
| H1 | 41 | 44 | 10 | LSTM |
| H1_2 | 47 | 50 | 12 | LSTM |
七、4096 并行环境机制详解
7.1 什么是并行环境?
4096 个并行环境指的是在 GPU 上同时运行 4096 个独立的仿真世界,每个世界都有一个机器人实例,它们共享同一个策略网络(权重参数)但各自拥有完全不同的初始状态(关节位置在默认值 0.5~1.5 倍随机、基座位置和速度随机、摩擦系数随机、地形类型随机、速度指令随机)和完全不同的动作(因为观测不同导致策略输出不同),这种设计通过多样性训练让策略网络能够从 4096 个"平行宇宙"中同时收集经验,从而在约 1-2 小时内收敛出能在各种场景(冰面/水泥地、平地/楼梯、受外力扰动等)下都能稳定行走的鲁棒控制器,相比传统串行训练效率提升数千倍。
7.2 初始状态的差异化设计
关节位置随机化
每个环境的关节位置在默认值的 0.5~1.5 倍之间随机采样:
self.dof_pos[env_ids] = self.default_dof_pos * torch_rand_float(
0.5, 1.5, (len(env_ids), self.num_dof), device=self.device)
基座状态随机化
- 位置:环境原点 + [-1, 1] 米随机偏移(崎岖地形模式)
- 速度:[-0.5, 0.5] 范围内随机扰动
速度指令随机化
每个环境独立采样:
- 线速度:
lin_vel_x ∈ [-1.0, 1.0] m/s - 线速度:
lin_vel_y ∈ [-1.0, 1.0] m/s - 角速度:
ang_vel_yaw ∈ [-1.0, 1.0] rad/s(或由航向角计算)
摩擦系数随机化
预生成 64 个摩擦系数桶,每个环境随机分配:
friction_range = [0.5, 1.25] # 从冰面到粗糙水泥地
地形差异(课程学习)
- 第 0-2 行:最简单(平地、缓坡 5cm)
- 第 3-5 行:中等(楼梯 8-15cm)
- 第 6-9 行:最难(楼梯 18-23cm + 崎岖地形)
7.3 动作为什么不同?
动作由策略网络根据观测生成:
actions = policy(obs.detach())
由于每个环境的观测向量完全不同(基座速度、姿态、关节状态、指令等都不同),即使是同一个策略网络,输出也会不同。
观测空间构成(以 GO2 为例,48 维):
obs = [
base_lin_vel (3), # 机体坐标系线速度
base_ang_vel (3), # 机体坐标系角速度
projected_gravity (3), # 重力投影向量
commands (3), # 速度指令
dof_pos (12), # 关节位置
dof_vel (12), # 关节速度
prev_actions (12) # 上一步动作
]
八、关键文件与注释详解
8.1 包初始化模块
__init__.py(legged_gym/)
| 项目 | 说明 |
|---|---|
| 功能 | 定义项目根目录 LEGGED_GYM_ROOT_DIR 和环境目录 LEGGED_GYM_ENVS_DIR 的全局路径常量 |
| 注释意义 | 阐明路径常量的用途,便于理解其他模块中资源文件的引用方式 |
envs/__init__.py
| 项目 | 说明 |
|---|---|
| 功能 | 导入所有机器人配置和环境类,将它们注册到全局任务注册表 task_registry |
| 注册任务 | go2→LeggedRobot, h1→H1Robot, h1_2→H1_2Robot, g1→G1Robot |
| 注释意义 | 说明注册表模式的设计意图——解耦任务定义与使用,统一创建接口 |
8.2 基类模块(envs/base/)
base_config.py
| 项目 | 说明 |
|---|---|
| 核心类 | BaseConfig |
| 功能 | 配置系统基类,实现递归实例化机制——将嵌套的类定义自动转换为实例对象 |
| 设计理念 | 声明式配置:子类只需声明 class env: num_envs=4096,无需手动写 __init__ |
| 注释意义 | 解释递归实例化的规则和注意事项,帮助理解整个配置体系的工作原理 |
关键方法:
init_member_classes(obj): 遍历对象属性,将类类型属性替换为实例,递归处理嵌套
base_task.py
| 项目 | 说明 |
|---|---|
| 核心类 | BaseTask |
| 功能 | 强化学习任务基类,封装 Isaac Gym 仿真的核心生命周期 |
| 核心职责 | 仿真环境创建、GPU/CPU 张量缓冲区分配、渲染管理、键盘事件处理 |
| 注释意义 | 阐明数据流(策略→动作→仿真→观测/奖励)和子类必须实现的接口 |
关键方法:
__init__(): 解析设备、分配缓冲区、创建仿真、注册键盘事件step(): 抽象方法,子类实现一步仿真reset(): 重置所有环境并返回初始观测render(): 渲染当前帧,处理 ESC/V 键盘事件
legged_robot.py
| 项目 | 说明 |
|---|---|
| 核心类 | LeggedRobot(继承 BaseTask) |
| 功能 | 足式机器人强化学习的核心实现,包含完整的仿真 - 训练循环 |
| 代码量 | 项目最大文件(~600 行),涵盖控制、奖励、观测、域随机化等全部逻辑 |
| 注释意义 | 逐函数解释物理含义和设计决策,帮助理解奖励工程和域随机化的实现细节 |
核心功能模块:
| 模块 | 方法 | 功能 |
|---|---|---|
| 仿真步进 | step() |
动作裁剪→PD 控制→物理仿真→后处理 |
| 后处理 | post_physics_step() |
状态更新→终止检测→奖励计算→域随机化→观测构建 |
| PD 控制 | _compute_torques() |
支持 P(位置)/V(速度)/T(力矩) 三种控制模式 |
| 观测构建 | compute_observations() |
拼接速度 + 重力 + 指令 + 关节 + 动作,可选噪声注入 |
| 终止检测 | check_termination() |
碰撞/倾覆/超时三种终止条件 |
| 域随机化 | _process_rigid_shape_props() |
摩擦力随机化(64 桶预生成) |
_process_rigid_body_props() |
基座质量随机化 | |
_push_robots() |
随机推力扰动 | |
| 奖励函数 | 17 个 _reward_* 方法 |
速度跟踪/姿态保持/力矩惩罚/碰撞惩罚等 |
奖励函数体系:
| 奖励名称 | 类型 | 物理含义 |
|---|---|---|
tracking_lin_vel |
奖励 | 高斯核线速度跟踪 |
tracking_ang_vel |
奖励 | 高斯核角速度跟踪 |
lin_vel_z |
惩罚 | Z 轴线速度(防止跳跃) |
ang_vel_xy |
惩罚 | XY 轴角速度(防止翻滚) |
orientation |
惩罚 | 基座倾斜(重力投影偏差) |
base_height |
惩罚 | 高度偏离目标值 |
torques |
惩罚 | 关节力矩(节能) |
dof_vel |
惩罚 | 关节速度(平滑运动) |
dof_acc |
惩罚 | 关节加速度(减少抖动) |
action_rate |
惩罚 | 动作变化率(连贯性) |
collision |
惩罚 | 非足部碰撞 |
termination |
惩罚 | 非超时终止(倒地) |
dof_pos_limits |
惩罚 | 关节位置超限 |
feet_air_time |
奖励 | 足部腾空时间(步态周期) |
stumble |
惩罚 | 绊倒检测 |
stand_still |
惩罚 | 无指令时关节偏移 |
feet_contact_forces |
惩罚 | 高接触力(柔和着地) |
legged_robot_config.py
| 项目 | 说明 |
|---|---|
| 核心类 | LeggedRobotCfg + LeggedRobotCfgPPO |
| 功能 | 定义完整的默认配置体系,所有机器人配置的父类 |
| 配置层次 | 12 个子配置类,覆盖仿真/控制/奖励/域随机化等全部参数 |
| 注释意义 | 为每个参数标注物理单位、取值范围和设计意图,便于调参和理解 |
配置子类一览:
| 子类 | 功能 | 关键参数 |
|---|---|---|
env |
环境维度 | num_envs=4096, num_actions=12 |
terrain |
地形参数 | mesh_type, 摩擦系数,高度场分辨率 |
commands |
速度指令 | 采样范围,重采样间隔,课程学习 |
init_state |
初始状态 | 基座位置/姿态,默认关节角 |
control |
控制器 | control_type=‘P’, PD 增益,decimation |
asset |
机器人资产 | URDF 路径,碰撞配置,物理属性 |
domain_rand |
域随机化 | 摩擦力/质量/推力随机范围 |
rewards |
奖励权重 | 各奖励项权重,软限制系数 |
normalization |
归一化 | 观测缩放系数,裁剪范围 |
noise |
噪声 | 各维度噪声缩放,噪声等级 |
viewer |
查看器 | 相机位置和注视点 |
sim |
仿真参数 | dt=0.005s, PhysX 求解器参数 |
8.3 机器人特定模块
g1/g1_config.py + g1/g1_env.py
| 项目 | 说明 |
|---|---|
| 机器人 | G1 双足人形机器人,12 自由度(含脚踝 roll) |
| 配置特点 | LSTM 策略、步态相位奖励、强姿态/高度惩罚 |
| 环境特点 | 步态相位注入观测 (sin/cos 编码)、足部状态追踪、5 个额外奖励函数 |
| 观测空间 | 47 维 = 角速度 (3) + 重力 (3) + 指令 (3) + 关节 (12×3) + 相位 (2) |
| 特权观测 | 50 维 = 线速度 (3) + Actor 观测 (47) |
| 注释意义 | 解释步态相位计算逻辑和足部追踪机制,阐明 G1 特有的奖励函数设计 |
G1 特有奖励函数:
| 函数 | 功能 |
|---|---|
_reward_contact |
步态接触一致性(XOR 检测) |
_reward_feet_swing_height |
摆腿高度(目标 0.08m) |
_reward_alive |
存活奖励(鼓励不倒) |
_reward_contact_no_vel |
接触时速度惩罚(减少滑步) |
_reward_hip_pos |
髋关节位置惩罚(索引 [1,2,7,8]) |
go2/go2_config.py
| 项目 | 说明 |
|---|---|
| 机器人 | GO2 四足机器人,12 自由度 |
| 特殊之处 | 无自定义环境类,直接使用通用 LeggedRobot |
| 配置特点 | 标准 ActorCritic 策略(非 LSTM)、站立姿态初始关节角 |
| 注释意义 | 说明为何四足机器人无需自定义环境——默认实现已足够 |
h1/h1_config.py + h1/h1_env.py
| 项目 | 说明 |
|---|---|
| 机器人 | H1 双足人形机器人,10 自由度(单自由度踝关节) |
| 与 G1 区别 | 无脚踝 roll、含手臂关节 (4 个)、更高 PD 刚度 |
| 观测空间 | 41 维 = 角速度 (3) + 重力 (3) + 指令 (3) + 关节 (10×3) + 相位 (2) |
| 注释意义 | 对比 H1 与 G1 的关节索引差异,解释髋关节惩罚索引 [0,1,5,6] 的来源 |
h1_2/h1_2_config.py + h1_2/h1_2_env.py
| 项目 | 说明 |
|---|---|
| 机器人 | H1_2 双足人形机器人,12 自由度(双自由度踝关节) |
| 与 H1 区别 | 增加踝 roll、更高 PD 刚度 (髋 200/膝 300)、更小仿真步 (0.0025s)、非零 armature |
| 策略频率 | 50Hz = 0.0025s × 8(decimation) |
| 注释意义 | 解释高刚度下需要更小仿真步的原因(数值稳定性),armature 参数的作用 |
8.4 脚本模块(scripts/)
train.py
| 项目 | 说明 |
|---|---|
| 功能 | 训练入口脚本 |
| 流程 | 解析参数 → 创建环境 → 创建 PPO 运行器 → 训练循环 |
| 使用 | python train.py --task go2 |
| 注释意义 | 说明训练脚本的简洁设计——核心逻辑由任务注册表和运行器封装 |
play.py
| 项目 | 说明 |
|---|---|
| 功能 | 推理/测试入口脚本 |
| 流程 | 加载配置 → 覆盖测试参数 → 创建环境 → 加载策略 → 推理循环 → 可选 JIT 导出 |
| 测试参数覆盖 | 减少环境数 (≤100)、关闭噪声/随机化、启用实时同步 |
| JIT 导出 | 支持标准 MLP 和 LSTM 策略,导出为 TorchScript 用于 C++ 部署 |
| 注释意义 | 解释测试模式与训练模式的参数差异,JIT 导出的部署意义 |
8.5 工具模块(utils/)
helpers.py
| 项目 | 说明 |
|---|---|
| 核心函数 | 7 个辅助函数 + 1 个 JIT 导出器类 |
| 功能 | 配置转换、参数解析、模型加载、JIT 导出 |
| 注释意义 | 详细说明每个函数的输入输出和设计决策 |
关键函数:
| 函数 | 功能 |
|---|---|
class_to_dict() |
配置类→字典(递归,用于序列化) |
update_class_from_dict() |
字典→配置类(递归更新) |
set_seed() |
全局随机种子(Python/NumPy/PyTorch) |
parse_sim_params() |
合并命令行和配置文件的仿真参数 |
get_load_path() |
查找最新模型检查点路径 |
get_args() |
解析命令行参数(集成 Isaac Gym 参数) |
export_policy_as_jit() |
导出策略为 JIT(支持 MLP 和 LSTM) |
PolicyExporterLSTM |
LSTM 策略 JIT 导出器(封装隐状态管理) |
isaacgym_utils.py
| 项目 | 说明 |
|---|---|
| 核心函数 | get_euler_xyz() |
| 功能 | 四元数→欧拉角(ZYX 内旋顺序) |
| 注释意义 | 解释与 Isaac Gym 自带函数的区别——TorchScript 兼容、GPU 加速 |
logger.py
| 项目 | 说明 |
|---|---|
| 核心类 | Logger |
| 功能 | 训练日志记录,支持状态量和奖励统计 |
| 注释意义 | 说明按回合聚合奖励的加权平均计算方式 |
math.py
| 项目 | 说明 |
|---|---|
| 核心函数 | 3 个数学工具函数 |
| 注释意义 | 解释每个函数的物理含义和应用场景 |
| 函数 | 功能 | 应用场景 |
|---|---|---|
quat_apply_yaw() |
仅偏航旋转的四元数变换 | 航向指令计算 |
wrap_to_pi() |
角度归一化到 [-π,π] | 航向误差最短路径 |
torch_rand_sqrt_float() |
平方根分布随机数 | 减少极端值采样 |
task_registry.py
| 项目 | 说明 |
|---|---|
| 核心类 | TaskRegistry(全局单例 task_registry) |
| 功能 | 任务的注册、查询、环境创建、算法创建 |
| 设计模式 | 注册表模式——解耦任务定义与使用 |
| 注释意义 | 阐明统一创建接口的设计意图和完整创建流程 |
核心方法:
register(): 注册任务(名称→环境类 + 配置)make_env(): 创建仿真环境(解析参数→设置种子→创建实例)make_alg_runner(): 创建 PPO 运行器(设置日志→创建运行器→可选恢复)
terrain.py
| 项目 | 说明 |
|---|---|
| 核心类 | Terrain + 2 个辅助函数 |
| 功能 | 程序化地形生成,支持 7 种地形类型和课程学习 |
| 注释意义 | 详细说明地形类型的选择逻辑和难度参数化 |
地形类型:
| 类型 | 函数 | 参数 |
|---|---|---|
| 平滑斜坡 | pyramid_sloped_terrain() |
slope=difficulty×0.4 |
| 粗糙斜坡 | pyramid_sloped_terrain() + random_uniform_terrain() |
slope + 随机高度±0.05m |
| 上楼梯 | pyramid_stairs_terrain() |
step_height=0.05+0.18×difficulty |
| 下楼梯 | pyramid_stairs_terrain() |
step_height=-(0.05+0.18×difficulty) |
| 离散障碍 | discrete_obstacles_terrain() |
height=0.05+0.2×difficulty |
| 踏步石 | stepping_stones_terrain() |
size=1.5×(1.05-difficulty) |
| 缝隙 | gap_terrain() |
gap_size=1.0×difficulty |
| 坑洞 | pit_terrain() |
depth=1.0×difficulty |
九、快速上手
9.1 环境依赖
# 基础依赖
pip install torch>=1.10 numpy scipy
# Isaac Gym (需从 NVIDIA 官网下载)
# https://developer.nvidia.com/isaac-gym
# rsl_rl (强化学习算法库)
pip install git+https://github.com/leggedrobotics/rsl_rl.git
9.2 训练
# 训练 GO2 四足机器人
python legged_gym/scripts/train.py --task go2
# 训练 G1 双足机器人
python legged_gym/scripts/train.py --task g1
# 无头模式训练(服务器环境)
python legged_gym/scripts/train.py --task h1 --headless
# 指定随机种子
python legged_gym/scripts/train.py --task go2 --seed 42
# 指定最大迭代次数
python legged_gym/scripts/train.py --task g1 --max_iterations 5000
9.3 推理与测试
# 测试 GO2
python legged_gym/scripts/play.py --task go2
# 指定运行名称加载模型
python legged_gym/scripts/play.py --task h1_2 --load_run <run_name>
# 指定检查点编号
python legged_gym/scripts/play.py --task g1 --load_run <run_name> --checkpoint 1000
9.4 自定义机器人配置示例
from legged_gym.envs.base.legged_robot_config import LeggedRobotCfg
class MyRobotCfg(LeggedRobotCfg):
class env(LeggedRobotCfg.env):
num_envs = 2048
num_observations = 48
num_actions = 12
class control(LeggedRobotCfg.control):
control_type = 'P'
stiffness = {'joint': 50.}
damping = {'joint': 2.}
action_scale = 0.25
decimation = 4
class asset(LeggedRobotCfg.asset):
file = '{LEGGED_GYM_ROOT_DIR}/resources/robots/my_robot/robot.urdf'
name = "my_robot"
foot_name = "foot"
class rewards(LeggedRobotCfg.rewards):
class scales(LeggedRobotCfg.rewards.scales):
tracking_lin_vel = 2.0 # 增大线速度跟踪权重
torques = -0.0005 # 调整力矩惩罚
9.5 自定义奖励函数示例
from legged_gym.envs.base.legged_robot import LeggedRobot
class MyRobot(LeggedRobot):
def _reward_custom(self):
"""自定义奖励:惩罚基座前后晃动"""
return torch.abs(self.base_ang_vel[:, 1]) # pitch 角速度绝对值
然后在配置中添加权重:
class MyRobotCfg(LeggedRobotCfg):
class rewards(LeggedRobotCfg.rewards):
class scales(LeggedRobotCfg.rewards.scales):
custom = -0.1
9.6 依赖清单
- Isaac Gym (Preview)
- PyTorch ≥ 1.10
- rsl_rl (强化学习算法库)
- numpy, scipy
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)