MediumOptimization

Implement Nadam (Nesterov + Adam)

Optimization

Medium

Problem

Perform one Nadam update without bias correction. Update the moments:

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

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

Form the Nesterov-adjusted first moment:

\widetilde{m}_t = \beta_1m_t + (1-\beta_1)g_t

Then update the parameters:

w_t = w_{t-1} - \eta\frac{\widetilde{m}_t}{\sqrt{v_t}+\varepsilon}

Here, w contains parameters, g contains gradients, m and v are moment arrays, and \eta is lr. Return new_w, new_m, and new_v in a dictionary of NumPy arrays.

Theory

Training a neural network means finding the set of weights that minimizes a loss function. The loss surface is a high-dimensional landscape with hills, valleys, flat regions, and saddle points. The optimizer's job is to navigate this landscape efficiently.

The simplest approach is vanilla gradient descent: compute the gradient of the loss with respect to every parameter, then take a step in the opposite direction:

w_t = w_{t-1} - \eta \cdot g_t

where \eta is the learning rate and g_t is the gradient at step t.

This works, but it has problems:

Modern optimizers fix these issues by adding momentum and adaptive learning rates.


Momentum: Using the Past

Instead of relying only on the current gradient, momentum accumulates a running average of past gradients. This is called the first moment (the mean):

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

Think of it like a ball rolling downhill. Instead of stopping and restarting at each point, it accumulates velocity. If the gradient keeps pointing the same way, the ball accelerates. If the gradient suddenly reverses, the accumulated momentum dampens the change.


Adaptive Learning Rates: The Second Moment

Different parameters need different step sizes. A parameter whose gradient is always large probably needs a smaller learning rate. A parameter with tiny gradients needs a larger one.

The second moment tracks how large the gradients have been:

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

This is the core idea behind RMSProp and Adam.


Adam: Combining Both

Adam (Adaptive Moment Estimation) combines momentum and adaptive rates:

  1. Update first moment: m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t
  2. Update second moment: v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2
  3. Update parameters: w_t = w_{t-1} - \eta \cdot \frac{m_t}{\sqrt{v_t} + \epsilon}

The numerator m_t gives direction and momentum. The denominator \sqrt{v_t} + \epsilon scales each parameter's update by how large its gradients have been. The \epsilon (typically 10^{-8}) prevents division by zero.


Nesterov Momentum: Looking Ahead

Standard momentum has a weakness: it computes the gradient at the current position, then applies momentum. But by the time the update is applied, the momentum has already carried the parameters further.

Nesterov momentum fixes this by effectively "looking ahead." Instead of computing the gradient where you are, it computes the gradient at where momentum is about to take you. This gives a better estimate of where you should actually go.

In the original formulation for plain SGD, Nesterov momentum works like this:

The result is faster convergence and better responsiveness to changes in the loss surface. When the optimizer is heading toward a minimum and starts to overshoot, Nesterov momentum detects this sooner because it evaluates the gradient at the future position.


Nadam: Nesterov + Adam

Nadam (Nesterov-accelerated Adaptive Moment Estimation) brings Nesterov's lookahead idea into Adam.

The first two steps are identical to Adam:

  1. Update first moment: m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t
  2. Update second moment: v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2

The difference is in the parameter update. Instead of using m_t directly in the numerator, Nadam uses a Nesterov-adjusted combination:

w_t = w_{t-1} - \eta \cdot \frac{\beta_1 m_t + (1 - \beta_1) g_t}{\sqrt{v_t} + \epsilon}

Breaking down the numerator \beta_1 m_t + (1 - \beta_1) g_t:

Compare this to standard Adam, which uses just m_t in the numerator. Nadam's version incorporates the current gradient more directly into the update, giving it the Nesterov "lookahead" property.


A Concrete Example

Parameters: w = [1.0, -1.0], moments: m = [0.1, -0.1], v = [0.01, 0.01], gradient: g = [0.2, -0.3], \eta = 0.002, \beta_1 = 0.9, \beta_2 = 0.999

Step 1 (first moment):

Step 2 (second moment):

Step 3 (Nesterov update for first parameter):


When to Use Nadam

Examples

Example 1

Input
w = [1.0, -1.0], m = [0.1, -0.1], v = [0.01, 0.01], grad = [0.2, -0.3], lr = 0.002, beta1 = 0.9, beta2 = 0.999, eps = 1e-8
Output
{"new_w": [0.997624, -0.997251], "new_m": [0.11, -0.12], "new_v": [0.01003, 0.01008]}
Explanation
The updated first moment is combined with the current gradient before adaptive scaling.

Example 2

Input
w = [1.0, 2.0], m = [0.1, 0.2], v = [0.01, 0.04], grad = [0.0, 0.0], lr = 0.002, beta1 = 0.9, beta2 = 0.999, eps = 1e-8
Output
{"new_w": [0.998379, 1.998379], "new_m": [0.09, 0.18], "new_v": [0.00999, 0.03996]}

Example 3

Input
w = [1.0, 2.0], m = [0.0, 0.0], v = [0.0, 0.0], grad = [0.1, 0.2], lr = 0.002, beta1 = 0.9, beta2 = 0.999, eps = 1e-8
Output
{"new_w": [0.987983, 1.987983], "new_m": [0.01, 0.02], "new_v": [0.00001, 0.00004]}

Hints

  1. Compute the ordinary first and second moments before nesterov_m.
  2. Use beta1 * new_m + (1 - beta1) * grad in the numerator.

Requirements

Constraints

Starter Code

import numpy as np

def nadam_step(w: list, m: list, v: list, grad: list, lr: float = 0.002, beta1: float = 0.9, beta2: float = 0.999, eps: float = 1e-8) -> dict:
    """
    Returns a dictionary with new_w, new_m, and new_v.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Non-zero accumulatorsExample 1public
Zero gradientExample 2public
First stepExample 3public