If you’ve trained even a handful of neural networks, you’ve almost certainly typed optimizer = Adam(...) without thinking twice. Adam has become the default optimizer across nearly every deep learning framework and tutorial. But what’s actually happening inside it? In this article, I’ll unpack Adam piece by piece — where it came from, the math that drives it, why it works so well in practice, and where it falls short.
Where Adam Comes From
Adam stands for Adaptive Moment Estimation, introduced by Diederik Kingma and Jimmy Ba in 2014. It was designed to combine the best properties of two earlier optimizers:
- Momentum — which smooths the optimization trajectory by accumulating a moving average of past gradients (the “first moment”).
- RMSProp — which adapts the learning rate for each parameter individually based on the magnitude of recent squared gradients (the “second moment”).
Adam essentially says: let’s track both of these simultaneously, and use them together to compute a smarter, per-parameter update.
The Two Moving Averages
Adam maintains two exponentially decaying moving averages for every parameter:
First moment estimate (mean of gradients):
$$m_t = \beta_1 m_{t-1} + (1 – \beta_1) g_t$$
Second moment estimate (mean of squared gradients):
$$v_t = \beta_2 v_{t-1} + (1 – \beta_2) g_t^2$$
where $g_t = \nabla_\theta J(\theta_t)$ is the gradient at step $t$, and $\beta_1$, $\beta_2$ are decay rates, typically set to $0.9$ and $0.999$ respectively.
- $m_t$ behaves like classical momentum — it estimates the direction the gradient has been consistently pointing.
- $v_t$ estimates the magnitude of recent gradients (specifically, their uncentered variance) for each parameter individually.
Bias Correction
Since $m_t$ and $v_t$ are initialized at zero, they’re biased toward zero in the early steps of training (especially when $\beta_1$ and $\beta_2$ are close to 1). Adam corrects for this bias explicitly:
$$\hat{m}_t = \frac{m_t}{1 – \beta_1^t}$$
$$\hat{v}_t = \frac{v_t}{1 – \beta_2^t}$$
As $t$ grows large, $\beta_1^t$ and $\beta_2^t$ shrink toward zero, so this correction naturally fades and $\hat{m}_t \to m_t$, $\hat{v}_t \to v_t$.
The Final Update Rule
$$\theta_{t+1} = \theta_t – \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$$
where $\eta$ is the base learning rate and $\epsilon$ is a small constant (commonly $10^{-8}$) added purely for numerical stability, to avoid dividing by zero when $\hat{v}_t$ is tiny.
Let’s unpack why this update is so effective:
- The numerator $\hat{m}_t$ gives you the smoothed direction to move in — just like momentum.
- The denominator $\sqrt{\hat{v}_t}$ normalizes the step size per parameter. If a parameter has consistently had large gradients, $\hat{v}_t$ is large, so its effective step shrinks. If a parameter has had small, sparse gradients, $\hat{v}_t$ is small, so its effective step grows relatively larger.
This means Adam effectively gives each parameter its own adaptive learning rate, scaled inversely to the recent magnitude of its gradients.
Default Hyperparameters
| Hyperparameter | Typical value | Role |
|---|---|---|
| $\eta$ (learning rate) | 0.001 | Base step size |
| $\beta_1$ | 0.9 | Decay rate for first moment (momentum) |
| $\beta_2$ | 0.999 | Decay rate for second moment (variance) |
| $\epsilon$ | $10^{-8}$ | Numerical stability constant |
These defaults, proposed in the original paper, work remarkably well out of the box across a huge range of problems — a big reason for Adam’s popularity.
Adam Step by Step
flowchart TD
A["Compute gradients"] --> B["Update first moment estimate"]
B --> C["Update second moment estimate"]
C --> D["Apply bias correction"]
D --> E["Compute parameter update"]
E --> F["Update model parameters"]
F --> G{"Training converged?"}
G -- No --> A
G -- Yes --> H["Training complete"]Implementing Adam From Scratch
import numpy as np
def adam(grad_fn, theta_init, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8, steps=1000):
theta = theta_init.copy()
m = np.zeros_like(theta)
v = np.zeros_like(theta)
for t in range(1, steps + 1):
g = grad_fn(theta)
m = beta1 * m + (1 - beta1) * g
v = beta2 * v + (1 - beta2) * (g ** 2)
m_hat = m / (1 - beta1 ** t)
v_hat = v / (1 - beta2 ** t)
theta = theta - lr * m_hat / (np.sqrt(v_hat) + eps)
return theta
In PyTorch, using Adam takes a single line:
import torch
model = torch.nn.Linear(10, 1)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-8)
And in TensorFlow/Keras:
import tensorflow as tf
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='categorical_crossentropy'
)
Advantages of Adam
- Per-parameter adaptive learning rates — no need to hand-tune a single global learning rate as carefully as with plain SGD.
- Fast convergence — in most cases, Adam reaches a low training loss faster than SGD, especially in early training.
- Works well with sparse gradients — a common scenario in NLP tasks with large embedding layers, where only a subset of parameters receive nonzero gradients at each step.
- Robust default hyperparameters — the original paper’s suggested defaults work well across a very wide range of problems with little tuning.
- Combines the strengths of momentum and adaptive scaling — you get smoothing and per-parameter step-size adaptation simultaneously.
Disadvantages and Limitations
- Can generalize worse than SGD — several empirical studies have found that models trained with Adam sometimes achieve lower test accuracy than equivalent models trained with well-tuned SGD + momentum, particularly in computer vision.
- Memory overhead — Adam stores two additional state vectors ($m_t$ and $v_t$) per parameter, tripling the memory footprint of the optimizer state compared to plain SGD (which stores none) — relevant for very large models.
- Convergence issues in some cases — the original Adam algorithm was shown to fail to converge on certain simple convex problems; this led to the AMSGrad variant, which uses the maximum of past $v_t$ values instead of the exponential moving average to fix the issue.
- Sensitive to $\epsilon$ in edge cases — in some settings (particularly mixed-precision training), the default $\epsilon$ can cause numerical issues, requiring adjustment.
- Weight decay coupling issue — naive L2 regularization added directly to the gradient interacts oddly with Adam’s adaptive scaling; this motivated the AdamW variant (see below).
Adam vs. AdamW
A subtle but important issue: when you add L2 regularization to a loss function and then optimize with Adam, the regularization term gets divided by $\sqrt{\hat{v}_t} + \epsilon$ just like the primary gradient. This isn’t what you actually want, since weight decay should shrink weights uniformly, not adaptively.
AdamW fixes this by decoupling weight decay from the gradient-based update entirely, applying it directly to the parameters:
$$\theta_{t+1} = \theta_t – \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} – \eta \lambda \theta_t$$
where $\lambda$ is the weight decay coefficient. AdamW has become the standard choice for training transformers and large language models.
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)
Why the Second Moment Matters: An Intuitive Walkthrough
It’s worth slowing down on exactly why dividing by $\sqrt{\hat{v}_t}$ is so useful. Imagine two parameters in a network: parameter $A$ consistently receives large gradients (say, magnitude around 1.0) on every batch, while parameter $B$ receives small, sparse gradients (say, magnitude around 0.01, and only nonzero occasionally). Under vanilla SGD with a single global learning rate, parameter $A$ would move in huge steps while parameter $B$ would barely move at all, even though $B$ might genuinely need larger relative updates to learn effectively.
Adam’s second moment estimate $v_t$ tracks the recent squared-gradient magnitude for each parameter separately. Dividing by $\sqrt{\hat{v}_t}$ effectively normalizes each parameter’s update to a comparable scale: parameter $A$’s large gradients get divided by a large $\sqrt{\hat{v}_t}$, shrinking its effective step; parameter $B$’s small gradients get divided by a small $\sqrt{\hat{v}_t}$, growing its effective step. The net effect is that every parameter tends to move by a roughly similar relative amount per step, rather than being dominated by whichever parameters happen to have the largest raw gradients. This is especially valuable in architectures like embeddings, where most rows of a large embedding table receive gradient updates only rarely.
Adam’s Sensitivity to Its Three Hyperparameters
| Hyperparameter | Effect of increasing it | Effect of decreasing it |
|---|---|---|
| $\eta$ (learning rate) | Faster but riskier convergence, more prone to instability | Slower but more stable convergence |
| $\beta_1$ | Smoother, more momentum-like trajectory, slower to react to gradient direction changes | Less smoothing, more reactive to recent gradients, closer to plain adaptive SGD |
| $\beta_2$ | Longer memory of past squared gradients, slower adaptation to changing gradient scale | Faster adaptation, but noisier scaling estimates |
In practice, $\beta_1$ and $\beta_2$ are rarely tuned away from their defaults (0.9 and 0.999) except in specialized settings — the learning rate $\eta$ remains the primary hyperparameter worth tuning carefully for most tasks.
A Common Misconception: Adam Doesn’t Eliminate the Need for a Learning Rate Schedule
Because Adam already adapts per-parameter step sizes, newcomers sometimes assume it removes the need for a global learning rate schedule entirely. This isn’t quite right — Adam adapts the relative scaling between parameters, but the overall global learning rate $\eta$ still benefits enormously from scheduling (particularly warmup for transformer models, and decay over the course of long training runs). Think of Adam’s adaptive scaling and a learning rate schedule as solving two different, complementary problems: one balances updates across parameters, the other controls the overall step size over time.
Adam vs. Other Optimizers: Comparison Table
| Optimizer | Adaptive LR | Momentum | Memory overhead | Common use case |
|---|---|---|---|---|
| SGD | No | Optional | None | CV models, when generalization matters most |
| RMSProp | Yes | No | 1x extra state | RNNs, older architectures |
| Adam | Yes | Yes | 2x extra state | Default for most tasks |
| AdamW | Yes | Yes | 2x extra state | Transformers, LLMs |
| AdaGrad | Yes | No | 1x extra state | Sparse data, NLP with rare features |
Real-World Use Cases
- Natural language processing: Adam (and especially AdamW) is the standard optimizer for training transformer architectures, including BERT and GPT-family models.
- Generative adversarial networks (GANs): Adam’s adaptive per-parameter scaling helps stabilize the notoriously tricky min-max optimization dynamics of GAN training.
- Reinforcement learning: Policy gradient methods often use Adam due to its robustness to noisy and non-stationary gradient signals.
- Rapid prototyping: Because Adam requires minimal tuning to get “good enough” results quickly, it’s the go-to choice when iterating on model architecture rather than optimization hyperparameters.
Best Practices
- Start with the default hyperparameters ($\eta=0.001$, $\beta_1=0.9$, $\beta_2=0.999$) and only tune if you have a specific reason.
- Prefer AdamW over plain Adam whenever you’re using weight decay/L2 regularization.
- Combine Adam with a learning rate schedule (warmup + decay is especially common for transformers).
- If final test accuracy matters more than training speed and you have time to tune carefully, consider comparing against SGD + momentum — Adam isn’t always the best generalizer.
- Watch for instability in the first few hundred steps of training; a learning rate warmup phase often fixes this.
- Consider AMSGrad if you observe convergence issues on a problem with a well-understood convex or near-convex structure.
Frequently Asked Questions
Why does Adam sometimes generalize worse than SGD with momentum, despite converging faster? This is one of the more actively studied puzzles in deep learning optimization. One widely cited explanation is that Adam’s adaptive per-parameter scaling can steer the optimizer toward sharper minima in the loss landscape — regions that fit the training data very well but are more sensitive to small perturbations, which tends to correlate with worse generalization. SGD’s less aggressive, uniform step sizes appear to more often find flatter minima, which tend to generalize better. This isn’t a universal rule, but it’s common enough in computer vision benchmarks that many top-performing CNN training recipes still favor well-tuned SGD with momentum over Adam for final model quality.
Do I need to tune $\beta_1$ and $\beta_2$? Rarely. The default values (0.9 and 0.999) were chosen carefully in the original paper and tend to work robustly across an enormous range of tasks. The learning rate $\eta$ remains, by far, the most important hyperparameter to tune when using Adam. Some specialized settings — such as training with very large batch sizes, or certain reinforcement learning setups with highly non-stationary objectives — occasionally benefit from adjusting $\beta_2$ downward to make the second-moment estimate more reactive, but this is the exception rather than the rule.
Is AdamW strictly better than Adam? For any setting where you’re using weight decay (which is most practical training setups), yes — AdamW’s decoupled weight decay is a strictly more principled implementation of the same underlying idea, and it has become the standard choice in large language model and transformer training precisely for this reason. If you’re not using any weight decay at all, Adam and AdamW behave identically.
Can Adam fail to converge even on simple problems? Yes — this was actually demonstrated formally in the paper that introduced AMSGrad, which constructed simple convex counterexamples where standard Adam fails to converge to the optimal solution due to how quickly its exponential moving average of squared gradients can “forget” large gradients encountered early in training. In practice, this failure mode is rare on typical deep learning loss landscapes, but it’s worth knowing about if you ever observe genuinely strange non-convergent behavior on a well-behaved, simple problem.
Practical Checklist Before Training With Adam
- Confirm you’re using
AdamWrather than plainAdamif you plan to apply weight decay. - Start with $\eta = 0.001$ for general tasks, or $\eta = 3\times10^{-4}$ to $5\times10^{-5}$ for fine-tuning large pretrained models, and adjust from there based on validation performance.
- Add a warmup phase for transformer-style architectures, even a short one (a few hundred steps), to avoid early instability.
- Watch the first few hundred steps of training closely — Adam’s fast initial convergence also means it can diverge quickly if the learning rate is poorly chosen, so problems tend to surface early rather than late.
- If final generalization quality matters more than training speed and you have the compute budget to tune carefully, benchmark against SGD with momentum before locking in Adam as your final choice.
A Quick Mental Model to Keep
Adam is best understood as “momentum plus adaptive per-parameter step sizing, with a correction for early-training bias.” If you’re already comfortable with classical momentum, the leap to understanding Adam is mostly about grasping how the second moment estimate $v_t$ normalizes each parameter’s step individually — everything else is bookkeeping to make sure that normalization behaves well from the very first training step onward.
Summary
Adam works by tracking two exponentially decaying moving averages of the gradient — one estimating its direction (momentum) and one estimating its magnitude (adaptive scaling) — and combining them into a per-parameter adaptive update rule with bias correction for early training steps. This combination gives Adam fast, robust convergence with minimal hyperparameter tuning, which is exactly why it has become the default optimizer across so much of deep learning. That said, it isn’t a universal best choice: for some vision tasks, well-tuned SGD with momentum can still generalize better, and for tasks using weight decay, AdamW is the more principled choice. Understanding the mechanics behind Adam — not just calling it — helps you know when to reach for it and when to reach for something else.
References and Further Reading
- Kingma, D. P., & Ba, J. (2015). “Adam: A Method for Stochastic Optimization.” ICLR. https://arxiv.org/abs/1412.6980
- Reddi, S. J., Kale, S., & Kumar, S. (2018). “On the Convergence of Adam and Beyond.” ICLR. (Introduces AMSGrad)
- Loshchilov, I., & Hutter, F. (2019). “Decoupled Weight Decay Regularization.” ICLR. (Introduces AdamW) https://arxiv.org/abs/1711.05101
- PyTorch Adam documentation: https://pytorch.org/docs/stable/generated/torch.optim.Adam.html
- PyTorch AdamW documentation: https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html
