Machine Learning · ML Notebook · July 2026

Gradient DescentWalking Downhill in Mathematics

Stand anywhere on the cost curve, feel which way is uphill, take a small step the other way, repeat. That is how machines learn.

01 Why optimization exists

A model is a rule with knobs. A line \(y = mx + b\) has two knobs, \(m\) and \(b\). An error function such as

\[ J(m, b) = \frac{1}{n}\sum_{i=1}^{n} \big(y_i - (m x_i + b)\big)^2 \]

scores any knob setting with one number: how wrong the predictions are. Optimization is the process of finding the best parameters for our function by minimizing errors. Gradient descent is a general optimization algorithm from calculus, and it is not private to one model. Linear regression, logistic regression, neural networks, SVMs, all of them hand their cost function to this same walker and ask it to find the bottom.

It minimizes a function by moving in the direction of steepest decrease. To do that it first needs to know which way is steep, and that is the job of the gradient.

02 What a gradient is

In one variable the gradient is just the derivative, the slope of the curve at a point. Take \(f(x) = x^2\), so \( \frac{df}{dx} = 2x \):

\[ x = 3:\ \frac{df}{dx} = 2 \cdot 3 = 6 \qquad\qquad x = -3:\ \frac{df}{dx} = 2 \cdot (-3) = -6 \]

Read the sign as a compass. At \(x = 3\) the gradient is +6: moving right increases the cost, moving left decreases it. At \(x = -3\) it is −6: the exact mirror. The gradient always points uphill, so the negative gradient points downhill, toward the minimum at \(x = 0\). A larger magnitude means a steeper slope.

figure 01 — the gradient as a compass · f(x) = x²
gradient 2x = 6 downhill is left ←

The straight line is the tangent at your point, its slope is the gradient. Wherever you stand, stepping against the gradient walks you toward the global minimum at \(x=0\).

With two or more variables the gradient becomes the vector of all partial derivatives, one slope per direction. For a cost \(J(m, c)\):

\[ \nabla J(m, c) = \begin{bmatrix} \dfrac{\partial J}{\partial m} \\[6pt] \dfrac{\partial J}{\partial c} \end{bmatrix} \]

\(\partial J / \partial m\) says how sensitive the cost is to the slope \(m\); \(\partial J / \partial c\) says how sensitive it is to the intercept \(c\). In one phrase: how much the cost changes if we change that parameter slightly. Example, \( f(x, y) = x^2 + y^2 \):

\[ \frac{\partial f}{\partial x} = 2x, \quad \frac{\partial f}{\partial y} = 2y \;\;\Rightarrow\;\; \nabla f(x,y) = \begin{bmatrix} 2x \\ 2y \end{bmatrix}, \qquad \nabla f(1, 2) = \begin{bmatrix} 2 \\ 4 \end{bmatrix} \]

At the point \((1,2)\) the surface (a 3D bowl) rises with slope 2 along the x-direction and slope 4 along the y-direction. The vector \([2, 4]\) packs two answers at once: direction, which way is steepest uphill, and magnitude, how steep that climb is.

03 The update rule

If the gradient points uphill, learning is one line: step the other way. For any parameter collection \(\theta\) (linear regression's note calls the same collection \(\beta\); it's a naming choice, not a different idea):

\( \theta := \theta - \alpha \, \nabla J(\theta) \)
new parameter = old parameter − learning rate × gradient

Component by component, for linear regression:

\[ \theta_1 := \theta_1 - \alpha \frac{\partial J}{\partial \theta_1}, \qquad \theta_0 := \theta_0 - \alpha \frac{\partial J}{\partial \theta_0} \qquad\Big(\text{same as}\ m := m - \alpha\tfrac{\partial J}{\partial m},\ c := c - \alpha\tfrac{\partial J}{\partial c}\Big) \]

The recipe in full:

Initialize the parameters, e.g. \( \theta_0 := 0,\ \theta_1 := 0 \) (or random values).
Choose a learning rate \(\alpha\), the step size.
Compute the cost (MSE for regression) at the current parameters.
Calculate the gradient, the derivative of the cost function. It shows the direction of steepest increase.
Update the parameters, moving them in the opposite direction of the gradient.
Repeat until the cost stops decreasing or a fixed number of iterations is reached (a thousand is a common default).
Stop when the algorithm converges to a minimum, gradient close to zero.

A lovely built-in behaviour: the step size is \( \alpha \times \text{slope} \), and the slope shrinks as you approach the bottom. So gradient descent naturally takes big steps when far away and baby steps when close, even with a fixed \(\alpha\).

04 The learning rate

