BRAGA

02 Writing02-02 Notes02-02-221

Adam Optimizer (second moment)

Published 16 Apr 20269 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples
vt=β2vt−1+(1−β2)gt2v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2
(1)

Deep Breakdown

What it tells me (Situation Awareness): This is the second moment update in Adam (Adaptive Moment Estimation). vtv_t is an exponentially weighted moving average (EWMA) of the squared gradients: it tracks the per-parameter magnitude of recent gradients, approximating the uncentered variance (second raw moment). Where the first moment mtm_t gives direction, vtv_t gives scale: it measures how noisy/large the gradient has been recently for each parameter independently. Parameters with large, consistent gradients get large vtv_t and therefore a smaller effective learning rate; parameters with rare, small gradients get small vtv_t and a larger effective learning rate. This is the mechanism behind Adam's "adaptive" name. In DA/DS: sparse features (embeddings, rare NLP tokens) get amplified updates via small vtv_t; frequent features get moderated updates. Together with mtm_t, vtv_t forms the denominator of the Adam parameter update.

Flowchart

Initialize: v_0 =0, beta_2 (e.g.,0.999)At step t:computegradient g_t =nabla L(theta_t)Computesquaredgradient: g_t^2(element-wise)Update secondmoment: v_t =beta_2 * v_{t-1}+ (1-beta_2) *g_t^2Bias correction:v_hat_t = v_t / (1- beta_2^t)v_hat_tapproximatesE[g_t^2]:per-parametergradientvarianceUse v_hat_t inAdam update:theta_{t+1} =theta_t - eta *m_hat_t /(sqrt(v_hat_t) +eps)Large g_t^2: v_tgrows,denominatorgrows, effectiveLR shrinksSmall g_t^2: v_tstays small,effective LRstays large

Real-world use

Where it is used

Adam optimizer in all modern deep learning frameworks (PyTorch torch.optim.Adam, TensorFlow tf.keras.optimizers.Adam). Key for: transformer fine-tuning (BERT, GPT), sparse feature models where embedding gradients are sparse, training CNNs, NLP language models. RMSProp is the special case using vtv_t alone without mtm_t.

Problem it solves

Raw gradient magnitude varies enormously across parameters: embedding parameters for rare words receive sparse, variable-magnitude gradients; final-layer parameters receive dense, large gradients. A single global learning rate cannot handle this heterogeneity well. vtv_t tracks per-parameter gradient scale, enabling Adam to normalize each parameter's update by its own historical gradient magnitude.

How it solves the problem

The EWMA vt=β2vt−1+(1−β2)gt2v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2 tracks the running average of squared gradients element-wise. At β2=0.999\beta_2 = 0.999, the effective window is 1/(1−β2)=10001/(1-\beta_2) = 1000 steps. The bias correction v^t=vt/(1−β2t)\hat{v}_t = v_t/(1-\beta_2^t) corrects for zero initialization (at t=1t=1: v1=(1−β2)g12≪g12v_1 = (1-\beta_2)g_1^2 \ll g_1^2 without correction). The Adam update η/(v^t+ϵ)\eta / (\sqrt{\hat{v}_t} + \epsilon) is then a per-parameter adaptive learning rate: large historical gradients reduce the effective step; small historical gradients amplify it.

Example in a business

A DS trains a text embedding model on a 50k-word vocabulary. Frequent words (e.g., "the") receive dense gradient updates: vtv_t grows large, effective LR shrinks to ~0.00010.0001. Rare words (e.g., "ephemeral") receive sparse gradients: vtv_t stays small, effective LR remains ~0.0010.001. Without vtv_t, rare-word embeddings would be undertrained. With Adam, all vocabulary items adapt at their own effective learning rate.

