LeRobot documentation

LeRobotDataset v3.0

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

LeRobotDataset v3.0

LeRobotDataset v3.0 est un format standardisé pour les données d’apprentissage robotique. Il offre un accès unifié aux séries temporelles multimodales, aux signaux sensorimoteurs et aux vidéos multicaméras, ainsi qu’à des métadonnées riches pour l’indexation, la recherche et la visualisation sur le Hugging Face Hub.

To see what a dataset in this format holds before reading about its layout, open one in the LeRobot Dataset Visualizer: camera recordings, synchronized state and action charts, and every episode, in the browser. See Explore a Dataset in the Browser.

Cette documentation vous guidera pour :

  • Comprendre la conception et la structure de répertoires de la v3.0
  • Enregistrer un jeu de données et le pousser vers le Hub
  • Charger des jeux de données pour l'entraînement avec LeRobotDataset
  • Diffuser des jeux de données sans les télécharger à l'aide de StreamingLeRobotDataset
  • Appliquer des transformations d’images pour l’augmentation des données lors de l’entraînement
  • Migrer les jeux de données v2.1 existants vers v3.0
  • Expérimentez avec d'autres formats et implémentations de LeRobotDataset tels que Lance

Nouveautés de la v3

  • Stockage par fichiers : de nombreux épisodes par fichier Parquet/MP4 (la v2 utilisait un fichier par épisode).
  • Métadonnées relationnelles : Les limites des épisodes et les recherches sont résolues via les métadonnées, et non par les noms de fichiers.
  • Streaming natif du Hub : Consommez des jeux de données directement depuis le Hub avec StreamingLeRobotDataset.
  • Réduire la pression sur le système de fichiers : moins de fichiers, plus volumineux ⇒ initialisation plus rapide et moins de problèmes à grande échelle.
  • Organisation unifiée : structure de répertoires propre avec des modèles de chemins cohérents pour les données et les vidéos.
  • Backends de stockage de jeux de données : les jeux de données peuvent également être stockés sous forme de tables LanceDB pour un accès aléatoire et un indexage directement depuis le stockage d'objets (voir Backend de jeu de données LanceDB).

Installation

LeRobotDataset v3.0 est inclus dans lerobot >= 0.4.0. Suivez le Guide d’installation pour l’installer.

Enregistrer un jeu de données

Exécutez la commande ci-dessous pour enregistrer un jeu de données avec le SO-101 et le pousser vers le 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

Consultez le guide d’enregistrement pour plus de détails.

Conception du format

Un principe fondamental de v3 est le découplage du stockage de l'API utilisateur : les données sont stockées de manière efficace (peu de fichiers volumineux), tandis que l'API publique expose un accès intuitif au niveau des épisodes.

v3 repose sur trois piliers :

  1. Données tabulaires : signaux de faible dimension et haute fréquence (états, actions, horodatages) stockés dans Apache Parquet. L’accès est effectué par mappage mémoire ou en flux via la pile datasets.
  2. Données visuelles : Images de caméra concaténées et encodées en MP4. Les images d'un même épisode sont regroupées ; les vidéos sont découpées par caméra pour des tailles pratiques.
  3. Métadonnées : enregistrements JSON/Parquet décrivant le schéma (noms des fonctionnalités, dtypes, formes), les fréquences d'images, les statistiques de normalisation et le segmentation des épisodes (décalages de début/fin dans les fichiers Parquet/MP4 partagés).

Pour passer à l’échelle de millions d’épisodes, les lignes tabulaires et les images vidéo issues de plusieurs épisodes sont concaténées dans des fichiers plus volumineux. Les vues spécifiques à chaque épisode sont reconstruites via les métadonnées, et non par les limites de fichiers.

LeRobotDataset v3 diagram
Des jeux de données basés sur des épisodes aux jeux de données basés sur des fichiers

