4. LeRobot数据集格式
在模仿学习工程中,数据集格式决定了训练管线的上限。LeRobot 采用了一套“列式存储 + 元数据索引”的混合方案:底层用 Parquet 存放逐帧的观测与动作张量,用 HDF5 兼容格式承载高维视频与原始传感器块,再用 JSON 元数据把 episode、任务、统计量串起来。理解这套结构,是排查“训练不收敛”“回放错位”“归一化异常”等问题的前提。
一、核心原理:为什么是 Parquet + HDF5 双轨
机器人数据有两个截然不同的访问模式。训练时,我们按帧随机采样,需要快速读取某一帧的 observation.state、action 等低维向量;而回放或可视化时,我们按 episode 顺序读取整段视频。Parquet 的列式压缩和谓词下推非常适合前者,HDF5 的分块(chunk)与连续存储则适合后者。LeRobot 因此把低维时序数据写入 Parquet,把视频帧、深度图等大块数据写入 HDF5 或独立视频文件,二者通过 episode_index 与 frame_index 对齐。
二、目录结构与 episode 组织
一个标准的 LeRobot 数据集目录通常如下:
dataset/
├── meta/
│ ├── info.json # 全局元数据:fps、特征名、形状、dtype
│ ├── episodes.jsonl # 每个 episode 的任务、长度、索引
│ ├── tasks.jsonl # 任务文本与 task_index 映射
│ └── stats.json # 各特征的均值、方差、min、max
├── data/
│ └── chunk-000/
│ ├── episode_000000.parquet
│ └── episode_000001.parquet
└── videos/
└── chunk-000/
├── observation.images.top/
│ └── episode_000000.mp4
└── observation.images.wrist/
└── episode_000000.mp4
注意 chunk-000 这一层:当 episode 数量很大时,LeRobot 会把它们分块,避免单目录文件过多导致文件系统性能下降。每个 Parquet 文件对应一个 episode,内部每一行是一帧,列名即特征名,例如 observation.state、action、timestamp、frame_index、episode_index、task_index。这种“一行一帧、一文件一 episode”的设计,让采样器可以精确控制 episode 边界,避免跨轨迹污染。
三、元数据:info.json 与 stats.json
info.json 是数据集的“身份证”,它声明了 fps、特征列表、每个特征的 shape 与 dtype,以及视频编码信息。训练框架读取它来构建输入张量,如果 shape 写错,模型第一层就会报维度不匹配。stats.json 则保存了归一化所需的统计量,通常按特征给出 mean、std、min、max,并且可能区分“全局统计”和“按 episode 统计”。
| 文件 | 作用 | 常见字段 |
|---|---|---|
| info.json | 声明数据集结构与特征规格 | fps、features、shape、dtype、video |
| episodes.jsonl | 逐 episode 索引与任务绑定 | episode_index、tasks、length |
| tasks.jsonl | 任务文本到 task_index 的映射 | task、task_index |
| stats.json | 归一化统计量 | mean、std、min、max、count |
四、时间同步:timestamp 与 fps 的对齐
多传感器采集时,相机、关节编码器、力传感器的采样频率往往不同。LeRobot 的做法是:以统一的目标 fps 为基准,把各模态数据重采样到同一时间轴,并在每帧写入 timestamp。这个 timestamp 是相对 episode 起点的秒数,而不是绝对墙钟时间。训练时,frame_index 与 timestamp 必须满足 timestamp ≈ frame_index / fps,否则说明重采样或丢帧处理有问题。
五、归一化:从 stats.json 到训练输入
模仿学习对输入尺度非常敏感。LeRobot 默认使用均值-方差归一化,把状态和动作映射到相近范围。归一化参数来自 stats.json,而不是训练时临时计算,这样可以保证训练、验证、部署三阶段使用同一套尺度。对于图像,通常只做 /255 或 ImageNet 标准化;对于关节角度和末端位姿,则使用逐维 mean/std。
# 伪代码:按 stats.json 归一化
state_norm = (state - stats["observation.state"]["mean"]) / (stats["observation.state"]["std"] + 1e-8)
action_norm = (action - stats["action"]["mean"]) / (stats["action"]["std"] + 1e-8)
如果发现策略输出动作幅度异常小或异常大,优先检查 stats.json 是否与当前数据集匹配,以及是否在部署时错误地重复归一化。
六、案例:一次“回放错位”的排查
某团队训练 ACT 策略时,回放视频里机械臂动作比画面慢约 0.3 秒。排查发现:相机以 30fps 采集,关节以 100fps 采集,脚本直接按行数截断到最短长度,没有做时间重采样。修复方式是先用 timestamp 做线性插值,把关节数据重采样到 30fps,再写入 Parquet。修复后 timestamp 与 frame_index/fps 的误差小于 1ms,回放同步正常。
七、复盘与检查清单
- 确认
info.json中每个特征的 shape 与实际 Parquet 列一致。 - 确认
episodes.jsonl的 length 之和等于 Parquet 总行数。 - 确认
timestamp单调递增,且与frame_index/fps偏差在容差内。 - 确认
stats.json覆盖所有需要归一化的特征,且 mean/std 非零。 - 确认视频文件与 Parquet 的 episode_index 一一对应,没有缺帧或错位。
掌握这套结构后,你会发现很多“玄学”问题其实都有明确的定位路径:先看 meta 是否自洽,再看 Parquet 与视频是否对齐,最后检查归一化统计量是否匹配。把这三层理顺,模仿学习的训练稳定性会显著提升。