Build the mental model
Training a model requires feeding it data over and over, and PyTorch splits that job into two clean responsibilities. A `Dataset` answers exactly one question — given an index, what is that single sample? — by implementing `__getitem__` (return one sample and its label) and `__len__` (report how many samples exist); it says nothing about batching, shuffling, or iteration order. `DataLoader` wraps any `Dataset` and handles all of that: it groups individual samples into batches, optionally shuffles the sample order each epoch, and can even load and preprocess batches in parallel using worker processes while the model trains on the previous batch. This separation matters because training one sample at a time is both computationally wasteful (modern hardware is built for parallel batch operations, not one-at-a-time scalar work) and statistically noisy (a gradient estimated from a single example swings wildly compared to one averaged over a batch, making training unstable). By keeping 'how do I access one item' and 'how do I efficiently feed many items' as separate concerns, the same Dataset can be reused with different batch sizes, shuffling strategies, or parallel loading setups without changing its code.
Connect it to a real scenario
When the Tutorial Platform trains its duplicate-image detector on thousands of uploaded lesson screenshots, it wraps the image files and their labels in a custom `Dataset` whose `__getitem__` loads and resizes one image at a time — keeping that logic in one place regardless of how the images end up batched. A `DataLoader` around it then feeds the model shuffled batches of, say, 32 images each epoch, so training sees a fresh mix instead of always the same course-by-course order, which would otherwise bias the model toward whatever course happened to be uploaded first. The same `Dataset` also plugs into a bigger `DataLoader` later if the platform upgrades to more powerful training hardware.
Try the working example
import torch
from torch.utils.data import Dataset, DataLoader
class ToyDataset(Dataset):
def __init__(self, num_samples=20):
self.features = torch.randn(num_samples, 3)
self.labels = torch.randint(0, 2, (num_samples,))
def __len__(self):
return len(self.features)
def __getitem__(self, idx):
return self.features[idx], self.labels[idx]
dataset = ToyDataset()
loader = DataLoader(dataset, batch_size=4, shuffle=True)
batch_features, batch_labels = next(iter(loader))
print("Batch features shape:", batch_features.shape)
print("Batch labels shape:", batch_labels.shape)It prints `Batch features shape: torch.Size([4, 3])` and `Batch labels shape: torch.Size([4])`, confirming the DataLoader grouped four individual samples into one shuffled batch.5-minute try-it
Change `batch_size` to 5 and print `len(loader)` to see how many batches one full pass over the 20-sample dataset now produces.
One important caution
Returning raw Python lists or numbers from `__getitem__` instead of tensors — `DataLoader`'s default batching (`collate_fn`) expects tensor-like samples and can fail or silently produce the wrong batch shape otherwise.
Setting `shuffle=True` on a validation or test `DataLoader` — shuffling only matters for training; on evaluation it just adds pointless overhead and can make results harder to reproduce and compare across runs.
PyTorch Docs — Datasets & DataLoaders — Deep Learning