Hello! In our last lesson, you successfully assembled a complete training loop, integrating forward propagation, cost calculation, backpropagation, and parameter updates. You now have a working neural network that learns using the fundamental vanilla gradient descent algorithm.
However, as you might imagine, "vanilla" is rarely the final word. While effective, standard gradient descent can be slow to converge, especially on complex loss surfaces. Today, we'll address this by implementing a powerful and widely used optimization algorithm: SGD with Momentum. You will learn how it works and modify your existing update rule to incorporate it, significantly speeding up your network's training process.
1. The Problem: The Winding Road of Gradient Descent
In the previous lesson, your update_parameters function took a simple step in the direction of the steepest descent: W = W - alpha * dW. This approach is greedy—it only considers the gradient at the current position. This can lead to two major problems:
- Oscillations in Ravines: If the loss function forms a narrow, steep valley (a "ravine"), the gradient will point sharply down the walls. Standard GD will oscillate back and forth across the valley, making very slow progress along the valley floor toward the minimum.
- Slowness on Plateaus: If the loss surface is very flat (a "plateau"), the gradients will be tiny, and GD will take very small, slow steps, drastically increasing training time.
Let's watch a short clip that illustrates this oscillation problem.
Gradient Descent With Momentum (C2W2L06)
The video 'Gradient Descent With Momentum' from DeepLearningAI provides an excellent visualization of why standard gradient descent can be inefficient.
Watch from the beginning to 01:57. Pay close attention to the animation of the blue line oscillating back and forth in the elliptical contour plot. This represents vanilla gradient descent struggling to find the most direct path to the minimum.
This animation of various optimizers navigating a loss surface vividly shows the zig-zagging path of basic SGD (the orange line) compared to more advanced methods.

Our goal is to smooth out these oscillations and build "speed" in the correct direction.
2. The Intuition: A Ball Rolling Down a Hill
The core idea of Momentum is inspired by physics. Instead of a lightweight object that is pushed around by the gradient at every step, imagine a heavy ball rolling down the loss surface.
- The ball accumulates momentum, so it doesn't stop or change direction instantly.
- In a ravine, the oscillations from side to side will tend to cancel each other out, while the momentum carries it steadily down the valley floor.
- On a plateau, even with a small gradient (a gentle slope), its accumulated momentum will carry it forward.
This approach uses an exponentially weighted average of past gradients to inform the current update. In essence, it gives the update step a "memory" of which direction has been consistently good.
Who's Adam and What's He Optimizing? | Deep Dive into Optimizers for Machine Learning!
This video from Sourish Kundu provides a great analogy and a side-by-side animation comparing vanilla SGD with SGD with Momentum.
Watch from 05:24 to 07:05. This segment introduces the physics intuition and shows a clear animation where Momentum overshoots slightly but ultimately reaches the minimum much faster than standard SGD.
3. The Math Behind Momentum
To translate this physical intuition into an algorithm, we introduce a new variable called velocity (denoted by v), which will accumulate the weighted average of the gradients. At each step t, we perform two calculations:
- Update the velocity: The new velocity is a combination of the old velocity and the current gradient.
- Update the parameters: The parameters are updated using this new velocity instead of the raw gradient.
For a weight matrix W, the equations are:
Let's break this down:
- is the velocity of the gradient of
Wat the current stept. - is the momentum hyperparameter. It controls how much of the past velocity is retained. A common value is 0.9. If , the equations collapse back to vanilla gradient descent.
- is the gradient calculated at the current step
t. - is the learning rate, as before.
The velocity is an exponentially weighted average of the gradients. The term is a normalization factor that keeps the magnitude of the velocity in a similar range to the gradient itself. This specific formulation is popular because it makes the learning rate less dependent on the value of .
The following image provides a concise summary of the update rule and its implementation in pseudocode.

