四足机器人强化学习系列 03:Observation 里面到底放什么

上一篇整理:02:用四足机器人理解 MDP

写在前面:Observation 是 policy 的“输入接口”

上一篇把四足 locomotion 任务先放进了 MDP 框架里:状态、动作、转移、奖励、折扣因子。

这一篇继续往下拆一个最容易在代码里遇到、但也最容易被忽略的东西:observation

我现在对 observation 的理解是:它不是“机器人真实拥有的所有信息”,而是我选择让 policy 看到的那一串数字。这串数字决定了神经网络凭什么做判断,也决定了后面真机部署时我能不能把同样的输入喂给它。

如果 action 是 policy 的输出接口,那么 observation 就是 policy 的输入接口。

一句话结论

Observation 是 policy 真正能看到的信息。它通常由 IMU、编码器、命令、上一帧 action、地形信息等拼成一个固定顺序的一维向量。四足机器人强化学习里,obs 设计的核心不是“给得越多越好”,而是“训练时能稳定学,部署时也能真实拿到”。

可以先记住这个形式:

at=π(ot)a_t=\pi(o_t)

其中:

  • oto_t:当前 observation。
  • π\pi:policy 网络。
  • ata_t:policy 输出的 action。

也就是说,policy 并不是直接根据完整世界状态 sts_t 做决策,而是根据我喂给它的 oto_t 做决策。

State 和 Observation 不完全一样

理论上 MDP 里常说 state sts_t。但在四足机器人代码里,更常见的是 obs、observations、obs_buf。

我现在先把两者区分成:

state:环境真实完整状态
observation:policy 实际拿到的状态切片 / 传感器量 / 人为构造量

比如仿真器内部可能知道:

  • 机器人 base 在世界坐标系下的精确位置。
  • 机器人 base 的精确线速度。
  • 每个足端的接触力。
  • 地面真实摩擦系数。
  • 机器人质量、惯量、关节摩擦。
  • 地形完整高度图。
  • 所有外部扰动。

但真机上 policy 不一定能直接知道这些。

所以真正喂给 policy 的 observation 往往会更克制:

IMU 可以测到的
电机编码器可以测到的
上层命令给出的
上一帧自己输出过的
少量可以估计或采样的

这也是为什么很多四足 locomotion 任务严格说更像 POMDP,而不是完全可观测的 MDP。

一个常见的四足 locomotion observation 结构

以速度跟踪任务为例,常见 observation 可以写成:

ot=[ωbasegprojccmdqqdefaultq˙at1]o_t= \begin{bmatrix} \omega_{base} \\ g_{proj} \\ c_{cmd} \\ q-q_{default} \\ \dot q \\ a_{t-1} \end{bmatrix}

对应成更工程一点的列表就是:

base_ang_vel
projected_gravity
velocity_commands
dof_pos - default_dof_pos
dof_vel
previous_action

如果机器人有 12 个关节,一个典型维度可能是:

项目 维度 含义
base angular velocity 3 机身角速度
projected gravity 3 重力方向投影到机身坐标系
commands 3 期望 vx,vy,ωzv_x, v_y, \omega_z
joint position error 12 关节角相对默认站姿偏差
joint velocity 12 关节速度
previous action 12 上一帧 policy 输出
合计 45 policy 输入维度

所以一个很常见的 obs 维度就是 45 维。当然不同项目会不一样,加入地形高度采样以后可能一下变成一百多维、两百多维。

注意:表格只是理解用,具体维度一定要以代码里的 num_obs 或 observation 拼接逻辑为准。

1. base angular velocity:机身角速度

base angular velocity 通常是机身角速度,可以理解成 IMU 陀螺仪能给出的量。

它一般是 3 维:

ωbase=[ωx,ωy,ωz]\omega_{base}=[\omega_x,\omega_y,\omega_z]

这个量告诉 policy:

身体现在是不是正在翻滚?
是不是正在俯仰?
是不是正在偏航旋转?

对四足机器人来说,这很重要。因为 locomotion 不只是腿在动,身体姿态的稳定也很关键。

比如机器人快要向前栽倒时,pitch 方向角速度会变大;policy 如果能看到这个量,就有机会通过腿部动作把身体拉回来。

2. projected gravity:比 roll/pitch 更常见的姿态表达

很多四足 RL 代码不会直接把 roll、pitch、yaw 喂给 policy,而是喂 projected gravity。

直觉上,projected gravity 表示:

重力方向在机器人机身坐标系下看起来指向哪里

