Download evaluation/behavior-1k-solution/src/b1k/training/data_loader.py from davidwdw/b1k_model: direct link, hf CLI and curl.
- Browser
- Download file 9.04 kB
-
https://huggingface.co/davidwdw/b1k_model/resolve/main/evaluation/behavior-1k-solution/src/b1k/training/data_loader.py
- Command line
-
hf download hf://davidwdw/b1k_model/evaluation/behavior-1k-solution/src/b1k/training/data_loader.py
-
curl -L -o data_loader.py https://huggingface.co/davidwdw/b1k_model/resolve/main/evaluation/behavior-1k-solution/src/b1k/training/data_loader.py
9.04 kB
| """Data loading for BEHAVIOR-1K dataset. | |
| Reference: https://github.com/wensi-ai/openpi/tree/behavior | |
| """ | |
| import logging | |
| import time | |
| # Import all base data loading from OpenPI | |
| from openpi.training.data_loader import ( | |
| Dataset, | |
| IterableDataset, | |
| DataLoader, | |
| TransformedDataset, | |
| IterableTransformedDataset, | |
| FakeDataset, | |
| TorchDataLoader, | |
| RLDSDataLoader, | |
| create_torch_dataset, | |
| create_rlds_dataset, | |
| transform_iterable_dataset, | |
| create_data_loader, | |
| create_torch_data_loader, | |
| create_rlds_data_loader, | |
| ) | |
| import openpi.training.config as _config | |
| import openpi.transforms as _transforms | |
| from b1k.models.observation import Observation | |
| from b1k.transforms_normalize import NormalizeWithPerTimestamp | |
| class DataLoaderImpl(DataLoader): | |
| """Custom DataLoader using our Observation with fast_tokens.""" | |
| def __init__(self, data_config: _config.DataConfig, data_loader: TorchDataLoader | RLDSDataLoader): | |
| self._data_config = data_config | |
| self._data_loader = data_loader | |
| def data_config(self) -> _config.DataConfig: | |
| return self._data_config | |
| def __iter__(self): | |
| for batch in self._data_loader: | |
| yield Observation.from_dict(batch), batch["actions"] | |
| def create_behavior_dataset(data_config: _config.DataConfig, action_horizon: int, seed: int | None = None) -> Dataset: | |
| """Create a BEHAVIOR-1K dataset for training. | |
| Uses OmniGibson's BehaviorLeRobotDataset for efficient loading of BEHAVIOR-1K data. | |
| Args: | |
| data_config: Data configuration | |
| action_horizon: Action horizon for delta timestamps | |
| seed: Random seed for shuffling. If None, uses random seed based on current time. | |
| Returns: | |
| Dataset instance with BEHAVIOR-1K data | |
| """ | |
| from omnigibson.learning.datas.lerobot_dataset import BehaviorLeRobotDataset | |
| # Use random seed if not provided | |
| if seed is None: | |
| seed = int(time.time() * 1000) % (2**32) | |
| logging.info(f"Using random seed for BehaviorLeRobotDataset: {seed}") | |
| tasks = [ | |
| "picking_up_trash", # difficulty: 2 | |
| "putting_away_Halloween_decorations", # difficulty: 3 | |
| "cleaning_up_plates_and_food", # difficulty: 3.5 | |
| "setting_mousetraps", # difficulty: 2 | |
| "hiding_Easter_eggs", # difficulty: 2 | |
| "set_up_a_coffee_station_in_your_kitchen", # difficulty: 3 | |
| "putting_dishes_away_after_cleaning", # difficulty: 3 | |
| "preparing_lunch_box", # difficulty: 3 | |
| "loading_the_car", # difficulty: 3.5 | |
| "carrying_in_groceries", # difficulty: 3.5 | |
| "turning_on_radio", # difficulty: 1 | |
| "picking_up_toys", # difficulty: 3.5 | |
| "can_meat", # difficulty: 3.5 | |
| "rearranging_kitchen_furniture", # difficulty: 3 | |
| "putting_up_Christmas_decorations_inside", # difficulty: 2 | |
| "bringing_in_wood", # difficulty: 1.5 | |
| "moving_boxes_to_storage", # difficulty: 1.5 | |
| "bringing_water", # difficulty: 1.5 | |
| "tidying_bedroom", # difficulty: 2 | |
| "outfit_a_basic_toolbox", # difficulty: 2, | |
| "sorting_vegetables", | |
| "collecting_childrens_toys", | |
| "putting_shoes_on_rack", | |
| "boxing_books_up_for_storage", | |
| "storing_food", | |
| "clearing_food_from_table_into_fridge", | |
| "assembling_gift_baskets", | |
| "sorting_household_items", | |
| "getting_organized_for_work", | |
| "clean_up_your_desk", | |
| "setting_the_fire", | |
| "clean_boxing_gloves", | |
| "wash_a_baseball_cap", | |
| "wash_dog_toys", | |
| "hanging_pictures", | |
| "attach_a_camera_to_a_tripod", | |
| "clean_a_patio", | |
| "clean_a_trumpet", | |
| "spraying_for_bugs", | |
| "spraying_fruit_trees", | |
| "make_microwave_popcorn", | |
| "cook_cabbage", | |
| "make_pizza", | |
| "chop_an_onion", | |
| "slicing_vegetables", | |
| "chopping_wood", | |
| "canning_food", | |
| "cook_hot_dogs", | |
| "cook_bacon", | |
| "freeze_pies", | |
| ] | |
| dataset = BehaviorLeRobotDataset( | |
| repo_id=data_config.repo_id, | |
| root=data_config.behavior_dataset_root, | |
| tasks=tasks, | |
| modalities=["rgb"], | |
| local_only=True, | |
| delta_timestamps={ | |
| key: [t / 30.0 for t in range(action_horizon)] for key in data_config.action_sequence_keys | |
| }, | |
| episodes=data_config.episodes_index, | |
| chunk_streaming_using_keyframe=False, | |
| shuffle=True, | |
| seed=seed, | |
| ) | |
| if data_config.prompt_from_task: | |
| dataset = TransformedDataset(dataset, [_transforms.PromptFromLeRobotTask(dataset.meta.tasks)]) | |
| return dataset | |
| def transform_dataset(dataset: Dataset, data_config: _config.DataConfig, *, skip_norm_stats: bool = False) -> Dataset: | |
| """Transform dataset with B1K-specific per-timestamp normalization support. | |
| CRITICAL: This overrides wensi-ai's transform_dataset to pass use_per_timestamp to Normalize. | |
| wensi-ai's version doesn't support per-timestamp normalization which causes huge action losses! | |
| """ | |
| norm_stats = {} | |
| if data_config.repo_id != "fake" and not skip_norm_stats: | |
| if data_config.norm_stats is None: | |
| raise ValueError( | |
| "Normalization stats not found. " | |
| "Make sure to run `scripts/compute_norm_stats.py --config-name=<your-config>`." | |
| ) | |
| norm_stats = data_config.norm_stats | |
| # Build transform list | |
| transforms_list = [ | |
| *data_config.repack_transforms.inputs, | |
| *data_config.data_transforms.inputs, | |
| # Use custom Normalize with per-timestamp support (wensi-ai's doesn't have it!) | |
| NormalizeWithPerTimestamp( | |
| norm_stats, | |
| use_quantiles=data_config.use_quantile_norm, | |
| use_per_timestamp=data_config.use_per_timestamp_norm # CRITICAL: Per-timestamp normalization! | |
| ), | |
| ] | |
| # Add subtask state computation for PI_BEHAVIOR models (needs dataset reference) | |
| model_transforms = [] | |
| for transform in data_config.model_transforms.inputs: | |
| # ComputeSubtaskStateFromMeta needs dataset reference to access episode lengths | |
| if hasattr(transform, '__class__') and transform.__class__.__name__ == 'ComputeSubtaskStateFromMeta': | |
| # Replace placeholder with dataset-aware version | |
| from b1k import transforms as b1k_transforms | |
| if hasattr(dataset, 'meta') and hasattr(dataset.meta, 'episodes'): | |
| model_transforms.append(b1k_transforms.ComputeSubtaskStateFromMeta(dataset=dataset)) | |
| logging.info("Added dataset-aware ComputeSubtaskStateFromMeta transform") | |
| else: | |
| logging.warning("Skipping subtask state computation - dataset has no meta.episodes") | |
| else: | |
| model_transforms.append(transform) | |
| transforms_list.extend(model_transforms) | |
| return TransformedDataset(dataset, transforms_list) | |
| def extract_episode_lengths_from_dataset(dataset) -> dict[int, float]: | |
| """Extract episode lengths from B1K dataset metadata. | |
| Args: | |
| dataset: BehaviorLeRobotDataset instance | |
| Returns: | |
| Dictionary mapping episode_index to episode_length (in frames) | |
| Raises: | |
| ValueError: If dataset doesn't have required metadata | |
| """ | |
| if not hasattr(dataset, 'episode_data_index'): | |
| raise ValueError("Dataset must have episode_data_index attribute") | |
| episode_data_index = dataset.episode_data_index | |
| if 'to' not in episode_data_index or 'from' not in episode_data_index: | |
| raise ValueError("episode_data_index must have 'to' and 'from' keys") | |
| episode_to = episode_data_index['to'] | |
| episode_from = episode_data_index['from'] | |
| episodes = dataset.episodes | |
| episode_lengths = {} | |
| for i, episode_index in enumerate(episodes): | |
| if i < len(episode_to) and i < len(episode_from): | |
| episode_length = episode_to[i] - episode_from[i] | |
| episode_lengths[episode_index] = float(episode_length) | |
| logging.info(f"Extracted {len(episode_lengths)} episode lengths from dataset") | |
| return episode_lengths | |
| def create_behavior_data_loader( | |
| config: _config.TrainConfig, | |
| *, | |
| sharding=None, | |
| shuffle: bool = False, | |
| num_batches: int | None = None, | |
| skip_norm_stats: bool = False, | |
| ) -> DataLoader: | |
| """Create a data loader for BEHAVIOR-1K training.""" | |
| import jax | |
| import time | |
| data_config = config.data.create(config.assets_dirs, config.model) | |
| # Use random seed if not provided | |
| seed = config.seed | |
| if seed is None: | |
| seed = int(time.time() * 1000) % (2**32) | |
| logging.info(f"Using random seed: {seed}") | |
| dataset = create_behavior_dataset(data_config, action_horizon=config.model.action_horizon, seed=seed) | |
| dataset = transform_dataset(dataset, data_config, skip_norm_stats=skip_norm_stats) | |
| data_loader = TorchDataLoader( | |
| dataset, | |
| local_batch_size=config.batch_size // jax.process_count(), | |
| sharding=sharding, | |
| shuffle=shuffle, | |
| num_batches=num_batches, | |
| num_workers=config.num_workers, | |
| seed=seed, | |
| ) | |
| return DataLoaderImpl(data_config, data_loader) | |