MediumOptimization

Implement AdaDelta Update Step

Optimization

Medium

Problem

Perform one AdaDelta update. First update the running squared-gradient average:

E[g^2]_t = \rho E[g^2]_{t-1} + (1-\rho)g_t^2

Compute the parameter change:

\Delta w_t = -\frac{\sqrt{E[\Delta w^2]_{t-1}+\varepsilon}}{\sqrt{E[g^2]_t+\varepsilon}}g_t

Update the running squared-change average:

E[\Delta w^2]_t = \rho E[\Delta w^2]_{t-1} + (1-\rho)(\Delta w_t)^2

Finally update the parameters:

w_t = w_{t-1} + \Delta w_t

Here, w contains parameters, g contains gradients, \rho is the decay rate, and \varepsilon is eps. Return new_w, new_E_grad_sq, and new_E_update_sq in a dictionary of NumPy arrays.

Theory

Every optimizer we have seen so far requires you to set a learning rate \eta. This is arguably the most important and most frustrating hyperparameter in deep learning:

Practitioners spend enormous effort tuning learning rates: grid search, learning rate finders, warmup schedules, decay schedules. What if we could eliminate the learning rate entirely?


AdaDelta: No Learning Rate Required

AdaDelta (Zeiler, 2012) is designed to do exactly this. It computes the step size automatically by maintaining two running statistics, and the learning rate \eta does not appear anywhere in its update formula.

The key idea: use the ratio of the root-mean-square of recent parameter updates to the root-mean-square of recent gradients as the step size.


The Two Running Averages

AdaDelta maintains two exponentially decaying averages:

1. Running average of squared gradients E[g^2]_t:

E[g^2]_t = \rho \cdot E[g^2]_{t-1} + (1 - \rho) \cdot g_t^2

2. Running average of squared parameter updates E[\Delta w^2]_t:

E[\Delta w^2]_t = \rho \cdot E[\Delta w^2]_{t-1} + (1 - \rho) \cdot (\Delta w_t)^2


The Update Rule

The parameter change at step t is:

\Delta w_t = -\frac{\text{RMS}[\Delta w]_{t-1}}{\text{RMS}[g]_t} \cdot g_t

Expanding the RMS terms:

\Delta w_t = -\frac{\sqrt{E[\Delta w^2]_{t-1} + \epsilon}}{\sqrt{E[g^2]_t + \epsilon}} \cdot g_t

Then apply:

w_t = w_{t-1} + \Delta w_t

And update the second accumulator:

E[\Delta w^2]_t = \rho \cdot E[\Delta w^2]_{t-1} + (1 - \rho) \cdot (\Delta w_t)^2

Notice: there is no learning rate \eta anywhere. The step size is entirely determined by the ratio of past update sizes to current gradient sizes.


Why the Ratio Makes Sense

The numerator \text{RMS}[\Delta w]_{t-1} captures "how much have we been changing this parameter?"

The denominator \text{RMS}[g]_t captures "how large are the gradients?" (same as RMSProp)

Together, the ratio acts as an automatically-tuned learning rate that adjusts based on the training dynamics.


The Bootstrap Issue

At t = 0, both accumulators are initialized to zero:

For the first update:

The first update is approximately:

\Delta w_1 \approx -\frac{\sqrt{\epsilon}}{\sqrt{(1-\rho) g_1^2 + \epsilon}} \cdot g_1

This is very small (controlled by \epsilon, which is typically 10^{-6}). AdaDelta starts with extremely cautious steps and gradually increases step size as the update accumulator fills in. This provides a natural warmup behavior.


A Detailed Example

Parameters: w = [2.0], accumulators: E[g^2] = [0], E[\Delta w^2] = [0], \rho = 0.9, \epsilon = 10^{-6}

Step 1 with gradient g = [1.0]:

  1. Update gradient accumulator:

    • E[g^2] = 0.9 \times 0 + 0.1 \times 1.0 = 0.1
  2. Compute update:

    • Numerator: \sqrt{E[\Delta w^2] + \epsilon} = \sqrt{0 + 10^{-6}} = 0.001
    • Denominator: \sqrt{E[g^2] + \epsilon} = \sqrt{0.1 + 10^{-6}} \approx 0.3162
    • \Delta w = -\frac{0.001}{0.3162} \times 1.0 \approx -0.00316
  3. Apply update:

    • w = 2.0 + (-0.00316) = 1.99684
  4. Update parameter change accumulator:

    • E[\Delta w^2] = 0.9 \times 0 + 0.1 \times (0.00316)^2 \approx 0.000001

The first step is very small because the numerator starts from \sqrt{\epsilon}. Over subsequent steps, as E[\Delta w^2] accumulates, the steps grow larger.

Step 2 with gradient g = [0.8]:

  1. E[g^2] = 0.9 \times 0.1 + 0.1 \times 0.64 = 0.154
  2. Numerator: \sqrt{0.000001 + 10^{-6}} \approx 0.00141
  3. Denominator: \sqrt{0.154 + 10^{-6}} \approx 0.3924
  4. \Delta w = -\frac{0.00141}{0.3924} \times 0.8 \approx -0.00288

The steps are gradually increasing as the update accumulator builds up.


Choosing Rho

\rho is the only hyperparameter (besides \epsilon):


AdaDelta vs. RMSProp vs. Adam

How AdaDelta compares to other adaptive optimizers:

AdaDelta's main advantage is eliminating \eta. Its main disadvantage is limited control over step size. In practice, being able to tune \eta (as in Adam) usually outweighs the convenience of not having one.


Where AdaDelta Shows Up

Examples

Example 1

Input
w = [1.0, -1.0], grad = [0.1, -0.2], E_grad_sq = [0.01, 0.04], E_update_sq = [0.001, 0.004], rho = 0.9, eps = 1e-6
Output
{"new_w": [0.968363, -0.936747], "new_E_grad_sq": [0.01, 0.04], "new_E_update_sq": [0.001, 0.004]}
Explanation
The ratio of the two running root-mean-square values scales each gradient before the state is updated.

Example 2

Input
w = [1.0, 2.0], grad = [0.0, 0.0], E_grad_sq = [0.01, 0.04], E_update_sq = [0.001, 0.004], rho = 0.9, eps = 1e-6
Output
{"new_w": [1.0, 2.0], "new_E_grad_sq": [0.009, 0.036], "new_E_update_sq": [0.0009, 0.0036]}

Example 3

Input
w = [1.0, 2.0], grad = [0.1, 0.2], E_grad_sq = [0.0, 0.0], E_update_sq = [0.0, 0.0], rho = 0.9, eps = 1e-6
Output
{"new_w": [0.996839, 1.996838], "new_E_grad_sq": [0.001, 0.004], "new_E_update_sq": [0.000001, 0.000001]}

Hints

  1. Update the squared-gradient average before computing the parameter change.
  2. Use the new parameter change to update new_E_update_sq.

Requirements

Constraints

Starter Code

import numpy as np

def adadelta_step(w: list, grad: list, E_grad_sq: list, E_update_sq: list, rho: float = 0.9, eps: float = 1e-6) -> dict:
    """
    Returns a dictionary with new_w, new_E_grad_sq, and new_E_update_sq.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Basic update with non-zero accumulatorspublic
Zero gradient - averages decaypublic
First step zero accumulatorsExample 3public