如果机器人是正着站的,重力大致沿着机身坐标系的负 z 方向;如果机器人身体歪了,重力在机身坐标系下的投影也会跟着变。

它通常也是 3 维:

gprojR3g_{proj}\in\mathbb{R}^3

我现在把它理解成 policy 的“身体倾斜感”。它告诉策略:

  • 机身是不是向前倾了。
  • 机身是不是向侧面倒了。
  • 机身是不是接近翻车了。

为什么不直接给欧拉角?

主要有几个原因:

  1. 欧拉角有奇异性和跳变问题。
  2. yaw 对很多速度跟踪任务不一定重要。
  3. projected gravity 对姿态倾斜表达更直接。
  4. 它和 IMU 姿态估计结果更容易对应。

比如四足机器人在原地朝不同方向转头,只要身体没有倾倒,重力在机身坐标系下的投影不会因为世界系 yaw 改变而无意义地变化。这对学习稳定步态是有好处的。

3. commands:上层希望机器人怎么走

commands 是速度命令,通常来自上层控制器、键盘、遥控器或者课程采样器。

常见是 3 维:

ccmd=[vxcmd,vycmd,ωzcmd]c_{cmd}=[v_x^{cmd},v_y^{cmd},\omega_z^{cmd}]

分别表示:

  • vxcmdv_x^{cmd}:希望机器人向前 / 向后走多快。
  • vycmdv_y^{cmd}:希望机器人横向走多快。
  • ωzcmd\omega_z^{cmd}:希望机器人绕 z 轴转多快。

为什么 commands 要放进 observation?

因为同一个 policy 通常不是只学一种速度,而是要学一个“速度跟踪器”。

如果不给 command,policy 不知道当前到底应该:

站着不动
向前走
向后走
横移
左转
右转

给了 command 之后,policy 学到的是一个条件策略:

at=π(ot,ccmd)a_t=\pi(o_t,c_{cmd})

也就是:在当前身体状态下,结合目标速度,输出合适的腿部动作。

4. joint position:通常给相对默认姿态的偏差

关节角是四足机器人最基础的本体感知信息之一。

但常见做法不是直接给 qq,而是给:

qqdefaultq-q_{default}

其中:

  • qq:当前关节角。
  • qdefaultq_{default}:默认站姿关节角。

这样做的好处是让输入更居中。默认站姿附近的关节角偏差接近 0,神经网络更容易处理。

如果机器人有 12 个主动关节,这一项就是 12 维。

FL_hip, FL_thigh, FL_calf,
FR_hip, FR_thigh, FR_calf,
RL_hip, RL_thigh, RL_calf,
RR_hip, RR_thigh, RR_calf

具体顺序非常关键。训练和部署必须完全一致。

5. joint velocity:关节速度

关节速度 q˙\dot q 也是常见 observation。

它告诉 policy:

每个关节现在正在往哪个方向动?动得快不快?

只知道关节位置还不够,因为同一个关节角下,关节可能正在快速向前摆,也可能正在快速向后收。速度信息能帮助 policy 判断运动趋势。

如果有 12 个关节,这一项也是 12 维。

不过真机上关节速度可能噪声比较大,所以工程里经常会做滤波,或者在训练中加噪声,让 policy 不要过度依赖过于干净的速度值。

6. previous action:上一帧动作为什么要放进去

上一帧 action at1a_{t-1} 在四足 RL 里很常见。

它看起来有点奇怪:上一帧动作不是 policy 自己输出的吗?为什么还要再喂回来?

我的理解是,它至少有三个作用:

  1. 让 policy 知道自己上一刻刚刚命令了什么。
  2. 帮助输出更连续,不要每一帧剧烈跳变。
  3. 在没有显式历史网络的时候,给 policy 一点点“短期记忆”。

很多 reward 里还会惩罚 action rate:

atat12\|a_t-a_{t-1}\|^2

这会鼓励 action 不要抖得太厉害。

把 previous action 放到 observation 里,也让 policy 更容易根据上一帧动作调整当前动作。

复杂地形时会加入 height samples

如果任务不是平地速度跟踪,而是走楼梯、斜坡、石头路等复杂地形,policy 往往还需要看到地形信息。

一种常见做法是在机器人周围采样一圈或一片高度点:

height samples around base

比如在机身前方、侧方采样若干个点,得到相对机身高度。

这相当于告诉 policy:

前面有没有台阶?
左脚附近是不是有坑?
右前方是不是有凸起?

