| """ |
| HuggingFace datasets — обгортка для PyTorch DataLoader'а. |
| |
| Чому це краще ніж torchvision: |
| - Більше готових датасетів (CIFAR-100, Tiny ImageNet, Food-101, etc.) |
| - Уніфікований API |
| - Можна стрімити великі датасети (для майбутнього ImageNet) |
| - Легко підмінити датасет — тільки змінити рядок |
| |
| Решта пайплайну (poison_generator, detector, models) НЕ ЗМІНЮЄТЬСЯ: |
| DataLoader повертає звичайні (image_tensor, label) пари — |
| рівно як torchvision робив. |
| """ |
|
|
| from typing import Optional, Tuple |
| import torch |
| from torch.utils.data import Dataset, DataLoader |
| from torchvision import transforms |
| from datasets import load_dataset |
|
|
|
|
| class HFImageDataset(Dataset): |
| """ |
| Адаптер між HuggingFace Dataset і PyTorch Dataset. |
| |
| HF повертає dict з PIL-зображенням, нам треба (tensor, label). |
| """ |
|
|
| def __init__( |
| self, |
| hf_dataset, |
| image_col: str = "img", |
| label_col: str = "label", |
| transform: Optional[transforms.Compose] = None, |
| ): |
| self.ds = hf_dataset |
| self.image_col = image_col |
| self.label_col = label_col |
| |
| |
| self.transform = transform or transforms.ToTensor() |
|
|
| def __len__(self) -> int: |
| return len(self.ds) |
|
|
| def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]: |
| item = self.ds[idx] |
| img = item[self.image_col] |
| label = item[self.label_col] |
|
|
| |
| if self.transform: |
| img = self.transform(img) |
|
|
| |
| |
| |
|
|
| return img, label |
|
|
|
|
| |
| DATASET_CONFIGS = { |
| "cifar10": { |
| "hf_name": "cifar10", |
| "image_col": "img", |
| "label_col": "label", |
| "in_channels": 3, |
| "num_classes": 10, |
| "image_size": 32, |
| }, |
| "cifar100": { |
| "hf_name": "cifar100", |
| "image_col": "img", |
| "label_col": "fine_label", |
| "in_channels": 3, |
| "num_classes": 100, |
| "image_size": 32, |
| }, |
| "mnist": { |
| "hf_name": "mnist", |
| "image_col": "image", |
| "label_col": "label", |
| "in_channels": 1, |
| "num_classes": 10, |
| "image_size": 28, |
| }, |
| "tiny_imagenet": { |
| "hf_name": "zh-plus/tiny-imagenet", |
| "image_col": "image", |
| "label_col": "label", |
| "in_channels": 3, |
| "num_classes": 200, |
| "image_size": 64, |
| }, |
| "fashion_mnist": { |
| "hf_name": "fashion_mnist", |
| "image_col": "image", |
| "label_col": "label", |
| "in_channels": 1, |
| "num_classes": 10, |
| "image_size": 28, |
| }, |
| } |
|
|
|
|
| def get_dataloaders( |
| dataset: str, |
| batch_size: int, |
| cache_dir: Optional[str] = None, |
| num_workers: int = 2, |
| resize_to: Optional[int] = None, |
| ) -> Tuple[DataLoader, DataLoader, int, int]: |
| """ |
| Завантажує датасет з HuggingFace Hub і повертає PyTorch DataLoader'и. |
| |
| Args: |
| dataset: ключ з DATASET_CONFIGS ("cifar10", "cifar100", тощо) |
| batch_size: розмір батча |
| cache_dir: куди кешувати датасет (None → за замовч. ~/.cache/huggingface) |
| num_workers: процесів для завантаження даних |
| resize_to: якщо вказано — змінює розмір зображень (для уніфікації архітектури) |
| |
| Returns: |
| (train_loader, test_loader, in_channels, num_classes) |
| """ |
| if dataset not in DATASET_CONFIGS: |
| raise ValueError( |
| f"Unknown dataset: {dataset}. Available: {list(DATASET_CONFIGS.keys())}" |
| ) |
|
|
| cfg = DATASET_CONFIGS[dataset] |
|
|
| |
| print(f"Loading {cfg['hf_name']} from HuggingFace Hub...") |
| ds = load_dataset(cfg["hf_name"], cache_dir=cache_dir) |
|
|
| |
| train_split = "train" |
| test_split = "test" if "test" in ds else ("validation" if "validation" in ds else "valid") |
|
|
| print(f"Splits available: {list(ds.keys())}, using train='{train_split}', test='{test_split}'") |
| print(f"Train size: {len(ds[train_split])}, Test size: {len(ds[test_split])}") |
|
|
| |
| tx_list = [] |
| if resize_to is not None: |
| tx_list.append(transforms.Resize((resize_to, resize_to))) |
| tx_list.append(transforms.ToTensor()) |
| transform = transforms.Compose(tx_list) |
|
|
| train_set = HFImageDataset( |
| ds[train_split], |
| image_col=cfg["image_col"], |
| label_col=cfg["label_col"], |
| transform=transform, |
| ) |
| test_set = HFImageDataset( |
| ds[test_split], |
| image_col=cfg["image_col"], |
| label_col=cfg["label_col"], |
| transform=transform, |
| ) |
|
|
| train_loader = DataLoader( |
| train_set, batch_size=batch_size, shuffle=True, |
| num_workers=num_workers, pin_memory=torch.cuda.is_available(), |
| ) |
| test_loader = DataLoader( |
| test_set, batch_size=batch_size, shuffle=False, |
| num_workers=num_workers, pin_memory=torch.cuda.is_available(), |
| ) |
|
|
| return train_loader, test_loader, cfg["in_channels"], cfg["num_classes"] |
|
|
|
|
| if __name__ == "__main__": |
| |
| train_loader, test_loader, in_ch, n_cls = get_dataloaders("cifar10", batch_size=32) |
| x, y = next(iter(train_loader)) |
| print(f"\nBatch shape: {x.shape}") |
| print(f"Labels shape: {y.shape}") |
| print(f"Pixel range: [{x.min():.3f}, {x.max():.3f}]") |
| print(f"In channels: {in_ch}, Classes: {n_cls}") |
| print(f"Train batches: {len(train_loader)}, Test batches: {len(test_loader)}") |
|
|