A constant learning rate is almost never the right choice for deep learning. Early in training, the model needs large steps to escape its random initialization and reach a useful basin. Late in training, large steps overshoot the minimum and prevent convergence. Learning rate schedules manage this transition automatically — and they are often the difference between a model that reaches 95% accuracy and one that plateaus at 90%.
Why a constant learning rate fails
Pick a single learning rate and you have to compromise. A rate large enough for fast early progress is too large for fine convergence. A rate small enough for fine convergence is too small for early training, and the model takes forever to escape its initialization.
The empirical pattern across nearly every deep learning task is the same:
- Phase 1 (Warmup): small steps to stabilize early training, especially with adaptive optimizers whose statistics are noisy in the first few hundred iterations.
- Phase 2 (Main training): large steps to make rapid progress through the bulk of the loss landscape.
- Phase 3 (Annealing): progressively smaller steps to converge precisely to a low-loss point.
Schedules implement this three-phase pattern. The differences between schedules are about exactly how each phase is shaped.
Step decay: the classical schedule
Step decay multiplies the learning rate by a constant factor at predetermined epochs:
Typical values: , , step size epochs. The learning rate stays constant for 30 epochs, then drops by 10×, stays at the new value for 30 more epochs, and so on. ResNet's original ImageNet recipe uses this exact schedule with drops at epochs 30, 60, and 90.
from torch.optim.lr_scheduler import StepLR
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(num_epochs):
train_one_epoch(model, train_loader, optimizer)
scheduler.step()Step decay is simple and reliable but produces visible jumps in training loss at each step. Those jumps are not bugs — they reflect the optimizer suddenly taking smaller steps and finding a slightly lower point in the basin. Loss curves with step decay show characteristic stair-step patterns that signal the schedule is doing its job.
Exponential decay: smooth instead of stepped
Exponential decay multiplies the learning rate by a fixed factor every epoch:
With , the learning rate drops by 5% every epoch, reaching about 5% of its initial value after 60 epochs. This produces a smooth decay curve without the discontinuities of step decay.
Exponential decay is rarely the best choice in practice — it decays too aggressively early and too gently late. Cosine annealing, described next, fixes both problems.
Cosine annealing: the modern default
Cosine annealing follows the shape of a cosine curve from down to over epochs:
The shape is smooth, decays slowly at the start (preserving large steps where they matter), accelerates through the middle, and decays slowly at the end (allowing precise convergence). It produces visibly smoother loss curves than step decay, often with better final accuracy.
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs, eta_min=1e-6)
for epoch in range(num_epochs):
train_one_epoch(model, train_loader, optimizer)
scheduler.step()Cosine annealing has become the default for fine-tuning Transformers and large-scale image classification. Setting equal to the total number of training epochs is the standard recipe — the learning rate hits its minimum at the last epoch.
Warmup: linear ramp-up at the start
Most modern recipes prepend a warmup phase: linearly increase the learning rate from a small value to over the first steps, then apply the main schedule.
Warmup matters most for two reasons. First, large-batch training can produce very large initial gradients that destabilize training; warmup softens this. Second, adaptive optimizers like Adam have noisy second-moment estimates in the first few hundred iterations — the bias correction handles part of this, but warmup adds another layer of safety.
Typical warmup: 1000 steps for medium-sized models, 4000-10000 for very large Transformer training. After warmup, the learning rate transitions to whatever main schedule (cosine, step, linear) is being used.
Cosine with warmup: the Transformer recipe
Combining warmup and cosine annealing produces the schedule used in nearly every modern Transformer training recipe:
from torch.optim.lr_scheduler import LambdaLR
import math
def cosine_with_warmup(current_step):
if current_step < warmup_steps:
return current_step / max(1, warmup_steps)
progress = (current_step - warmup_steps) / max(1, total_steps - warmup_steps)
return 0.5 * (1.0 + math.cos(math.pi * progress))
scheduler = LambdaLR(optimizer, lr_lambda=cosine_with_warmup)The shape: linear ramp from 0 to peak over the first warmup_steps updates, then cosine decay from peak to 0 over the remaining steps. BERT, GPT, T5, and most subsequent large-scale Transformer models use this schedule with minor variations.
ReduceLROnPlateau: adaptive based on validation loss
A different approach: instead of a predetermined schedule, monitor validation loss and reduce the learning rate when validation stops improving:
from torch.optim.lr_scheduler import ReduceLROnPlateau
scheduler = ReduceLROnPlateau(
optimizer,
mode='min', # we want validation loss to decrease
factor=0.5, # halve the LR when triggered
patience=3, # wait 3 epochs before reducing
threshold=1e-4, # consider an improvement only if it's at least this much
)
for epoch in range(num_epochs):
train_one_epoch(model, train_loader, optimizer)
val_loss = evaluate(model, val_loader)
scheduler.step(val_loss) # note: pass the metric to step()ReduceLROnPlateau is reactive rather than predetermined. It works well when training duration is uncertain or when different stages of training take different amounts of time. The downside is that it requires a validation set to monitor, and the patience parameter introduces a hyperparameter that interacts with how the loss curve actually evolves.
Combining ReduceLROnPlateau with cosine annealing is sometimes useful: cosine for the smooth main descent, plateau-based reduction for cases where the model is still making progress at the end of the cosine cycle.
Cyclical and restart schedules
Cyclical learning rates oscillate between a minimum and a maximum, repeatedly. The motivation: the larger learning rate phase can escape sharp local minima, while the smaller phase fine-tunes precision.
Cosine annealing with warm restarts (SGDR) periodically resets the learning rate back to its peak and starts a new cosine cycle, often with a longer period each time:
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
scheduler = CosineAnnealingWarmRestarts(
optimizer,
T_0=10, # length of first cycle
T_mult=2, # double cycle length each restart
eta_min=1e-6,
)Each restart can knock the optimizer out of a local minimum into a different basin, sometimes finding better generalization. SGDR is most useful for ensemble methods where you train one model with restarts and average the snapshots from different points in the training cycle.
How to pick a schedule
Three rules cover most situations:
For Transformer training and fine-tuning: linear warmup + cosine annealing. This is the modern default. Total steps and warmup ratio (typically 6-10% of total) are the only knobs to tune.
For CNN training (e.g., ResNet on ImageNet): step decay or cosine annealing both work. Step decay produces stair-step loss curves but is well-tested. Cosine annealing produces smoother curves and slightly better final accuracy in modern recipes.
For uncertain training duration or short experiments: ReduceLROnPlateau. The reactivity to validation loss handles cases where you do not know in advance how long training will take.
The 1-cycle policy: an aggressive variant
Leslie Smith's 1-cycle policy combines warmup, peak, and annealing into a single asymmetric cycle:
- Phase A: linearly increase LR from to over the first 45% of training.
- Phase B: linearly decrease LR from to over the next 45% of training.
- Phase C: anneal LR from down to over the last 10%.
Combined with momentum that varies inversely (high when LR is low, low when LR is high), 1-cycle can dramatically accelerate training in some settings. It is more aggressive than cosine annealing and benefits from a learning rate range test (a quick scan to find safe upper and lower bounds) before training.
The main takeaway
Learning rate schedules implement the universally applicable observation that no single learning rate is right for the whole of training. Warmup stabilizes the early phase. The middle phase needs large steps to make rapid progress. The end phase needs small steps to converge precisely.
In practice, three schedules cover almost everything:
- Linear warmup + cosine annealing for Transformer training and most fine-tuning. Smooth, well-tested, the modern default.
- Step decay for classical CNN training following the ResNet recipe. Simple, reliable, and battle-tested on ImageNet.
- ReduceLROnPlateau for shorter or experimental training where the duration is uncertain. Adapts to whatever loss curve actually appears.
The optimizer is one decision, the schedule is another, and they interact. Adam with cosine annealing behaves quite differently from SGD with step decay, even on the same model and dataset. Treating both as part of a single training recipe — chosen together, tuned together — is what mature deep learning practice looks like.