如果没有地形 observation,机器人只能靠接触之后的反馈来反应;如果提前看到高度采样,就能更早调整摆腿高度和落脚位置。

但 height samples 也有部署问题:

  • 真机上需要深度相机、激光雷达或其他地形估计模块。
  • 感知延迟会影响 policy。
  • 高度图噪声会影响策略稳定性。
  • 仿真里的完美高度信息不一定能在现实中复现。

所以是否加入地形观测,要看任务目标。如果只是先学平地 locomotion,可以不急着加。

privileged observation:训练时能看,部署时不能看

四足 RL 里还经常出现一个概念:privileged observation。

它指的是:训练时 critic 或 teacher 可以看到的额外信息,但部署时 actor / policy 不直接看到。

比如:

真实摩擦系数
机器人质量扰动
外部推力
精确机身线速度
足端接触力
地形真实参数

这些信息在仿真里很好拿,但真机上不一定能拿到。

一个常见结构是:

actor observation:部署时 policy 能看到的信息
critic observation:训练时 value function 能看到的更多信息

也就是说:

at=π(otactor)a_t=\pi(o_t^{actor})

而 critic 可能用:

V=V(otcritic)V=V(o_t^{critic})

这样做的好处是训练时 value 估计更准,但最终部署的 actor 仍然只依赖真机可获得的信息。

我后面看代码时要注意区分:

num_observations
num_privileged_obs
obs_buf
privileged_obs_buf
critic_obs

它们不一定是同一个东西。

为什么 observation 不是越多越好

直觉上可能会觉得:信息越多,policy 越聪明。

但四足机器人里不一定。

因为多给的信息可能带来几个问题:

  1. 真机拿不到
    仿真里能直接读 base linear velocity,真机上可能只能估计。

  2. 噪声太大
    传感器信号不干净,policy 如果过度依赖,部署会变脆。

  3. 维度变高,训练更难
    不重要的信息太多,网络要自己学会忽略。

  4. 造成仿真作弊
    policy 在仿真里利用了现实不存在的信息,sim2real 时直接失效。

  5. 不同频率和延迟
    IMU、电机、视觉、高度图的更新频率可能不同,把它们强行拼一起会引入时序问题。

所以 observation 设计的原则不是“全塞进去”,而是:

足够完成任务
尽量贴近真实传感器
维度别太臃肿
顺序和尺度稳定
部署时能复现

observation 的尺度也很重要

神经网络不喜欢不同输入量级差异太大。

比如:

角速度可能是 0~几 rad/s
关节角可能是 -1~1 rad
关节速度可能是 -几十 rad/s
速度命令可能是 -2~2 m/s
height samples 可能是 -1~1 m

如果不做 scale,有些量会在网络输入里显得特别大,有些量几乎被淹没。

所以代码里经常有类似:

obs = torch.cat((
base_ang_vel * obs_scales.ang_vel,
projected_gravity,
commands * commands_scale,
(dof_pos - default_dof_pos) * obs_scales.dof_pos,
dof_vel * obs_scales.dof_vel,
actions,
), dim=-1)

这些 scale 不是随便写的,它们会影响训练稳定性。

我看代码时应该特别关注:

obs_scales.ang_vel
obs_scales.dof_pos
obs_scales.dof_vel
commands_scale
clip_observations

因为最后喂给 policy 的不是原始物理量,而是缩放、裁剪、拼接后的向量。

observation noise:训练时故意加噪声

真机传感器一定有噪声,所以训练时经常会给 observation 加噪声。

例如:

角速度加一点噪声
projected gravity 加一点噪声
关节角加一点噪声
关节速度加一点噪声
height samples 加一点噪声

这样 policy 不会只适应仿真里的完美观测。

可以把它理解成一种 domain randomization:

不是只随机物理参数,也随机机器人看到的东西

但噪声也不是越大越好。噪声太大会让任务变得过难,policy 学不到稳定规律。

observation history:为什么有时要堆叠历史帧

如果 policy 只看当前一帧 observation,它对过去发生了什么知道得很少。

有些项目会把多帧 observation 拼起来:

o~t=[ot,ot1,ot2,]\tilde o_t=[o_t,o_{t-1},o_{t-2},\cdots]

这样可以给 MLP policy 一点历史信息。

比如:

当前帧看起来一样,但过去几帧趋势不同

堆叠历史后,policy 更容易估计速度、延迟、接触状态等隐含信息。