Structure du répertoire (simplifiée)

  • meta/info.json : schéma canonique (fonctionnalités, formes/types de données), FPS, version de la base de code et modèles de chemins pour localiser les fragments de données/vidéos.
  • meta/stats.json : statistiques globales des fonctionnalités (moyenne/écart-type/min/max) utilisées pour la normalisation ; exposées sous la forme dataset.meta.stats.
  • meta/tasks.parquet : descriptions de tâches en langage naturel associées à des identifiants entiers pour les politiques conditionnées par tâche.
  • meta/episodes/ : enregistrements par épisode (longueurs, tâches, décalages) stockés au format Parquet chunked pour assurer l'évolutivité.
  • data/ : shards Parquet image par image ; chaque fichier contient généralement de nombreux épisodes.
  • videos/ : shards MP4 par caméra ; chaque fichier contient généralement de nombreux épisodes.

Charger un jeu de données pour l'entraînement

LeRobotDataset renvoie des dictionnaires Python de tenseurs PyTorch et s’intègre à torch.utils.data.DataLoader. Voici un exemple de code illustrant son utilisation :

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)

Diffuser un jeu de données pendant l'entraînement

Définissez --dataset.streaming=true pour entraîner sans télécharger au préalable les fichiers Parquet et vidéo du jeu de données. Les épisodes sont récupérés à la volée dans un pool mémoire borné, et chaque image sélectionnée est toujours visitée exactement une fois par époque :

lerobot-train \
  --dataset.repo_id=yaak-ai/L2D-v3 \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming

Le même lecteur est disponible en Python, pour les jeux de données du Hub et pour les jeux de données stockés dans un HF Storage Bucket :

from lerobot.datasets import StreamingLeRobotDataset

dataset = StreamingLeRobotDataset("yaak-ai/L2D-v3")
bucket_dataset = StreamingLeRobotDataset("my-org/my-bucket", repo_type="bucket")

Depuis l’interface en ligne de commande, sélectionnez un bucket avec --dataset.repo_type=bucket ou --dataset.root=hf://buckets/my-org/my-bucket.

Voir Training Dataset Streaming pour comprendre comment les workers et les shards se rapportent à la concurrence du streaming, l’index MP4 sidecar, les paramètres de mémoire, la reprise et le dépannage.

StreamingLeRobotDataset
Stream directly from the Hub for on‑the‑fly training.

Transformations d'images

Les transformations d’images sont des augmentations de données appliquées aux images de la caméra pendant l’entraînement afin d’améliorer la robustesse et la généralisation du modèle. LeRobot prend en charge diverses transformations, notamment les ajustements de luminosité, de contraste, de saturation, de teinte et de netteté.

Utilisation de transformations lors de la création/enregistrement du jeu de données

Actuellement, les transformations sont appliquées uniquement pendant l’entraînement, et non lors de l’enregistrement. Lorsque vous créez ou enregistrez un jeu de données, les images brutes sont stockées sans transformation. Cela vous permet d’expérimenter avec différentes augmentations plus tard sans avoir à réenregistrer les données.

Ajouter des transformations à des jeux de données existants (API)

Utilisez le paramètre image_transforms lors du chargement d'un jeu de données pour l'entraînement :

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
)

Types de transformation disponibles

Le type d'une transformation peut être n'importe quelle classe de torchvision.transforms.v2, par exemple :

  • ColorJitter : Ajuste la luminosité, le contraste, la saturation et la teinte
  • SharpnessJitter : Ajuste aléatoirement la netteté de l'image
  • RandomAffine, RandomRotation : jitter géométrique
  • GaussianNoise, MotionBlur, JPEGCompression, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection, PlanckianJitter : Effets de caméra et d’éclairage observés sur des robots réels
  • Identity : aucune transformation (utile pour les tests)

Toute autre classe de torchvision.transforms.v2 peut également être indiquée comme type (par exemple RandomResizedCrop ou GaussianBlur) ; elle est appliquée échantillon par échantillon. Vous pouvez également passer directement une transformation torchvision au paramètre image_transforms.

Chaque transformation est désormais exécutée par lots, et les mêmes transformations par lots sont appliquées sur les deux backends. Les classes par échantillon (SharpnessJitter, GaussianNoise, MotionBlur, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection, PlanckianJitter, RandomSubsetApply) et make_transform_from_config restent importables en tant que frontends non recommandés au-dessus de leurs équivalents Batched* et émettent un avertissement lors de leur utilisation ; elles seront supprimées dans une future version. ImageTransforms est un simple torch.nn.Module qui prend en entrée un tenseur de forme (C, H, W) ou (T, C, H, W). Les configurations qui nomment une transformation par type ne sont pas affectées.

