具身智能数据归一化¶
背景知识
机器人各数据模态的数值范围差异悬殊——关节角度在 [-π, π] 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% |
按数据模态分述¶
图像¶
图像归一化最简单,几乎所有框架都用同一种方式:
映射到 [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]:
Tedrake 的实验显示,per-timestep 归一化带来的性能提升比架构改动大 40 倍(success rate 提升 20+ 个百分点)1。VLA Foundry 已内置支持此选项。
动作离散化:另一条路¶
RT-2 和 OpenVLA 不做连续归一化,而是将动作离散化为 token:
- 先用 q01/q99 将动作归一化到 [-1, 1]
- 均匀切分为 256 个 bin
- 每个维度的动作映射到最近的 bin index
- Bin index 作为 token ID 送入自回归语言模型
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 种可选 | 可选 | 是 |
参考资料¶
-
Tedrake et al. Per-Timestep Action Normalization for Robot Learning. MIT. 2025. 实验显示 per-timestep 归一化对 success rate 的提升比架构改动大 40 倍。 ↩