← All tutorials

Adaptive Optimizers: AdaGrad, RMSProp and Adam

Each weight gets its own step size.

On this page

1 One learning rate cannot fit every direction 2 AdaGrad 3 RMSProp 4 Adam 5 Nadam 6 Racing the optimizers 7 Adaptive optimizers in PyTorch 8 Failure modes 9 References
SGD and momentumone step size for all weights
divides by size
AdaGradsum of all past squares
forgets old gradients
RMSPropmoving average of squares
adds momentum, fixes zero start
Adamtwo averages, corrected
reads ahead
NadamNesterov preview inside Adam
training-loop controls
AdamW, SAM, schedules, clippingOptimizers in Practice

Each method keeps the one above and adds one change.

1 One learning rate cannot fit every direction

Every figure here uses the loss of the series, L = 5x² + 0.2y², started at (−3.5, 5). I call its shape, a long narrow valley, the ravine; it stands for the ill-conditioned loss of a real network, where one direction is far steeper than another. Its numbers are illustrative, computed by script. Plain SGD at α 0.15 takes this first step.

x moves 17.5 times farther, though y has farther to go.
axisgradientSGD step
x (steep)−35+5.25
y (flat)2−0.30
ratio x to y17.517.5
SGD, recap
θ ← θ − α·g
θ is the weights, α the learning rate and g the gradient; the same α multiplies every coordinate's gradient.

The steep axis caps α (SGD, Momentum and Nesterov, section 1.1), so the flat axis crawls. The methods below give each coordinate its own step size.

2 AdaGrad

AdaGrad divides each coordinate's gradient by the size of all its past gradients.

Step length per axis, α 0.50.250.512345step t1x (steep)y (flat)Step = α/√t for a steady gradient0.5 at t 10.25 at t 40.05 at t 10021100step t
Figure 1. ① Both axes step 0.5 at first; ② steps then fall as α/√t.
AdaGrad's running sum of squares and divided step
Gt = Gt−1 + gt²
Square today's gradient (the sign drops out) and add it to a running total that never forgets.
θ ← θ − α·gt√Gt + ε
√G is the total size of past gradients, in gradient units. g√G compares today's push with all past pushes, and α turns it into distance. ε keeps the divide away from zero.

At step 1, √G = |g|, so every coordinate steps exactly α. After that the sum only grows, so the steps only shrink. GloVe word vectors were trained with AdaGrad at an initial rate of 0.05 (Pennington et al. 2014): a rare word's row gets few gradients, so its steps stay large.

AdaGrad knobs in PyTorch.
knobraise itlower itdefault
α (lr)larger first step, which then decays as α/√tevery step shorter0.01
ε (eps)small gradients step lesstiny gradients step closer to α1e-10

3 RMSProp

RMSProp replaces AdaGrad's forever-sum with an average that forgets. With a decay rate β of 0.99, the share of the old average kept each step, a value from k steps ago gets this weight.

Recent values count most; the last 100 steps hold 63% of the weight.
k (steps ago)weight (1−β)βk
00.0100
10.0099
100.0090
1000.0037
An exponential moving average
at = β·at−1 + (1 − β)·ut
Keep the fraction β of the old average and mix in 1 − β of today's value u.
at = (1 − β)·ut + (1 − β)β·ut−1 + …
Written out, the value from k steps ago is weighted (1 − β)βk. The weights add up to 1, and the window is about 11 − β steps.

This one average also keeps batch norm's running statistics. RMSProp applies it to g² and calls the average E. At its first step, with β2 0.99 and α 0.5:

Both axes jump 5, ten times α, from (−3.5, 5) to (1.5, 0).
axisg1√E1first step
x−353.5+5.0
y20.2−5.0
RMSProp's average of squared gradients and divided step
Et = β2·Et−1 + (1 − β2)·gt²
The moving average above with u = g², keeping 99% of the old average and mixing in 1% of today's squared gradient.
θ ← θ − α·gt√Et + ε
√E is the typical recent gradient size; g√E is today's push in units of recent pushes.
√E1 = √1 − β2·|g1|
The average starts at zero, so after one step it holds only 1 − β2 of g². Its root is 10 times too small, so the first step is 10 times too big.

