LeRobot v3.0 数据集格式详细分析
LeRobot Dataset v3.0 是 Hugging Face 推出的标准化机器人学习数据格式,提供统一的多模态时序数据访问接口,支持感官运动信号、多摄像头视频,以及丰富的元数据用于索引、搜索和可视化。版本信息: v3.0(包含在 lerobot >= 0.4.0 中)文件组织: v3.0使用文件基础而非episode基础的存储元数据驱动: Episode边界通过元数据而非文件名解析高效访
·
一、概述
LeRobot Dataset v3.0 是 Hugging Face 推出的标准化机器人学习数据格式,提供统一的多模态时序数据访问接口,支持感官运动信号、多摄像头视频,以及丰富的元数据用于索引、搜索和可视化。
版本信息: v3.0(包含在 lerobot >= 0.4.0 中)
二、v3.0 核心特性
2.1 主要改进点
| 特性 | v2.1 (旧版) | v3.0 (新版) |
|---|---|---|
| 存储方式 | 每集一个文件 | 多集合并到大文件中 |
| 元数据格式 | JSON Lines | Apache Parquet |
| 文件组织 | 基于文件名的边界 | 基于元数据的关系查询 |
| 流式加载 | 不支持 | Hub原生流式支持 |
| 文件系统压力 | 高(数千个小文件) | 低(少量大文件) |
| 初始化速度 | 慢 | 快(3-5倍提升) |
2.2 关键优势
- ✅ 文件整合: 多个episode合并到同一个Parquet/MP4文件
- ✅ 关系型元数据: 通过元数据而非文件名解析episode边界
- ✅ Hub流式支持: 可直接从Hub消费数据,无需下载
- ✅ 存储效率: 更少、更大的文件 → 更快的初始化
- ✅ 内存映射: 通过PyArrow实现高效的内存使用
三、数据集结构
3.1 设计理念
v3.0 的核心原则是解耦存储与用户API:
- 存储层: 高效存储(少量大文件)
- API层: 直观的episode级访问
3.2 三大支柱
1. 表格数据 (Tabular Data)
- 格式: Apache Parquet
- 内容: 低维、高频信号(状态、动作、时间戳)
- 访问: 内存映射或通过
datasets库流式访问 - 存储路径:
data/chunk-XXX/file-XXX.parquet
2. 视觉数据 (Visual Data)
- 格式: MP4 视频
- 内容: 摄像头帧,按episode分组
- 组织: 每个摄像头一个分片
- 存储路径:
videos/{camera_key}/chunk-XXX/file-XXX.mp4
3. 元数据 (Metadata)
- 格式: JSON + Parquet
- 内容:
- Schema定义(特征名、数据类型、形状)
- 帧率信息
- 归一化统计
- Episode分割信息(在共享文件中的起止偏移)
3.3 目录结构
dataset_root/
├── meta/ # 元数据目录
│ ├── info.json # 数据集schema信息
│ ├── stats.json # 全局特征统计(归一化用)
│ ├── tasks.parquet # 任务描述映射
│ └── episodes/ # Episode元数据(分块存储)
│ └── chunk-000/
│ └── file-000.parquet # Episode记录(长度、任务、偏移)
│
├── data/ # 表格数据目录
│ └── chunk-000/
│ └── file-000.parquet # 多个episode的帧数据
│
└── videos/ # 视频数据目录
├── front_camera/ # 按摄像头分目录
│ └── chunk-000/
│ └── file-000.mp4 # 多个episode的视频帧
└── wrist_camera/
└── chunk-000/
└── file-000.mp4
3.4 路径模板常量
# 文件路径模板
CHUNK_FILE_PATTERN = "chunk-{chunk_index:03d}/file-{file_index:03d}"
# 各类文件的默认路径
INFO_PATH = "meta/info.json"
STATS_PATH = "meta/stats.json"
DEFAULT_TASKS_PATH = "meta/tasks.parquet"
DEFAULT_EPISODES_PATH = "meta/episodes/" + CHUNK_FILE_PATTERN + ".parquet"
DEFAULT_DATA_PATH = "data/" + CHUNK_FILE_PATTERN + ".parquet"
DEFAULT_VIDEO_PATH = "videos/{video_key}/" + CHUNK_FILE_PATTERN + ".mp4"
# 文件大小限制
DEFAULT_CHUNK_SIZE = 1000 # 每个chunk最多1000个文件
DEFAULT_DATA_FILE_SIZE_IN_MB = 100 # 数据文件最大100MB
DEFAULT_VIDEO_FILE_SIZE_IN_MB = 500 # 视频文件最大500MB
四、元数据文件详解
4.1 meta/info.json
存储数据集的Schema和配置信息:
{
"codebase_version": "v3.0",
"robot_type": "so100",
"fps": 30,
"features": {
"timestamp": {
"dtype": "float32",
"shape": [1],
"names": null
},
"episode_index": {
"dtype": "int64",
"shape": [1],
"names": null
},
"observation.state": {
"dtype": "float32",
"shape": [8],
"names": ["joint_1", "joint_2", ..., "gripper"]
},
"observation.images.front": {
"dtype": "image",
"shape": [480, 640, 3],
"names": ["height", "width", "channels"]
},
"action": {
"dtype": "float32",
"shape": [8],
"names": ["joint_1", "joint_2", ..., "gripper"]
}
},
"data_path": "data/chunk-{chunk_index:03d}/file-{file_index:03d}.parquet",
"video_path": "videos/{video_key}/chunk-{chunk_index:03d}/file-{file_index:03d}.mp4"
}
默认特征(所有数据集都包含):
DEFAULT_FEATURES = {
"timestamp": {"dtype": "float32", "shape": (1,)}, # 时间戳
"frame_index": {"dtype": "int64", "shape": (1,)}, # 帧索引
"episode_index": {"dtype": "int64", "shape": (1,)}, # Episode索引
"index": {"dtype": "int64", "shape": (1,)}, # 全局索引
"task_index": {"dtype": "int64", "shape": (1,)}, # 任务索引
}
4.2 meta/stats.json
存储用于归一化的全局统计信息:
{
"observation.state": {
"mean": [0.0, 0.0, 0.0, ...],
"std": [1.0, 1.0, 1.0, ...],
"min": [-3.14, -3.14, ...],
"max": [3.14, 3.14, ...]
},
"action": {
"mean": [0.0, 0.0, ...],
"std": [1.0, 1.0, ...],
"min": [-3.14, ...],
"max": [3.14, ...]
}
}
访问方式:
dataset = LeRobotDataset(repo_id)
stats = dataset.meta.stats # 返回字典,值为numpy数组
4.3 meta/tasks.parquet
任务描述的映射表:
| task_index | task |
|---|---|
| 0 | “Grab the red cube” |
| 1 | “Place the cube in the box” |
| 2 | “Open the drawer” |
4.4 meta/episodes/chunk-XXX/file-XXX.parquet
每个episode的元数据记录:
| 列名 | 类型 | 说明 |
|---|---|---|
episode_index |
int64 | Episode索引 |
length |
int64 | Episode帧数 |
task_index |
int64 | 关联的任务ID |
data/chunk_index |
int64 | 数据文件chunk索引 |
data/file_index |
int64 | 数据文件file索引 |
data/from |
int64 | 在数据文件中的起始行 |
data/to |
int64 | 在数据文件中的结束行 |
videos/{camera}/chunk_index |
int64 | 视频chunk索引 |
videos/{camera}/file_index |
int64 | 视频file索引 |
videos/{camera}/from |
int64 | 视频起始帧 |
videos/{camera}/to |
int64 | 视频结束帧 |
dataset_from_index |
int64 | 在dataset中的起始索引 |
dataset_to_index |
int64 | 在dataset中的结束索引 |
示例:
# 访问第0个episode的信息
episode_info = dataset.meta.episodes.iloc[0]
print(episode_info['length']) # 150帧
print(episode_info['data/chunk_index']) # 0
print(episode_info['data/from']) # 0
print(episode_info['data/to']) # 150
五、数据加载与使用
5.1 基本加载
from lerobot.datasets.lerobot_dataset import LeRobotDataset
# 方式1: 加载整个数据集(会缓存到本地)
dataset = LeRobotDataset("lerobot/aloha_mobile_cabinet")
# 方式2: 只加载特定episodes
dataset = LeRobotDataset(
"lerobot/aloha_mobile_cabinet",
episodes=[0, 10, 11, 23]
)
# 方式3: 流式加载(不下载到本地)
from lerobot.datasets.streaming_dataset import StreamingLeRobotDataset
dataset = StreamingLeRobotDataset("lerobot/aloha_mobile_cabinet")
5.2 数据访问
# 随机访问单帧
sample = dataset[100]
# 返回字典:
# {
# 'observation.state': tensor([...]), # 形状: (state_dim,)
# 'action': tensor([...]), # 形状: (action_dim,)
# 'observation.images.front': tensor([...]), # 形状: (C, H, W)
# 'timestamp': tensor(1.234),
# ...
# }
# 访问episode
episode_idx = 0
from_idx = dataset.meta.episodes["dataset_from_index"][episode_idx]
to_idx = dataset.meta.episodes["dataset_to_index"][episode_idx]
episode_frames = [dataset[i] for i in range(from_idx, to_idx)]
5.3 时序窗口 (Delta Timestamps)
# 定义相对时间偏移
delta_timestamps = {
# 加载4张图像: 当前帧、以及之前1秒、0.5秒、0.2秒的帧
"observation.images.front": [-1.0, -0.5, -0.2, 0.0],
# 加载6个状态向量
"observation.state": [-1.5, -1.0, -0.5, -0.2, -0.1, 0.0],
# 加载64个未来动作(用于action chunking)
"action": [t / 30 for t in range(64)], # 假设fps=30
}
dataset = LeRobotDataset(repo_id, delta_timestamps=delta_timestamps)
sample = dataset[100]
print(sample["observation.images.front"].shape) # [4, C, H, W]
print(sample["observation.state"].shape) # [6, state_dim]
print(sample["action"].shape) # [64, action_dim]
5.4 PyTorch DataLoader集成
import torch
dataloader = torch.utils.data.DataLoader(
dataset,
batch_size=32,
num_workers=4,
shuffle=True,
)
for batch in dataloader:
observations = batch["observation.state"].to("cuda") # [32, T, state_dim]
actions = batch["action"].to("cuda") # [32, T, action_dim]
images = batch["observation.images.front"].to("cuda") # [32, T, C, H, W]
# 前向传播
# output = model(batch)
5.5 图像增强 (Image Transforms)
from lerobot.datasets.transforms import ImageTransforms, ImageTransformsConfig
# 配置数据增强
transforms_config = ImageTransformsConfig(
enable=True,
max_num_transforms=3, # 每帧最多应用3种变换
random_order=True, # 随机顺序应用
tfs={
"brightness": {
"weight": 1.0,
"type": "ColorJitter",
"kwargs": {"brightness": (0.7, 1.3)}
},
"contrast": {
"weight": 2.0, # 更高权重 = 更可能被选中
"type": "ColorJitter",
"kwargs": {"contrast": (0.8, 1.2)}
},
"sharpness": {
"weight": 0.5,
"type": "SharpnessJitter",
"kwargs": {"sharpness": (0.3, 2.0)}
},
}
)
dataset = LeRobotDataset(
repo_id,
image_transforms=ImageTransforms(transforms_config)
)
六、数据集创建与记录
6.1 创建数据集
from lerobot.datasets.lerobot_dataset import LeRobotDataset
# 定义特征
features = {
"observation.state": {"dtype": "float32", "shape": (8,)},
"observation.images.front": {"dtype": "image"},
"action": {"dtype": "float32", "shape": (8,)},
}
# 创建数据集
dataset = LeRobotDataset.create(
repo_id="your-username/my-dataset",
fps=30,
robot_type="your_robot",
features=features,
use_videos=True,
)
# 记录episodes
for episode_idx in range(num_episodes):
for frame_data in episode_frames:
dataset.add_frame({
"observation.state": state_array,
"observation.images.front": image_array, # 形状: (H, W, C)
"action": action_array,
})
dataset.save_episode(task="Pick up the cube")
# ⚠️ 重要: 完成后必须调用finalize()
dataset.finalize() # 刷新缓冲区、关闭parquet写入器
# 推送到Hub
dataset.push_to_hub()
6.2 命令行记录
lerobot-record \
--robot.type=so101_follower \
--robot.port=/dev/ttyUSB0 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 1920, height: 1080, fps: 30}}" \
--teleop.type=so101_leader \
--teleop.port=/dev/ttyUSB1 \
--dataset.repo_id=${HF_USER}/my-dataset \
--dataset.num_episodes=50 \
--dataset.single_task="Grab the black cube"
七、数据格式转换
7.1 从 v2.1 迁移到 v3.0
# 安装包含v3支持的版本
pip install "lerobot>=0.4.0"
# 转换Hub上的v2.1数据集
python -m lerobot.datasets.v30.convert_dataset_v21_to_v30 \
--repo-id=your-username/your-v21-dataset
转换操作:
- 聚合parquet文件:
episode-0000.parquet, … →chunk-000/file-000.parquet - 聚合视频文件:
episode-0000.mp4, … →chunk-000/file-000.mp4 - 更新元数据格式: JSON Lines → Parquet
- 添加episode偏移信息
7.2 大规模数据集迁移 (SLURM)
对于大型数据集(如DROID的76k episodes),推荐使用集群并行处理:
# 1. 并行转换分片
python examples/port_datasets/slurm_port_shards.py \
--raw-dir /data/droid/1.0.1 \
--repo-id your-username/droid_v3 \
--workers 2048 \
--cpus-per-task 8 \
--mem-per-cpu 1950M
# 2. 聚合分片
python examples/port_datasets/slurm_aggregate_shards.py \
--repo-id your-username/droid_v3 \
--workers 2048
# 3. 上传到Hub
python examples/port_datasets/slurm_upload.py \
--repo-id your-username/droid_v3 \
--workers 50
八、特征类型系统
8.1 支持的数据类型
| 类型 | dtype | 说明 | 示例 |
|---|---|---|---|
| 标量 | float32, float64, int32, int64, bool |
单个数值 | 温度、成功标志 |
| 向量 | float32 + shape |
一维数组 | 关节角度、笛卡尔位置 |
| 矩阵 | float32 + shape |
多维数组 | 力矩传感器矩阵 |
| 图像 | image |
RGB图像 | 摄像头帧 |
| 字符串 | string |
文本 | 任务描述 |
8.2 特征命名约定
LeRobot使用层级命名,用 / 或 . 分隔:
# 观测数据
"observation.state" # 机器人状态
"observation.images.front" # 前置摄像头
"observation.images.wrist_left" # 左腕摄像头
"observation.force_torque" # 力矩传感器
# 动作数据
"action" # 标准动作
"action.gripper_position" # 夹爪位置
"action.cartesian_position" # 笛卡尔空间位置
# 元数据
"timestamp" # 时间戳
"episode_index" # Episode ID
"task_index" # 任务ID
8.3 图像特征特殊处理
# 定义图像特征
features = {
"observation.images.front": {
"dtype": "image",
"shape": (480, 640, 3), # (H, W, C) - 存储格式
"names": ["height", "width", "channels"]
}
}
# 存储: 图像编码为MP4视频
# - 路径: videos/observation.images.front/chunk-000/file-000.mp4
# - 编码: H.264/H.265
# - 多个episode的帧连接在一起
# 加载: 返回PyTorch张量
image = dataset[0]["observation.images.front"]
print(image.shape) # [C, H, W] - PyTorch格式(通道优先)
print(image.dtype) # torch.float32
print(image.min(), image.max()) # [0.0, 1.0] - 归一化到[0,1]
九、性能优化
9.1 内存映射
# PyArrow自动使用内存映射访问Parquet文件
dataset = LeRobotDataset(repo_id)
# 优势:
# - 不需要加载整个数据集到RAM
# - 操作系统自动管理内存
# - 支持大于RAM的数据集
9.2 批量加载优化
# 使用多进程DataLoader
dataloader = torch.utils.data.DataLoader(
dataset,
batch_size=32,
num_workers=8, # 多进程预加载
prefetch_factor=2, # 每个worker预取2个batch
persistent_workers=True, # 保持worker进程
)
9.3 流式加载
from lerobot.datasets.streaming_dataset import StreamingLeRobotDataset
# 直接从Hub流式加载,无需下载
dataset = StreamingLeRobotDataset(
repo_id="yaak-ai/L2D-v3",
# 自动从HF Hub流式读取Parquet和MP4
)
# 优势:
# - 零本地存储需求
# - 快速开始训练
# - 自动处理网络缓存
十、最佳实践
10.1 数据集创建
- 合理的FPS: 根据任务选择合适的采样率(通常10-30 Hz)
- 一致的特征: 确保所有episode使用相同的特征schema
- 任务标注: 为每个episode关联清晰的任务描述
- 及时finalize: 记录完成后立即调用
dataset.finalize()
10.2 训练优化
- 使用delta_timestamps: 加载动作轨迹和观测历史
- 适当的batch_size: 根据GPU内存调整
- 图像增强: 提高模型泛化能力,但不要过度
- 多进程加载: 使用DataLoader的num_workers
10.3 存储管理
- 定期清理缓存: LeRobot缓存在
~/.cache/huggingface/lerobot/ - 使用流式加载: 对于非常大的数据集
- 合理的文件大小: 使用默认的100MB/500MB限制
十一、常见问题
Q1: 如何查看数据集元数据而不下载数据?
from lerobot.datasets.lerobot_dataset import LeRobotDatasetMetadata
meta = LeRobotDatasetMetadata(repo_id)
print(meta.total_episodes)
print(meta.total_frames)
print(meta.features)
Q2: 如何只加载部分episodes?
dataset = LeRobotDataset(repo_id, episodes=[0, 5, 10, 15])
Q3: ParquetWriter错误如何解决?
确保调用 finalize():
dataset.finalize() # 关闭writer,写入footer
dataset.push_to_hub()
Q4: 如何处理不同分辨率的图像?
在加载时统一调整:
from torchvision.transforms import v2
transforms = v2.Compose([
v2.Resize((480, 640)),
# 其他变换...
])
dataset = LeRobotDataset(repo_id, image_transforms=transforms)
Q5: 如何计算数据集统计?
from lerobot.datasets.compute_stats import compute_stats
stats = compute_stats(dataset)
# stats包含每个特征的mean/std/min/max
十二、总结
关键要点
- 文件组织: v3.0使用文件基础而非episode基础的存储
- 元数据驱动: Episode边界通过元数据而非文件名解析
- 高效访问: 内存映射 + 流式加载 = 低内存占用
- 标准化格式: 统一的schema支持跨数据集训练
- Hub集成: 原生支持Hugging Face生态
与其他格式对比
| 特性 | LeRobot v3.0 | RLDS/TensorFlow | RoboMimic HDF5 |
|---|---|---|---|
| 格式 | Parquet + MP4 | TFRecord | HDF5 |
| 流式支持 | ✅ | ❌ | ❌ |
| Hub集成 | ✅ | ❌ | ❌ |
| 跨平台 | ✅ | Python + TF | Python |
| 随机访问 | 快速 | 中等 | 快速 |
| 大规模数据 | 优秀 | 良好 | 一般 |
适用场景
- ✅ 大规模机器人数据集 (如DROID的76k episodes)
- ✅ 多模态时序数据 (图像 + 状态 + 动作)
- ✅ 分布式训练 (Hub流式加载)
- ✅ 数据共享 (Hugging Face Hub生态)
- ✅ 快速原型开发 (标准化API)
参考资源:
- 官方文档: https://huggingface.co/docs/lerobot
- GitHub仓库: https://github.com/huggingface/lerobot
- 数据集浏览: https://huggingface.co/datasets?other=LeRobot
- 示例代码:
examples/dataset/目录
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)