The learning rate is a hyperparameter, a setting we choose rather than learn. It controls how much the parameters are updated per iteration based on the gradient, and it determines how fast and how stable gradient descent learns:

learning rate αbehavior
too smallslow convergence, may take many iterations
too largemay overshoot the minimum or even diverge
properly tunedfast, stable convergence to the global minimum

Don't take the table's word for it. Break it yourself. The cost curve below is \( J(\theta_0) = (\theta_0 - 25)^2 \), the exam-score example from the linear regression note. Try \(\alpha = 0.1\), then 0.5, then exactly 1.0, then 1.05:

figure 02 — descend, oscillate, or explode · J(θ₀) = (θ₀ − 25)²
θ₀ = 0.00 J = 625.00 steps = 0

The update is \( \theta_0 := \theta_0 - \alpha \cdot 2(\theta_0 - 25) \), starting from \(\theta_0 = 0\). Small \(\alpha\) crawls, \(\alpha = 0.5\) lands on the minimum in one hop, \(\alpha = 1.0\) bounces between 0 and 50 forever, and anything larger spirals out of the picture. This is the whole learning-rate table, live.

05 A descent computed by hand

To trust the machine, do one example on paper. Training data \((1,1), (2,2), (3,3)\), model \( h_\theta(x) = \theta_0 + \theta_1 x \) with \(\theta_0\) pinned at 0, and the halved cost

\[ J(\theta_0, \theta_1) = \frac{1}{2m}\sum_{i=1}^{m}\big(h_\theta(x^{(i)}) - y^{(i)}\big)^2, \qquad m = 3 \]

When \(\theta_1 = 1\): predictions 1, 2, 3 match the actuals exactly, every error is \((1-1)^2 = 0\), so

\[ J(0, 1) = \frac{1}{2 \cdot 3}\,(0 + 0 + 0) = 0 \]

When \(\theta_1 = 0.5\): predictions 0.5, 1, 1.5 against actuals 1, 2, 3:

\[ J(0, 0.5) = \frac{1}{2 \cdot 3}\Big[(0.5-1)^2 + (1-2)^2 + (1.5-3)^2\Big] = \frac{3.5}{6} = \frac{7}{12} \approx 0.58 \]

When \(\theta_1 = 0\): the model predicts 0 everywhere, \( J(0,0) = \frac{1+4+9}{6} = \frac{14}{6} \approx 2.33 \). And by the same arithmetic, \( J(0, 1.5) \approx 0.58 \) and \( J(0, 2) \approx 2.33 \). Plotting the five values:

θ₁00.511.52
J(0, θ₁)2.330.5800.582.33

A perfect U with its global minimum at \(\theta_1 = 1\), which is exactly the true line \(y = x\). The minimum is the point where any small change in the parameters increases the error. Gradient descent is nothing more than an automated way of sliding down this table.

06 Deriving the gradients

Where do the update formulas come from? Differentiate the cost of a single example, \( \tfrac{1}{2}(h_\theta(x) - y)^2 \), with respect to one parameter \(\theta_j\). Chain rule, outside first, then inside:

\( \dfrac{\partial}{\partial \theta_j} J(\theta) = \dfrac{\partial}{\partial \theta_j}\, \tfrac{1}{2}\big(h_\theta(x) - y\big)^2 \)
\( = 2 \cdot \tfrac{1}{2}\big(h_\theta(x) - y\big) \cdot \dfrac{\partial}{\partial \theta_j}\big(h_\theta(x) - y\big) \) power rule, the ½ cancels the 2
\( = \big(h_\theta(x) - y\big) \cdot \dfrac{\partial}{\partial \theta_j}\Big(\textstyle\sum_{i=0}^{n} \theta_i x_i - y\Big) \) expand hθ
\( = \big(h_\theta(x) - y\big)\, x_j \) only the θⱼxⱼ term survives

Every other \(\theta_i x_i\) is a constant with respect to \(\theta_j\), so it differentiates to zero. Summing over the dataset gives the batch gradients used in the linear regression note:

\[ \frac{\partial J}{\partial \theta_0} = \frac{1}{m}\sum_{i=1}^{m}\big(h_\theta(x^{(i)}) - y^{(i)}\big), \qquad \frac{\partial J}{\partial \theta_1} = \frac{1}{m}\sum_{i=1}^{m}\big(h_\theta(x^{(i)}) - y^{(i)}\big)\, x^{(i)} \]

The same computation without the \(\tfrac12\) convention leaves a factor of 2 in front, \( \frac{\partial E}{\partial m} = -\frac{2}{n}\sum x_i\big(y_i - (mx_i + b)\big) \) and \( \frac{\partial E}{\partial b} = -\frac{2}{n}\sum \big(y_i - (mx_i + b)\big) \). Identical direction, only the scale differs, and the learning rate absorbs scale.