RMSProp's memory is about 100 steps at β2 0.99 instead of forever. The memory matters when gradient sizes change, as when a balancer cuts one task's weight (Gradient Balancing Inside the Training Loop, section 7.1).

Figure 2. The average starts warmed up; ① gradients drop 10×.

RMSProp knobs in PyTorch; eps defaults to 1e-8.
knobraise itlower itdefault
β2 (PyTorch alpha)longer window, slower to reactreacts fast, noisier steps0.99
α (lr)every step longerevery step shorter0.01

4 Adam

Adam keeps momentum's average direction, RMSProp's average size, and a fix for the zero start of both.

4.1 Two running averages

Adam averages the gradient into m and its square into v, both from zero; this v is not momentum's velocity. At step 1 on the ravine:

m keeps 10% of the gradient, v keeps 0.1% of its square.
axisg1m1v1
x−35−3.51.225
y20.20.004
Adam's two moving averages
mt = β1·mt−1 + (1 − β1)·gt
The first moment m is the moving average of the gradient, the recent average direction.
vt = β2·vt−1 + (1 − β2)·gt²
The second moment v is the moving average of the squared gradient, the recent average size. This is RMSProp's E renamed; it is not momentum's velocity.

m is momentum's velocity scaled by (1 − β1): an average instead of a sum (SGD, Momentum and Nesterov, section 2).

4.2 The betas set how far back each average looks

Figure 3. β2 0.999 looks back about 1,000 steps.

Both must stay below 1; at 1 an average never updates and its correction divides by zero.
knobraise itlower itdefault
β1smoother direction, coasts longer, turns slowerfollows today's gradient; 0 makes m = g0.9
β2steadier step size, slower to notice a scale changereacts faster, noisier steps; LLaMA, Meta's large language model, used 0.95 (Touvron et al. 2023)0.999

4.3 Correcting the zero start

Both averages start at zero, so for a steady gradient the uncorrected step grows.

Uncorrected, the step grows from 3.16α to 5.80α in five steps; corrected, it is 1.00α on every row.
t1 − β1t1 − β2tuncorrected step ÷ α
10.1000.00103.16
20.1900.00204.25
30.2710.00304.95
40.3440.00405.44
50.4100.00505.80

Dividing each average by the total weight it has received so far makes it a true average from step 1.

Bias correction
m̂t = mt1 − β1t
1 − β1t is the weight m has collected: 0.1 at t 1, 0.19 at t 2, close to 1 after about 10 steps.
v̂t = vt1 − β2t
1 − β2t grows only 0.001 per step, 0.632 at t 1,000, so v needs its fix for about 1,000 steps.

m's weight grows about 0.1 per step while v's grows only 0.001, so the raw ratio of m to √v keeps growing; correcting m alone would make step 1 31.6α, which is why both are corrected. Every Adam run takes these corrected first steps, the DQN agent's at 5e-4 too (Deep Q-Networks, section 4).

Step ÷ α for a steady gradient15101RMSProp from zero2Adam, uncorrectedAdam, corrected1101001000step tFirst five steps, α 0.5startcorrecteduncorrected, y −3.383y = 0
Figure 4. ① Raw RMSProp, 10α; ② raw Adam peaks at 6.57α; ③ y overshoots.

4.4 One full Adam step

On the first step, x's gradient is −35 and y's is 2. SGD would move x 17.5 times farther than y. Adam moves both by 0.5.

Corrected, both ratios are ±1, so both steps are α.
axisgm̂/√v̂step at α 0.5
x−35−1.00+0.50
y2+1.00−0.50

Adam steps by the average direction divided by the average size, times α.

The Adam step
θ ← θ − α·m̂t√v̂t + ε
m̂ is which way this coordinate has been pushed lately; √v̂ is how hard. Their ratio is close to ±1 when the gradient is steady and smaller when it keeps changing. α turns the ratio into distance; ε matters only when gradients are as small as ε.