不过代价是输入维度会变大。例如单帧 45 维,堆 4 帧就是 180 维。

另一种路线是用 RNN / GRU,让网络自己维护隐藏状态。但工程上 MLP + frame stack 更直接。

obs 顺序是部署时最容易踩的坑

这一点非常重要。

训练时 obs 拼接顺序如果是:

角速度 → 重力方向 → 命令 → 关节角 → 关节速度 → 上一帧动作

部署时就必须完全一样。

如果部署时写成:

命令 → 角速度 → 关节角 → 重力方向 → 关节速度 → 上一帧动作

从维度上看可能完全没报错,但 policy 会直接乱掉。

因为神经网络不会知道“第 0~2 维现在不是角速度了”。它只会按训练时学到的权重解释每一维。

这类 bug 很隐蔽:

程序能跑
维度也对
模型也加载成功
但机器人行为完全不对

所以部署前最好做一个 obs 对照表,把训练端和部署端逐项核对。

一个简单的 obs 拼接示例

假设我现在有一个 12 自由度四足机器人,采用平地速度跟踪任务,可以先这样理解:

obs = concat([
base_ang_vel * ang_vel_scale, # 3
projected_gravity, # 3
commands[:, :3] * commands_scale, # 3
(dof_pos - default_dof_pos) * dof_pos_scale, # 12
dof_vel * dof_vel_scale, # 12
actions, # 12
])

维度就是:

3 + 3 + 3 + 12 + 12 + 12 = 45

这 45 个数每一步都会输入 policy,policy 输出下一步 action。

如果加上 187 个 height samples,那可能变成:

45 + 187 = 232

这就是为什么看代码时 num_obs 很重要。它必须和模型训练时的输入维度一致。

真机部署时要核对哪些东西

如果以后要把训练好的 policy 放到真机上,observation 这一块我觉得至少要核对:

  1. 维度是否一致
    训练时 num_obs=45,部署时也必须是 45。

  2. 顺序是否一致
    每一段的排列顺序必须一样。

  3. 关节顺序是否一致
    仿真里的 12 个关节顺序和真机驱动里的顺序可能不同。

  4. 单位是否一致
    rad 还是 degree?m/s 还是 mm/s?

  5. 坐标系是否一致
    角速度是在 body frame 还是 world frame?

  6. 符号是否一致
    某个关节正方向在仿真和真机是否相反?

  7. scale 是否一致
    训练时乘了 obs_scales,部署时也要乘同样的 scale。

  8. clip 是否一致
    训练时 observation clip 到某个范围,部署时也要一致。

  9. 上一帧 action 是否一致
    是网络原始输出?还是 scale 后的 action?这个要看训练代码。

  10. 频率是否一致
    policy 是 50 Hz、100 Hz 还是其他频率?不同频率下 obs 动态分布会变。

这些问题里,最容易出错的是:关节顺序、坐标系、符号、scale。

我现在的理解小结

这篇先把 observation 理成四足 RL 里的“输入接口”。

我现在会这样记:

obs 不是完整 state
obs 是 policy 实际看到的一维向量
obs 通常包含 IMU、关节、本体命令、上一帧动作
复杂地形可能加入 height samples
训练时可能有 privileged obs,但部署 actor 不能依赖它
obs 的顺序、尺度、坐标系、单位必须严格一致

最重要的一句话是:

训练时 policy 看到什么,部署时就必须以同样顺序、同样单位、同样尺度再给它一次。

否则模型本身没坏,机器人也可能跑得很离谱。

下一篇整理:04:Action 为什么常用关节位置目标

algorithms axis-angle bang-bang bode calibration chrome cmake cmakelists cnn colcon conan control cpp cpu d435i data_struct db design-pattern dots economics eigen factory-pattern fcpx figure finance forge fov gazebo gdb git gnu ibus interest isaac gym isaaclab kdl latex launch learning-notes legged locomotion legged-robot life linux mac math matlab matrix memory mlp money motion-control motor moveit mpc network ocs2 ode operator optimal algorithm optimal-control perf performance personal-finance ppo profiling python qos realsense reinforcement learning rnn robot robotics ros ros2 rtb security shell simulation stl thread tools twist ubuntu uml unitree urdf vae valgrind vcxsrv velocity vim web wifi work wsl 中文输入 交叉编译 依赖管理 分支管理 四足机器人 实验诊断 强化学习 机器人视觉 构建系统 深度学习 深度相机 点云 版本控制 神经网络 训练曲线 输入法 配置类
知识共享许可协议