The formula, piece by piece

  • vtv_t: second moment estimate at step tt; a vector of same dimension as θ\theta and gtg_t; all elements ≥0\ge 0
  • β2\beta_2: exponential decay rate for the second moment; typical value 0.9990.999; β2∈(0,1)\beta_2 \in (0, 1)
  • vt−1v_{t-1}: previous second moment estimate; initialized at v0=0v_0 = \mathbf{0}
  • gtg_t: gradient at step tt; gt=∇θL(θt−1)g_t = \nabla_\theta L(\theta_{t-1})
  • gt2g_t^2: element-wise square of the gradient; always ≥0\ge 0; captures magnitude regardless of sign
  • (1−β2)(1-\beta_2): weight of the current squared gradient; equals 0.0010.001 when β2=0.999\beta_2 = 0.999
  • EWMA expansion: vt=(1−β2)[gt2+β2gt−12+β22gt−22+…]v_t = (1-\beta_2)[g_t^2 + \beta_2 g_{t-1}^2 + \beta_2^2 g_{t-2}^2 + \ldots]
  • Bias correction: v^t=vt/(1−β2t)\hat{v}_t = v_t / (1 - \beta_2^t); corrects for zero initialization
  • Effective window: ≈1/(1−β2)\approx 1/(1-\beta_2) steps; at β2=0.999\beta_2 = 0.999: ~1000 steps; much longer memory than mtm_t
  • v^t\hat{v}_t vs Var[gt]\text{Var}[g_t]: v^t≈E[gt2]\hat{v}_t \approx \mathbb{E}[g_t^2] (uncentered variance, not centered); includes mean squared component

The formula in words

"Keep a running average of squared past gradients. At each step, blend 99.9% of the current average with 0.1% of the new squared gradient. This gives a smoothed estimate of how large the gradient has been recently for each parameter — a per-parameter scale factor." Tiny trace: g1=2g_1 = 2, g2=−1g_2 = -1, g3=3g_3 = 3. β2=0.999\beta_2 = 0.999. v0=0v_0 = 0. v1=0.999⋅0+0.001⋅4=0.004v_1 = 0.999 \cdot 0 + 0.001 \cdot 4 = 0.004. v2=0.999⋅0.004+0.001⋅1=0.003996+0.001=0.004996v_2 = 0.999 \cdot 0.004 + 0.001 \cdot 1 = 0.003996 + 0.001 = 0.004996. v3=0.999⋅0.004996+0.001⋅9=0.004991+0.009=0.013991v_3 = 0.999 \cdot 0.004996 + 0.001 \cdot 9 = 0.004991 + 0.009 = 0.013991.

Bias corrected: v^1=0.004/(1−0.999)=0.004/0.001=4.0=g12\hat{v}_1 = 0.004 / (1 - 0.999) = 0.004 / 0.001 = 4.0 = g_1^2 (correct). v^2=0.004996/(1−0.9992)=0.004996/0.001999≈2.499\hat{v}_2 = 0.004996 / (1 - 0.999^2) = 0.004996 / 0.001999 \approx 2.499. v^3=0.013991/(1−0.9993)=0.013991/0.002997≈4.668\hat{v}_3 = 0.013991 / (1 - 0.999^3) = 0.013991 / 0.002997 \approx 4.668.