Changement de comportement pour les recettes existantes. L’entraînement lit les images en uint8, et les transformations LeRobot autres que ColorJitter, RandomAffine et RandomRotation renvoyaient auparavant l’entrée non-flottante sans la modifier. Les recettes activant GaussianNoise, MotionBlur, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection ou PlanckianJitter s’entraînaient donc sans ces transformations. Elles s’appliquent désormais sur les deux backends, si bien qu’une telle recette s’entraîne différemment qu’auparavant : elle bénéficie de l’augmentation demandée. Les transformations qui s’appliquaient déjà ne changent pas, à l’exception de l’arrondi, car le chemin par lots calcule en flottant et n’arrondit qu’une seule fois à la fin, plutôt qu’après chaque transformation.

Options de configuration

  • enable : Activer/désactiver les transformations (par défaut : False)
  • max_num_transforms : Nombre maximal de transformations appliquées par image (par défaut : 3)
  • random_order : Appliquer les transformations dans un ordre aléatoire par rapport à l'ordre standard (par défaut : False)
  • weight : Probabilité d'échantillonnage pour chaque transformation (plus la valeur est élevée, plus la probabilité est grande ; si la somme des poids n'est pas égale à 1, ils seront normalisés)
  • kwargs : paramètres spécifiques à la transformation (par ex., plage de luminosité)
  • backend : l’endroit où les transformations sont exécutées pendant l’entraînement, "dataloader" (par défaut) ou "gpu" ; voir ci-dessous
  • gpu_compile : sur le backend "gpu", torch.compile les transformations par lots (par défaut : True)
  • gpu_chunk_size : sur le backend "gpu", augmente ce nombre d'échantillons à la fois pour limiter la mémoire transitoire (par défaut : 32)

Exécuter des transformations sur le GPU

Par défaut, les transformations s’exécutent par échantillon dans les workers du DataLoader. Avec plusieurs caméras, c’est souvent l’opération la plus coûteuse pour les workers, et le GPU finit par attendre les données. Définissez backend=gpu pour que les workers ne fassent que le décodage et appliquent les mêmes transformations à chaque lot sur le dispositif de la politique :

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

La randomisation reste inchangée : chaque échantillon tire ses propres transformations et paramètres, et les images de l’historique d’une même caméra les partagent. L’augmentation est appliquée uniquement aux lots d’entraînement, avant le préprocesseur de politique ; les lots d’évaluation, les déroulements et les points de contrôle sauvegardés ne sont pas modifiés.

À savoir :

  • Nécessite un accélérateur ; --policy.device=cpu est refusé. Un jeu de données construit manuellement avec son propre appelable image_transforms est également refusé, car ceux-ci s'exécutent dans les workers.
  • Prend en charge tous les types de transformation pris en charge par le backend du dataloader. Les types sans implémentation par lot (JPEGCompression, et les classes torchvision autres que ColorJitter, RandomAffine, RandomRotation et Identity) sont appliqués un échantillon à la fois au sein du lot, et ne bénéficient donc pas de l’accélération ; JPEGCompression effectue également un aller-retour via le CPU. Une transformation qui modifie la taille des images est refusée sur un lot de plus d’un échantillon.
  • Reproductible à partir de --seed, chaque processus étant initialisé différemment. L’augmentation ne consomme pas le flux aléatoire de la politique, et gpu_compile et gpu_chunk_size ne modifient pas ce qui est appliqué.
  • Les deux backends exécutent les mêmes transformations par lots en float et n'arrondissent qu'une seule fois à la fin, de sorte qu'un échantillon augmenté sur l'un est identique à l'échantillon augmenté sur l'autre, à paramètres égaux.
  • gpu_compile coûte 15 à 30 s une fois par forme d’image. La mémoire de pointe est d’environ deux copies flottantes des images du lot, plus quelques copies d’un seul bloc ; réduisez gpu_chunk_size si la mémoire est limitée. random_order=true multiplie le temps de transformation par max_num_transforms.

Migrer de v2.1 à v3.0

Un convertisseur agrège les fichiers par épisode en shards plus volumineux et écrit les décalages/métadonnées des épisodes. Convertissez votre jeu de données en suivant les instructions ci-dessous.

# 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>

