Each weight gets its own step size.
On this page
Each method keeps the one above and adds one change.
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.
| axis | gradient | SGD step |
|---|---|---|
| x (steep) | −35 | +5.25 |
| y (flat) | 2 | −0.30 |
| ratio x to y | 17.5 | 17.5 |
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.
AdaGrad divides each coordinate's gradient by the size of all its past gradients.
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.
| knob | raise it | lower it | default |
|---|---|---|---|
α (lr) | larger first step, which then decays as α/√t | every step shorter | 0.01 |
ε (eps) | small gradients step less | tiny gradients step closer to α | 1e-10 |
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.
| k (steps ago) | weight (1−β)βk |
|---|---|
| 0 | 0.0100 |
| 1 | 0.0099 |
| 10 | 0.0090 |
| 100 | 0.0037 |
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:
| axis | g1 | √E1 | first step |
|---|---|---|---|
| x | −35 | 3.5 | +5.0 |
| y | 2 | 0.2 | −5.0 |
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×.
| knob | raise it | lower it | default |
|---|---|---|---|
β2 (PyTorch alpha) | longer window, slower to react | reacts fast, noisier steps | 0.99 |
α (lr) | every step longer | every step shorter | 0.01 |
Adam keeps momentum's average direction, RMSProp's average size, and a fix for the zero start of both.
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:
| axis | g1 | m1 | v1 |
|---|---|---|---|
| x | −35 | −3.5 | 1.225 |
| y | 2 | 0.2 | 0.004 |
m is momentum's velocity scaled by (1 − β1): an average instead of a sum (SGD, Momentum and Nesterov, section 2).
Figure 3. β2 0.999 looks back about 1,000 steps.
| knob | raise it | lower it | default |
|---|---|---|---|
| β1 | smoother direction, coasts longer, turns slower | follows today's gradient; 0 makes m = g | 0.9 |
| β2 | steadier step size, slower to notice a scale change | reacts faster, noisier steps; LLaMA, Meta's large language model, used 0.95 (Touvron et al. 2023) | 0.999 |
Both averages start at zero, so for a steady gradient the uncorrected step grows.
| t | 1 − β1t | 1 − β2t | uncorrected step ÷ α |
|---|---|---|---|
| 1 | 0.100 | 0.0010 | 3.16 |
| 2 | 0.190 | 0.0020 | 4.25 |
| 3 | 0.271 | 0.0030 | 4.95 |
| 4 | 0.344 | 0.0040 | 5.44 |
| 5 | 0.410 | 0.0050 | 5.80 |
Dividing each average by the total weight it has received so far makes it a true average from step 1.
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).
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.
| axis | g | m̂/√v̂ | step at α 0.5 |
|---|---|---|---|
| x | −35 | −1.00 | +0.50 |
| y | 2 | +1.00 | −0.50 |
Adam steps by the average direction divided by the average size, times α.
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.
| knob | raise it | lower it | default |
|---|---|---|---|
α (lr) | every coordinate steps about α further; past the bottom of steep directions the loss gets noisy | steadier and slower | 1e-3; LLaMA 7B used a peak of 3.0e-4 |
Figure 5. Faint bars are SGD at α 0.15.
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)
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).
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:
| c | SGD step 1 on x | Adam step 1 on x |
|---|---|---|
| 0.01 | 0.0525 | 0.50 |
| 1 | 5.25 | 0.50 |
| 256 | 1344 | 0.50 |
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|:
| |g| | step ÷ α |
|---|---|
| 1e−7 | 0.91 |
| 1e−8 | 0.50 |
| 1e−10 | 0.01 |
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.
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.
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).
Nadam puts the Nesterov preview into Adam: it uses the momentum the next step will use.
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.
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.
| optimizer | extra state | memory beyond the weights |
|---|---|---|
| SGD | none | 0 |
| Momentum, Nesterov | v | 1× |
| AdaGrad, RMSProp | G or E | 1× |
| Adam, Nadam | m and v | 2× |
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
| symptom | cause | what to check | fix |
|---|---|---|---|
| some weights never move | gradients far below ε | gradient sizes per layer | lower ε or rescale the loss |
| huge first steps, early loss spike | bias correction off | a hand-written Adam | divide by 1 − βt |
| noisy, jumpy steps | β2 lowered too far | step size per layer | raise β2 back toward 0.999 |
| steps tiny long after the loss settled | v still remembers large old gradients | the √v̂ buffer | wait one window or lower β2 |