Data pipelines: Dataset, DataLoader & augmentation
Real projects rarely come as a neat torchvision.datasets download. You will have a folder of product photos, a CSV of transactions, or a database table. PyTorch's Dataset and DataLoader classes turn any of these into fast, shuffled batches — and a good pipeline is often the difference between a GPU that is 30% busy and one that is 95% busy.
The Dataset contract
A map-style dataset needs just two methods: __len__ (how many examples) and __getitem__ (return one example by index).
import pandas as pd
import torch
from torch.utils.data import Dataset
class DemandDataset(Dataset):
# Each row: store_id, day_of_week, is_festival, temp_c, units_sold
def __init__(self, csv_path: str):
df = pd.read_csv(csv_path)
self.x = torch.tensor(
df[["day_of_week", "is_festival", "temp_c"]].to_numpy(),
dtype=torch.float32,
)
self.y = torch.tensor(df["units_sold"].to_numpy(), dtype=torch.float32)
def __len__(self):
return len(self.y)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
For small tabular data, converting everything to tensors once in __init__ is fastest. For images, load lazily in __getitem__ so you never hold thousands of photos in RAM.
Folders of images
If your images are arranged as root/class_name/image.jpg, ImageFolder does the work:
sweets/
train/
jalebi/ 001.jpg 002.jpg ...
kaju_katli/ ...
rasgulla/ ...
val/
jalebi/ ...
from torchvision import datasets, transforms
train_tfms = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.7, 1.0)),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
val_tfms = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
train_ds = datasets.ImageFolder("sweets/train", transform=train_tfms)
val_ds = datasets.ImageFolder("sweets/val", transform=val_tfms)
print(train_ds.class_to_idx) # {'jalebi': 0, 'kaju_katli': 1, 'rasgulla': 2}
The normalisation values are the ImageNet channel means and standard deviations, required when using ImageNet-pretrained models.
A custom image dataset from a CSV
When labels live in a spreadsheet instead of folder names:
from pathlib import Path
from PIL import Image
class LabelledImages(Dataset):
def __init__(self, csv_path, img_dir, transform=None):
self.df = pd.read_csv(csv_path) # columns: filename, label
self.img_dir = Path(img_dir)
self.classes = sorted(self.df["label"].unique())
self.to_idx = {c: i for i, c in enumerate(self.classes)}
self.transform = transform
def __len__(self):
return len(self.df)
def __getitem__(self, i):
row = self.df.iloc[i]
img = Image.open(self.img_dir / row["filename"]).convert("RGB")
if self.transform:
img = self.transform(img)
return img, self.to_idx[row["label"]]
The .convert("RGB") call protects you from grayscale or PNG-with-alpha images that would otherwise break batching.
DataLoader settings that matter
from torch.utils.data import DataLoader
train_dl = DataLoader(train_ds, batch_size=32, shuffle=True,
num_workers=4, pin_memory=True, drop_last=True)
val_dl = DataLoader(val_ds, batch_size=64, shuffle=False, num_workers=4)
| Argument | Effect |
|---|---|
batch_size |
Larger is faster per epoch but uses more memory |
shuffle |
True for training only |
num_workers |
Parallel loading processes; try the number of CPU cores |
pin_memory |
Faster CPU-to-GPU copies |
drop_last |
Avoids a tiny last batch (useful with batch norm) |
On Windows and macOS, code that uses num_workers > 0 must run under if __name__ == "__main__":.
Augmentation principles
Augmentation creates plausible variations so the model learns what matters. Choose transforms that preserve the label:
- Horizontal flip is fine for sweets or clothes, wrong for digits like 6 or text.
- Strong colour jitter can be harmful when colour is the signal (ripe vs unripe mango).
- Apply augmentation to training data only.
Handling class imbalance
If you have 2,000 kaju katli photos but only 150 of rasmalai, use a WeightedRandomSampler so each class is sampled roughly equally:
from collections import Counter
from torch.utils.data import WeightedRandomSampler
counts = Counter(train_ds.targets)
weights = [1.0 / counts[t] for t in train_ds.targets]
sampler = WeightedRandomSampler(weights, num_samples=len(weights), replacement=True)
train_dl = DataLoader(train_ds, batch_size=32, sampler=sampler) # no shuffle with sampler
Common mistakes
- Splitting data after augmentation, leaking near-duplicates into validation.
- Forgetting
Normalizewith pretrained models. - Random splits for time-series data — always split by date instead.
- Near-identical photos (burst shots) in both train and validation, inflating accuracy.
Practice
Collect or download about 50 images each of three product categories (for example, kurta, saree, dupatta) and arrange them in train/ and val/ folders (80/20). Build ImageFolder datasets with the transforms above, then write a script that takes one training batch, un-normalises it and shows the 16 images in a grid with their labels using torchvision.utils.make_grid. Confirm that augmentations look realistic and labels are correct.