Historically this per-example update rule, \( \theta_j := \theta_j - \alpha\,\big(h_\theta(x^{(i)}) - y^{(i)}\big) x_j^{(i)} \), is called the Least Mean Squares (LMS) rule: initialize \(\theta\) randomly, then for each training example compute the prediction \(h_\theta(x) = \theta^{\mathsf T}x\), compute the error, update each parameter, and stop when \(J(\theta)\) becomes sufficiently small or the changes in \(\theta\) are very small.

07 Batch vs stochastic

The gradients above contain a sum over all \(m\) examples. When to compute that sum is a real design choice.

Batch gradient descent

Uses all training data to compute the gradient before each parameter update:

θⱼ := θⱼ − α · (1/m) Σ ∂J⁽ⁱ⁾/∂θⱼ

Gradient computed over the entire dataset. Slow, stable, smooth convergence. Best for small datasets.

Stochastic gradient descent

Updates the parameters using one training example at a time, immediately after seeing it (also called incremental gradient descent).

θⱼ := θⱼ − α · (hθ(x⁽ⁱ⁾) − y⁽ⁱ⁾) xⱼ⁽ⁱ⁾

Gradient computed from one data point. Fast and noisy, but often faster initial convergence. Faster per iteration and able to handle large data. Best for very large datasets.

Stochastic descent repeats for multiple epochs, full passes over the shuffled dataset: for each training example, compute the prediction, compute the gradients from that single point, update. In practice most modern training uses the compromise, mini-batch gradient descent, small batches of 32 or 64 examples, blending batch's stability with stochastic's speed.

08 Convexity, minima, and guarantees

Where you can get to depends on the shape of the landscape. Two definitions first. A global minimum is a point where the function value is lower than at all other points in the entire domain; it is the lowest possible value the cost can attain, the best possible parameters. Local minima are points where the function value is lower than their nearby points, but not the lowest overall.

A function \(f\) is convex if for any two points \(x_1, x_2\) in its domain and any \(\lambda \in [0, 1]\):

\[ f\big(\lambda x_1 + (1 - \lambda)x_2\big) \;\le\; \lambda f(x_1) + (1 - \lambda) f(x_2) \]

Geometrically, any straight line connecting two points on the curve stays on or above the curve everywhere in between. Convex means curving upward, and a convex function has exactly one minimum, the global one. If we optimize a convex function we're sure we will always find the global minimum. A function is non-convex if at least one straight chord dips below the curve somewhere. Non-convex surfaces have multiple peaks and valleys, and an optimizer can settle into a local valley and miss the global bottom.

figure 03 — convex vs non-convex · one valley or many
convex · one global minimum
local global non-convex · valleys everywhere

In linear regression the cost function is a parabola in one parameter and a bowl in two, always convex, one global minimum and no local minima to worry about, so gradient descent is guaranteed to find the best coefficients. There is even a clean theorem behind the guarantee:

If the cost function \(J(\theta)\) is convex and differentiable with L-Lipschitz continuous gradients, and the learning rate satisfies \( 0 < \alpha \le \tfrac{1}{L} \), then gradient descent converges to the global minimum of \(J(\theta)\). convergence theorem of gradient descent

L-Lipschitz gradients simply means the slope can't change faster than some constant \(L\), the curvature is bounded, so a step of size \(1/L\) can never overshoot badly. That is figure 02's lesson wearing formal clothes.

In non-linear models (deep networks especially) the loss surface is non-convex and the cost surface changes shape, so no such guarantee exists. That is why initialization, learning-rate schedules, and stochastic noise, which can shake a walker out of shallow local dips, matter so much in deep learning.

09 The whole algorithm in fifteen lines
# batch gradient descent for y = w*x + b, data: exam scores
import numpy as np
x = np.array([1, 2, 3]);  y = np.array([35, 45, 55])
w, b, alpha = 0.0, 0.0, 0.1

for step in range(1000):
    y_hat = w * x + b                      # predictions
    error = y_hat - y                      # how wrong, per point
    g_w = (error * x).mean()               # ∂J/∂w
    g_b = error.mean()                     # ∂J/∂b
    w -= alpha * g_w                       # step downhill
    b -= alpha * g_b
    if abs(g_w) < 1e-9 and abs(g_b) < 1e-9: # gradient ≈ 0 → converged
        break

print(round(w, 3), round(b, 3))            # → 10.0 25.0, marks = 10·hours + 25

Change alpha to 1.5 and watch the numbers explode, figure 02 again, this time in your terminal.

Sources
Tanvir Hossain
10 · 07 · 2026