第74章:RL 全身控制基础——IsaacLab 多肢体环境搭建¶
74.0 前置自测¶
答不出两题以上,建议先回到 足式/190_腿足RL训练栈、足式/70_腿足简化模型理论 与 复合/20_浮动基座臂统一动力学 复习。
- 腿足 RL 中为什么通常输出关节位置目标,而不是直接输出关节力矩?
model.nq与model.nv在含浮动基座机器人中为什么不相等?- Domain Randomization 的目标是让仿真更逼真,还是让策略更鲁棒?
- Teacher-Student 训练中,哪些信息可以给 Critic 或 Teacher,哪些信息不能给部署策略?
- 当机械臂末端追踪目标与基座稳定性冲突时,为什么不能简单提高末端跟踪奖励权重?
本章目标¶
本章把足式 RL 训练栈扩展到四足+臂与人形全身控制。
学完后,你应能设计一个含机械臂或上半身的 IsaacLab RL 环境,解释观测、动作、奖励和随机化背后的物理含义,并能用最小代码验证训练信号是否与理论一致。
本章不是追求某个开源仓库的一键复现,而是建立可迁移的设计框架:先理解为什么这样建,再把它落实到 IsaacLab、rsl_rl、MuJoCo 或自研训练栈。
知识树¶
复合 RL 全身控制的根问题是:如何让一个高维浮动基座系统在接触不确定、感知不完整和操作目标变化的情况下输出可部署的全身动作。
RL 全身控制
├── 问题建模
│ ├── MDP 与 POMDP
│ ├── 多时间尺度
│ └── locomotion 与 manipulation 的目标冲突
├── 环境搭建
│ ├── IsaacLab scene
│ ├── action manager
│ ├── observation manager
│ ├── reward manager
│ └── event manager
├── 观测设计
│ ├── 本体感知
│ ├── 任务命令
│ ├── 末端目标
│ ├── 历史窗口
│ └── 特权信息
├── 动作设计
│ ├── 关节位置目标
│ ├── 残差动作
│ ├── 夹爪动作
│ └── 多头动作
├── 奖励设计
│ ├── 末端跟踪
│ ├── 基座稳定
│ ├── 接触安全
│ ├── 能耗与平滑
│ └── 因果解耦
├── 随机化与蒸馏
│ ├── 负载与惯量随机化
│ ├── 延迟与噪声
│ ├── 外力与接触扰动
│ ├── Teacher-Student
│ └── 适应模块
└── 训练与部署
├── curriculum
├── 指标监控
├── ablation
├── sim2sim
└── sim2real 安全门槛
这棵树的根不是“怎样把动作维度从 12 改成 18”,而是“怎样让学习系统理解全身耦合”。
如果只改维度,不改奖励、观测和随机化,得到的往往不是全身控制,而是一个更容易摔倒的腿足策略。
74.1 从纯腿足 RL 到复合 RL:问题为什么变难 ⭐⭐¶
这一节解决的问题是:四足加臂或人形上半身以后,RL 环境到底新增了什么难度。
动机:为什么不能照搬 Go2 velocity tracking¶
回顾足式 RL:典型 Go2 平地行走环境的任务很清楚。
策略输入本体感知和速度命令,输出 12 个腿关节的位置偏移。
奖励主要由速度跟踪、姿态稳定、足端接触、能耗和平滑组成。
这个问题已经很难,但目标之间的冲突相对单一。
当我们把 Z1、ARX5、WidowX 或人形上半身接进来时,任务从“身体走得稳”变成“身体走得稳,同时手要到指定位置,必要时还要施力”。
这不是简单加一个末端目标。
机械臂的每一次加速都会通过浮动基座改变全身动量。
末端接触物体时,物体反力又会通过雅可比转化为基座扰动和足端载荷变化。
如果策略只从奖励中看到“手离目标越近越好”,它可能牺牲支撑裕度去追手。
如果策略只从奖励中看到“基座越稳越好”,它又可能把手臂冻结在安全姿态附近,完全不操作。
这就是复合 RL 的核心矛盾:末端精度与全身稳定并不是天然一致的目标。
反面案例:末端奖励过强导致摔倒¶
假设我们在 Go2 velocity tracking 环境中新增一项:
然后把它的权重设为 \(w_{ee}=10\),高于速度跟踪和姿态稳定。
训练早期策略会发现一个捷径:大幅扭动基座可以更快把末端推向目标。
短期内 \(r_{ee}\) 上升,长期却导致 roll/pitch 角速度变大。
一旦足端接触相位与臂摆动叠加,支撑多边形边缘的法向力下降,策略就会摔倒。
如果 episode 终止惩罚不够强,策略甚至会学到“冲过去碰一下目标再摔倒也划算”。
这不是 PPO 的问题,而是任务目标被错误表达的结果。
历史来源:从 Deep-WBC 到视觉全身控制¶
早期腿足 RL 主要关注速度跟踪和地形鲁棒性。
RMA、Walk These Ways、Miki 的感知行走证明了本体感知、历史窗口和特权信息可以训练出可部署的行走策略。
四足+臂的 Deep Whole-Body Control 把这个范式扩展到统一动作空间:腿和臂由同一个策略一起输出。
Visual Whole-Body Control 又把高层视觉目标规划和低层全身目标追踪分成两个训练阶段。
UMI-on-Legs 进一步把固定臂操作策略输出的末端轨迹交给腿足全身控制器执行。
这些方法表面不同,本质都在回答同一个问题:高层任务只想要手到达目标,低层策略必须自己处理基座、腿、臂和接触之间的耦合。
人形方向也走向类似路径。
ExBody 强调上半身追踪与下半身平衡解耦。
FALCON 使用上下体双策略头分别处理末端跟踪和行走稳定。
SoFTA 进一步把上体和下体的控制频率分开。
从这些工作可以看到一个趋势:越接近真实操作,越不能把全身策略看成一个普通高维 MLP。
理论建模:从 MDP 到 POMDP¶
标准 RL 把问题写成马尔可夫决策过程:
其中 \(s_t \in \mathcal{S}\) 是完整状态,\(a_t \in \mathcal{A}\) 是动作,\(P(s_{t+1}|s_t,a_t)\) 是转移概率。
在仿真中,完整状态可以包含基座位姿、所有关节、所有连杆速度、接触力、摩擦系数、负载质量和物体真实位姿。
但部署策略看不到完整状态。
它能看到的是观测:
这里 \(\Omega\) 是传感器映射,\(\epsilon_t\) 是噪声和延迟。
因此真实问题更接近 POMDP。
POMDP 的策略严格来说应依赖历史:
工程上通常用固定长度历史、RNN、TCN 或适应模块近似这个历史依赖。
对于复合机器人,历史信息比纯腿足更重要。
原因是机械臂和物体交互引入了更多不可直接观测的变量。
负载质量改变了同样动作下的基座加速度。
夹爪接触状态改变了末端受力。
电机延迟使得动作和响应之间出现时差。
单帧观测无法区分这些情况,历史窗口却能从“我刚才怎么动,身体怎样响应”中推断出来。
本质洞察:复合 RL 的难点不是动作维度从 12 增到 18 或 29,而是观测中的缺失信息变多了。 策略必须从历史中推断负载、接触、延迟和扰动,这才是全身控制比普通速度跟踪更难的根本原因。
多时间尺度:腿、臂、任务命令并不等频¶
复合系统中至少存在四种时间尺度。
| 层级 | 典型频率 | 内容 | RL 环境中的体现 |
|---|---|---|---|
| 物理仿真 | 200-1000 Hz | 接触、关节动力学 | sim_dt |
| 低层控制 | 100-500 Hz | PD、WBC、策略输出保持 | decimation |
| 操作命令 | 5-50 Hz | 末端轨迹、抓取阶段 | command buffer |
| 任务规划 | 0.5-5 Hz | 目标选择、物体状态 | high-level command |
纯腿足速度命令通常变化较慢。
末端目标却可能在 10 Hz 左右给出一段未来轨迹。
如果每个控制步都随机改变末端目标,策略会学到追逐噪声。
如果末端目标太久不更新,策略又会滞后。
因此复合 RL 环境必须明确命令频率和保持方式。
一种常见做法是让策略在 50-100 Hz 输出关节目标,但给它一个未来末端轨迹窗口。
这相当于告诉策略“接下来一小段时间手要怎么走”。
策略可以提前调整基座和腿,而不是等误差出现后再补救。
双重解读:统一策略与分层策略¶
统一策略可以理解为“一个大脑同时控制腿和臂”。
它的优点是耦合可以自然被学习。
它的缺点是探索空间大,奖励冲突强,数据需求高。
分层策略可以理解为“高层只说手要去哪,低层负责全身执行”。
它的优点是接口清晰,固定臂操作策略更容易复用。
它的缺点是高层如果给出不可达或不安全的末端轨迹,低层只能尽力补救。
两者不是谁替代谁,而是适合不同任务阶段。
| 任务类型 | 推荐结构 | 理由 |
|---|---|---|
| 平地边走边伸手 | 统一低层策略 | 耦合较强但目标简单 |
| 视觉抓取与移动操作 | 高层策略 + 全身低层 | 视觉和控制时间尺度不同 |
| 人形上体遥操作 | 上下体解耦 | 上肢精度和下肢平衡目标不同 |
| 接触丰富推拉 | 混合 MPC/RL 或双策略头 | 力与稳定性冲突明显 |
小型推导:动作维度增加为什么会改变探索难度¶
设策略输出为 \(a \in \mathbb{R}^{n_a}\)。
若每个动作维度初始近似独立高斯:
则动作向量范数期望满足:
从 12 维腿关节增加到 18 维腿臂关节,初始随机动作能量增加 50%。
如果 action scale 不变,训练早期的随机动作更激烈。
如果机械臂关节也使用腿关节同样的 scale,末端会产生不合理的大幅摆动。
所以复合 RL 中 action scale 必须分组设置。
腿关节偏移可以较大,因为腿需要产生步态。
臂关节偏移通常较小,因为臂末端位置对关节角非常敏感。
夹爪动作还可能是低频离散或准连续信号,不能和髋关节同样处理。
代码验证:动作维度与初始动作能量¶
import torch
def expected_action_energy(dim: int, sigma: float = 1.0, samples: int = 100000):
"""估计随机高斯动作的平均平方范数。"""
a = sigma * torch.randn(samples, dim)
return torch.mean(torch.sum(a * a, dim=-1)).item()
for dim in [12, 18, 29]:
energy = expected_action_energy(dim, sigma=0.2)
print(f"动作维度 {dim:2d}: E[||a||^2] = {energy:.4f}")
# 结论:
# 维度越高,初始随机动作的总能量越大。
# 因此复合机器人不能直接沿用纯腿足的 action scale。
这段代码没有控制机器人,却验证了一个训练设计原则。
动作空间越大,随机探索越容易产生强扰动。
这就是为什么复合 RL 通常需要更保守的 action scale、更强的终止条件和更细的 curriculum。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:所有关节共用一个 action scale
错误做法:腿、臂、腰、夹爪全部使用
scale=0.25。现象:训练早期机械臂快速乱甩,基座 roll/pitch 剧烈波动。
根本原因:不同关节对末端和质心的灵敏度不同,统一 scale 会让高杠杆关节过度探索。
正确做法:按关节组设置 scale,例如腿 0.25 rad、臂肩 0.12 rad、腕部 0.08 rad、夹爪单独限幅。
💡 概念误区:把多肢体 RL 理解成纯腿足 RL 加末端奖励
新手想法:只要把动作维度扩展,再加一个末端误差奖励即可。
实际情况:臂改变质量分布、动量和接触反力,奖励和随机化都要重新设计。
正确理解:复合 RL 是一个耦合控制问题,末端目标必须通过全身稳定性约束来实现。
🧠 思维陷阱:看到统一策略成功就放弃结构化接口
新手想法:端到端策略既然能学,就不需要高层轨迹、WBC 或 MPC。
实际情况:统一策略在固定任务上可能很好,但任务变化、真机安全和故障定位都需要接口化设计。
正确做法:能端到端训练,也要保留清晰的命令、观测、奖励和评估边界。
小节练习¶
- 以 Go2+Z1 为例,列出纯腿足策略和复合策略在动作空间、观测空间、奖励项、随机化参数上的差异。
- 推导如果动作维度从 18 增到 29,但高斯探索标准差不变,初始动作平方范数期望增加多少。
- 设计一个训练失败案例:只奖励末端位置、不奖励基座姿态,预测策略可能学到的三种异常行为。
74.2 IsaacLab 多肢体环境的结构化搭建 ⭐⭐¶
这一节解决的问题是:如何把复合机器人环境拆成可调试的 IsaacLab 模块。
动机:为什么环境配置比网络结构更重要¶
在腿足 RL 中,很多项目使用相似的 PPO 网络结构。
差异真正大的地方往往是环境。
复合机器人更是如此。
同样一个 PPO,如果环境定义错误,训练曲线可能看似上升,但学到的是不可部署行为。
如果观测里泄漏仿真真值,策略在仿真中会非常强,真机却无法运行。
如果奖励项单位不一致,末端厘米级误差和基座弧度误差会被错误权重混合。
如果随机化只随机腿不随机臂,策略在空载时很好,一拿物体就失稳。
环境配置决定了策略“认为世界是什么样子”。
网络只是学习这个世界中的映射。
反面案例:环境能跑但观测顺序错¶
复合环境最常见的 bug 不是程序崩溃,而是程序能跑但语义错。
例如观测向量约定为:
部署代码却按另一种顺序解析:
维度完全相同,所以不会报错。
但策略看到的“髋关节角”实际是“肩关节角”。
这种错误会表现为训练中能收敛、导出后行为荒谬。
因此 IsaacLab 中应把观测项命名化、分组化,而不是手写一个巨大拼接函数后不再检查。
历史来源:Manager-Based 环境的意义¶
早期 legged_gym 环境通常在一个类里写完 reset、step、reward 和 randomization。
这种方式对于单一四足行走足够高效。
但一旦加入机械臂、视觉、夹爪、物体和外部任务,单个类会迅速膨胀。
IsaacLab 的 Manager-Based 设计把环境拆成多个管理器。
ObservationManager 负责观测。
ActionManager 负责动作解释。
RewardManager 负责奖励项。
TerminationManager 负责终止条件。
CommandManager 负责命令采样。
EventManager 负责随机化和扰动。
这种拆分不是为了形式美观,而是为了定位错误。
当末端跟踪失败时,你可以单独打印命令、末端观测、奖励分项和动作输出。
当真机部署异常时,你可以逐项核对部署观测是否与训练观测一致。
复合环境的推荐模块¶
| 模块 | 复合机器人新增内容 | 必查问题 |
|---|---|---|
| Scene | 统一 USD/URDF、臂、夹爪、物体 | 坐标系和惯量是否正确 |
| Action | 腿臂分组 scale、夹爪动作 | 动作维度和关节顺序是否一致 |
| Observation | 末端目标、目标历史、臂关节 | 是否泄漏仿真真值 |
| Reward | 末端位姿、基座稳定、接触力 | 权重是否冲突 |
| Termination | 跌倒、超限、自碰撞、力矩饱和 | 是否过早或过晚终止 |
| Command | EE 轨迹、速度命令、模式命令 | 命令是否可达 |
| Event | 负载、延迟、摩擦、外扰 | 随机化是否覆盖真机 |
理论:环境是一个可组合的状态转移包装器¶
物理引擎给出基本转移:
这里 \(u_t\) 是底层关节命令,\(\xi\) 是环境参数。
RL 策略输出的不是直接物理输入,而是高层动作 \(a_t\)。
ActionManager 实现:
ObservationManager 实现:
RewardManager 实现:
CommandManager 实现:
EventManager 改变参数:
把这些函数分开,等价于把一个复杂 MDP 显式分解。
这让我们能逐个验证每个函数。
本质洞察:IsaacLab 管理器不是工程包装,而是把 RL 问题的数学构件拆出来。 Action 对应策略输出到物理输入的映射,Observation 对应部分可观测传感器,Reward 对应优化目标,Event 对应环境参数分布。
环境骨架代码¶
from dataclasses import dataclass
@dataclass
class WholeBodyEnvShape:
"""用一个小结构体记录维度,防止训练和部署各写各的。"""
num_leg_joints: int = 12
num_arm_joints: int = 6
num_gripper_joints: int = 1
ee_pose_dim: int = 6
base_cmd_dim: int = 3
history_steps: int = 4
@property
def action_dim(self) -> int:
# 腿、臂、夹爪都由策略输出目标偏移
return self.num_leg_joints + self.num_arm_joints + self.num_gripper_joints
@property
def proprio_dim(self) -> int:
# base_ang_vel(3) + projected_gravity(3)
# joint_pos + joint_vel + last_action
joint_dim = self.num_leg_joints + self.num_arm_joints + self.num_gripper_joints
return 3 + 3 + joint_dim + joint_dim + joint_dim
@property
def command_dim(self) -> int:
# 底盘速度命令 + 末端 SE(3) 命令
return self.base_cmd_dim + self.ee_pose_dim
@property
def policy_obs_dim(self) -> int:
# 当前观测 + 若干步历史
return (self.proprio_dim + self.command_dim) * self.history_steps
shape = WholeBodyEnvShape()
print("动作维度:", shape.action_dim)
print("策略观测维度:", shape.policy_obs_dim)
这类代码看似简单,却能避免很多隐藏错误。
当你换机器人时,第一件事不是改网络,而是重新计算这些维度并写成可测试的配置。
IsaacLab 配置伪代码¶
class Go2ArmWholeBodyEnvCfg:
"""Go2+Arm 全身 RL 环境配置示意。"""
# 场景:机器人、地面、目标物体、传感器
scene = {
"num_envs": 4096,
"env_spacing": 3.0,
"robot_asset": "go2_arx5.usd",
"terrain": "plane_or_rough",
}
# 动作:腿和臂分组缩放
actions = {
"leg_joint_pos": {
"joint_names": ["FL_.*", "FR_.*", "RL_.*", "RR_.*"],
"scale": 0.25,
},
"arm_joint_pos": {
"joint_names": ["arm_joint.*"],
"scale": 0.10,
},
"gripper": {
"joint_names": ["gripper_joint"],
"scale": 0.02,
},
}
# 观测:策略只能看部署可获得的信息
observations = {
"policy": [
"base_ang_vel",
"projected_gravity",
"joint_pos_rel",
"joint_vel_rel",
"last_action",
"base_velocity_command",
"ee_target_in_task_frame",
"ee_error_in_base_frame",
],
# critic 可以使用特权信息,但 actor 不可以
"critic": [
"payload_mass",
"friction_coeff",
"external_force",
"contact_force_truth",
],
}
# 奖励:所有项必须能解释物理意义
rewards = {
"ee_position_tracking": 2.0,
"ee_orientation_tracking": 0.5,
"base_orientation": -1.0,
"base_ang_vel_xy": -0.05,
"joint_torque": -1.0e-5,
"action_rate": -0.02,
"self_collision": -2.0,
"survival": 0.5,
}
这段代码不是可以直接运行的完整 IsaacLab 配置。
它的作用是展示结构和命名。
真正的项目中应把每个字符串绑定到 IsaacLab 的 observation term、reward term 和 event term。
验证环境的最小闭环¶
复合环境建好后,不要立刻训练 4096 个环境。
先做单环境检查。
| 检查项 | 方法 | 合格现象 |
|---|---|---|
| 关节顺序 | 打印 action index 到 joint name | 顺序与部署约定一致 |
| 默认姿态 | reset 后可视化 | 站立稳定,臂不穿模 |
| 末端坐标 | 手动给臂关节角,比较 FK | 末端移动方向符合预期 |
| 奖励分项 | 随机动作下打印均值 | 无 NaN,量级可解释 |
| 终止条件 | 人为倾倒或超限 | 及时终止 |
| 随机化 | 每个 episode 打印样本 | 范围与配置一致 |
单环境冒烟测试代码¶
def smoke_test_env(env, steps: int = 200):
"""用随机小动作检查环境是否存在维度、NaN 和终止异常。"""
obs, info = env.reset()
print("初始观测 shape:", obs.shape)
for t in range(steps):
# 使用小幅随机动作,避免一开始就把机器人打翻
action = 0.05 * torch.randn(env.num_envs, env.action_space.shape[0], device=env.device)
obs, reward, terminated, truncated, info = env.step(action)
if torch.isnan(obs).any():
raise RuntimeError(f"第 {t} 步观测出现 NaN")
if torch.isnan(reward).any():
raise RuntimeError(f"第 {t} 步奖励出现 NaN")
if t % 20 == 0:
# 打印奖励分项,比只看 total reward 更有诊断价值
reward_terms = info.get("reward_terms", {})
print(t, {k: float(v.mean()) for k, v in reward_terms.items()})
print("冒烟测试结束")
如果环境连小随机动作都不能稳定执行几十步,通常是模型、关节顺序、PD 增益或终止条件有问题。
这时不要启动长训练。
先把单环境调通。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:训练观测和部署观测分别手写
错误做法:训练环境中拼接一套观测,部署脚本中重新拼接一套观测。
现象:仿真训练曲线很好,导出后 sim2sim 行为完全不一致。
根本原因:观测顺序、归一化、坐标系或单位出现微小差异。
正确做法:把观测规格写成单一配置,训练、回放、导出和部署共用同一份顺序定义。
💡 概念误区:把 Critic 能看到的信息也给 Actor
错误想法:反正训练在仿真中,给策略多看一点真值能学得更快。
现象:训练性能虚高,部署时缺少这些输入导致策略不可用。
根本原因:Actor 输入必须是部署可获得的信息,Critic 或 Teacher 才能使用特权信息。
正确做法:严格区分
policy观测组和critic观测组。🧠 思维陷阱:环境一能跑就开始大规模训练
错误想法:4096 并行很快,先训一轮看看。
现象:几个小时后才发现奖励符号错、末端坐标系错或随机化没有生效。
正确做法:先单环境可视化,再小批量随机动作,再短训练,再完整训练。
小节练习¶
- 为 Go2+Z1 写出动作维度、策略观测维度和 Critic 特权观测维度的计算表。
- 设计一个检查观测顺序的单元测试:给每个关节一个唯一角度,验证拼接向量中对应位置正确。
- 解释为什么 Critic 可以看接触力真值,但 Actor 不应该直接看仿真的接触力数组。
74.3 观测空间设计:策略到底应该看见什么 ⭐⭐⭐¶
这一节解决的问题是:如何设计复合机器人的观测,使策略既能学习耦合,又不依赖真机不可得信息。
动机:观测不是越多越好¶
很多初学者面对复杂任务时会自然想到:把所有状态都喂给策略。
在仿真中,这样做确实会提高训练速度。
但部署时,很多状态无法获得。
例如真实摩擦系数、物体质量、精确接触力、无延迟的基座线速度、仿真中的目标物体真值,都不是低层策略天然可用的输入。
如果 Actor 依赖这些信息,策略学到的是“读答案做题”。
一旦答案消失,行为就崩溃。
复合 RL 的观测设计必须遵守一个原则:Actor 只能看部署闭环中真实可提供的信息。
可以由状态估计器提供的信息要建模噪声和延迟。
可以由高层规划器提供的信息要明确坐标系和更新频率。
不可部署的信息只能给 Critic、Teacher 或用于离线诊断。
反面案例:把世界系末端误差直接喂给 Actor¶
假设训练时给 Actor 输入:
这看似合理。
但真机上 \(\mathbf{p}_{ee}^{world}\) 需要外部定位、SLAM 或 VIO。
如果部署系统只有本体感知和基座里程计,这个量会带噪声、漂移和延迟。
训练中无噪声的世界系误差会让策略依赖厘米级精度。
真机中 5 cm 漂移就可能导致末端来回补偿,引起基座晃动。
更稳健的做法是输入任务帧或基座帧中的目标,训练时注入延迟和噪声,并让低层只承担短时跟踪。
观测类别总表¶
| 类别 | 例子 | Actor 可用性 | 说明 |
|---|---|---|---|
| 本体感知 | 关节角、关节速度、IMU、上一动作 | 高 | 部署核心输入 |
| 基座估计 | 线速度、姿态、高度 | 中 | 需状态估计器,必须加噪声 |
| 任务命令 | 速度命令、末端目标、模式命令 | 高 | 来自高层或操作者 |
| 末端状态 | 末端位姿、末端速度 | 中 | 可由 FK 算得,但依赖基座估计 |
| 接触信息 | 足端触地、夹爪接触 | 中 | 可估计,真值不能直接用 |
| 历史信息 | 过去观测、过去动作 | 高 | 用于近似 POMDP 信念 |
| 特权信息 | 摩擦、负载、外力真值、地形真值 | 低 | 只给 Critic 或 Teacher |
坐标系选择:世界系、基座系、任务帧¶
观测中的同一个向量,用不同坐标系表达会改变学习难度。
世界系表达直观,但依赖全局定位。
基座系表达部署友好,但基座晃动会把目标也变成晃动信号。
任务帧表达适合操作,因为目标相对桌面、门把手或物体定义,而不是相对机器人身体定义。
| 表达方式 | 形式 | 优点 | 风险 |
|---|---|---|---|
| 世界系 | \(\mathbf{p}_{ee}^{W}\) | 与地图和任务一致 | 依赖全局定位 |
| 基座系 | \(\mathbf{p}_{ee}^{B}\) | 本体感知可计算 | 基座抖动会污染目标 |
| 任务帧 | \(\mathbf{p}_{ee}^{T}\) | 与操作对象绑定 | 需要任务帧估计 |
在四足臂任务中,常见做法是低层策略输入未来末端目标在任务帧或基座帧中的表示。
同时输入基座姿态和角速度,让策略知道身体正在怎样晃动。
高层视觉策略负责慢速更新任务帧。
低层全身策略负责短时补偿。
理论:观测历史如何近似隐变量¶
设不可见环境参数为 \(z_t\),例如负载质量、摩擦、延迟和接触状态。
策略真正需要的是:
但部署时看不到 \(z_t\)。
历史窗口提供了一个估计:
于是策略变成:
这就是 RMA、Teacher-Student 和许多历史堆叠方法的共同结构。
历史窗口不是“给网络更多输入”这么简单。
它提供了辨识动力学的证据。
同样的关节命令,如果负载更重,基座响应会更慢。
同样的脚端速度,如果摩擦更低,身体会出现滑移。
同样的末端命令,如果夹爪碰到物体,臂关节力矩和速度会出现不同模式。
这些差异都需要时间序列才能看出来。
观测维度设计示例¶
以 Go2+6DOF 臂+夹爪为例,使用 19 维动作。
| 观测项 | 维度 | 坐标系 | 说明 |
|---|---|---|---|
| base angular velocity | 3 | body | IMU 角速度 |
| projected gravity | 3 | body | 姿态的紧凑表示 |
| velocity command | 3 | body/task | \(v_x,v_y,\omega_z\) |
| joint position relative | 19 | joint | 相对默认姿态 |
| joint velocity | 19 | joint | 编码器速度 |
| last action | 19 | action | 动作平滑和延迟补偿 |
| ee target position | 3 | task/base | 末端位置命令 |
| ee target orientation | 3 | task/base | 轴角或 log SO(3) |
| ee current error | 6 | base/task | 由 FK 得到 |
| mode command | 2 | scalar | 行走/站立/接触模式 |
| estimated contact | 4 | foot | 足端触地估计 |
单帧合计为 81 维。
如果堆叠 4 帧,策略输入为 324 维。
如果使用 TCN,则可以保留形状 [batch, time, feature],让卷积处理时间维。
归一化原则¶
观测归一化不是小细节。
PPO 对输入尺度很敏感。
复合机器人中,不同观测项的自然尺度差异很大。
关节角通常在弧度量级。
关节速度可能达到十几 rad/s。
末端位置误差在米级或厘米级。
动作历史在归一化动作空间中通常是 \([-1,1]\)。
如果不归一化,网络会优先关注数值大的通道。
| 信号 | 推荐尺度处理 | 原因 |
|---|---|---|
| 关节角 | 相对默认姿态并除以关节范围 | 避免绝对零位差异 |
| 关节速度 | clip 后乘以缩放系数 | 限制异常尖峰 |
| 末端位置误差 | 以米为单位,clip 到任务范围 | 防止早期误差过大 |
| 姿态误差 | 用 SO(3) log,clip 到 \(\pi\) | 避免欧拉角跳变 |
| 接触估计 | 0/1 或平滑概率 | 避免硬真值依赖 |
| 历史动作 | 保持归一化动作 | 与策略输出空间一致 |
代码:构造可检查的观测字典¶
import torch
class WholeBodyObservationBuilder:
"""把命名观测转换为策略向量,同时保留调试字典。"""
def __init__(self):
self.order = [
"base_ang_vel",
"projected_gravity",
"velocity_cmd",
"joint_pos_rel",
"joint_vel",
"last_action",
"ee_target",
"ee_error",
"mode_cmd",
"contact_est",
]
def build(self, terms: dict):
pieces = []
debug_shapes = {}
for name in self.order:
value = terms[name]
if torch.isnan(value).any():
raise RuntimeError(f"{name} 出现 NaN")
pieces.append(value)
debug_shapes[name] = tuple(value.shape)
obs = torch.cat(pieces, dim=-1)
return obs, debug_shapes
builder = WholeBodyObservationBuilder()
# 示例:batch=4096
terms = {
"base_ang_vel": torch.zeros(4096, 3),
"projected_gravity": torch.zeros(4096, 3),
"velocity_cmd": torch.zeros(4096, 3),
"joint_pos_rel": torch.zeros(4096, 19),
"joint_vel": torch.zeros(4096, 19),
"last_action": torch.zeros(4096, 19),
"ee_target": torch.zeros(4096, 6),
"ee_error": torch.zeros(4096, 6),
"mode_cmd": torch.zeros(4096, 2),
"contact_est": torch.zeros(4096, 4),
}
obs, shapes = builder.build(terms)
print(obs.shape)
print(shapes)
这段代码的关键不是 torch.cat。
关键是 order 成为唯一观测顺序来源。
训练、回放和部署都应使用同一顺序。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:末端姿态用欧拉角差
错误做法:直接用
roll - roll_ref, pitch - pitch_ref, yaw - yaw_ref。现象:接近 \(\pi\) 或 \(-\pi\) 时误差跳变,奖励突然变差。
根本原因:SO(3) 不是欧氏空间,欧拉角有周期和奇异性。
正确做法:用旋转矩阵或四元数计算相对旋转,再用李代数 log 得到 3 维误差。
💡 概念误区:历史越长越好
错误想法:历史窗口越长,策略知道的信息越多。
现象:输入维度过大,训练变慢,旧信息干扰当前控制。
根本原因:历史窗口应覆盖系统辨识所需时间尺度,而不是无限增长。
正确做法:从 3-5 帧短历史开始;若要辨识负载或地形,再用 0.5-1.0 秒窗口配合 TCN。
🧠 思维陷阱:把特权信息泄漏当成性能提升
错误想法:仿真里能拿到接触力和摩擦系数,给策略看可以更快训练。
现象:仿真成功率很高,真机或带噪 sim2sim 崩溃。
正确做法:特权信息只能用于 Critic、Teacher、诊断指标或蒸馏标签。
小节练习¶
- 为人形 G1 上体操作任务设计一个 Actor 观测表,明确每个观测项的维度、坐标系和部署来源。
- 写出用四元数计算末端姿态误差的伪代码,并说明为什么比欧拉角差更稳定。
- 设计一个实验比较单帧观测、4 帧历史和 TCN 历史编码在随机负载任务中的差异。
74.4 动作空间:让策略输出什么才安全 ⭐⭐¶
这一节解决的问题是:复合机器人策略的动作应该定义为关节位置、速度、力矩,还是更高层的残差。
动机:动作空间决定探索边界¶
RL 训练早期的策略几乎是随机的。
如果动作直接是力矩,随机输出会直接作用到机器人动力学上。
对于四足臂或人形,这非常危险。
腿的随机力矩可能让脚打滑。
臂的随机力矩可能让末端撞到身体。
腰部的随机力矩可能把基座快速扭倒。
所以多数可部署腿足 RL 采用关节位置目标。
策略输出归一化动作 \(a_t \in [-1,1]^n\)。
ActionManager 将其映射为:
底层 PD 再输出力矩:
这样策略探索被关节目标和 PD 增益包裹起来。
反面案例:直接力矩输出导致高频抖动¶
直接力矩输出最大的问题是没有自然平滑约束。
PPO 的动作噪声会让相邻控制步的力矩变化很大。
即使平均力矩不大,高频力矩也会造成电机发热、齿轮冲击和接触抖动。
在复合机器人中,机械臂惯量更集中在上方,高频扭动会通过基座放大到足端接触。
如果仿真器的电机模型过于理想,策略甚至会学会利用高频力矩“震动”末端接近目标。
这种行为在真机上通常不可接受。
动作设计选项¶
| 动作形式 | 输出 | 优点 | 风险 | 推荐阶段 |
|---|---|---|---|---|
| 关节位置目标 | \(q^{des}\) | 稳定,易部署 | 依赖 PD 增益 | 入门与大多数任务 |
| 关节位置残差 | \(\Delta q\) | 可叠加参考轨迹 | 参考质量影响结果 | 模仿和操作 |
| 关节速度目标 | \(\dot q^{des}\) | 平滑轨迹方便 | 积分漂移 | 运动学低层 |
| 关节力矩 | \(\tau\) | 控制自由度最大 | 探索危险 | 研究级力控 |
| 末端速度 | \(\dot x_{ee}\) | 接口直观 | 需要 IK/WBC | 分层控制 |
| 混合动作 | 腿 \(q^{des}\) + 臂 residual | 利用结构先验 | 接口复杂 | 复杂任务 |
分组 scale:腿、臂、腰、夹爪不能同权¶
动作映射应写成分块形式:
其中 \(s_{\ell}\)、\(s_a\)、\(s_g\) 应由关节功能和安全边界决定。
腿关节要覆盖步态运动,因此 scale 可以较大。
臂肩关节具有大力臂,对基座影响明显,因此 scale 应较小。
腕关节对末端姿态灵敏,也应谨慎。
夹爪动作常常需要慢速、限位和滞回,不能像普通关节那样每步剧烈改变。
控制降采样¶
在 IsaacLab 中,物理仿真频率通常高于策略频率。
例如仿真步长 0.005 s,策略每 4 个仿真步更新一次,则策略频率为 50 Hz。
动作在 4 个仿真步内保持不变。
这称为 decimation。
控制降采样有两个作用。
第一,降低策略推理频率,减少计算。
第二,给 PD 和物理系统时间响应,避免策略每个物理步都改变目标。
复合机器人中,decimation 还影响末端平滑性。
如果策略频率太低,末端轨迹会阶梯化。
如果策略频率太高,动作噪声会直接进入机械臂。
常见选择是仿真 200 Hz、策略 50 Hz 或 100 Hz。
上体精细稳定任务可以采用更高频上体头,但要明确和下体头的同步方式。
残差动作:参考轨迹上的安全探索¶
很多操作任务已有参考轨迹。
例如末端从当前位姿移动到目标位姿,可以由 IK 或高层策略给出 nominal 关节轨迹。
此时 RL 不必从零学习所有动作。
可以让策略输出残差:
残差动作好比自行车的辅助轮。
参考轨迹提供基本方向。
RL 只学习补偿基座扰动、接触误差和模型误差。
这种设计能显著降低探索难度。
但它也会限制策略多样性。
如果参考轨迹本身不可行,残差策略很难救回来。
因此残差动作适合有明确任务轨迹的操作,不适合从零发现新步态。
代码:分组动作映射¶
import torch
class GroupedJointPositionAction:
"""把归一化动作映射到分组关节目标。"""
def __init__(self, default_q, leg_ids, arm_ids, gripper_ids):
self.default_q = default_q
self.leg_ids = leg_ids
self.arm_ids = arm_ids
self.gripper_ids = gripper_ids
self.leg_scale = 0.25
self.arm_scale = 0.10
self.gripper_scale = 0.02
def __call__(self, action):
q_des = self.default_q.clone()
n_leg = len(self.leg_ids)
n_arm = len(self.arm_ids)
n_gripper = len(self.gripper_ids)
a_leg = action[:, :n_leg]
a_arm = action[:, n_leg:n_leg + n_arm]
a_gripper = action[:, n_leg + n_arm:n_leg + n_arm + n_gripper]
q_des[:, self.leg_ids] += self.leg_scale * torch.clamp(a_leg, -1.0, 1.0)
q_des[:, self.arm_ids] += self.arm_scale * torch.clamp(a_arm, -1.0, 1.0)
q_des[:, self.gripper_ids] += self.gripper_scale * torch.clamp(a_gripper, -1.0, 1.0)
# 最后再做一次关节限位裁剪,避免默认姿态加残差后越界
return q_des
这段代码体现了两个原则。
动作裁剪发生在归一化空间。
关节限位裁剪应发生在物理关节空间。
两者都需要。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:只裁剪 action,不裁剪关节目标
错误做法:
action = clip(action, -1, 1)后直接映射。现象:默认姿态靠近关节限位时,
q_default + scale * action仍然越界。根本原因:action 边界不等于关节物理边界。
正确做法:归一化动作裁剪后,还要对
q_des做关节限位裁剪。💡 概念误区:直接力矩一定更高级
错误想法:力矩控制更接近真实动力学,所以一定更好。
实际情况:直接力矩探索更危险,对执行器模型、奖励和安全约束要求更高。
正确理解:关节位置 PD 不是低级方案,而是把安全先验注入动作空间。
🧠 思维陷阱:把夹爪当成普通关节
错误想法:夹爪也只是一个 DOF,放进动作向量即可。
实际情况:夹爪有接触、滞回、力限制和任务阶段逻辑。
正确做法:夹爪动作低频更新,并在奖励中区分接近、闭合、持握和释放阶段。
小节练习¶
- 设计 Go2+ARX5 的分组 action scale,并说明每个 scale 的物理依据。
- 比较
q_des = q_default + scale * action与q_des = q_ref + scale * action的适用任务。 - 在仿真中固定策略为随机动作,扫描不同 arm scale,记录基座 roll/pitch 的标准差。
74.5 奖励设计:末端跟踪与基座稳定的冲突 ⭐⭐⭐¶
这一节解决的问题是:如何把“手要准”和“身体要稳”写成不会互相破坏的奖励。
动机:奖励函数是目标排序的语言¶
复合 RL 的奖励设计比纯腿足更难。
纯腿足速度跟踪的主目标通常是速度命令。
复合任务至少有两个主目标:行走稳定和末端任务。
这两个目标经常冲突。
手要够远,身体可能需要前倾。
手要够快,基座可能被反作用力矩扰动。
手要施力,足端法向力和摩擦裕度会变化。
奖励函数必须表达优先级,而不是把所有误差简单相加。
反面案例:奖励项量纲混乱¶
设奖励为:
末端误差单位是米。
基座姿态单位是弧度。
如果末端误差 0.1 m,平方为 0.01。
如果基座 pitch 0.2 rad,平方为 0.04。
看似基座项更大。
但实际不同任务中误差范围、梯度和安全含义完全不同。
如果不做权重和归一化,奖励会隐含一个不透明的目标排序。
这就是为什么奖励项必须先做尺度分析,再谈权重。
奖励总体结构¶
推荐把复合奖励分成四层。
| 层级 | 内容 | 作用 | 示例 |
|---|---|---|---|
| 任务奖励 | 末端位置、姿态、速度、抓取成功 | 驱动操作目标 | \(r_{ee}\) |
| 稳定奖励 | 姿态、角速度、支撑裕度、速度跟踪 | 防止摔倒 | \(r_{base}\) |
| 安全惩罚 | 自碰撞、关节限位、力矩饱和 | 阻止危险行为 | \(r_{safe}\) |
| 品质正则 | 能耗、动作平滑、关节居中 | 改善可部署性 | \(r_{reg}\) |
最终奖励:
注意安全项通常还应进入 termination,而不只是奖励。
自碰撞、严重跌倒、关节超限不能只靠一个负奖励慢慢学习。
它们应当立即终止 episode。
末端跟踪奖励¶
位置误差:
姿态误差:
组合奖励:
为什么用指数形式?
它把奖励限制在有界范围。
它让不同奖励项更容易平衡。
它在误差很大时不会产生巨大梯度。
但指数也有风险。
如果 \(\sigma_p\) 太小,训练早期误差很大,奖励接近 0,策略收不到信号。
所以复合任务中常用课程学习:先给大 \(\sigma_p\),让策略学会靠近;再逐步缩小 \(\sigma_p\),提高精度。
基座稳定奖励¶
基座稳定至少包括四类信号。
| 奖励项 | 公式 | 目的 |
|---|---|---|
| 姿态水平 | \(-\|g_{xy}^{body}\|^2\) | 限制 roll/pitch |
| 横滚俯仰角速度 | \(-\|\omega_{xy}\|^2\) | 抑制晃动 |
| 垂直速度 | \(-v_z^2\) | 避免跳跃和跌落 |
| 支撑裕度 | \(d(\text{CoM}, \partial \mathcal{P})\) | 保持质心投影在支撑多边形内 |
纯腿足策略常用前三项。
复合操作建议加入支撑裕度或其近似。
因为机械臂外伸时,roll/pitch 可能还不大,但 CoM 投影已经接近支撑边界。
如果只看姿态,策略会低估危险。
操作与稳定的动态权重¶
复合任务不能使用固定权重解决所有阶段。
站立抓取时,可以提高末端精度。
快速行走时,应降低末端权重,优先保持稳定。
接触施力时,应把力控和基座稳定放在高优先级。
可以定义阶段权重:
其中通常:
也可以用速度命令连续调节:
速度越大,末端权重越低。
这种设计把“走稳优先”写进奖励函数。
Advantage Mixing 的直觉¶
Deep-WBC 中提出的 Advantage Mixing 试图缓解腿和臂奖励互相污染。
普通 PPO 使用同一个 advantage 评估整条动作向量。
但腿动作主要影响行走稳定,臂动作主要影响末端跟踪。
如果末端误差很大,整条动作都会被惩罚,腿策略也会收到不该承担的负反馈。
如果基座摔倒,臂动作也会被同样惩罚,策略难以判断到底是哪部分出了问题。
Advantage Mixing 的思想是把不同奖励来源对应到不同动作子空间。
腿动作更关注 locomotion advantage。
臂动作更关注 manipulation advantage。
这不是完全解耦动力学,而是让信用分配更符合因果结构。
类比多人项目中的成绩分配:如果只给团队总分,每个人不知道自己哪里做错;如果把腿的稳定指标和臂的末端指标分开反馈,学习信号更清晰。
代码:奖励分项计算¶
import torch
def exp_tracking_reward(error, sigma):
"""有界指数追踪奖励。"""
error_sq = torch.sum(error * error, dim=-1)
return torch.exp(-error_sq / (sigma * sigma))
def whole_body_rewards(data):
"""计算 Go2+Arm 的核心奖励分项。"""
# 末端位置和姿态误差
r_ee_pos = exp_tracking_reward(data["ee_pos_err"], sigma=0.15)
r_ee_rot = exp_tracking_reward(data["ee_rot_err"], sigma=0.50)
# 基座稳定
r_base_level = -torch.sum(data["projected_gravity_xy"] ** 2, dim=-1)
r_base_ang = -torch.sum(data["base_ang_vel_xy"] ** 2, dim=-1)
r_z_vel = -(data["base_lin_vel_z"] ** 2)
# 能耗与动作平滑
r_torque = -torch.sum(data["joint_torque"] ** 2, dim=-1)
r_action_rate = -torch.sum((data["action"] - data["last_action"]) ** 2, dim=-1)
# 安全项
r_collision = -data["self_collision_count"].float()
r_joint_limit = -data["joint_limit_violation"].float()
# 速度越大,末端权重越低
speed = torch.linalg.norm(data["velocity_cmd"][:, :2], dim=-1)
w_ee = 2.0 * torch.exp(-1.5 * speed)
reward = (
w_ee * r_ee_pos
+ 0.5 * w_ee * r_ee_rot
+ 1.0 * r_base_level
+ 0.05 * r_base_ang
+ 0.5 * r_z_vel
+ 1.0e-5 * r_torque
+ 0.02 * r_action_rate
+ 2.0 * r_collision
+ 5.0 * r_joint_limit
)
terms = {
"ee_pos": r_ee_pos,
"ee_rot": r_ee_rot,
"base_level": r_base_level,
"base_ang": r_base_ang,
"z_vel": r_z_vel,
"torque": r_torque,
"action_rate": r_action_rate,
"collision": r_collision,
"joint_limit": r_joint_limit,
"w_ee": w_ee,
}
return reward, terms
注意这里的权重符号。
r_torque、r_action_rate、r_collision 已经是负数。
最终乘正权重。
很多项目的 bug 来自“负奖励又乘了负权重”,结果变成鼓励错误行为。
奖励调试指标¶
只看总 reward 没有意义。
必须同时记录分项。
| 指标 | 正常趋势 | 异常含义 |
|---|---|---|
ee_pos |
逐渐上升 | 末端目标不可达或权重太低 |
base_level |
绝对值下降 | 基座在晃或臂扰动过强 |
torque |
初期高,后期下降 | 动作粗糙或能耗惩罚太弱 |
action_rate |
逐渐平滑 | 策略高频抖动 |
collision |
接近 0 | 自碰撞几何或动作 scale 有问题 |
w_ee |
随命令变化 | 动态权重未生效 |
⚠️ 常见陷阱¶
⚠️ 编程陷阱:奖励符号双重取负
错误做法:
r_torque = -||tau||^2,配置中又写torque = -1e-5。现象:策略被奖励使用大扭矩。
根本原因:负项和权重符号没有统一约定。
正确做法:统一约定“函数返回有符号项,配置权重为正”或“函数返回正代价,配置权重为负”,不要混用。
💡 概念误区:末端误差越小一定越好
错误想法:只要手更准,策略就更好。
实际情况:行走中末端 1 cm 精度可能换来基座大幅晃动和足端打滑。
正确理解:操作精度要在稳定性和安全约束内优化。
🧠 思维陷阱:一次性打开所有奖励项
错误想法:论文里有的奖励项都加上,训练会更完整。
现象:策略学不到主任务,或卡在不动的局部最优。
正确做法:先训最小任务奖励和关键安全项,再逐步加入正则,并做消融实验。
小节练习¶
- 设计一个站立抓取、慢走抓取、接触推门三阶段的动态末端权重函数。
- 写出一个奖励分项表,要求每一项都说明单位、典型数值范围和期望趋势。
- 做一个消融实验:删除
action_rate、放大ee_pos、删除base_ang_vel_xy,预测各自的行为变化。
74.6 Domain Randomization:含臂系统的鲁棒性设计 ⭐⭐⭐¶
这一节解决的问题是:复合机器人需要随机化哪些比纯腿足更多的因素。
动机:机械臂让 sim2real gap 放大¶
纯腿足策略已经需要随机化质量、摩擦、电机强度、延迟和传感器噪声。
加入机械臂后,gap 的来源更多。
臂末端可能拿起未知负载。
夹爪可能接触软物体。
腕部相机或 iPhone VIO 有延迟。
臂的惯量标定比腿更容易偏差。
末端接触力通过长力臂放大为基座力矩。
这些因素使同一个末端命令在仿真和真机上产生不同的全身响应。
如果训练中没有覆盖这些变化,策略会过度适应空载、理想臂、无延迟和刚性接触。
反面案例:只随机腿不随机负载¶
假设训练时随机化地面摩擦和腿部电机强度,却始终让机械臂空载。
策略会学到一种比较激进的臂摆动方式。
真机上夹爪拿起 1 kg 物体后,臂的等效惯量和重力力矩明显增加。
同样的动作导致基座前倾。
腿部策略虽然见过摩擦变化,却没见过“上方质量突然改变”的情况。
结果是行走仍然能走,但末端跟踪和支撑裕度同时恶化。
这说明复合 RL 的 DR 不能只沿用腿足参数表。
DR 的贝叶斯视角回顾¶
Domain Randomization 的训练目标是:
其中 \(\xi\) 是环境参数。
真实参数 \(\xi_{real}\) 未知。
随机化分布 \(p(\xi)\) 应覆盖真实可能值。
如果范围太窄,真机落在分布外。
如果范围太宽,策略变得过度保守。
复合机器人中,\(p(\xi)\) 的维度更高。
因此更需要按物理来源分类,而不是随意列几个参数。
复合机器人随机化分类¶
| 类别 | 参数 | 典型范围 | 物理意义 |
|---|---|---|---|
| 全身质量 | base/link mass | \(\pm 10\%-20\%\) | CAD 与装配误差 |
| 臂负载 | payload mass | 0-2 kg 或按任务 | 抓取物体质量 |
| 负载质心 | payload CoM | 夹爪附近几厘米 | 物体握持偏心 |
| 惯量 | link inertia | 随质量缩放 | 旋转响应 |
| 地面摩擦 | foot friction | 0.3-1.2 | 足端打滑 |
| 末端摩擦 | gripper/object friction | 0.2-1.0 | 抓取与推拉 |
| 电机强度 | motor scale | 0.85-1.15 | 执行器能力 |
| 控制延迟 | action delay | 0-40 ms | 通信和执行器带宽 |
| 感知延迟 | target delay | 0-150 ms | VIO/视觉高层延迟 |
| IMU 噪声 | angular velocity noise | 按传感器标定 | 姿态估计 |
| 关节噪声 | encoder noise | 0.002-0.02 rad | 编码器误差 |
| 外部推力 | base push | 0-100 N | 碰撞与扰动 |
| 末端外力 | ee wrench | 0-50 N | 推门、拉车、按压 |
负载随机化的物理约束¶
负载不是简单给总质量加一个随机数。
负载加在夹爪附近,会改变全身质心:
其中 \(m_L\) 是负载质量,\(\mathbf{p}_L\) 是负载质心。
负载还改变臂末端的重力力矩:
这意味着负载越远离基座,扰动越大。
随机化时应同时改变质量、质心和惯量。
只改质量不改惯量会产生非物理系统。
末端外力随机化¶
推门、拉抽屉、搬运和按压都涉及末端外力。
末端外力通过雅可比进入关节空间:
也通过整体动力学影响浮动基座。
训练中可以施加随机外力:
但要注意可行性。
如果外力超过电机或摩擦极限,策略不可能稳定。
因此外力 curriculum 很重要。
先从 0-5 N 开始。
策略稳定后逐步增加到任务需要范围。
人形力敏感操作中常见 0-40 N、甚至更高的课程力。
四足臂小平台则要根据重量和电机能力保守设置。
延迟随机化¶
复合系统有两类延迟。
第一类是低层动作延迟。
策略输出到电机响应之间存在延迟。
第二类是任务目标延迟。
高层视觉、VIO 或操作策略输出的末端目标可能滞后 50-150 ms。
这两类延迟对策略影响不同。
动作延迟让所有关节命令滞后。
目标延迟让策略追逐旧目标。
因此应分开建模。
| 延迟类型 | 作用对象 | 训练实现 | 典型症状 |
|---|---|---|---|
| action delay | 关节命令 | 动作环形缓冲 | 动作相位滞后 |
| observation delay | 传感器 | 观测环形缓冲 | 状态估计慢 |
| command delay | 末端目标 | 目标轨迹缓冲 | 手追旧目标 |
| random packet drop | 高层目标 | 保持上一命令 | 末端卡顿 |
代码:动作和目标延迟缓冲¶
class DelayBuffer:
"""固定最大长度的延迟缓冲,训练时随机读取旧值。"""
def __init__(self, max_delay_steps: int, initial_value):
self.max_delay_steps = max_delay_steps
self.buffer = [initial_value.clone() for _ in range(max_delay_steps + 1)]
def push(self, value):
self.buffer.insert(0, value.clone())
self.buffer.pop()
def sample(self, delay_steps):
delay_steps = int(delay_steps)
delay_steps = max(0, min(delay_steps, self.max_delay_steps))
return self.buffer[delay_steps]
def apply_random_delay(current_action, current_target, action_buffer, target_buffer):
"""分别对动作和目标施加随机延迟。"""
action_buffer.push(current_action)
target_buffer.push(current_target)
action_delay = torch.randint(low=0, high=3, size=()).item()
target_delay = torch.randint(low=0, high=8, size=()).item()
delayed_action = action_buffer.sample(action_delay)
delayed_target = target_buffer.sample(target_delay)
return delayed_action, delayed_target
真实系统中延迟不是常数。
随机延迟比固定延迟更接近通信和视觉管线。
DR 逐步加严¶
复合任务建议分阶段扩大随机化。
| 阶段 | 随机化内容 | 目标 |
|---|---|---|
| S0 | 无随机化或极小噪声 | 验证任务和奖励正确 |
| S1 | 腿足标准 DR | 学会基本行走 |
| S2 | 臂负载和惯量 DR | 学会负载补偿 |
| S3 | 末端外力 DR | 学会接触扰动 |
| S4 | 目标延迟和丢包 | 学会部署鲁棒性 |
| S5 | 混合扰动 | 接近最终部署分布 |
这不是说正式训练必须从无 DR 开始。
而是调试时可以分阶段定位。
一旦确认环境正确,正式训练应尽早加入适度 DR。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:随机化负载质量但不改变重力力矩
错误做法:只在观测或奖励里记录 payload,不把负载附加到物理模型。
现象:策略以为见过负载,实际动力学没有变化。
根本原因:随机变量没有进入物理转移函数。
正确做法:将负载作为刚体、固定关节或外力真正作用到仿真动力学中。
💡 概念误区:DR 范围越大越安全
错误想法:既然不知道真实参数,就把范围设得特别宽。
现象:策略保守、动作迟钝、末端跟踪精度低。
根本原因:策略在过宽分布上优化平均性能,需要牺牲单点性能。
正确做法:用硬件标定、系统辨识和实验日志收窄范围。
🧠 思维陷阱:只随机低层物理,不随机高层接口
错误想法:只要随机质量和摩擦,sim2real 就足够。
实际情况:操作任务常常被视觉/VIO 延迟、目标漂移和丢包击垮。
正确做法:对高层命令的延迟、噪声和保持策略也做训练建模。
小节练习¶
- 为“Go2+ARX5 拿起 0.5 kg 物体慢走”设计 DR 参数表,并说明每个范围如何从硬件估计得到。
- 推导负载质心前移 10 cm 对全身 CoM 的影响,假设机器人质量 15 kg,负载 1 kg。
- 实现目标延迟缓冲,对比无延迟训练和随机延迟训练在 100 ms 部署延迟下的末端误差。
74.7 Teacher-Student 与特权学习:让部署策略少看但不少懂 ⭐⭐⭐¶
这一节解决的问题是:如何利用仿真真值提高训练效率,同时不让部署策略依赖真值。
动机:训练时的信息优势不能浪费¶
仿真中有很多真机难以获得的信息。
摩擦系数、负载质量、精确接触力、外部扰动力、地形高度、物体真实位姿都可以直接读取。
如果完全不用这些信息,训练会更慢。
如果直接给 Actor,部署会失败。
Teacher-Student 和不对称 Actor-Critic 的价值就在这里。
它们让训练过程使用信息优势,而部署策略保持输入可行。
三种常见结构¶
| 结构 | Actor 输入 | Critic/Teacher 输入 | 训练复杂度 | 部署形式 |
|---|---|---|---|---|
| 对称 PPO | 本体 + 命令 | 同 Actor | 低 | 单策略 |
| 不对称 Actor-Critic | 本体 + 命令 | 本体 + 特权 | 中 | 单 Actor |
| Teacher-Student | Student 看本体历史 | Teacher 看特权 | 高 | Student |
| RMA 风格适应 | Base + latent | Encoder 看特权 | 高 | Base + 适应模块 |
不对称 Actor-Critic¶
不对称 Actor-Critic 中,Actor 的输入保持部署可用。
Critic 可以看到特权信息。
PPO 的策略更新依赖 advantage。
Critic 更准确,advantage 噪声更低,Actor 学习更稳定。
部署时只保留 Actor。
这种方法实现简单,适合作为复合 RL 的第一版。
但它没有显式训练 Actor 从历史中推断隐变量。
如果任务强依赖负载、摩擦或接触状态,Teacher-Student 或 RMA 更有效。
Teacher-Student 蒸馏¶
Teacher 输入:
Student 输入:
Teacher 通过 PPO 训练。
Student 用监督学习模仿 Teacher 动作:
如果只用 Teacher 轨迹蒸馏,会出现分布偏移。
Student 有小误差后,会到达 Teacher 轨迹中没有的状态。
因此更稳健的方法是让 Student 参与采样,再用 Teacher 给这些状态标注动作。
这和 DAgger 的思想一致。
RMA 风格 latent 蒸馏¶
RMA 不直接蒸馏动作,而是蒸馏环境 latent。
Teacher Encoder 把特权信息编码为:
Base policy 使用:
部署时 Adaptation Module 从历史估计:
然后:
这种结构的优点是职责清晰。
Base policy 学控制。
Adaptation Module 学辨识。
latent 不必等于真实物理参数。
它只需要包含“控制应该如何改变”的信息。
复合机器人中的特权信息¶
| 特权信息 | 用途 | 不应直接给 Actor 的原因 |
|---|---|---|
| payload mass | 负载适应 | 真机抓取物质量未知 |
| payload CoM | 惯量补偿 | 夹持位置难精确估计 |
| foot friction | 打滑适应 | 真实摩擦不可直接测 |
| ee contact force truth | 接触力学习 | 无力传感器时不可得 |
| external push | 抗扰学习 | 外力不可直接读取 |
| object pose truth | 操作目标 | 视觉估计有噪声延迟 |
| terrain height truth | 感知行走 | 真机只能由传感器估计 |
这些信息可以进入 Critic、Teacher 或用于 reward 计算。
进入 reward 不等于进入 Actor。
例如末端真实误差可以用于奖励,因为训练时需要评价任务完成度。
但 Actor 输入的末端目标和误差必须模拟部署可获得的估计。
代码:冻结 Teacher 的动作蒸馏¶
import torch
import torch.nn.functional as F
def distill_step(student, teacher, batch, optimizer):
"""Teacher-Student 动作蒸馏的一步。"""
obs_student = batch["obs_student"]
obs_teacher = batch["obs_teacher"]
# Teacher 只提供标签,不参与梯度更新
with torch.no_grad():
target_action = teacher(obs_teacher)
pred_action = student(obs_student)
loss = F.mse_loss(pred_action, target_action)
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(student.parameters(), 1.0)
optimizer.step()
return {"distill_loss": float(loss.detach())}
蒸馏阶段最重要的是冻结 Teacher。
如果 Teacher 也更新,Student 会追逐一个移动目标。
训练会变得不稳定。
代码:latent 蒸馏¶
def latent_distill_step(adaptation, privileged_encoder, batch, optimizer):
"""让历史编码器预测特权编码 latent。"""
history = batch["history_obs_action"]
priv = batch["privileged_obs"]
with torch.no_grad():
z_target = privileged_encoder(priv)
z_pred = adaptation(history)
loss = F.mse_loss(z_pred, z_target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
return {"latent_loss": float(loss.detach())}
latent 蒸馏比动作蒸馏更模块化。
但它要求 Base policy 在训练时已经学会使用 latent。
工程实现上更复杂。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:蒸馏时忘记关闭 Teacher 梯度
错误做法:Teacher 和 Student 同时进入优化器。
现象:loss 震荡,Student 总追不上 Teacher。
根本原因:标签本身在变化。
正确做法:Teacher
eval(),参数requires_grad_(False),并在torch.no_grad()中产生标签。💡 概念误区:Student 性能差是因为信息少,无法避免
错误想法:Teacher 看真值,Student 不看真值,所以 Student 必然很差。
实际情况:很多隐变量能从历史响应中推断,例如负载、摩擦和接触。
正确理解:Student 的关键是历史窗口和训练分布,而不是单帧输入。
🧠 思维陷阱:所有任务都要两阶段蒸馏
错误想法:Teacher-Student 更高级,所以默认使用。
实际情况:平地短时末端跟踪可能用不对称 Actor-Critic 就足够。
正确做法:先用简单结构建立基线,再为感知、负载或接触复杂任务加入蒸馏。
小节练习¶
- 设计一个不对称 Actor-Critic 观测配置:Actor 看哪些量,Critic 看哪些量。
- 为随机负载任务设计 RMA latent,说明历史窗口应包含哪些信号。
- 分析动作蒸馏和 latent 蒸馏在四足臂推门任务中的优缺点。
74.8 训练流程:从能动到能用 ⭐⭐¶
这一节解决的问题是:如何把复合 RL 训练拆成可诊断、可复现的阶段。
动机:不要从最难任务开始¶
一个常见失败路径是:一开始就让机器人在随机地形上边走边抓取随机物体,还打开全部 DR。
这会让失败原因不可定位。
策略不收敛时,你不知道是末端目标不可达、奖励冲突、动作 scale 过大、随机化过强,还是模型坐标系错。
复合 RL 训练应像调试控制器一样分阶段。
先验证站立。
再验证臂跟踪。
再加入慢速行走。
再加入负载和延迟。
最后再做复杂任务。
推荐训练阶段¶
| 阶段 | 任务 | 主要目标 | 通过标准 |
|---|---|---|---|
| P0 | 单环境随机动作 | 环境无 NaN | 200 步无异常 |
| P1 | 站立保持 | 基座稳定 | roll/pitch 小 |
| P2 | 站立末端跟踪 | 臂能到目标 | EE 误差下降 |
| P3 | 慢速行走 | 腿能稳走 | 速度跟踪可用 |
| P4 | 行走中末端跟踪 | 耦合学习 | EE 与 base 同时稳定 |
| P5 | 随机负载 | 鲁棒操作 | 不同负载成功 |
| P6 | 延迟与外力 | 部署鲁棒 | sim2sim 稳定 |
| P7 | 任务流水线 | 高层接口 | 端到端完成 |
Curriculum 设计¶
课程学习不是只调地形难度。
复合任务至少有五个课程轴。
| 课程轴 | 从简单到复杂 |
|---|---|
| 末端目标距离 | 近距离小范围到远距离大范围 |
| 目标速度 | 静态点到连续轨迹 |
| 行走速度 | 站立到慢走到正常走 |
| 负载质量 | 空载到轻载到任务负载 |
| 扰动强度 | 无外力到小外力到任务外力 |
这些课程轴不要同时快速增加。
每次只增加一个主要难度。
否则训练曲线变化无法解释。
评估指标¶
复合 RL 的评估不能只看 episode reward。
至少应记录以下指标。
| 指标 | 单位 | 意义 |
|---|---|---|
| EE position RMSE | m | 末端位置精度 |
| EE orientation RMSE | rad | 末端姿态精度 |
| base roll/pitch RMS | rad | 基座稳定性 |
| base angular velocity RMS | rad/s | 晃动程度 |
| CoM margin | m | 支撑裕度 |
| torque RMS | Nm | 能耗和安全 |
| action rate RMS | normalized | 平滑度 |
| fall rate | % | 安全性 |
| collision rate | % | 自碰撞风险 |
| success rate | % | 任务完成度 |
训练日志解读¶
| 现象 | 可能原因 | 优先检查 |
|---|---|---|
| reward 上升但成功率不升 | reward hacking | 成功条件和分项奖励 |
| EE 误差下降但摔倒率上升 | 末端权重过高 | base 奖励和动态权重 |
| torque 很低但不动 | 能耗惩罚过强 | torque/action 正则 |
| action_rate 很高 | 动作噪声或频率过高 | decimation 和平滑奖励 |
| 训练早期大量终止 | action scale 或终止过严 | 随机动作回放 |
| 加 DR 后崩溃 | 随机化范围过大 | 单项 DR 扫描 |
代码:训练前配置检查¶
def validate_whole_body_cfg(cfg):
"""训练前做静态检查,尽早发现维度和权重错误。"""
assert cfg.action_dim == cfg.num_leg_joints + cfg.num_arm_joints + cfg.num_gripper_joints
assert cfg.leg_action_scale > cfg.arm_action_scale
assert cfg.arm_action_scale > 0.0
assert cfg.gripper_action_scale >= 0.0
assert "ee_position_tracking" in cfg.reward_weights
assert "base_orientation" in cfg.reward_weights
assert "action_rate" in cfg.reward_weights
for name, weight in cfg.reward_weights.items():
if not isinstance(weight, float):
raise TypeError(f"{name} 的权重必须是 float")
if cfg.domain_rand.payload_mass_max > cfg.robot_mass * 0.2:
print("警告:负载质量超过机器人质量 20%,需要确认硬件可行性")
print("配置检查完成")
这类检查不能替代仿真,但能防止低级配置错误进入长训练。
sim2sim 测试¶
在真机部署前,应先做 sim2sim。
训练可能在 IsaacLab 中完成。
部署前可以把策略放到 MuJoCo、Gazebo 或另一个 IsaacLab 场景中测试。
sim2sim 的目的不是证明真机一定成功。
它用于暴露对仿真器细节的过拟合。
如果策略在 IsaacLab 中很好,在 MuJoCo 中完全失败,说明它可能依赖了 PhysX 的接触细节、关节阻尼或观测真值。
sim2sim 中应保留同样的观测接口和动作接口。
只替换物理后端和模型细节。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:训练日志只记录总 reward
错误做法:只看 TensorBoard 的 episode reward。
现象:reward 上升但行为变差,无法定位原因。
正确做法:记录所有奖励分项、关键物理指标和成功率。
💡 概念误区:训练成功等于任务成功
错误想法:平均 reward 足够高就可以部署。
实际情况:reward 是代理指标,可能被策略钻空子。
正确理解:必须用任务成功率、物理安全指标和 sim2sim 稳定性共同判断。
🧠 思维陷阱:失败后立刻改算法
错误想法:PPO 不收敛就换 SAC、换网络、换更大模型。
实际情况:多数失败来自环境、奖励、随机化和动作接口。
正确做法:先做单环境、分项奖励、随机动作、短训练和消融,再考虑算法。
小节练习¶
- 为 Go2+Z1 设计 P0 到 P6 的训练课程,每阶段写出通过标准。
- 选三个指标说明为什么总 reward 无法替代它们。
- 设计一个 sim2sim 验证方案,把 IsaacLab 训练策略迁移到 MuJoCo 回放。
74.9 综合项目:从零搭建 Go2+Arm EE Tracking 环境 ⭐⭐¶
本章的综合项目是搭建一个最小可训练的四足臂全身 RL 环境。
目标不是一次做出完整抓取系统。
目标是建立一个可扩展的低层全身策略:输入本体感知和末端目标,输出腿臂关节位置目标,在站立和慢走中跟踪末端。
项目目标¶
- 加载 Go2+6DOF 臂统一模型。
- 定义 18 或 19 维关节位置动作。
- 构造部署可用 Actor 观测。
- 构造含特权信息的 Critic 观测。
- 实现末端位置与姿态跟踪奖励。
- 实现基座稳定、能耗和平滑正则。
- 加入负载、延迟和外力随机化。
- 完成站立末端跟踪与慢走末端跟踪两阶段训练。
项目目录建议¶
whole_body_ee_tracking/
├── assets/
│ └── go2_arm.usd
├── cfg/
│ ├── env_cfg.py
│ ├── obs_cfg.py
│ ├── reward_cfg.py
│ └── rand_cfg.py
├── tasks/
│ ├── commands.py
│ ├── observations.py
│ ├── rewards.py
│ ├── terminations.py
│ └── events.py
├── train.py
├── play.py
└── export.py
目录拆分要与环境概念一致。
观测、奖励和随机化不要混在一个大文件里。
里程碑 1:站立末端跟踪¶
固定基座速度命令为 0。
末端目标在机器人前方小范围采样。
只要求末端位置跟踪,不要求姿态精度。
奖励权重以基座稳定为主。
通过标准:
| 指标 | 阈值 |
|---|---|
| fall rate | < 5% |
| EE position RMSE | < 8 cm |
| base roll/pitch RMS | < 0.08 rad |
| self collision | 接近 0 |
里程碑 2:慢走末端跟踪¶
加入小速度命令。
例如 \(v_x \in [0, 0.4]\) m/s。
末端目标仍在较小范围。
动态降低末端权重,避免走路时追手过激。
通过标准:
| 指标 | 阈值 |
|---|---|
| velocity tracking RMSE | < 0.15 m/s |
| EE position RMSE | < 12 cm |
| fall rate | < 10% |
| action rate RMS | 持续下降 |
里程碑 3:随机负载¶
在夹爪处附加 0-0.8 kg 负载。
Actor 不直接看负载真值。
Critic 可以看负载质量。
通过标准:
| 负载 | 目标 |
|---|---|
| 0 kg | 保持基线性能 |
| 0.3 kg | EE RMSE 增加不超过 30% |
| 0.8 kg | 不摔倒,允许精度下降 |
里程碑 4:目标延迟¶
对末端目标加入 0-100 ms 随机延迟。
训练时记录目标时间戳。
评估时固定延迟 100 ms。
通过标准:
| 情况 | 目标 |
|---|---|
| 无延迟 | 基线精度 |
| 50 ms | 精度轻微下降 |
| 100 ms | 不出现持续振荡 |
项目中的关键检查点¶
| 检查点 | 检查方法 |
|---|---|
| FK 正确 | 关节手动扫描,末端移动方向可视化 |
| 目标可达 | 采样目标用 IK 或几何范围过滤 |
| 奖励量级 | 训练前随机策略统计分项均值 |
| 动作安全 | 随机动作 roll/pitch 不爆炸 |
| DR 生效 | 每个 episode 打印随机参数抽样 |
| 导出一致 | 导出策略与训练回放动作一致 |
综合练习¶
- 完成站立末端跟踪训练,并画出 EE RMSE、base roll/pitch、torque RMS 三条曲线。
- 在同一策略上分别测试空载、0.3 kg、0.8 kg,写出负载对基座稳定性的影响。
- 把 body-frame 末端目标改成 task-frame 末端目标,比较目标延迟下的世界系 EE 抖动。
- 将 Critic 特权信息中的 payload mass 删除,观察训练速度和最终鲁棒性变化。
- 结合
复合/30_多模态MPC,解释如果把 EE tracking 从 reward 移到 MPC cost,会改变哪些接口。
74.10 延伸阅读¶
| 主题 | 推荐材料 | 难度 | 阅读目的 |
|---|---|---|---|
| 腿足 RL 训练栈 | 足式/190_腿足RL训练栈 |
⭐⭐ | 回顾 IsaacLab、PPO、DR、Teacher-Student |
| 简化模型 | 足式/70_腿足简化模型理论 |
⭐⭐ | 理解 CoM、ZMP、SRBD 与稳定性 |
| 统一动力学 | 复合/20_浮动基座臂统一动力学 |
⭐⭐⭐ | 理解臂对基座的耦合 |
| 多模态 MPC | 复合/30_多模态MPC |
⭐⭐⭐ | 理解 EE cost、自碰撞与力控 |
| Deep-WBC | Fu et al. CoRL 2022 | ⭐⭐⭐ | 统一四足臂策略 |
| Visual WBC | Liu et al. CoRL 2024 | ⭐⭐⭐ | 视觉高层与低层全身策略 |
| UMI-on-Legs | Ha et al. CoRL 2024 | ⭐⭐⭐ | task-frame EE tracking 与跨平台操作 |
| RMA | Kumar et al. RSS 2021 | ⭐⭐⭐ | 历史适应模块 |
延伸阅读的重点不是记住每篇方法名称。
阅读时应追问四个问题。
第一,它把哪些信息给 Actor,哪些信息给 Teacher 或 Critic。
第二,它的动作空间是统一关节目标、残差动作,还是分层接口。
第三,它如何平衡末端任务和基座稳定。
第四,它如何处理 sim2real gap。
🔧 故障排查手册¶
| 症状 | 可能原因 | 排查步骤 | 相关知识 |
|---|---|---|---|
| 训练一开始大量摔倒 | action scale 过大或 PD 增益不合适 | 1. 随机小动作回放 2. 降低臂 scale 3. 检查默认姿态 | 74.4 |
| EE reward 不上升 | 目标不可达或坐标系错误 | 1. 可视化目标 2. 手动 FK 验证 3. 暂时固定基座 | 74.3/74.5 |
| EE 准但基座很晃 | 末端权重过高 | 1. 打印 base 指标 2. 降低行走时 EE 权重 3. 加动作平滑 | 74.5 |
| 策略不动 | 能耗或动作惩罚过强 | 1. 关闭部分正则 2. 提高任务奖励 3. 检查 alive 奖励 | 74.5 |
| 加负载后失败 | payload DR 不足 | 1. 增加负载随机化 2. 检查负载是否作用到物理模型 3. 加历史观测 | 74.6/74.7 |
| 导出后行为异常 | 观测顺序或归一化不一致 | 1. 打印训练和部署观测 2. 用固定状态比对 3. 共用观测配置 | 74.2/74.3 |
| sim2sim 失败 | 过拟合仿真器接触或电机模型 | 1. 增加 DR 2. 调整电机延迟 3. 比较接触力和力矩 | 74.6/74.8 |
| 自碰撞频繁 | 臂目标采样不安全或缺少碰撞惩罚 | 1. 可视化目标范围 2. 加自碰撞终止 3. 缩小腕部 scale | 74.4/74.5 |
| 末端高频抖动 | 策略频率过高或 action_rate 太弱 | 1. 提高 decimation 2. 加动作二阶平滑 3. 检查目标延迟 | 74.4/74.6 |
| Critic loss 很低但 Actor 部署失败 | 特权信息泄漏到 Actor 或训练部署观测不一致 | 1. 检查 policy 观测组 2. 删除不可部署输入 3. 做导出回放 | 74.3/74.7 |
本章小结¶
| 知识点 | 核心结论 | 工程动作 |
|---|---|---|
| 问题建模 | 复合 RL 难在耦合和部分可观测 | 用历史和特权学习处理隐变量 |
| IsaacLab 环境 | 管理器对应 MDP 构件 | 分离 action、observation、reward、event |
| 观测空间 | Actor 只能看部署可得信息 | 命名观测顺序并统一训练部署 |
| 动作空间 | 关节位置目标是安全先验 | 腿臂夹爪分组 scale |
| 奖励设计 | 末端精度必须服从稳定安全 | 分项记录并动态调权 |
| 随机化 | 含臂系统要随机负载、延迟和末端外力 | 分阶段扩大 DR |
| 特权学习 | 训练可用真值,部署不能依赖真值 | 使用不对称 Critic、Teacher-Student 或 RMA |
| 训练流程 | 从能动到能用需要阶段化 | P0 到 P7 逐步验证 |
本章回答了“如何把腿足 RL 扩展成全身 RL”的基础问题。
下一章将把低层全身策略放进更完整的操作技能接口中,讨论高层 Diffusion Policy、ACT、VLA 或遥操作系统如何向低层发送 task-frame 末端命令,并让机器人在移动中执行抓取、推拉和工具使用。
章末统一练习与故障排查¶
⚠️ 易错点一:只看单个指标。 40_RL全身控制基础 中的任何结论都应同时检查任务指标、物理约束和软件接口。只看总误差或总奖励,容易把模型错误误判为参数问题。
💡 易错点二:忽略坐标系和时间戳。 复合机器人控制链很长,坐标系、采样频率和延迟一旦没有显式记录,后续所有优化和学习结果都会失去解释力。
🧠 易错点三:把演示成功当成系统可靠。 教学实验应至少包含一次扰动、一次异常输入和一次日志复盘,才能说明方法的边界。
练习¶
- 选择本章一个核心公式,写出每一项的单位、坐标系和数据来源。
- 选择本章一个代码片段,说明它依赖哪些配置项;如果配置错一个符号,会出现什么日志现象?
- 设计一个只改变单个因素的实验,用来验证本章最关键的工程判断。
本质洞察:复合机器人文档中的公式、代码和项目不是三块孤立内容。公式定义可行边界,代码实现边界,项目用日志证明边界是否真实存在。
故障排查¶
| 症状 | 优先怀疑 | 验证动作 |
|---|---|---|
| 仿真正常但部署异常 | 观测、坐标系或时间戳不一致 | 用同一段日志离线回放训练端和部署端 |
| 指标突然变差 | 模式切换、限幅或安全壳触发 | 画出模式、保护标志和控制命令 |
| 调参没有效果 | 根因不是权重而是模型假设错误 | 回到最小实验,关闭无关模块 |
| 结果难以复现 | 配置没有版本化 | 保存模型哈希、配置哈希和随机种子 |