So each coordinate steps about α, whatever the size of its gradient; the divide is element by element (Vectors and Matrices, section 6.3). This is RMSProp's step with m̂ in place of g, both averages corrected. Late in training a coordinate that gets its first gradient steps 3.16α, like uncorrected step 1 above.

Because m̂/√v̂ is near ±1, α is close to the step length.
knobraise itlower itdefault
α (lr)every coordinate steps about α further; past the bottom of steep directions the loss gets noisysteadier and slower1e-3; LLaMA 7B used a peak of 3.0e-4
Step 1 of 5, line 1

Figure 5. Faint bars are SGD at α 0.15.

This step, line by line
m = β1·m + (1 − β1)·g
v = β2·v + (1 − β2)·g²
m̂ = m1 − β1t
v̂ = v1 − β2t
θ ← θ − α·m̂√v̂ + ε

The code below is the same five steps.

import numpy as np

def adam_step(theta, g, m, v, t, lr=1e-3, b1=0.9, b2=0.999, eps=1e-8):
    m = b1 * m + (1 - b1) * g
    v = b2 * v + (1 - b2) * g * g
    m_hat = m / (1 - b1 ** t)
    v_hat = v / (1 - b2 ** t)
    theta = theta - lr * m_hat / (np.sqrt(v_hat) + eps)
    return theta, m, v

theta = np.array([-3.5, 5.0])
m = np.zeros(2)
v = np.zeros(2)
for t in range(1, 6):
    g = np.array([10 * theta[0], 0.4 * theta[1]])
    theta, m, v = adam_step(theta, g, m, v, t, lr=0.5)
  1. 1The two moving averages, m and v.
  2. 2Bias correction of both.
  3. 3The step, element by element; returns the new averages too.
  4. 4The ravine from (−3.5, 5); the first step is (0.5, −0.5), as in the figure.

Gradient balancing meets this divide in Adam's per-coordinate scaling and the tilt of Adam's step (Gradient Balancing Inside the Training Loop, sections 7 and 7.2).

4.5 How Adam ignores the gradient's scale, and where it stops

Switch a loss from the mean over a batch of 256 examples to the sum, and every gradient is 256 times larger (Gradient Balancing Inside the Training Loop, section 5). Multiply every gradient by c:

SGD would need α 0.15/256 to behave as before; Adam needs no change.
cSGD step 1 on xAdam step 1 on x
0.010.05250.50
15.250.50
25613440.50
Scale invariance
c·m̂√c²·v̂ = m̂√v̂ for c > 0
Scaling every gradient by c scales m̂ by c and √v̂ by c, so the ratio and the step do not change. It works per coordinate, so it also undoes the 17.5 to 1 split between the axes.
Every gradient times c

Figure 6. SGD's step scales with c; Adam's does not.

Invariance has two limits. The first is ε. For a steady gradient of size |g|:

Only a coordinate with near-zero gradients, such as a rarely seen token's row, feels ε.
|g|step ÷ α
1e−70.91
1e−80.50
1e−100.01
Where ε takes over
step ÷ α = |g||g| + ε
Near |g| = ε the step is half of α; well below ε, Adam acts like SGD with rate αε.

The second limit is time. Invariance holds for a steady scale, not for one that moves within v's window. After 2,000 steps at gradient 1 the gradient drops to 0.1.

Step ÷ α after the gradient drops 10×0.10.51SGD, one tenth at onceAdam121101001000steps after the drop
Figure 7. ① m adapts in about 10 steps; ② v in about 1,000.

For hundreds of steps Adam steps about 10 times smaller than α, so its step does shrink when the gradient shrinks, just later. LLaMA trains with β2 0.95, a window of about 20 steps; with it the same drop recovers to 0.9α 118 steps after the drop.

4.6 What Adam's momentum carries after the gradient changes

Distance glided after the gradient stops0.50.91momentum, α 0.01Adam, α 0.12010203040steps after the stopAngle off the new direction after a turn10°45°90°first moment m (momentum)Adam’s step341510152025steps after the turn
Figure 8. ① Stop; ② both glide 0.9; ③ Adam turns at once, ④ m in 7 steps.
Coasting
mt+k = β1k·mt
With no new gradient (g = 0 from step t on), m shrinks by 0.9 per step.
vt+k = β2k·vt
v shrinks by 0.999 per step, so √v by only 0.9995, and the step shrinks by about 0.9 per step, like momentum with μ 0.9.