Critical Concepts

  • vtv_t vs mtm_t timescale: β2=0.999\beta_2 = 0.999 gives ~1000-step memory vs β1=0.9\beta_1 = 0.9 giving ~10-step memory. vtv_t is intentionally slow-changing: the adaptive normalization should reflect long-run gradient scale, not react to individual spiky gradients.
  • Uncentered variance: v^t≈E[gt2]\hat{v}_t \approx \mathbb{E}[g_t^2], not E[(gt−E[gt])2]\mathbb{E}[(g_t - \mathbb{E}[g_t])^2]. The uncentered version is cheaper (no subtraction of mean), always ≥0\ge 0, and sufficient for normalization. The centered variance would be v^t−m^t2\hat{v}_t - \hat{m}_t^2.
  • ϵ\epsilon in denominator: Adam uses ηm^t/(v^t+ϵ)\eta \hat{m}_t / (\sqrt{\hat{v}_t} + \epsilon) with ϵ≈10−8\epsilon \approx 10^{-8}. When v^t≈0\hat{v}_t \approx 0 (parameter has barely received any gradient), ϵ\epsilon prevents division by zero and caps the effective learning rate.
  • RMSProp connection: RMSProp is Adam without the first moment: θt+1=θt−ηgt/(vt+ϵ)\theta_{t+1} = \theta_t - \eta g_t / (\sqrt{v_t} + \epsilon). It uses raw gtg_t in numerator (no momentum smoothing). Adam adds mtm_t as a smoother numerator.
  • AMSGrad variant: replaces v^t\hat{v}_t with max⁡(v^t−1,v^t)\max(\hat{v}_{t-1}, \hat{v}_t) to ensure non-increasing effective learning rate, addressing Adam's convergence proof issues (Reddi et al., 2018).

More conservative but provably convergent.

Step-by-step calculation (tiny example)

Compute vtv_t and v^t\hat{v}_t for 5 gradient steps. β2=0.999\beta_2 = 0.999, gradients: g=[0.5,−0.3,0.8,0.2,−0.6]g = [0.5, -0.3, 0.8, 0.2, -0.6].

tt gtg_t gt2g_t^2 vt=0.999vt−1+0.001gt2v_t = 0.999 v_{t-1} + 0.001 g_t^2 1−β2t1-\beta_2^t v^t=vt/(1−β2t)\hat{v}_t = v_t / (1-\beta_2^t)
1 0.500 0.2500 0.000250 0.001000 0.2500
2 -0.300 0.0900 0.000340 0.001999 0.1701
3 0.800 0.6400 0.001000 0.002997 0.3337
4 0.200 0.0400 0.001040 0.003994 0.2604
5 -0.600 0.3600 0.001399 0.004990 0.2804

Code examples

Beginner example (Python)

python
import numpy as np

def ewma_second_moment(
    gradients: list, beta2: float = 0.999,
) -> tuple:
    """Compute EWMA second moment v_t and bias-corrected v_hat_t."""
    v = 0.0
    moments, moments_corrected = [], []
    for t, g in enumerate(gradients, start=1):
        v = beta2 * v + (1 - beta2) * g**2
        v_hat = v / (1 - beta2**t)
        moments.append(v)
        moments_corrected.append(v_hat)
    return moments, moments_corrected


# Example: 10 gradient steps
gradients = [0.5, -0.3, 0.8, 0.2, -0.6, 1.0, -0.4, 0.7, 0.1, -0.2]
v_raw, v_corrected = ewma_second_moment(gradients, beta2=0.999)
print(f"{'t':>3} {'g_t':>8} {'g_t^2':>8} {'v_t (raw)':>12} {'v_hat_t (corrected)':>20}")
for t, (g, v, vh) in enumerate(zip(gradients, v_raw, v_corrected), start=1):
    print(f"{t:>3} {g:>8.3f} {g**2:>8.4f} {v:>12.6f} {vh:>20.5f}")
print(f"\nBias correction at t=1: v_1={v_raw[0]:.6f}, v_hat_1={v_corrected[0]:.5f}")
print(f"(v_hat_1 should equal g_1^2 = {gradients[0]**2})")
print(f"\nEffective window (1/(1-beta2)): {1/(1-0.999):.0f} steps")

Beginner example (R)

