๐ŸŽ“ A1Learner
๐Ÿ”

PyTorch K-Fold Cross-Validation

K-fold cross-validation rotates the validation slice through the data k times and averages the scores. Every row validates exactly once, so small datasets give a steadier score.

The rotation

Thirty rows cut five ways give folds of six rows. Each fold validates once while the other 24 rows train.

rows = 30
k = 5
fold_size = rows // k
for fold in range(k):
    start = fold * fold_size
    print(f"Fold {fold + 1} validates rows {start} to {start + fold_size - 1}")

Average the fold scores

Five scores become one by the plain mean. One lucky split can no longer carry the verdict alone.

scores = [0.967, 1.000, 1.000, 0.967, 0.967]
print(f"{sum(scores) / len(scores):.3f}")

Example

import torch
from torch import nn

torch.manual_seed(0)
per_class = 50
centres = [(-2.0, 0.0), (2.0, 0.0), (0.0, 2.5)]
parts_x, parts_y = [], []
for label, (cx, cy) in enumerate(centres):
    parts_x.append(torch.randn(per_class, 2) * 0.6 + torch.tensor([cx, cy]))
    parts_y.append(torch.full((per_class,), label, dtype=torch.long))
x = torch.cat(parts_x)
y = torch.cat(parts_y)
order = torch.randperm(len(x))
x, y = x[order], y[order]

k = 5
fold_size = len(x) // k
scores = []
for fold in range(k):
    val_idx = list(range(fold * fold_size, (fold + 1) * fold_size))
    train_idx = [i for i in range(len(x)) if i not in val_idx]
    train_x, train_y = x[train_idx], y[train_idx]
    val_x, val_y = x[val_idx], y[val_idx]
    torch.manual_seed(1)
    model = nn.Sequential(nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 3))
    loss_fn = nn.CrossEntropyLoss()
    opt = torch.optim.Adam(model.parameters(), lr=0.05)
    for epoch in range(40):
        loss = loss_fn(model(train_x), train_y)
        opt.zero_grad()
        loss.backward()
        opt.step()
    model.eval()
    with torch.no_grad():
        pred = model(val_x).argmax(dim=1)
    acc = (pred == val_y).float().mean().item()
    scores.append(acc)
    print(f"Fold {fold + 1}: accuracy {acc:.3f} on {len(val_x)} rows")

mean = sum(scores) / len(scores)
print(f"Mean accuracy: {mean:.3f}")

Key points

  • Fold: one of the k equal parts the data is cut into
  • Each fold takes a turn as the validation slice
  • The other folds train the model for that round
  • Use a fresh model for every fold so no round inherits trained weights
  • The mean of the fold scores is the reported score

In this example, one seed builds 150 rows in three blobs. Each blob holds 50 rows around its own centre. A shuffle mixes the classes before the folds are cut. Five folds of 30 rows follow from the shuffled order. Each round trains a fresh model on 120 rows for 40 epochs. The seed resets before each model, so every round starts from the same kind of start. The held-out fold then scores the model. The fold scores print one per line, from 0.967 to 1.000. Their mean prints last at 0.980.

Note

K-fold costs k training runs, so it suits small data better than large data

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Python Playground

Exercise

In 5-fold cross-validation, how many times does each row serve as validation?