4. Implementing the Momentum Update Rule
Now, let's implement this. Since you have a strong background in Python and have already built the training scaffold, we can directly modify your existing structure. This will involve two changes:
- Initializing a
velocitiesdictionary to store the velocity terms for each parameter. - Creating a new
update_parameters_with_momentumfunction that implements the equations above.
Step 1: Initialize the Velocities
Just like you initialized your parameters, you need a dictionary to hold the velocity terms (v_dW, v_db, etc.). These should all start at zero. You can create a helper function for this.
import numpy as np
def initialize_velocities(parameters):
"""
Initializes the velocity as a dictionary with keys "v_dW1", "v_db1", ...,
and values as numpy arrays of zeros of the same shape as the corresponding
gradients/parameters.
Args:
parameters (dict): Contains the parameters "W1", "b1", etc.
Returns:
v (dict): Contains the initialized velocities.
"""
num_layers = len(parameters) // 2
v = {}
for l in range(1, num_layers + 1):
v[f"v_dW{l}"] = np.zeros_like(parameters[f"W{l}"])
v[f"v_db{l}"] = np.zeros_like(parameters[f"b{l}"])
return v
Step 2: Create the New Update Function
This function will now take velocities, beta, and learning_rate as inputs. It will update the velocities and then use them to update the parameters.
def update_parameters_with_momentum(parameters, gradients, velocities, beta, learning_rate):
"""
Update parameters using Momentum.
Args:
parameters (dict): Contains current parameters.
gradients (dict): Contains gradients from backprop.
velocities (dict): Contains the current velocities.
beta (float): The momentum hyperparameter.
learning_rate (float): The learning rate.
Returns:
parameters (dict): The updated parameters.
velocities (dict): The updated velocities.
"""
num_layers = len(parameters) // 2
for l in range(1, num_layers + 1):
# Update velocities
velocities[f"v_dW{l}"] = beta * velocities[f"v_dW{l}"] + (1 - beta) * gradients[f"dW{l}"]
velocities[f"v_db{l}"] = beta * velocities[f"v_db{l}"] + (1 - beta) * gradients[f"db{l}"]
# Update parameters
parameters[f"W{l}"] = parameters[f"W{l}"] - learning_rate * velocities[f"v_dW{l}"]
parameters[f"b{l}"] = parameters[f"b{l}"] - learning_rate * velocities[f"v_db{l}"]
return parameters, velocities
Step 3: Integrate into Your Training Loop
Finally, modify your train_model function to use these new components.
- Call
initialize_velocitiesbefore the loop starts. - Inside the loop, call
update_parameters_with_momentuminstead of your old update function. - Make sure to pass the
velocitiesdictionary into the function and capture the updated version it returns.
Your training loop would now look something like this:
# In your main training function...
# ...
# Hyperparameters
learning_rate = 0.01
beta = 0.9 # Momentum hyperparameter
# 1. Initialize parameters and velocities
parameters = initialize_parameters(layer_dims)
velocities = initialize_velocities(parameters)
# 2. Start the training loop
for i in range(num_iterations):
# Forward prop, compute cost, backprop...
AL, cache = forward_propagation(X, parameters)
cost = compute_cost(AL, Y)
gradients = back_propagation(AL, Y, cache)
# Update parameters with momentum
parameters, velocities = update_parameters_with_momentum(parameters, gradients, velocities, beta, learning_rate)
# ... rest of the loop (print cost, etc.)
Test your understanding!
What would happen during training if you accidentally re-initialized velocities to all zeros at the start of every iteration inside the for loop?
Show answer
If you reset the velocities to zero in every iteration, the momentum term would always be zero. The update equation would simplify to and . This is mathematically equivalent to vanilla gradient descent, but with an effective learning rate of . You would lose the entire benefit of accumulating momentum, as the update would have no "memory" of past gradients.
Conclusion
You have now implemented one of the most fundamental improvements to gradient descent. By incorporating a velocity term that tracks an exponentially weighted average of past gradients, you've enabled your optimizer to move faster and more smoothly toward the minimum of the loss function.
Key Takeaways:
- Vanilla SGD can be inefficient, causing oscillations in ravines and slow progress on plateaus.
- Momentum addresses this by adding a velocity term, which acts as an exponentially weighted average of past gradients.
- The hyperparameter controls the "friction" of the system, with a typical value of 0.9 providing a good balance of memory and responsiveness.
- Implementing Momentum requires initializing a
velocitiesdictionary and modifying the parameter update step to first update the velocity, then update the parameters using that velocity.
Preview of the Next Lesson:
Momentum gives our optimizer a sense of direction and speed. But what if we could also adapt the learning rate for each parameter individually? In our next and final lesson of this module, you will implement the Adam optimizer from scratch, a powerful algorithm that combines the idea of momentum with adaptive learning rates to create one of the most effective and popular optimizers in deep learning today.