跳转至

具身智能数据归一化

背景知识
  • 具身智能数据类型:机器人学习的通用四元组(图像、本体感知、动作、语言指令) → 详见
  • LeRobot Dataset v3:Hugging Face 的机器人数据格式,stats.json 存储归一化统计量 → 详见

机器人各数据模态的数值范围差异悬殊——关节角度在 [-π, π] rad,末端位置在 [0, 2] m,夹爪在 [0, 1],图像在 [0, 255]。不做归一化,梯度会被大尺度特征主导,小尺度特征学不动。

归一化的核心是两步:预计算统计量(离线扫描数据集)→ 训练时线性变换(将每个维度映射到统一尺度)。


归一化方案

公式中 \(\varepsilon\)\(10^{-6}\)\(10^{-8}\),防止除零。

方案 公式 输出范围 一句话目的
z-score \((x - \mu) / (\sigma + \varepsilon)\) 无界 按实际分布缩放,保留形状;默认选择
min-max \(2(x - x_{\min}) / (x_{\max} - x_{\min} + \varepsilon) - 1\) [-1, 1] 有硬边界的量映射到固定范围
分位数 \(2(x - q_{01}) / (q_{99} - q_{01} + \varepsilon) - 1\) [-1, 1] min-max 的鲁棒版,自动忽略两端各 1% 离群值
宽分位数 \(2(x - q_{10}) / (q_{90} - q_{10} + \varepsilon) - 1\) [-1, 1] 更激进的离群值处理,忽略两端各 10%

按数据模态分述

图像

图像归一化最简单,几乎所有框架都用同一种方式:

\[\text{img}_{\text{norm}} = \frac{\text{img}_{\text{uint8}}}{255}\]

映射到 [0, 1],不需要从数据集预计算统计量。

框架 图像归一化 备注
LeRobot / Octo / OpenPI / GR00T [0, 1] uint8 / 255
ACT / ALOHA(原始实现) ImageNet mean/std 历史遗留,新实现已改为 [0, 1]
Diffusion Policy [0, 1] 或 [-1, 1] 取决于任务配置

深度图例外:标准归一化会破坏度量信息。深度数据需要保留米/毫米尺度,通常用 log 压缩或专用的 metric-aware 归一化。

本体感知(State)与动作(Actions)

这两类数据结构相同(float32 向量),归一化方式也一致。所有框架都做逐维度归一化——每个维度独立计算统计量,独立变换。但不同物理量的数值特征差异很大,适合的方案也不同:

关节角度(rad)→ z-score

关节角度的硬件极限通常是 [-π, π](或更小,如 [-2.87, 2.87]),但实际操作中每个关节的使用范围差异很大:肩关节可能在 [-2.5, 2.5] 大范围摆动,而腕关节可能只在 [-0.3, 0.3] 微调。

如果用 min-max,尺度由硬件极限决定,腕关节的有效分辨率就被浪费了([-0.3, 0.3] 映射到 [-1, 1] 中一小段)。z-score 按实际分布的 μ 和 σ 缩放,每个关节都能充分利用归一化后的数值范围。

此外,不同关节的数据分布通常近似正态(围绕常用姿态聚集),z-score 天然适配。

末端位置(m)→ min-max 或分位数

末端位置受工作空间物理约束,有明确的硬边界(如 Franka 的 x: [0.2, 0.8] m, z: [0.0, 0.6] m),且 x/y/z 各轴的范围可能差几倍。min-max 将每个轴独立映射到 [-1, 1],直观且不浪费分辨率。

多任务数据集中,不同任务使用工作空间的不同区域,少数轨迹可能触及极端位置。此时分位数(q01/q99)比 min-max 更稳健——不会被一条异常轨迹的极端坐标拉垮。

末端姿态(旋转)→ z-score

旋转有多种表示,归一化策略不同:

表示 值域 注意事项 方案
欧拉角 [-π, π] ±π 处不连续(-π 和 +π 是同一姿态),z-score 的 μ 会被"撕裂" z-score 可用但不理想,如果数据跨越 ±π 边界需特殊处理
四元数 [-1, 1] 单位球面约束,q 和 -q 是同一旋转 z-score(先统一符号消歧)
6D 连续表示 [-1, 1] 无不连续性,最适合学习 z-score
轴角 角度 [0, π],轴 [-1, 1] 角度为 0 时轴无定义 z-score

实践中 6D 连续表示用得最多(无奇异性,梯度友好),欧拉角因为不连续性正在被淘汰。

关节速度(rad/s)→ z-score

速度数据的典型特征是以 0 为中心对称分布——机器人大部分时间在缓慢运动(近零),偶尔快速移动。z-score 正好将 0 映射到 0,σ 捕捉运动幅度的尺度。

速度数据噪声通常较大(传感器分辨率有限),如果数据质量差可以考虑分位数方案。

夹爪 → 通常不归一化

夹爪值域本身就在合理范围内,且分布特殊:

夹爪类型 值域 分布特征 做法
二值开关 {0, 1} 或 只有两个值 不归一化
连续位置 [0, 1](归一化后) 强双峰(大部分时间全开或全闭) 不归一化(已在合理范围)
原始位置 [0, 0.08] m(如 Franka) 同上但尺度小 min-max 映射到 [0, 1]

对连续夹爪做 z-score 会很别扭——双峰分布的 μ 落在两个峰之间的空白处,σ 被峰间距主导,归一化后的值域不直观。

速查表

