Recommended resources
The freely available resources this lesson will direct you to.
Hello! Let's dive into the practical side of optimization.
Introduction
In our previous lesson, we rigorously derived the gradient descent update rule from first principles:
We established that moving in the direction of the negative gradient, , is the path of steepest descent, providing the theoretical justification for this algorithm.
Today, we will translate this mathematical theory into practice. Your goal for this lesson is to implement the gradient descent algorithm to find the minimum of a simple convex function. This will connect the abstract derivations directly with your Python programming experience. You'll write the code to make a point "roll downhill" on a function's surface to find its minimum, solidifying your understanding of this fundamental optimization workhorse.
Part 1: Defining the Optimization Problem
Before we write any code, we must first define the function we want to minimize. As per the learning outcome, we'll use a simple, multivariable convex function. This ensures that the local minimum we find is also the global minimum, a property you proved in a prior lesson.
Let's choose the following quadratic function:
This function is a paraboloid, but stretched more steeply along the -axis. Its minimum is clearly at .
The first step in implementing gradient descent is always to compute the gradient of the objective function. Given your calculus background, you can quickly find the partial derivatives:
So, the gradient vector is:
With and defined, we have all the mathematical components needed to implement our algorithm.
Part 2: Implementing Gradient Descent in Python
Now, we will write the Python code to perform the optimization. We will use NumPy for numerical operations and Matplotlib for visualization, tools you are already familiar with.
The following video provides an excellent walkthrough of implementing gradient descent from scratch. It starts with a simple 1D case and then moves to a 2D (or 3D surface) case. While the video uses a different, non-convex function for its 2D example, the implementation logic—defining the function, its gradient, and the iterative update loop—is universal.
Gradient Descent From Scratch in Python - Visual Explanation
Watch this video, "Gradient Descent From Scratch in Python" by NeuralNine, to see a live demonstration of the implementation process.
Please watch these two key segments: 1D Implementation (03:44 - 10:31): Focus on how the code translates the math for f(x) = x^2. Notice the y_derivative function, the initial current_position, the learning rate, and the core update loop. 3D Implementation (16:51 - 24:35): Pay close attention to the general structure. See how it defines the z_function (our f), the calculate_gradient function (our grad_f), and the main loop that updates both x and y coordinates according to their respective partial derivatives. The logic here is directly applicable to our problem.
Now, it's your turn to build it. Using the patterns from the video and the structured approach outlined in the following text, you will implement gradient descent for our specific function, .
Introduction to Optimization and Visualizing algorithms
The document "Introduction to Optimization and Visualizing algorithms" provides another clear example, including a self-contained Gradient_Descent function. The section on the simpler quadratic function is particularly relevant.
Review the sections "Gradient Descent implementation" and "Gradient descent on a simpler function (quadratic)". You don't need to use the Rosenbrock function. Instead, notice how they structure their Gradient_Descent function and apply it to the simple quadratic f(x,y) = .01x^2 + .1y^2. This will serve as a great template for your own implementation.
Your Task: Implement Gradient Descent
Follow these steps in a Jupyter Notebook or Python script:
- Import libraries:
numpyandmatplotlib.pyplot. - Define the functions:
f(x): Takes a 2-element NumPy arrayxand returns .grad_f(x): Takes a 2-element NumPy arrayxand returns the gradient,np.array([2*x[0], 10*x[1]]).
- Set up the algorithm:
- Initialize a starting point, e.g.,
x_current = np.array([10.0, 2.0]). - Choose a learning rate, e.g.,
eta = 0.05. - Set the number of iterations, e.g.,
n_iterations = 100. - Create an empty list
historyto store the positionx_currentat each iteration.
- Initialize a starting point, e.g.,
- Create the main loop:
- Loop for
n_iterations. - In each iteration:
- Calculate the gradient at the current position:
gradient = grad_f(x_current). - Update the position using the rule:
x_current = x_current - eta * gradient. - Append
x_currentto yourhistorylist.
- Calculate the gradient at the current position:
- Loop for
- Visualize the results:
- After the loop, you can print the final position
x_currentto see how close it got to[0, 0]. - Use
matplotlibto create a contour plot of the function and overlay the path taken by the algorithm (from yourhistorylist). The resources above contain examples of how to generate these plots.
- After the loop, you can print the final position
Part 3: The Critical Role of the Learning Rate
In the last lesson, we noted the importance of the step size . Now you can see its effect firsthand. A poorly chosen can lead to slow convergence or even cause the algorithm to fail completely.
The video you watched earlier has an excellent segment demonstrating this.
Gradient Descent From Scratch in Python - Visual Explanation
Let's revisit the NeuralNine video to see a visual explanation of what happens when the learning rate is too small or too large.
Watch the segment from 10:31 to 15:09. Observe the three scenarios: Too Small: The algorithm makes painfully slow progress. Too Large (Oscillation): The algorithm overshoots the minimum and bounces back and forth. Way Too Large (Divergence): The values explode, and the algorithm fails.
Now, experiment with your own implementation. Run your code with three different learning rates:
- A small learning rate (e.g.,
eta = 0.001) - A good learning rate (e.g.,
eta = 0.05) - A large learning rate (e.g.,
eta = 0.19) - An even larger learning rate (e.g.,
eta = 0.21)
Visualize the path for each. You will see the behavior change dramatically.

Test your understanding!
Our objective function is . The gradient is . Notice that for the same point , the gradient component is larger in the direction.
If you use a learning rate that is slightly too large, in which direction ( or ) would you expect the algorithm to start oscillating or diverging first? Why?
Show answer
You would expect the algorithm to start oscillating or diverging in the direction first.
The update for each component is . Since the gradient component for () is five times larger than for () at any given point, the step taken in the direction is much larger. This larger step makes it more likely to overshoot the minimum along that axis, leading to oscillation or divergence. This illustrates how the function's curvature (which is steeper along the -axis) interacts with the learning rate.
Conclusion
Congratulations! You have successfully implemented one of the most fundamental algorithms in machine learning. By translating the mathematical formula into working code, you have bridged the gap between theory and practice.
Key Takeaways:
- Gradient descent is implemented as an iterative loop that repeatedly calculates the gradient and updates the current parameters.
- The essential ingredients are the objective function, its gradient, a starting point, a learning rate, and a number of iterations.
- The learning rate () is a critical hyperparameter. If it's too small, convergence is slow. If it's too large, the algorithm can oscillate or diverge.
- Visualizing the optimization path on a contour plot is a powerful tool for understanding the algorithm's behavior.
Preview of the Next Lesson:
We've built an intuition for how convergence works, but our choice of and the number of iterations was based on trial and error. To be more rigorous, we need to analyze the algorithm's convergence properties mathematically. In the next lesson, we will define two key properties of functions—smoothness and strong convexity—which will allow us to derive formal guarantees on the convergence rate of gradient descent. This will help us answer the question: "How fast does gradient descent find the minimum?"