EasyOptimization

Implement Adam Optimizer Step

Optimization

Easy

Problem

Implement one update step of the Adam optimizer. Given current parameter(s), gradient(s), and running first/second moments, return the updated parameter(s) and updated moments.

Step 1: Update First Moment

m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t

Step 2: Update Second Moment

v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot g_t^2

Step 3: Bias Correction

\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}

Step 4: Parameter Update

\theta_t = \theta_{t-1} - \alpha \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}

In these equations:

  • \theta: parameters
  • g: current gradients
  • m: first moment
  • v: second moment
  • \alpha: learning rate
  • t: one-based timestep

Theory

The evolution of optimizers for deep learning is built on two separate ideas, each solving a different problem:

Idea 1: Momentum (the first moment)

Instead of using only the current gradient, maintain a running average of past gradients. This is the "first moment" (the mean).

Why momentum helps:

The formula:

m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t

With \beta_1 = 0.9, the current gradient contributes 10% and the accumulated history contributes 90%.

Idea 2: Adaptive learning rates (the second moment)

Track how large the gradients have been for each parameter and scale the update inversely. This is the "second moment" (the uncentered variance).

Why adaptive rates help:

The formula:

v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot g_t^2

With \beta_2 = 0.999, this is a slowly-changing average of squared gradients.

SGD + momentum uses only idea 1. RMSProp uses only idea 2. Adam combines both.


The Bias Correction Problem

Both m and v are initialized to zero vectors. At the start of training, this creates a bias:

With \beta_1 = 0.9 and m_0 = 0:

The estimate is only 10% of the true gradient. This is not because the gradient is small; it is because the exponential average has not had time to warm up.

The same problem affects v. With \beta_2 = 0.999:

Without correction, the first few steps would have dramatically wrong magnitudes. The fix is bias correction:

\hat{m}_t = \frac{m_t}{1 - \beta_1^t}

\hat{v}_t = \frac{v_t}{1 - \beta_2^t}

How the correction works at step 1:

How it fades out over time:

The correction is crucial for the first ~10-20 steps and becomes negligible afterward.


The Full Adam Update

Given parameters w, gradient g_t, and step count t:

Step 1: Update first moment (momentum)

m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t

Step 2: Update second moment (adaptive rate)

v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot g_t^2

Step 3: Bias-correct both moments

\hat{m}_t = \frac{m_t}{1 - \beta_1^t} \qquad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}

Step 4: Update parameters

w_t = w_{t-1} - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}

The update decomposes into:


A Worked Example

Parameters: w = [1.0], moments: m = [0], v = [0], gradient: g = [0.5], step t = 1, \eta = 0.001

Step 1: m_1 = 0.9 \times 0 + 0.1 \times 0.5 = 0.05

Step 2: v_1 = 0.999 \times 0 + 0.001 \times 0.25 = 0.00025

Step 3 (bias correction):

Step 4: w_1 = 1.0 - 0.001 \times \frac{0.5}{\sqrt{0.25} + 10^{-8}} = 1.0 - 0.001 \times \frac{0.5}{0.5} = 1.0 - 0.001 = 0.999

Without bias correction, \hat{m}_1 would have been 0.05 and \hat{v}_1 would have been 0.00025, giving a very different (wrong) update.


The Default Hyperparameters

The Adam paper (Kingma and Ba, 2015) recommended:

Why \beta_2 is so much larger than \beta_1:

These defaults work well across a wide range of tasks. Most practitioners only tune \eta and leave the rest at defaults.


Why Adam Became the Default Optimizer

Adam became the most popular optimizer in deep learning because:

Known limitations:

Examples

Example 1

Input
param = [1.0, 2.0], grad = [0.0, 0.0], m = [0.0, 0.0], v = [0.0, 0.0], t = 1, lr = 0.001
Output
([1.0, 2.0], [0.0, 0.0], [0.0, 0.0])
Explanation
A zero gradient leaves the parameters and both running moments unchanged.

Example 2

Input
param = [0.0], grad = [0.1], m = [0.0], v = [0.0], t = 1, lr = 0.001
Output
([-0.001], [0.01], [0.00001])

Hints

  1. Update the first and second moments before computing their bias-corrected values.
  2. Use the one-based timestep in bias correction and add eps inside the update denominator.

Requirements

Constraints

Starter Code

import numpy as np

def adam_step(
    param: list,
    grad: list,
    m: list,
    v: list,
    t: int,
    lr: float = 1e-3,
    beta1: float = 0.9,
    beta2: float = 0.999,
    eps: float = 1e-8,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Returns (param_new, m_new, v_new) as NumPy arrays.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Zero gradientExample 1public
First step bias correctionExample 2public