物理量 值域 分布特征 推荐方案 原因
关节角度 [-π, π] 近正态,各关节范围不同 z-score 按实际使用范围缩放,不浪费分辨率
末端位置 工作空间内 有硬边界,各轴范围不同 min-max分位数 有界量用有界归一化;多任务用分位数更稳健
末端姿态 视表示而定 可能有不连续性 z-score 6D 表示最友好;欧拉角需注意边界
关节速度 ±最大速度 以 0 为中心对称 z-score 零中心对称,天然适配
夹爪 [0, 1] 或 双峰 不归一化 值域已合理,双峰分布不适合 z-score

语言指令

不做数值归一化。经 tokenizer(BPE / SentencePiece)转为 token 序列后直接送入模型。


Delta Actions 的归一化陷阱

如果使用增量动作(delta actions = action - current_state),统计量必须在增量值上计算,不能用绝对动作的统计量:

做法 归一化后的"零增量" 结果
用 delta 统计量归一化 delta 0 → 0 正确
用绝对动作统计量归一化 delta 0 → 非零(如 0.345) 错误,模型学到的"不动"不是零

OpenPI 的 DeltaActions transform 先做差分,然后由 compute_norm_stats.py 在差分后的数据上计算统计量。


Per-Timestep 归一化

Action chunk 中不同时间步的统计分布不同——chunk 开头的动作通常是小幅精调,越往后变化越大。对整个 chunk 用同一组 μ/σ 会模糊这种差异。

Per-timestep 方案:为 chunk 中每个时间步位置单独计算 μ[t] 和 σ[t]:

\[x_{\text{norm}}[t] = \frac{x[t] - \mu[t]}{\sigma[t] + \varepsilon}\]

Tedrake 的实验显示,per-timestep 归一化带来的性能提升比架构改动大 40 倍(success rate 提升 20+ 个百分点)1。VLA Foundry 已内置支持此选项。


动作离散化:另一条路

RT-2 和 OpenVLA 不做连续归一化,而是将动作离散化为 token

  1. 先用 q01/q99 将动作归一化到 [-1, 1]
  2. 均匀切分为 256 个 bin
  3. 每个维度的动作映射到最近的 bin index
  4. Bin index 作为 token ID 送入自回归语言模型
\[\text{bin} = \left\lfloor 255 \cdot \frac{x - x_{\min}}{x_{\max} - x_{\min}} \right\rfloor\]
\[x_{\text{recover}} = x_{\min} + (\text{bin} + 0.5) \cdot \frac{x_{\max} - x_{\min}}{256}\]

FAST tokenizer 在此基础上加了 DCT(离散余弦变换)+ BPE 压缩,将一个 action chunk 压缩为更少的 token,推理快 15 倍。


统计量的计算与存储

计算方式

框架 方法 分位数估算
LeRobot RunningQuantileStats,增量式 5000-bin 自适应直方图
OpenPI RunningStats,Welford 算法 5000-bin 自适应直方图
Diffusion Policy 全量加载后直接计算 numpy percentile

直方图方法的好处:不需要把整个数据集加载到内存,扫描一遍即可。bin 边界随 min/max 变化自动调整。

存储格式

LeRobot meta/stats.json

{
  "observation.state": {
    "mean": [...], "std": [...],
    "min": [...], "max": [...],
    "q01": [...], "q10": [...], "q50": [...], "q90": [...], "q99": [...],
    "count": 50000
  }
}

OpenPI norm_stats.json

{
  "state": { "mean": [...], "std": [...], "q01": [...], "q99": [...] },
  "actions": { "mean": [...], "std": [...], "q01": [...], "q99": [...] }
}

OpenPI 只存 q01/q99(分位数归一化够用),LeRobot 存 5 个分位数(支持多种归一化模式切换)。

统计量的作用域

作用域 做法 适用场景
Per-dataset 每个数据集独立计算 单数据集训练(最常见)
Per-robot 同一机器人的所有数据集合并 跨任务微调
Global 所有数据集合并 大规模跨形态预训练

多数据集合并统计量时需按样本数加权:\(\mu_{\text{global}} = \frac{\sum n_i \mu_i}{\sum n_i}\)


跨形态训练的归一化挑战

不同机器人形态(Franka 7-DoF、ALOHA 14-DoF、UR5e 6-DoF)的状态/动作维度、值域、物理含义完全不同,但需要送入同一个模型。

策略 做法 代表框架
Per-robot 统计量 + 推理时选择 每个机器人存独立 norm_stats,推理时按 robot_id 选取 OpenVLA (unnorm_key)
统一动作空间 + 全局归一化 所有机器人的动作转换到 EEF 6D 表示后统一归一化 X-VLA
零填充 + 统一 action_dim 填充到最大维度,超出的维度始终为零 OpenPI

OpenPI 的方案最简单——归一化在实际有效维度上做,零填充的维度不参与统计。代价是模型内部始终用 action_dim=32 的向量运算,浪费部分算力。


各框架归一化总览

框架 图像 State / Actions 分位数 Per-Timestep
LeRobot [0, 1] z-score(默认) 可选 q01/q99 或 q10/q90
Diffusion Policy [0, 1] min-max(默认)或 z-score
OpenPI (π₀) [0, 1] z-score
OpenPI (π₀.₅ / π₀-FAST) [0, 1] 分位数 q01/q99
Octo [0, 1] z-score 或分位数 可选
OpenVLA [0, 1] q01/q99 → 256-bin 离散化
ACT / ALOHA ImageNet mean/std z-score
VLA Foundry [0, 1] 4 种可选 可选

参考资料


  1. Tedrake et al. Per-Timestep Action Normalization for Robot Learning. MIT. 2025. 实验显示 per-timestep 归一化对 success rate 的提升比架构改动大 40 倍。