Mini-batches and data loading

Introduction

Estimate useful training directions from small groups of examples while controlling memory and gradient noise.

Learning goal

Explain how batch size, sampling, and update count change optimization.

Before you start

Tensor shapes, gradients, mean loss, and optimizer updates.

Lesson plan

  1. Compare batch and full gradients
  2. Track batch tensor shapes
  3. Audit shuffling and final batches

The problem

The full training gradient averages contributions from every example. It is stable, but one update may be slow or too large for memory. Updating from one example uses little memory, but its direction can be noisy.

A mini-batch is a middle choice. For input features shaped (N, D), a batch has shape (B, D), where N is dataset size, D is feature count, and B is batch size.

Work through a small example

Let one scalar parameter be w=0. For examples x=[1,2,3,4] and targets y=[2,4,6,8], use mean squared error. The two-example batch (1,2) gives gradient -10; batch (3,4) gives -50. Their average is the full-data gradient -30.

Two mini-batch gradient estimates combine into a full gradientFour scalar examples are grouped into two pastel batches. Their gradient circles, minus ten and minus fifty, point to the averaged full gradient minus thirty. 1234gradient −10gradient −50 −30

Either mini-batch is a noisy estimate, but it is unbiased under uniform sampling: across random batches, the expected estimate equals the full gradient.

The general rule

For per-example losses l_i(w), mini-batch loss is L_B=(1/B) sum l_i. Its gradient is the same average of per-example gradients. Larger B usually lowers variance but costs more memory and produces fewer updates per epoch.

Noise is not purely harmful. It can help optimization move through flat or narrow regions, but excessive noise makes progress unstable. Learning rate and batch size interact: changing one may require retuning the other.

Implement and inspect

import torch

x = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = 2 * x
w = torch.tensor([[0.0]], requires_grad=True)

for ids in (torch.tensor([0, 1]), torch.tensor([2, 3])):
    loss = ((x[ids] @ w - y[ids]) ** 2).mean()
    (gradient,) = torch.autograd.grad(loss, w)
    print(loss.detach().item(), gradient.detach().item())

The expected lines are 10.0 -10.0 and 50.0 -50.0: each contains that batch's mean loss and scalar gradient.

x[ids] has shape (2,1), w has shape (1,1), and the loss is scalar. autograd.grad returns a gradient with the same shape as w.

Engineering checks

Go deeper

Examples inside a batch may be correlated. Adjacent text windows or frames from one video can make the gradient less informative than B independent examples. Sampling strategy therefore matters as much as batch size.

Batch-dependent layers add another constraint. Batch normalization estimates moments from the current batch, so very small batches produce unstable statistics. Gradient accumulation can imitate a larger update batch for memory, but it does not reproduce batch-normalization statistics from one large forward pass.

Practice and recap

Question: A dataset has 130 examples and batch size 32. How many updates occur if the final batch is kept?

Worked answer

Four full batches cover 128 examples. One final batch contains two, so the epoch has five updates. With mean loss, that last update gives two examples the same update-level influence as a full batch; consider this when batches are extremely uneven.

Mini-batches trade exact gradients for practical memory and frequent updates. Inspect shapes, sampling, final-batch behavior, and gradient scale together.