LeRobot documentation
LeRobotDataset v3.0
LeRobotDataset v3.0
LeRobotDataset v3.0은 로봇 학습 데이터를 위한 표준화된 형식입니다. Hugging Face Hub에서 인덱싱, 검색 및 시각화를 위한 풍부한 메타데이터와 함께 멀티모달 시계열 데이터, 감각 운동 신호, 다중 카메라 비디오에 대한 통합된 접근을 제공합니다.
레이아웃에 대한 설명을 읽기 전에 이 형식의 데이터셋에 어떤 내용이 포함되어 있는지 확인하려면 LeRobot Dataset Visualizer에서 하나를 열어보세요: 브라우저에서 카메라 녹화, 동기화된 상태 및 액션 차트, 모든 에피소드를 볼 수 있습니다. Explore a Dataset in the Browser를 참조하세요.
이 문서는 다음을 안내합니다:
- v3.0의 설계 및 디렉터리 구조 이해
- 데이터셋을 기록하고 Hub에 업로드
LeRobotDataset로 학습용 데이터셋 로드StreamingLeRobotDataset를 사용하여 다운로드 없이 데이터셋을 스트리밍- 학습 중 데이터 증강을 위해 이미지 변환 적용
- 기존
v2.1데이터셋을v3.0으로 마이그레이션 - Lance와 같은 다른
LeRobotDataset형식 및 구현을 실험해 보세요
v3의 새로운 기능
- 파일 기반 저장: Parquet/MP4 파일당 에피소드 수를 많이 포함합니다(v2는 에피소드당 하나의 파일을 사용했습니다).
- 관계형 메타데이터: 에피소드 경계와 조회는 파일명이 아닌 메타데이터를 통해 해결됩니다.
- Hub 네이티브 스트리밍:
StreamingLeRobotDataset를 사용하여 Hub에서 데이터를 직접 소비합니다. - 파일 시스템 부하 감소: 파일 수는 적고 크기는 커서 초기화가 빨라지고 대규모 환경에서의 문제가 줄어듭니다.
- 통합 조직: 데이터와 비디오 전반에 걸쳐 일관된 경로 템플릿을 갖춘 깔끔한 디렉터리 레이아웃.
- Dataset 저장 백엔드: Datasets는 객체 스토리지에서 직접 랜덤 액세스와 인덱싱을 위해 LanceDB 테이블로 저장할 수도 있습니다(LanceDB dataset backend 참고).
설치
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자세한 내용은 녹음 가이드를 참조하세요.
포맷 설계
A core v3 principle is 스토리지 저장과 사용자 API의 분리입니다. 데이터는 효율적으로 저장(소수의 대형 파일)되고, 공개 API는 직관적인 에피소드 수준의 접근을 제공합니다.
v3에는 세 가지 핵심 요소가 있습니다:
- 표 형식 데이터: Apache Parquet에 저장된 저차원, 고주파 신호(상태, 액션, 타임스탬프).
datasets스택을 통해 메모리 매핑 또는 스트리밍 방식으로 접근합니다. - 시각 데이터: 카메라 프레임을 연결하여 MP4로 인코딩합니다. 같은 에피소드의 프레임은 그룹화되며, 실용적인 크기를 위해 카메라별로 비디오가 샤딩됩니다.
- 메타데이터: 스키마(피처 이름, dtype, shape), 프레임 레이트, 정규화 통계 및 에피소드 분할(공유된 Parquet/MP4 파일 내 시작/끝 오프셋)을 설명하는 JSON/Parquet 레코드
수백만 에피소드로 확장하려면, 여러 에피소드의 표 형식 행과 비디오 프레임을 더 큰 파일로 연결합니다. 에피소드별 뷰는 파일 경계가 아닌 메타데이터를 통해 재구성됩니다.
디렉터리 레이아웃 (단순화됨)
meta/info.json: 정식 스키마(피처, shape/dtype), FPS, 코드베이스 버전, 데이터/비디오 샤드를 찾기 위한 경로 템플릿.meta/stats.json: 정규화에 사용되는 전역 특징 통계(평균/표준편차/최소값/최대값);dataset.meta.stats로 노출됩니다.meta/tasks.parquet: 태도 조건부 정책을 위한 정수 ID에 매핑된 자연어 태스크 설명.meta/episodes/: 확장성을 위해 chunked Parquet로 저장된 에피소드별 기록(길이, 작업, 오프셋).data/: 프레임별 Parquet 샤드. 각 파일에는 일반적으로 여러 에피소드가 포함됩니다.videos/: 카메라별 MP4 샤드. 각 파일에는 일반적으로 여러 에피소드가 포함됩니다.
학습용 데이터셋 로드
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 및 비디오 파일을 먼저 다운로드하지 않고도 학습할 수 있습니다. 에피소드는 유한한 인메모리 풀로 온디맨드 방식으로 가져오며, 선택된 모든 프레임은 에포크당 정확히 한 번씩 방문됩니다:
lerobot-train \
--dataset.repo_id=yaak-ai/L2D-v3 \
--dataset.streaming=true \
--policy.type=act \
--output_dir=outputs/train/act_streamingPython에서도 동일한 리더를 사용할 수 있으며, 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를 사용하여 버킷을 선택합니다.
워커와 샤드가 스트리밍 동시성, MP4 사이드카 인덱스, 메모리 설정, 재개 및 문제 해결에 어떻게 매핑되는지 확인하려면 Training Dataset Streaming을 참조하세요.
이미지 변환
Image transforms는 학습 중에 카메라 프레임에 적용되는 데이터 증강 기법으로, 모델의 견고성과 일반화 성능을 향상시키기 위해 사용됩니다. LeRobot은 밝기, 대비, 채도, 색조, 선명도 조정 등 다양한 transforms를 지원합니다.
데이터셋 생성/기록 시 transforms 사용
현재 transforms는 학습 시에만 적용되며, 기록 시에는 적용되지 않습니다. 데이터셋을 생성하거나 기록할 때, transforms가 적용되지 않은 원본 이미지가 저장됩니다. 이를 통해 데이터를 다시 기록하지 않고도 나중에 다양한 증강 기법을 실험할 수 있습니다.
기존 데이터셋에 변환 추가 (API)
학습용 데이터셋을 로드할 때 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 매개변수에 직접 전달할 수도 있습니다.
모든 transform이 이제 배치 처리되며, 동일한 배치 transform이 두 백엔드 모두에서 실행됩니다. 샘플별 클래스(SharpnessJitter, GaussianNoise, MotionBlur, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection, PlanckianJitter, RandomSubsetApply)와 make_transform_from_config는 Batched* 대체 항목에 대한 비추천 프론트 엔드로서 여전히 가져올 수 있으며, 사용 시 경고를 표시합니다. 이들은 향후 릴리스에서 제거될 예정입니다. ImageTransforms는 (C, H, W) 또는 (T, C, H, W) 텐서를 받는 단순한 torch.nn.Module입니다. type으로 transform을 지정하는 구성에는 영향이 없습니다.
기존 레시피의 동작 변경. 학습은 프레임을
uint8로 읽으며,ColorJitter,RandomAffine,RandomRotation를 제외한 LeRobot 변환은 이전에는 float가 아닌 입력을 그대로 반환했습니다. 따라서GaussianNoise,MotionBlur,GaussianPatchBrightness,RandomShadow,CoarseDropout,GammaCorrection또는PlanckianJitter를 활성화한 레시피는 해당 변환 없이 학습되었습니다. 이제 이러한 변환은 두 백엔드 모두에 적용되므로, 해당 레시피는 이전과 다르게 학습됩니다: 요청한 증강이 적용됩니다. 이미 적용되던 변환은 반올림을 제외하고는 변경되지 않으며, 이는 배치 경로가 float로 계산한 후 모든 변환이 끝난 시점에 한 번만 반올림하기 때문입니다.
구성 옵션
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는 DataLoader 워커에서 샘플별로 실행됩니다. 카메라가 여러 대인 경우, 이는 워커가 수행하는 작업 중 가장 비용이 높은 작업이 되는 경우가 많으며, GPU가 데이터를 기다리게 됩니다. backend=gpu를 설정하면 워커는 디코딩만 수행하고, 동일한 transforms를 policy 디바이스에서의 각 배치에 적용합니다:
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무작위화는 변경되지 않습니다: 각 샘플은 자체 변환과 매개변수를 추출하며, 하나의 카메라 히스토리의 프레임들은 이를 공유합니다. 증강은 정책 전처리기 적용 전에 학습 배치에만 적용되며, 평가 배치, 롤아웃 및 저장된 체크포인트에는 영향을 주지 않습니다.
알아두실 점:
- 가속기가 필요합니다.
--policy.device=cpu는 거부됩니다. 자체image_transforms콜러블을 사용하여 수동으로 구축된 데이터셋도 거부되는데, 이는 해당 콜러블이 워커에서 실행되기 때문입니다. - dataloader 백엔드가 지원하는 모든 transform 유형을 지원합니다. 배치 구현이 없는 유형(
JPEGCompression,ColorJitter,RandomAffine,RandomRotation,Identity를 제외한 torchvision 클래스)은 배치 내에서 샘플별로 하나씩 적용되므로 속도 향상의 이점을 받지 못하며,JPEGCompression은 CPU를 통해 왕복 처리됩니다. 프레임 크기를 변경하는 transform은 샘플이 하나보다 많은 배치에서 거부됩니다. --seed로 재현 가능하며, 각 프로세스는 서로 다른 시드로 설정됩니다. 증강은 정책의 무작위 스트림을 소모하지 않으며,gpu_compile과gpu_chunk_size는 적용되는 내용을 변경하지 않습니다.- 두 백엔드 모두 동일한 배치 변환을 float로 실행하고 마지막에 한 번만 반올림을 수행하므로, 동일한 매개변수를 사용할 경우 한 백엔드에서 증강된 샘플은 다른 백엔드에서 증강된 동일한 샘플과 같습니다.
gpu_compile은 이미지 형식별로 한 번에 15~30초가 소요됩니다. 피크 메모리는 배치 이미지의 float 복사본 두 개와 한 청크의 몇 개 복사본 정도입니다. 메모리가 부족하면gpu_chunk_size를 낮추세요.random_order=true는 변환 시간을max_num_transforms배로 증가시킵니다.
v2.1 → v3.0 마이그레이션
A converter는 에피소드별 파일을 더 큰 샤드로 집계하고 에피소드 오프셋/메타데이터를 작성합니다. 아래 설명에 따라 데이터셋을 변환하세요.
# 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/*(chunked Parquet)를 에피소드별 길이, 작업, 바이트/프레임 오프셋으로 업데이트합니다.
자주 발생하는 문제
pushing 전에 항상 finalize()를 호출하세요
데이터셋을 생성하거나 기록할 때 parquet writers를 올바르게 닫으려면 반드시 dataset.finalize()를 호출해야 합니다. 자세한 내용은 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() 메서드는:
- 버퍼링된 에피소드 메타데이터를 디스크에 플러시합니다
- parquet 파일을 손상시키지 않도록 푸터 메타데이터를 기록하기 위해 parquet 작성기를 닫습니다
- 데이터셋이 로드하기에 유효한지 확인합니다
finalize()을 호출하지 않으면 parquet 파일이 불완전하여 데이터셋이 제대로 로드되지 않습니다.
기타 형식 및 구현
LanceDB 데이터셋 백엔드
v3 데이터셋은 LanceDB 테이블로 저장할 수도 있습니다. LanceDB는 AI 접근 패턴을 위해 Lance 포맷 위에 구축된 멀티모달 데이터 레이크하우스로, 임의의 행과 해당 비디오 바이트에 대한 빠른 랜덤 액세스와 동일한 테이블에 대한 스칼라, 벡터, 전체 텍스트 인덱싱을 지원합니다. 이를 통해 EDA, 마이닝, 큐레이션 및 학습이 객체 스토리지에서 직접 실행됩니다. 레이아웃은 meta/를 변경하지 않고, 테이블 데이터용 frames 테이블과 MP4를 블롭으로 저장하는 videos 테이블을 추가합니다. meta/info.json의 "storage_format": "lance"가 리더를 선택하며, LeRobotDataset API, 항목 및 학습 파이프라인은 변경되지 않습니다.
- GPU가 공급된 상태에서 Hub 또는 HF Storage Buckets에서 바로 학습을 수행합니다. 랜덤 프레임은 원위치에서 읽히며, 각 배치에 필요한 바이트만 읽으므로 모든 배치가 글로벌 셔플이 되고 사전 다운로드가 전혀 이루어지지 않습니다.
- 파일 복사 수준의 비용으로 변환합니다. 비디오는 다시 인코딩되지 않으므로 항목은 소스와 비트 단위로 동일합니다. DROID는 34분 만에 변환합니다.
- Zero-copy 기능 엔지니어링. 트레이너가 읽는 테이블에 점수, 임베딩 또는 수정된 레이블과 같은 컬럼을 추가하되, 비디오를 다시 작성하거나 데이터셋을 복사하지 않고, 인덱스를 통해 필터링 및 검색을 수행할 수 있습니다. 리더는 알지 못하는 컬럼을 무시합니다.
동작 방식, 벤치마크 및 예제는 LanceDB LeRobot 가이드를 참고하세요.
Camera 기능은 반드시 비디오 기반이어야 합니다. 카메라 프레임을 이미지로 저장하는 데이터셋은 리더에서 지원되지 않으며, 변환 전에 비디오로 다시 인코딩해야 합니다.
Lance 데이터셋을 읽으려면 lancedb extra가 필요합니다(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
Visualization은 두 뷰어 모두에서 작동합니다. lerobot-dataset-viz --repo-id lance-format/pusht-lance --episode-index 0은 에피소드를 Rerun에서 평소와 같이 열고, --display-mode foxglove은 이를 WebSocket을 통해 Foxglove에 제공합니다. 어느 경우든 화면에 표시된 프레임만 가져오며, MCAP 내보내기나 다운로드가 발생하지 않습니다.