Momentum at α 0.01 builds its step up to 10α, about 0.1. Adam at α 0.1 keeps its step near α, also 0.1. Both then coast about 9 last steps. After a turn, √v̂ is still large on the old axis and small on the new one, so the divide boosts the new direction: Adam's step is within 45° of it at once, while m takes 7 steps. That is the case of a multitask run where one task's gradient suddenly dominates a shared weight (Gradient Balancing Inside the Training Loop, section 6.1).

5 Nadam

Nadam puts the Nesterov preview into Adam: it uses the momentum the next step will use.

Step on x ÷ α, steps 1 to 5AdamNadamPyTorch NAdam0.511.512345step t12
Figure 9. ① Nadam's first step, 1.47α; ② PyTorch NAdam falls behind Adam.
Nadam (Dozat 2016, constant β1)
m̄t = (1 − β1)·gt1 − β1t + β1·mt1 − β1t+1
The first term is today's gradient with its own correction; the second is the corrected momentum one step ahead. At step 1 this is g + 0.474g, so the step is 1.47α.
θ ← θ − α·m̄t√v̂t + ε
Adam's step with m̄ in place of m̂.

PyTorch ships it as torch.optim.NAdam, which multiplies β1 by a warm-up factor, so its momentum weight μ starts near 0.45 and rises toward β1 over thousands of steps.

6 Racing the optimizers

All four run 200 steps on the ravine; AdaGrad (section 2) and Nadam (Figure 9) are left out to keep four paths readable. The noise toggle adds minibatch-like noise.

Figure 10. The thick path ends with the lowest loss.

With noise on, Adam has the lowest median loss over 20 noise seeds from α 0.07 up, RMSProp at 0.05 and 0.06; no optimizer wins everywhere.

What each optimizer keeps between steps.
optimizerextra statememory beyond the weights
SGDnone0
Momentum, Nesterovv1×
AdaGrad, RMSPropG or E1×
Adam, Nadamm and v2×

7 Adaptive optimizers in PyTorch

Each runs once per training step. The DQN page's Adam call is this same update.

opt = torch.optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.999), eps=1e-8)
loss.backward()
opt.step()
state = opt.state[next(model.parameters())]
state["exp_avg"]      # m, the first moment
state["exp_avg_sq"]   # v, the second moment
state["step"]         # t, used by the bias correction
  1. 1β1 and β2 are the betas pair; these are the defaults.
  2. 2One step after each backward pass.
  3. 3The two state tensors m and v per weight tensor, and the step count t.

8 Failure modes

Most of these trace back to ε, β2 or the bias correction.
symptomcausewhat to checkfix
some weights never movegradients far below εgradient sizes per layerlower ε or rescale the loss
huge first steps, early loss spikebias correction offa hand-written Adamdivide by 1 − βt
noisy, jumpy stepsβ2 lowered too farstep size per layerraise β2 back toward 0.999
steps tiny long after the loss settledv still remembers large old gradientsthe √v̂ bufferwait one window or lower β2

9 References

  1. Duchi, J., et al. (2011). Adaptive subgradient methods for online learning and stochastic optimization. Journal of Machine Learning Research.
  2. Tieleman, T., and Hinton, G. (2012). Lecture 6.5, RMSProp. Neural Networks for Machine Learning (Coursera).
  3. Pennington, J., Socher, R., and Manning, C. D. (2014). GloVe: global vectors for word representation. EMNLP.
  4. Kingma, D. P., and Ba, J. (2015). Adam: a method for stochastic optimization. ICLR.
  5. Dozat, T. (2016). Incorporating Nesterov momentum into Adam. ICLR workshop.
  6. Touvron, H., et al. (2023). LLaMA: open and efficient foundation language models. arXiv:2302.13971.
  7. PyTorch documentation, torch.optim (Adagrad, RMSprop, Adam and NAdam default settings).