The validation loop runs alongside training โ typically once per epoch โ to measure how well the model generalizes to data it hasn't been trained on, using no gradient computation at all.
The Complete Structure
model.eval() # sets the model to EVALUATION mode
val_loss = 0.0
correct = 0
with torch.no_grad(): # disables gradient tracking -- saves memory and compute
for X_batch, y_batch in val_loader:
predictions = model(X_batch)
loss = loss_fn(predictions, y_batch)
val_loss += loss.item()
correct += (predictions.argmax(dim=1) == y_batch).sum().item()
avg_val_loss = val_loss / len(val_loader)
val_accuracy = correct / len(val_loader.dataset)
print(f"Validation: loss={avg_val_loss:.4f}, accuracy={val_accuracy:.4f}")
Two Critical Differences From the Training Loop
| Difference | Why |
|---|---|
model.eval() instead of model.train() | Switches dropout off and batch normalization to use its running statistics instead of the current batch's โ see Dropout and Batch Normalization |
torch.no_grad() block | No backward pass will ever be run on validation data, so there's no need to cache the intermediate values a backward pass would require โ saving significant memory and compute (see Forward Pass) |
Crucially, there's no loss.backward(), no optimizer.step(), and no optimizer.zero_grad() anywhere in a validation loop โ the model's weights are never touched during validation; it's purely a read-only measurement.
Why Validation Runs Alongside Training, Not Just at the End
Tracking validation loss/accuracy every epoch (alongside training loss/accuracy) is exactly what reveals overfitting as it happens: if training loss keeps decreasing but validation loss starts increasing, the model is beginning to memorize training-specific noise instead of learning generalizable patterns โ precisely the signal Early Stopping and Checkpointing are built to respond to.
Visualizing the Signal
The gap that opens between training and validation loss, and validation loss turning upward, is the exact signal that stops training early.
Common Mistakes
- Forgetting
model.eval()before validation โ dropout stays active and batch norm keeps using batch-level statistics, producing noisy, misleadingly inconsistent validation metrics from run to run. - Forgetting
torch.no_grad()โ validation will still work correctly, but wastes memory and compute unnecessarily caching values that will never be used for a backward pass. - Accidentally calling
optimizer.step()orloss.backward()inside a validation loop โ this would leak validation data into training, corrupting the entire point of having a separate validation set.
Interview Relevance
Q: "What would happen if you forgot to call model.eval() before running validation?" Dropout would remain active, randomly zeroing activations during validation just as it does during training โ and batch normalization would use the current (validation) batch's statistics instead of its learned running averages. Both effects introduce noise and inconsistency into validation metrics, making them unreliable for judging true generalization performance or comparing across epochs.
Practice Question
Why is it safe and correct to disable gradient tracking (torch.no_grad()) during validation, but not during training?