Skip to main content

πŸ“ Overfitting

Description​

< What is it? >​

Overfitting is what happens when a model learns the training set rather than the pattern behind it. Training loss keeps falling while validation loss flattens and then climbs: the extra capacity is being spent memorizing noise, which does not transfer to unseen data. In bias–variance terms it is the high-variance regime β€” see Bias & Variance.

The response has three parts: notice it (monitoring), limit capacity (regularization), and stop in time (early stopping).

Key points​

< Monitoring overfitting >​

SignalHow to watch itWhat it tells you
Learning curvesPlot training and validation loss per epoch β€” tensorboardX or torch.utils.tensorboard give live plotsThe gap between the two curves is the overfitting; the epoch where validation turns is where to stop
Validation metricsScore a held-out validation set every epoch β€” torchmetrics computes accuracy, precision, recall and friends incrementallyLoss can improve while the metric you care about does not; track both
Unseen dataA holdout test set, or cross-validation when data is scarceThe only honest estimate of generalization β€” see Evaluation Metrics
Capacity vs. dataCompare parameter count to training-set size, and watch the weight norm βˆ₯ΞΈβˆ₯\lVert\theta\rVert grow during trainingA model with far more parameters than examples has the room to memorize; a weight norm that keeps climbing after validation loss bottoms out is that memorization happening

The test set stays sealed while you do this. Tuning against it turns it into a second validation set and the generalization estimate stops being an estimate.

< Regularization techniques >​

Each one trades a little bias for a large reduction in variance β€” Regularization covers the mechanisms in full.

TechniqueWhat it doesNotes
DropoutRandomly zeroes each unit with probability pp during trainingp = 0.1 in transformer blocks, 0.5 in wide fully connected layers
Weight decayPenalizes the magnitude of the weights, keeping the fitted function smooth1e-2 with AdamW, 1e-4 with SGD; see why it is not plain L2 under Adam
Data augmentationRotation, crop, zoom, noise β€” encodes invariances as extra training dataLowers variance without adding bias, unlike a penalty term
Batch normalizationNormalizes each layer's activations across the mini-batchIts minibatch noise regularizes mildly as a side effect; not a substitute for the above
Simpler architectureFewer layers or narrower onesThe most direct capacity cut, and the first thing to try when the dataset is small

< Hyperparameter optimization >​

  • Learning rate schedules. Decaying the rate over time, or on a validation plateau, lets training settle into a minimum instead of bouncing around it β€” the single most impactful knob, see Gradient Descent.
  • Optimization algorithm. Adam, RMSprop, and SGD with momentum reach different minima; AdamW is the usual default when weight decay is in play.
  • Regularization strength. The L2 coefficient Ξ»\lambda and the dropout probability pp are themselves hyperparameters β€” tune them on validation data, never on test data. Both directions have a signature: symptoms of getting Ξ» wrong.

< Early stopping >​

Stop training at the validation minimum. Fewer steps means less of the model's capacity is actually used, which makes early stopping a regularizer in its own right β€” provably close to L2 for linear models.

  • Monitor validation loss. Halt when it has not improved for a set number of epochs.
  • Use patience. Validation loss is noisy; requiring several bad epochs in a row (plus a min_delta threshold) avoids stopping on a single unlucky fluctuation.
  • Save the best model, not the last one. The final epoch is by definition past the minimum. Checkpoint whenever validation improves, and deploy that checkpoint.

< Code: early stopping in PyTorch >​

import torch
import torch.nn as nn
import torch.optim as optim

train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=64, shuffle=False)

model = YourModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)

# Early stopping criteria
patience = 3 # epochs without improvement before halting
min_delta = 0.001 # improvement smaller than this does not count
epochs = 100

best_val_loss = float("inf")
counter = 0

for epoch in range(epochs):
train_loss = train(model, train_loader, criterion, optimizer)
val_loss = validate(model, val_loader, criterion)

if val_loss < best_val_loss - min_delta:
best_val_loss = val_loss
counter = 0
# Checkpoint here: the last epoch is past the minimum, this one is at it.
torch.save(model.state_dict(), "best_model.pth")
else:
counter += 1
if counter >= patience:
print(f"Early stopping at epoch {epoch}")
break

# Load the best checkpoint for evaluation or deployment
best_model = YourModel()
best_model.load_state_dict(torch.load("best_model.pth", weights_only=True))
best_model.eval()

model.eval() matters at the end: it switches dropout off and makes batch norm use its running statistics, so inference is deterministic.

Reference​