LeRobot documentation
LeRobotDataset v3.0
LeRobotDataset v3.0
LeRobotDataset v3.0 是一种用于机器人学习数据的标准化格式。它提供对多模态时间序列数据、感觉运动信号和多摄像头视频的统一访问,并包含丰富的元数据,用于在 Hugging Face Hub 上进行索引、搜索和可视化。
在了解该格式数据集的布局之前,可以先在 LeRobot Dataset Visualizer 中打开一个数据集,查看其内容:包括摄像头录制画面、同步的状态与动作图表,以及所有 episode,全部在浏览器中呈现。请参阅 Explore a Dataset in the Browser。
本指南将引导您:
- 了解 v3.0 的设计和目录结构
- 记录数据集并将其推送到 Hub
- 使用
LeRobotDataset加载用于训练的数据集 - 使用
StreamingLeRobotDataset流式加载数据集而无需下载 - 在训练期间应用图像变换以进行数据增强
- 将现有的
v2.1数据集迁移到v3.0 - 尝试使用其他
LeRobotDataset格式和实现,例如 Lance
v3 的新功能
- 基于文件的存储:每个 Parquet/MP4 文件包含许多个 episode(v2 版本中每个 episode 对应一个文件)。
- 关系元数据:剧集边界和查找通过元数据而非文件名来解决。
- Hub 原生流式处理:使用
StreamingLeRobotDataset直接从 Hub 消费数据集。 - 降低文件系统压力:文件数量更少、体积更大 ⇒ 初始化速度更快,且在大规模场景下问题更少。
- 统一组织:目录结构清晰,数据和视频的路径模板保持一致。
- 数据集存储后端:数据集也可以存储为 LanceDB 表,以便直接从对象存储进行随机访问和索引(参见 LanceDB 数据集后端)。
安装
LeRobotDataset v3.0 包含在 lerobot >= 0.4.0 中。请按照安装指南进行安装。
记录数据集
运行以下命令,使用 SO-101 记录数据集并推送到 Hub:
lerobot-record \
--robot.type=so101_follower \
--robot.port=/dev/tty.usbmodem585A0076841 \
--robot.id=my_awesome_follower_arm \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 1920, height: 1080, fps: 30}}" \
--teleop.type=so101_leader \
--teleop.port=/dev/tty.usbmodem58760431551 \
--teleop.id=my_awesome_leader_arm \
--display_data=true \
--dataset.repo_id=${HF_USER}/record-test \
--dataset.num_episodes=5 \
--dataset.single_task="Grab the black cube" \
--dataset.streaming_encoding=true \
--dataset.encoder_threads=2
# Optional: --dataset.rgb_encoder.vcodec=auto有关更多详细信息,请参阅录制指南。
格式设计
v3 的核心原则之一是将存储与用户 API 解耦:数据以高效的方式存储(少量大文件),而公共 API 则提供直观的 episode 级别访问。
v3 包含三大支柱:
- 表格数据:以 Apache Parquet 格式存储的低维、高频信号(状态、动作、时间戳)。通过
datasets技术栈以内存映射或流式方式访问。 - 视觉数据:将相机帧拼接并编码为 MP4。同一 episode 的帧会被分组;视频按相机分片,以保持实际大小合理。
- 元数据:描述模式(特征名称、数据类型、形状)、帧率、归一化统计信息以及片段分割(指向共享 Parquet/MP4 文件的起始/结束偏移量)的 JSON/Parquet 记录。
为了扩展到数百万个回合,来自多个回合的表格行和视频帧会被拼接到更大的文件中。特定回合的视图是通过元数据而非文件边界来重建的。
目录结构(简化版)
meta/info.json:标准模式(特征、形状/数据类型)、FPS、代码库版本,以及用于定位数据/视频分片的路径模板。meta/stats.json:用于归一化的全局特征统计信息(均值/标准差/最小值/最大值);通过dataset.meta.stats暴露。meta/tasks.parquet:将自然语言任务描述映射为整数 ID,用于任务条件策略。meta/episodes/:以 chunked Parquet 格式存储的每集记录(长度、任务、偏移量),以实现可扩展性。data/:逐帧的 Parquet 分片;每个文件通常包含 多个 episode。videos/:每个摄像头的 MP4 分片;每个文件通常包含多个 episode。
加载用于训练的数据集
LeRobotDataset 返回包含 PyTorch 张量的 Python 字典,并与 torch.utils.data.DataLoader 集成。以下是一个展示其用法的代码示例:
import torch
from lerobot.datasets import LeRobotDataset
repo_id = "yaak-ai/L2D-v3"
# 1) Load from the Hub (cached locally)
dataset = LeRobotDataset(repo_id)
# 2) Random access by index
sample = dataset[100]
print(sample)
# {
# 'observation.state': tensor([...]),
# 'action': tensor([...]),
# 'observation.images.front_left': tensor([C, H, W]),
# 'timestamp': tensor(1.234),
# ...
# }
# 3) Temporal windows via delta_timestamps (seconds relative to t)
delta_timestamps = {
"observation.images.front_left": [-0.2, -0.1, 0.0] # 0.2s and 0.1s before current frame
}
dataset = LeRobotDataset(repo_id, delta_timestamps=delta_timestamps)
# Accessing an index now returns a stack for the specified key(s)
sample = dataset[100]
print(sample["observation.images.front_left"].shape) # [T, C, H, W], where T=3
# 4) Wrap with a DataLoader for training
batch_size = 16
data_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size)
device = "cuda" if torch.cuda.is_available() else "cpu"
for batch in data_loader:
observations = batch["observation.state"].to(device)
actions = batch["action"].to(device)
images = batch["observation.images.front_left"].to(device)
# model.forward(batch)在训练期间流式传输数据集
设置 --dataset.streaming=true 可在不预先下载数据集的 Parquet 和视频文件的情况下进行训练。Episode 会被即时获取到一个有界的内存池中,并且每个选定的帧在每个 epoch 中仍然只会被访问一次:
lerobot-train \
--dataset.repo_id=yaak-ai/L2D-v3 \
--dataset.streaming=true \
--policy.type=act \
--output_dir=outputs/train/act_streaming相同的读取器也可在 Python 中使用,适用于 Hub 数据集以及存储在 HF Storage Bucket 中的数据集中的数据集:
from lerobot.datasets import StreamingLeRobotDataset
dataset = StreamingLeRobotDataset("yaak-ai/L2D-v3")
bucket_dataset = StreamingLeRobotDataset("my-org/my-bucket", repo_type="bucket")在 CLI 中,使用 --dataset.repo_type=bucket 或 --dataset.root=hf://buckets/my-org/my-bucket 选择一个存储桶。
请参阅 Training Dataset Streaming,了解 worker 和 shard 如何映射到流式并发、MP4 旁路索引、内存设置、断点续传以及故障排除。
图像转换
图像变换是在训练期间应用于相机帧的数据增强,旨在提升模型的鲁棒性和泛化能力。LeRobot 支持多种变换,包括亮度、对比度、饱和度、色相和锐度调整。
在创建/录制数据集期间使用 transforms
目前,数据增强仅在训练阶段应用,而非在录制阶段。当您创建或录制数据集时,原始图像会在未应用任何增强的情况下存储。这允许您稍后尝试不同的数据增强方法,而无需重新录制数据。
为现有数据集添加转换(API)
在加载用于训练的 dataset 时,使用 image_transforms 参数:
from lerobot.datasets import LeRobotDataset
from lerobot.transforms import ImageTransforms, ImageTransformsConfig, ImageTransformConfig
# Option 1: Use default transform configuration (disabled by default)
transforms_config = ImageTransformsConfig(
enable=True, # Enable transforms
max_num_transforms=3, # Apply up to 3 transforms per frame
random_order=False, # Apply in standard order
)
transforms = ImageTransforms(transforms_config)
dataset = LeRobotDataset(
repo_id="your-username/your-dataset",
image_transforms=transforms
)
# Option 2: Create custom transform configuration
custom_transforms_config = ImageTransformsConfig(
enable=True,
max_num_transforms=2,
random_order=True,
tfs={
"brightness": ImageTransformConfig(
weight=1.0,
type="ColorJitter",
kwargs={"brightness": (0.7, 1.3)} # Adjust brightness range
),
"contrast": ImageTransformConfig(
weight=2.0, # Higher weight = more likely to be selected
type="ColorJitter",
kwargs={"contrast": (0.8, 1.2)}
),
"sharpness": ImageTransformConfig(
weight=0.5, # Lower weight = less likely to be selected
type="SharpnessJitter",
kwargs={"sharpness": (0.3, 2.0)}
),
}
)
dataset = LeRobotDataset(
repo_id="your-username/your-dataset",
image_transforms=ImageTransforms(custom_transforms_config)
)
# Option 3: Use pure torchvision transforms
from torchvision.transforms import v2
torchvision_transforms = v2.Compose([
v2.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
v2.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),
])
dataset = LeRobotDataset(
repo_id="your-username/your-dataset",
image_transforms=torchvision_transforms
)可用的转换类型
type 可以是 torchvision.transforms.v2 中的任意类,例如:
ColorJitter:调整亮度、对比度、饱和度和色相SharpnessJitter:随机调整图像锐度RandomAffine、RandomRotation:几何抖动GaussianNoise、MotionBlur、JPEGCompression、GaussianPatchBrightness、RandomShadow、CoarseDropout、GammaCorrection、PlanckianJitter:真实机器人上可见的相机和光照效果Identity:无变换(适用于测试)
任何其他 torchvision.transforms.v2 类都可以作为 type 指定(例如 RandomResizedCrop 或 GaussianBlur);它会逐个样本应用。您也可以直接将 torchvision 变换传递给 image_transforms 参数。
现在所有变换均已批处理化,相同的批处理变换在两个后端上运行。逐样本类(SharpnessJitter、GaussianNoise、MotionBlur、GaussianPatchBrightness、RandomShadow、CoarseDropout、GammaCorrection、PlanckianJitter、RandomSubsetApply)以及 make_transform_from_config 仍可作为其 Batched* 替代项的已弃用前端进行导入,使用时会发出警告;它们将在未来的版本中移除。ImageTransforms 是一个普通的 torch.nn.Module,接受 (C, H, W) 或 (T, C, H, W) 张量。通过 type 指定变换名称的配置不受影响。
现有配方(recipes)的行为变更。训练时以
uint8读取帧,而 LeRobot 中除ColorJitter、RandomAffine和RandomRotation之外的其他变换,过去会原样返回非浮点输入。因此,启用GaussianNoise、MotionBlur、GaussianPatchBrightness、RandomShadow、CoarseDropout、GammaCorrection或PlanckianJitter的配方实际上是在未应用这些变换的情况下进行训练的。现在这些变换在两个后端上都会生效,因此此类配方的训练效果与之前不同:它现在会应用所请求的数据增强。对于原本就已生效的变换,除舍入行为外保持不变,因为批量处理路径以浮点数进行计算,并在最后统一舍入一次,而不是在每个变换之后都进行舍入。
配置选项
enable:启用/禁用转换(默认值:False)max_num_transforms:每帧应用的最大变换数量(默认值:3)random_order:以随机顺序而非标准顺序应用转换(默认值:False)weight:每个变换的采样概率(值越高,被选中的可能性越大;如果权重之和不为 1,它们将被归一化)kwargs:特定于转换的参数(例如亮度范围)backend:训练期间执行转换的位置,"dataloader"(默认)或"gpu";详见下文gpu_compile:在"gpu"后端上,对批量转换使用torch.compile(默认值:True)gpu_chunk_size:在"gpu"后端上,每次增强这么多样本以限制瞬时内存(默认值:32)
在 GPU 上运行 transforms
默认情况下,transforms 在 DataLoader 的 workers 中按样本运行。当使用多个摄像头时,这通常是 workers 中最耗时的操作,导致 GPU 等待数据。设置 backend=gpu 可以让 workers 仅执行解码,并在策略设备上对每个 batch 应用相同的 transforms:
lerobot-train \
--dataset.repo_id=your-username/your-dataset \
--dataset.image_transforms.enable=true \
--dataset.image_transforms.backend=gpu \
--policy.type=act \
--policy.device=cuda随机化保持不变:每个样本独立抽取其自身的变换和参数,同一相机历史中的各帧共享这些变换和参数。数据增强仅应用于训练批次,且在策略预处理器之前执行;评估批次、rollout 以及保存的检查点均不受影响。
需要了解的事项:
- 需要加速器;
--policy.device=cpu会被拒绝。同样,使用自定义的image_transforms可调用对象手动构建的数据集也会被拒绝,因为这些操作在 worker 中运行。 - 支持数据加载器后端支持的所有转换类型。没有批量实现的类型(
JPEGCompression,以及除ColorJitter、RandomAffine、RandomRotation和Identity之外的 torchvision 类)会在批次内逐个样本应用,因此无法获得加速;JPEGCompression还会在 CPU 上进行往返处理。对于包含多个样本的批次,会拒绝改变帧大小的转换。 - 可通过
--seed复现,每个进程使用不同的种子。数据增强不会消耗策略的随机流,且gpu_compile和gpu_chunk_size不会改变所应用的内容。 - 两种后端都会执行相同的批处理变换,以浮点数形式运行,并在最后统一进行舍入,因此在相同参数下,一种后端增强后的样本与另一种后端增强后的相同样本结果一致。
gpu_compile针对每种图像形状仅产生一次 15-30 秒的开销。峰值内存约为批处理图像的两份 float 副本加上一个 chunk 的几份副本;如果内存紧张,请调低gpu_chunk_size。random_order=true会将变换时间乘以max_num_transforms。
从 v2.1 迁移到 v3.0
转换器会将每个剧集的文件聚合为更大的分片,并写入剧集偏移量/元数据。请按照以下说明转换您的数据集。
# Convert an existing v2.1 dataset hosted on the Hub:
python -m lerobot.scripts.convert_dataset_v21_to_v30 --repo-id=<HF_USER/DATASET_ID>功能说明
- 聚合 parquet 文件:
episode_000000.parquet、episode_000001.parquet、… →file-000.parquet、… - 聚合 mp4 文件:
episode_000000.mp4、episode_000001.mp4、… →file-000.mp4、… - 更新
meta/episodes/*(分块 Parquet),包含每个 episode 的长度、任务以及字节/帧偏移量。
常见问题
在推送之前始终调用 finalize()
在创建或记录数据集时,您必须调用dataset.finalize()以正确关闭 parquet 写入器。有关更多详细信息,请参阅PR #1903。
from lerobot.datasets import LeRobotDataset
# Create dataset and record episodes
dataset = LeRobotDataset.create(...)
for episode in range(num_episodes):
# Record frames
for frame in episode_data:
dataset.add_frame(frame)
dataset.save_episode()
# Call finalize() when done recording and before push_to_hub()
dataset.finalize() # Closes parquet writers, writes metadata footers
dataset.push_to_hub()为什么需要这样做?
Dataset v3.0 使用增量 parquet 写入和缓冲元数据以提高效率。finalize() 方法:
- 将任何已缓冲的 episode 元数据刷新到磁盘
- 关闭 parquet 写入器以写入 footer 元数据,否则 parquet 文件将会损坏
- 确保数据集有效以便加载
如果不调用 finalize(),你的 parquet 文件将不完整,数据集也无法正常加载。
其他格式和实现
LanceDB 数据集后端
v3 数据集也可以存储为 LanceDB 表。LanceDB 是一个基于 Lance 格式构建的多模态数据湖仓,专为 AI 访问模式设计:支持对任意行及其视频字节进行快速随机访问,并可在同一张表上建立标量、向量和全文索引,从而让 EDA、挖掘、数据整理和训练直接基于对象存储运行。该布局保持 meta/ 不变,并新增一个用于表格数据的 frames 表和一个以 blob 形式存储 MP4 的 videos 表。meta/info.json 中的 "storage_format": "lance" 用于选择读取器;LeRobotDataset API、数据项以及训练流水线均保持不变。
- 直接基于 Hub 或 HF Storage Buckets 进行训练,GPU 持续获得数据供给。 随机帧就地读取,每个批次仅读取其所需的字节,因此每个批次都是全局洗牌,且无需预先下载任何内容。
- 转换成本大约相当于一次文件复制。视频不会被重新编码,因此各项内容与源文件完全一致。DROID 的转换耗时为 34 分钟。
- 零拷贝特征工程。 向训练器读取的表中添加分数、嵌入或修正后的标签等列,而无需重写视频或复制数据集,然后使用索引对这些列进行过滤和搜索。读取器会忽略其不识别的列。
有关其工作原理、基准测试和示例,请参阅 LanceDB LeRobot 指南。
相机特征必须以视频形式存储。以图像形式存储相机帧的数据集不受读取器支持,在转换前需要重新编码为视频。
读取 Lance 数据集需要 lancedb 附加组件(pip install "lerobot[lancedb]")。该格式为只读:请在默认格式下记录和编辑数据集,然后使用 lerobot-lancedb 包进行转换:
pip install "lerobot-lancedb"
lerobot-lance-convert --repo-id lerobot/pusht --out ./pusht-lance # or --root <local dataset dir>加载使用相同的类:
from lerobot.datasets import LeRobotDataset
# Hub dataset repo
dataset = LeRobotDataset("lance-format/pusht-lance")
# HF Storage Bucket, read in place
dataset = LeRobotDataset("my-org/pusht-lance", repo_type="bucket")
# the same bucket as a root URI (other object store URIs work the same way)
dataset = LeRobotDataset("lerobot/pusht", root="hf://buckets/my-org/pusht-lance")HF Storage Bucket 选项在 lerobot-train 中可用:
lerobot-train \ --dataset.repo_id=my-org/pusht-lance \ --dataset.repo_type=bucket \ --policy.type=act
可视化功能同时支持两种查看器。lerobot-dataset-viz --repo-id lance-format/pusht-lance --episode-index 0 会像往常一样在 Rerun 中打开一个 episode,而 --display-mode foxglove 则通过 WebSocket 将其提供给 Foxglove。无论哪种方式,都只获取屏幕上的帧,无需进行 MCAP 导出或下载。