Fine-tuning Learning Rates › Learning Rate Decay
Language AI HandbookAll chapters Fine-tuning Learning Rates › Learning Rate DecayOne section · Sections in this chapter6 / 12 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
After warmup completes, learning rate decay schedules reduce the rate throughout training. This helps the model converge to a stable minimum rather than oscillating around it. The intuition is that large learning rates are useful early in training when the model needs to make significant adjustments, but become counterproductive later when the model is close to a good solution and only needs fine adjustments.
Think of decay as the difference between making broad brushstrokes and fine detail work when painting. Early in the process, you want big, confident strokes that establish the overall composition. As the painting develops, you switch to finer tools for precise work. A painter who keeps using the broad brush throughout the entire process will never reach the detail that the final piece requires. Similarly, a constant learning rate throughout fine-tuning tends to produce models that have adapted well on average but oscillate around the optimal parameters rather than converging to them precisely.
The choice of decay schedule shapes the final convergence and the entire trajectory of learning. Different schedules allocate the "learning budget" differently across the training run: some front-load it (maintaining higher rates early), others distribute it more uniformly, and others back-load it (maintaining higher rates in the middle of training). These differences can have real effects on final performance, particularly for tasks where the model needs to make large initial adjustments to the fine-tuning task before settling into precise calibration.
Linear Decay
The simplest decay schedule reduces the learning rate linearly to zero (or some minimum value) by the end of training. This approach gives a steady, predictable reduction in learning rate that is easy to reason about and implement.
We want to compute a learning rate that starts at the target value immediately after warmup ends and decreases uniformly to zero at the final training step. After warmup ends at step , the learning rate at step is:
where:
- : the learning rate at step
- : the learning rate at the start of decay (equal to the peak warmup value)
- : the current training step
- : the step number where decay begins (end of warmup)
- : the total number of training steps
Why does this formula make sense? The fraction is the progress through the decay phase, ranging from 0 at the start of decay to 1 at the final step. Subtracting this from 1 yields the remaining proportion of the learning rate. This creates a steady linear decline from down to 0. At each step during the decay phase, the learning rate decreases by exactly the same absolute amount. This keeps uniform reduction throughout training.
This is the default schedule for most BERT fine-tuning recipes because it strikes a reasonable balance between simplicity and effectiveness. The linear nature makes it trivial to predict the learning rate at any step, which is helpful for debugging and monitoring training progress.
Cosine Decay
Cosine decay gives a smoother transition that decays slowly at first, accelerates in the middle, and slows again near the end. This schedule has become increasingly popular because it maintains higher learning rates for longer during the middle of training, giving the optimizer more opportunity to explore the loss surface before settling into a final minimum.
We want to compute a smoothly decaying learning rate using the cosine function's natural properties. After warmup, the learning rate at step is:
where:
- : the learning rate at step
- : the minimum learning rate to reach at the end of training (often 0)
- : the target learning rate at the start of decay (peak warmup value)
- : the current training step
- : the step number where decay begins
- : the total number of training steps
Why does this formula produce an S-shaped decay? We can trace the mathematical transformations step by step to see the mechanism clearly:
The cosine transform is what creates the S-curve behavior. The derivative of is , which equals zero at and (the endpoints) but reaches its maximum magnitude at (the midpoint). This means the cosine schedule changes slowly at the beginning and end, and rapidly in the middle, which is exactly the behavior we want: the model maintains higher learning rates early to explore broadly, transitions through a rapid reduction phase in the middle, and then approaches its final value gently without overshooting.
import math
def cosine_decay_schedule(step, warmup_steps, total_steps, target_lr, min_lr=0):
"""
Linear warmup followed by cosine decay.
Args:
step: Current training step
warmup_steps: Number of warmup steps
total_steps: Total number of training steps
target_lr: Target learning rate after warmup
min_lr: Minimum learning rate at end of training
"""
if step < warmup_steps:
return target_lr * (step / warmup_steps)
else:
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return min_lr + (target_lr - min_lr) * 0.5 * (
1 + math.cos(math.pi * progress)
)
def polynomial_decay_schedule(
step, warmup_steps, total_steps, target_lr, min_lr=0, power=1.0
):
"""
Linear warmup followed by polynomial decay.
Power=1.0 gives linear decay, power=2.0 gives quadratic decay.
"""
if step < warmup_steps:
return target_lr * (step / warmup_steps)
else:
progress = (step - warmup_steps) / (total_steps - warmup_steps)
decay = (1 - progress) ** power
return min_lr + (target_lr - min_lr) * decayLet's compare these decay schedules:
import matplotlib.pyplot as plt
import numpy as np
use_book_style()
plt.rcParams["figure.figsize"] = (6.0, 4.0)
total_steps = 10000
warmup_steps = 1000
target_lr = 2e-5
steps = np.arange(total_steps)
linear_lrs = [
warmup_with_decay_schedule(s, warmup_steps, total_steps, target_lr)
for s in steps
]
cosine_lrs = [
cosine_decay_schedule(s, warmup_steps, total_steps, target_lr)
for s in steps
]
poly2_lrs = [
polynomial_decay_schedule(
s, warmup_steps, total_steps, target_lr, power=2.0
)
for s in steps
]
fig, ax = plt.subplots()
ax.plot(
steps, [lr * 1e5 for lr in linear_lrs], label="Linear decay", linewidth=1
)
ax.plot(
steps, [lr * 1e5 for lr in cosine_lrs], label="Cosine decay", linewidth=1
)
ax.plot(
steps, [lr * 1e5 for lr in poly2_lrs], label="Polynomial (p=2)", linewidth=1
)
ax.axvline(
x=warmup_steps,
color=PALETTE["muted"],
linestyle="--",
alpha=0.5,
label="End warmup",
)
ax.set_xlabel("Training Step")
ax.set_ylabel(r"Learning Rate ($\times 10^{-5}$)")
ax.set_title("Comparison of Learning Rate Decay Schedules")
ax.legend()
ax.set_xlim(0, total_steps)
polish_axes(ax)While all three schedules eventually reach zero, they allocate learning rate differently throughout training. Cosine decay maintains higher learning rates for longer in the middle of training, potentially letting the model to escape local minima more effectively than linear decay. Polynomial decay with power 2 drops quickly at first and then more slowly, front-loading the high learning rate period. The choice between these schedules is a trade-off between exploration and convergence speed. Linear decay is the most predictable and easiest to reason about; cosine decay is often slightly better in practice due to its gentler terminal convergence; polynomial decay gives the most control through its exponent parameter.
Choosing a Decay Schedule
The choice of decay schedule depends on your training dynamics and the specific characteristics of your task:
- Linear decay: Simple and effective; good default for short fine-tuning runs. The predictable reduction makes it easy to reason about training dynamics and diagnose issues such as learning rate being too high or too low.
- Cosine decay: Often slightly better performance; allows more learning early and late in training. The gentler decay at both ends can help the model both explore better initially and converge more precisely at the end. It is the recommended default when you have sufficient compute for even a modest hyperparameter search.
- Polynomial decay: Useful when you want to front-load learning (higher powers) or extend it (lower powers). The exponent parameter gives direct control over the shape of the decay curve, making this a flexible option for tasks with unusual training dynamics.
Empirically, cosine and linear decay perform similarly for most fine-tuning tasks, with cosine sometimes giving a small but consistent improvement. The difference is often smaller than the effect of other hyperparameters like the base learning rate or batch size, so either choice is reasonable for most applications. When in doubt, cosine decay is the better default because its smooth behavior near both endpoints reduces the risk of numerical instability.