À quoi ça sert

  • Agrège les fichiers parquet : episode_000000.parquet, episode_000001.parquet, … → file-000.parquet, …
  • Agrège les fichiers mp4 : episode_000000.mp4, episode_000001.mp4, … → file-000.mp4, …
  • Mise à jour de meta/episodes/* (Parquet par morceaux) avec les longueurs par épisode, les tâches et les décalages en octets/images.

Problèmes courants

Appelez toujours finalize() avant de pousser

Lors de la création ou de l'enregistrement de jeux de données, vous devez appeler dataset.finalize() pour fermer correctement les écrivains parquet. Consultez la PR #1903 pour plus de détails.

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()

Pourquoi est-ce nécessaire ?

Le jeu de données v3.0 utilise une écriture incrémentale de fichiers parquet avec des métadonnées tamponnées pour plus d'efficacité. La méthode finalize() :

  • Écrit les métadonnées d'épisode tamponnées sur le disque
  • Ferme les écrivains parquet pour écrire les métadonnées du pied de page, sinon les fichiers parquet seront corrompus
  • Vérifie que le jeu de données est valide pour le chargement

Sans appeler finalize(), vos fichiers parquet seront incomplets et le jeu de données ne se chargera pas correctement.

Autres formats et implémentations

Backend de jeu de données LanceDB

Un jeu de données v3 peut également être stocké sous forme de tables LanceDB. LanceDB est un data lakehouse multimodal construit sur le format Lance pour les modèles d'accès IA : accès aléatoire rapide à n'importe quelle ligne et à ses octets vidéo, et index scalaires, vectoriels et de recherche plein texte sur les mêmes tables, de sorte que l'EDA, la fouille, la curation et l'entraînement s'exécutent directement depuis le stockage d'objets. La structure conserve meta/ inchangé et ajoute une table frames pour les données tabulaires et une table videos contenant les MP4 sous forme de blobs. "storage_format": "lance" dans meta/info.json sélectionne le lecteur ; l'API LeRobotDataset, les éléments et le pipeline d'entraînement restent inchangés.

  • Entraînez directement depuis le Hub ou les buckets de stockage HF, avec des GPU alimentés. Des images aléatoires sont lues sur place, uniquement les octets nécessaires à chaque lot, de sorte que chaque lot constitue un mélange global et que rien n'est téléchargé au préalable.
  • Conversion au coût d’une copie de fichier. Les vidéos ne sont pas réencodées, les éléments sont donc identiques bit par bit à la source. DROID convertit en 34 minutes.
  • Ingénierie de caractéristiques sans copie. Ajoutez des colonnes telles que des scores, des embeddings ou des étiquettes corrigées aux tables lues par le formateur, sans réécrire les vidéos ni copier le jeu de données, puis filtrez et recherchez sur ces colonnes à l’aide des index. Le lecteur ignore les colonnes qu’il ne connaît pas.

Pour comprendre le fonctionnement, les benchmarks et les exemples, consultez le guide LanceDB LeRobot.

Les fonctionnalités de caméra doivent être adossées à une vidéo. Les jeux de données qui stockent les images de caméra sous forme d’images ne sont pas pris en charge par le lecteur et doivent être réencodés en vidéo avant la conversion.

La lecture des jeux de données Lance nécessite l’extra lancedb (pip install "lerobot[lancedb]"). Le format est en lecture seule : enregistrez et modifiez les jeux de données dans le format par défaut, puis convertissez-les avec le package lerobot-lancedb :

pip install "lerobot-lancedb"
lerobot-lance-convert --repo-id lerobot/pusht --out ./pusht-lance  # or --root <local dataset dir>

Le chargement utilise la même classe :

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")

Les options de bucket de stockage HF fonctionnent dans lerobot-train :

lerobot-train \
  --dataset.repo_id=my-org/pusht-lance \
  --dataset.repo_type=bucket \
  --policy.type=act

La visualisation fonctionne avec les deux visionneuses. lerobot-dataset-viz --repo-id lance-format/pusht-lance --episode-index 0 ouvre un épisode dans Rerun comme d’habitude, et --display-mode foxglove le transmet à Foxglove via WebSocket. Dans les deux cas, seules les images affichées à l’écran sont récupérées, sans export ni téléchargement MCAP.

Update on GitHub