r
# Adam second moment: EWMA of squared gradients with bias correction
ewma_second_moment <- function(gradients, beta2 = 0.999) {
  v <- 0
  v_list <- numeric(length(gradients))
  vhat_list <- numeric(length(gradients))
  for (t in seq_along(gradients)) {
    g <- gradients[t]
    v <- beta2 * v + (1 - beta2) * g^2
    v_hat <- v / (1 - beta2^t)
    v_list[t] <- v
    vhat_list[t] <- v_hat
  }
  list(v = v_list, v_hat = vhat_list)
}
grads <- c(0.5, -0.3, 0.8, 0.2, -0.6, 1.0, -0.4, 0.7, 0.1, -0.2)
result <- ewma_second_moment(grads, beta2 = 0.999)
cat(sprintf("%-3s %-8s %-8s %-12s %-20s\n", "t", "g_t", "g_t^2", "v_t (raw)", "v_hat_t (corrected)"))
for (t in seq_along(grads)) {
  cat(sprintf("%-3d %-8.3f %-8.4f %-12.6f %-20.5f\n", t, grads[t], grads[t]^2, result$v[t], result$v_hat[t]))
}
cat(sprintf("\nEffective window: %.0f steps\n", 1 / (1 - 0.999)))

Professional example (Python)

python
import numpy as np
np.random.seed(42)

# -- Visualize how v_t adapts LR differently across sparse vs dense parameters --
# Scenario: parameter A = frequent (large, dense gradients)
# parameter B = sparse (mostly zero, occasional large gradient)
N_STEPS = 300
BETA2 = 0.999
EPSILON = 1e-8
ETA = 0.001

# Simulate gradient streams
rng = np.random.default_rng(42)

# Dense parameter: gradients sampled N(0, 1) every step
grads_dense = rng.normal(0, 1.0, N_STEPS)

# Sparse parameter: gradient is 0 most steps, large occasionally (5% sparsity)
sparsity_mask = rng.binomial(1, 0.05, N_STEPS).astype(float)
grads_sparse = sparsity_mask * rng.normal(0, 2.0, N_STEPS)


def compute_effective_lr(
    gradients: np.ndarray, beta2: float = 0.999, eta: float = 0.001, epsilon: float = 1e-8,
) -> tuple:
    """Return v_hat and effective_lr = eta / (sqrt(v_hat) + epsilon) at each step."""
    v = 0.0
    v_hats, eff_lrs = [], []
    for t, g in enumerate(gradients, start=1):
        v = beta2 * v + (1 - beta2) * g**2
        v_hat = v / (1 - beta2**t)
        eff_lr = eta / (np.sqrt(v_hat) + epsilon)
        v_hats.append(v_hat)
        eff_lrs.append(eff_lr)
    return np.array(v_hats), np.array(eff_lrs)


v_hat_dense, eff_lr_dense = compute_effective_lr(grads_dense, BETA2, ETA, EPSILON)
v_hat_sparse, eff_lr_sparse = compute_effective_lr(grads_sparse, BETA2, ETA, EPSILON)
print("Adam Second Moment: Effective LR Adaptation (dense vs sparse gradients)")
print(f"\n{'Step':>6} {'v_hat (dense)':>15} {'eff_LR (dense)':>16} {'v_hat (sparse)':>16} {'eff_LR (sparse)':>17}")
for step in [9, 29, 99, 199, 299]:
    print(
        f"{step+1:>6} {v_hat_dense[step]:>15.6f} {eff_lr_dense[step]:>16.6f} "
        f"{v_hat_sparse[step]:>16.6f} {eff_lr_sparse[step]:>17.6f}"
    )
print(f"\nMean effective LR (dense): {eff_lr_dense.mean():.6f}")
print(f"Mean effective LR (sparse): {eff_lr_sparse.mean():.6f}")
print(f"LR ratio sparse/dense: {eff_lr_sparse.mean()/eff_lr_dense.mean():.2f}x")
print(f"\nInterpretation: sparse parameter gets {eff_lr_sparse.mean()/eff_lr_dense.mean():.1f}x higher effective LR")
print(f"because its v_hat is smaller (rare updates keep denominator small).")

Source: domain knowledge

#calculus#continuous-optimisation#advanced-optimisers-stochastic-and-adaptive

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close