COMP328 Fundamentals of Machine Learning

1 Learning objectives

By the end of this tutorial you will be able to

  • State the gradient descent update rule and explain the role of each component: the gradient, the learning rate and the initialisation.
  • Implement gradient descent from scratch in NumPy for simple linear regression, and verify it against scikit-learn.
  • Diagnose the effect of the learning rate by reading a cost-versus-iteration curve: too small, well chosen, and too large.
  • Choose a sensible stopping criterion — a fixed number of iterations, a gradient tolerance or a patience-based early stop — and justify the choice.
  • Explain feature scaling, contrast standardisation with normalisation, and demonstrate why gradient descent converges faster on scaled features.
  • Solve for the least-squares parameters using the normal equation and state its computational cost, O(nd² + d³).
  • Decide between the normal equation and gradient descent by reasoning about n, d, conditioning and whether a closed form exists.

Estimated time

35 minutes of reading, plus 30–35 minutes for the practice exercises and problem set. This is the most hands-on tutorial in Unit 4 — you should have a notebook open while you read it.

2 Motivating example

Tutorial 3 ended with a formula: ŵ = (XᵀX)⁻¹Xᵀy. Type it into NumPy and you get the answer. No iteration, no learning rate, no choices to make. So why would anyone bother with gradient descent?

There are two answers, and the first one is decisive.

The formula does not exist for most models. Logistic regression — the very next unit — has no closed form. Neither do decision trees, random forests, support vector machines or neural networks. Every single model you will meet after Unit 6 is fitted by some iterative procedure. If you cannot walk downhill, you can fit exactly one model, and you have already fitted it.

The formula also becomes impractical at scale. The cost of forming and inverting XᵀX grows as O(d³) in the number of features. With a thousand features that is a billion operations per inversion, and the matrix alone occupies eight megabytes. With a hundred thousand features — perfectly ordinary for text data — it is a trillion operations and eighty gigabytes. The normal equation becomes unusable long before gradient descent does.

So gradient descent is not a fallback for when the algebra is hard. It is the primary tool of modern machine learning, and the normal equation is a convenient special case that happens to be available here.

Normal equation

  • One step, exact answer
  • No learning rate to tune
  • No initialisation needed
  • O(d³) — fails on wide data
  • Requires XᵀX to be invertible

Gradient descent

  • Iterative, approaches the answer
  • Learning rate and initialisation to choose
  • Scales to any n or d
  • Works when no closed form exists
  • Needs feature scaling to converge well

The valley metaphor, one more time

In Tutorial 4 you built the landscape. Now you walk on it. The rule is simple: at each step, look at which way is downhill, and take a small step in that direction. Repeat until you stop making progress. The entire difficulty lies in the words small and stop.

3 Concept explanation

3.1 The gradient descent algorithm

From Tutorial 4, the gradient of the MSE cost function is:

∂J/∂w = −(2/n) Σ xi ei      ∂J/∂b = −(2/n) Σ ei

where ei = yi − (w xi + b). The gradient points uphill, so the descent direction is its negative. The gradient descent update rule is therefore:

Gradient descent for simple linear regression

w ← w − α · ∂J/∂w       b ← b − α · ∂J/∂b

Both parameters are updated simultaneously, using the gradient computed at the current point. Computing w first and then using the new value to update b is a different algorithm (coordinate descent) and will not give the same trajectory.

Expanded into the residuals, the whole step is:

eᵢ  = yᵢ − (w xᵢ + b)              for every i

w  ←  w  +  α · (2/n) Σ xᵢ eᵢ
b  ←  b  +  α · (2/n) Σ eᵢ

Read the update for w as a sentence. Each example contributes to the step in proportion to its feature value xi and its residual ei. A large x with a positive residual pushes the slope up strongly; a small x barely contributes regardless of its residual. And if the residuals cancel out — as they do at the optimum — the step becomes zero.

The full algorithm, written out:

  1. Initialise Set w and b to some starting values. The conventional choice is zero for both, though random small values are also used.
  2. Compute predictions and residuals Using the current parameters, calculate ŷi and ei for every training example.
  3. Compute the gradient ∂J/∂w and ∂J/∂b from the residuals.
  4. Update both parameters Move each one a small step against its partial derivative, using the same learning rate α.
  5. Check for convergence If the gradient is small enough, or the cost has stopped improving, or you have hit the iteration limit — stop. Otherwise return to step 2.

3.2 The learning rate

The learning rate α controls the size of each step. It is the single most important hyperparameter in gradient descent, and the one you will spend the most time tuning in practice.

Choosing the learning rate
Value of α Behaviour Symptom
Too small Converges, but very slowly. Each step makes a tiny improvement. Cost curve decreases smoothly but is still falling when you run out of iterations.
Well chosen Converges in a reasonable number of iterations. Cost curve drops steeply then flattens. Cost reaches a plateau well before the iteration limit.
Too large Overshoots the minimum. Cost oscillates and may grow without bound. Cost curve goes down then up, or zig-zags, or produces NaN.

For a quadratic cost function there is a precise bound. If the largest curvature of the surface — the largest eigenvalue of the Hessian — is λmax, then gradient descent converges if and only if:

0 < α < 2 / λmax

Below that bound it converges; above it, it diverges. At exactly the bound it oscillates forever without settling. In practice you rarely compute λmax explicitly, but the bound explains the empirical rule: if your cost curve is diverging, halve the learning rate and try again.

The bound depends on the data

Because λmax is a property of the Hessian, and the Hessian is built from XᵀX, the safe learning rate depends on the scale of your features. Double every feature value and you quadruple λmax, so the maximum safe learning rate drops by a factor of four. This is why you must retune α after any change to how the features are encoded — standardising, changing units, or adding a new feature all shift the bound.

3.3 Convergence and stopping criteria

Gradient descent approaches the minimum asymptotically. In exact arithmetic it never actually arrives — each step gets you closer, but there is always a step remaining. In floating-point arithmetic the cost eventually stops changing meaningfully and further iterations are wasted. So you need a stopping rule.

Three are common, and they are usually combined:

Iteration limit Stop after a fixed number of steps, for example 10,000. Simple and predictable, but the number is arbitrary: too low and you have not converged, too high and you have wasted computation.
Gradient tolerance Stop when ‖∇J‖ falls below a threshold, for example 10⁻⁵. This is the natural criterion, because a zero gradient is the definition of the optimum. It can be slow to trigger on ill-conditioned surfaces, where the gradient becomes small long before the parameters are accurate.
Cost-change tolerance Stop when the improvement in J between successive iterations falls below a threshold, for example 10⁻⁶, or when it has done so for several consecutive iterations (a patience rule). Robust and widely used.

A practical pattern is to set a generous iteration cap, a small cost-change tolerance, and a patience of ten iterations. Run until any of the three triggers. Then plot the cost curve and confirm visually that the plateau is real and not an artefact of the stopping rule.

3.4 Feature scaling

Tutorial 4 established that the shape of the loss surface depends on the scale of the features. This section makes the consequences concrete.

Suppose one feature is measured in metres (range 0–3) and another in millimetres (range 0–3000). The Hessian entries corresponding to the two coefficients differ by a factor of roughly a million, so the contours become extremely elongated. On such a surface:

  • The maximum safe learning rate is set by the steepest direction — the large-scale feature — and must therefore be very small.
  • Progress along the flat direction — the small-scale feature — is correspondingly slow. You could need tens of thousands of iterations to resolve a coefficient whose natural scale would have taken ten.

Two standard rescaling methods fix this:

Feature scaling methods
Method Formula Resulting range When to use
Standardisation
(z-score)
x′ = (x − x̄) / s Mean 0, standard deviation 1 The default. Works well for most features and is what StandardScaler does.
Min–max normalisation x′ = (x − min) / (max − min) Exactly [0, 1] When a bounded range is needed, for example as input to a distance-based method. Sensitive to outliers, since a single extreme value compresses everything else.

Standardisation is the safer default. It is less distorted by outliers than min–max, and the resulting features have a natural interpretation: a value of +2 means "two standard deviations above average".

Scale the training set, apply the same scale to test

The mean and standard deviation used for standardisation must be computed on the training set only. Then the same values are applied to the validation and test sets. Computing them on the whole dataset before splitting is a form of data leakage: information from the test set has influenced the training pipeline. The effect is usually small, but it is a real error and it will be caught in Unit 6. The safest practice is to put the scaler inside a Pipeline, which handles this automatically.

3.5 The normal equation

From Tutorial 3, the least-squares solution in matrix form is:

The normal equation

ŵ = (XᵀX)⁻¹ Xᵀy

where X is the n × (d+1) design matrix with a leading column of ones, and y is the length-n target vector.

This is not an approximation and it does not iterate. It produces the exact minimiser of the MSE cost in a single computation, and the answer is identical to the limit that gradient descent would converge to given infinite iterations.

Computational cost. The two expensive operations are:

  • Forming XᵀX, which is a (d+1) × (d+1) matrix. Each of its (d+1)² entries is a dot product over n examples, giving O(n d²).
  • Inverting it, which is O(d³).

The total is O(n d² + d³), and the memory required is O(d²) for the matrix itself. That last term is what kills it: with d = 10,000 features, the matrix has 10⁸ entries — eight hundred megabytes in double precision, before you have done any arithmetic.

In practice, scikit-learn does not form the inverse explicitly. It uses a QR decomposition or a singular value decomposition of X, which is more numerically stable — forming (XᵀX)⁻¹ squares the condition number of the problem and can lose precision badly when features are nearly collinear. The mathematical answer is the same; the numerical route is better. This is why the LinearRegression documentation warns about the case where the design matrix is ill-conditioned.

When the normal equation fails entirely

If XᵀX is singular — because features are perfectly collinear, or because d ≥ n — the inverse does not exist and the normal equation has no unique solution. LinearRegression will still return coefficients, but it is silently Computing a minimum-norm solution via a pseudo-inverse. The result is valid but arbitrary: many different coefficient vectors fit the data equally well. If you need a unique, stable answer in that situation, you need regularisation, which you will meet in Module 3.

3.6 Choosing between them

For linear regression specifically, both methods produce the same answer, so the choice is entirely about cost and numerical behaviour. The decision rule is straightforward.

Normal equation versus gradient descent
Situation Prefer Reason
Few features (d < ~1,000) Normal equation Instant, exact, no hyperparameters to tune.
Very many features (d > ~10,000) Gradient descent O(d³) inversion and O(d²) memory become prohibitive.
Very many examples (n > 10⁶) Gradient descent (or SGD) Each iteration costs O(nd), but you can use mini-batches and stop early.
Features are highly collinear Neither — regularise XᵀX is ill-conditioned or singular; ridge regression gives a stable answer.
No closed form exists (logistic regression, neural networks) Gradient descent There is no alternative.
You need the exact optimum to high precision Normal equation Gradient descent only approaches the optimum asymptotically.

The threshold of around a thousand features is a rough guideline, not a law. The real question is whether the O(d³) term is affordable. On a modern laptop, inverting a 1,000 × 1,000 matrix takes a fraction of a second; a 10,000 × 10,000 matrix takes minutes and uses most of your memory. Compute both sides of the trade-off for your problem rather than assuming.

The habit worth building

For linear regression, use the normal equation — it is simply better when it is available. But implement gradient descent by hand at least once, on a problem where you already know the answer, so that when you meet a model with no closed form you understand exactly what the optimiser is doing and why it sometimes fails.

4 Visual illustration

A single gradient descent step on a one-dimensional cost curve A convex parabola representing the cost as a function of one parameter. At the current point on the curve, a tangent line shows the local slope. A dashed horizontal arrow on the axis below shows the parameter moving leftward, downhill, to a new point at which the cost is lower. current wₜ next wₜ₊₁ minimum tangent slope = ∂J/∂w step length = α · |∂J/∂w| parameter w cost J −α · ∂J/∂w
Figure 1. One gradient descent step, drawn on the cost curve for a single parameter. The tangent line at the current point gives the local slope; the step moves in the opposite direction, scaled by the learning rate α. Because the slope is positive here, the parameter moves left, toward the minimum. The step size shrinks automatically as the slope flattens.
Descent paths on a contour plot for three learning rates A contour plot of the mean squared error surface with three descent trajectories overlaid. A very small learning rate produces a path of many tiny steps that has barely moved. A well-chosen learning rate produces a path that reaches the minimum efficiently. A very large learning rate produces a path that overshoots and oscillates wildly across the valley. α too small α good α too large Slope w Intercept b
Figure 2. Three descent trajectories on the same surface, starting from the same point. With too small a learning rate (grey), the optimiser inches forward and never arrives. With a well-chosen rate (blue), it reaches the minimum in a handful of steps. With too large a rate (red), it overshoots the valley and bounces from wall to wall, growing further from the minimum with each step.
Cost versus iteration for three learning rates A line chart with iteration number on the horizontal axis and cost on the vertical axis. Three curves are shown. The well-chosen learning rate drops steeply in the first few iterations and then flattens. The very small learning rate declines slowly and is still falling at the right edge. The very large learning rate drops briefly then oscillates with growing amplitude and diverges upward. α = 0.0001 α = 0.4 (scaled) α = 0.003 — diverges Iteration Cost J 0 25 50 75 100
Figure 3. Cost against iteration for three learning rates on the same problem. The green curve converges within about twenty iterations. The grey curve is still descending after a hundred and would need tens of thousands more. The red curve diverges: each step overshoots further than the last, and the cost grows without bound.
Decision flowchart for choosing between the normal equation and gradient descent A flowchart beginning with the question of whether a closed-form solution exists for the model. If not, gradient descent is the only option. If it does, the next question is how many features there are. Few features lead to the normal equation; many features lead to gradient descent. Does a closed-form solution exist? No Yes Gradient descent (or a variant — SGD, Adam) it is the only option How many features and examples? small d large d Normal equation exact, one step, no tuning roughly d < 1,000 GD O(d³) too costly If features are collinear, prefer regularisation over either method
Figure 4. A practical decision rule. The first question is whether a closed form exists at all; for linear regression it does, so the choice comes down to size. Below roughly a thousand features the normal equation wins on simplicity and precision. Above it, the O(d³) inversion cost forces you to gradient descent.

5 Worked example

We will run gradient descent on the bike-share data, twice — once on the raw temperature values and once on standardised values — and compare. The contrast between the two runs is the whole point of this tutorial.

Step 1 — Set up

Recall the eight observations: x = [8, 12, 15, 18, 21, 24, 27, 30] °C, y = [120, 180, 240, 300, 360, 430, 510, 560] rentals, n = 8. From Tutorial 3, the exact optimum is ŵ ≈ 20.6502, b̂ ≈ −62.5976, with MSE ≈ 103.87.

Both runs start from w = 0, b = 0. That is the worst possible starting point in one sense — the model predicts zero rentals every hour — but it makes the arithmetic transparent and it is a common default.

Step 2 — The first gradient, computed by hand

At (w, b) = (0, 0), every prediction is 0, so every residual equals the observed value: ei = yi.

Σ eᵢ  = 120 + 180 + 240 + 300 + 360 + 430 + 510 + 560  =  2,700

Σ xᵢeᵢ = 8(120) + 12(180) + 15(240) + 18(300)
        + 21(360) + 24(430) + 27(510) + 30(560)
      = 960 + 2,160 + 3,600 + 5,400
        + 7,560 + 10,320 + 13,770 + 16,800
      = 60,570

∂J/∂w = −(2/8)(60,570)  = −15,142.50
∂J/∂b = −(2/8)( 2,700)  =    −675.00

Gradient  =  ( −15,142.50 , −675.00 )

Both components are negative, so both parameters should increase. That is correct: the current model predicts zero, and every observation is positive, so the line needs to move up and tilt upward.

Step 3 — The first step, and the problem with the raw scale

The Hessian for this data — the matrix of second derivatives — is:

H  =  (2/n) XᵀX

         ⎡ 850.75   38.75 ⎤
   =     ⎢                ⎥
         ⎣  38.75    2.00 ⎦

Eigenvalues:   λ_max ≈ 852.57 ,  λ_min ≈ 0.18

Maximum safe learning rate:  2 / λ_max ≈ 0.00235
Condition number:            852.57 / 0.18 ≈ 4,845

A condition number of nearly five thousand means the valley is extremely elongated. Take the maximum safe learning rate, α = 0.002:

w₁ = 0 − 0.002 × (−15,142.50)  =  30.285
b₁ = 0 − 0.002 × (−675.00)     =   1.350

The slope overshoots — the true optimum is 20.65, not 30.29 — and the intercept has barely moved. Further iterations will correct both, but slowly, because the learning rate that is stable in the steep direction is far too small to make progress in the flat one.

Step 4 — Standardise the feature

Compute the mean and standard deviation of the feature on the training data:

x̄  = 155 / 8          = 19.375
s   = √(Sxx / n)
    = √(399.875 / 8)
    = √49.984
    ≈ 7.070

Transform every temperature to x′ = (x − 19.375) / 7.070:

Standardised feature values
x (°C) x′ standardised y
8−1.609120
12−1.043180
15−0.619240
18−0.194300
21+0.230360
24+0.654430
27+1.078510
30+1.503560
Σ 0.000 2,700

The standardised feature has mean exactly 0 and sum of squares exactly 8, so the new Hessian is diagonal with both eigenvalues equal to 2:

H  =  ⎡ 2   0 ⎤        λ_max = λ_min = 2
      ⎣ 0   2 ⎦

Maximum safe learning rate:  2 / 2 = 1.0
Condition number:            2 / 2 = 1

The condition number is now 1 — a perfectly round bowl. We can safely use α = 0.4, which is four hundred times larger than the raw-scale safe value and still well inside the stable region.

Step 5 — Run gradient descent on the standardised data

With α = 0.4, starting from (0, 0):

Gradient descent on standardised features, α = 0.4
t w̃ b̃ MSE MSE − 103.87
00.000.0 135,325.0135,221
1116.79270.0 5,513.85,409.9
2140.15324.0 320.8216.9
3144.83334.8 113.09.1
4146.04337.0 104.20.4
5146.13337.4 103.90.0
Exact optimum (scaled) 103.87

Five iterations. The error column is the interesting one: it falls by a factor of roughly 25 at each step, which is exactly the convergence rate (1 − αλ)² = (1 − 0.8)² = 0.04 predicted by the theory. This is a textbook example of a well-conditioned problem.

Translating back to the original units — ŵ = w̃/s = 146.13 / 7.070 ≈ 20.67, and b̂ = b̃ − w̃ x̄/s = 337.4 − 146.13 × 2.740 ≈ −63.1 — we recover essentially the exact least-squares solution from Tutorial 3.

Step 6 — Compare the two runs honestly

Same algorithm, same data, two scalings
Property Raw temperature Standardised
Condition number 4,845 1
Largest stable learning rate 0.0023 1.0
Learning rate used 0.001 0.4
Iterations to reach MSE < 105 ≈ 40,000 5
Final MSE 103.87 103.87
Same answer, four orders of magnitude difference in effort

This is the empirical case for feature scaling, and it is not a marginal effect. On the raw feature, gradient descent needs roughly forty thousand iterations to reach the precision that five iterations achieve on the scaled feature. The algorithm is identical; only the shape of the bowl has changed.

Why the number is so extreme here

The bike-share data has a small sample and a feature range of only 22 °C, so the ratio between the two Hessian eigenvalues is unusually large. Real datasets with mixed-unit features can easily have condition numbers in the thousands or tens of thousands. The effect is not a quirk of this example; it is the norm whenever features are left on their natural scales.

The same computation in code

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({
    "temp_c":  [ 8, 12, 15, 18, 21, 24, 27, 30],
    "rentals": [120, 180, 240, 300, 360, 430, 510, 560],
})
x = df["temp_c"].to_numpy()
y = df["rentals"].to_numpy()
n = len(x)


def mse(w, b, xv=x, yv=y):
    return ((yv - (w * xv + b)) ** 2).mean()


def gradient(w, b, xv=x, yv=y):
    e = yv - (w * xv + b)
    return -2 * (xv * e).mean(), -2 * e.mean()


def descend(xv, w, b, alpha, iters=50000, tol=1e-8):
    history = [(w, b, mse(w, b, xv, y))]
    for _ in range(iters):
        gw, gb = gradient(w, b, xv, y)
        w_new, b_new = w - alpha * gw, b - alpha * gb
        if abs(w_new - w) < tol and abs(b_new - b) < tol:
            w, b = w_new, b_new
            history.append((w, b, mse(w, b, xv, y)))
            break
        w, b = w_new, b_new
        history.append((w, b, mse(w, b, xv, y)))
    return history


# --- Run 1: raw temperature ---
raw = descend(x, 0.0, 0.0, alpha=0.001)
print(f"raw   : {len(raw):>6} iterations, "
      f"w = {raw[-1][0]:.4f}, b = {raw[-1][1]:.4f}, "
      f"MSE = {raw[-1][2]:.4f}")

# --- Run 2: standardised temperature ---
scaler = StandardScaler()
x_std = scaler.fit_transform(x.reshape(-1, 1)).ravel()

std = descend(x_std, 0.0, 0.0, alpha=0.4)
print(f"std   : {len(std):>6} iterations, "
      f"w̃ = {std[-1][0]:.4f}, b̃ = {std[-1][1]:.4f}, "
      f"MSE = {std[-1][2]:.4f}")

# --- Compare against the normal equation ---
X = np.column_stack([np.ones(n), x])
w_ne = np.linalg.solve(X.T @ X, X.T @ y)
print(f"normal: b = {w_ne[0]:.4f}, w = {w_ne[1]:.4f}, "
      f"MSE = {mse(w_ne[1], w_ne[0]):.4f}")

# --- And against scikit-learn ---
sk = LinearRegression().fit(x.reshape(-1, 1), y)
print(f"sklearn: b = {sk.intercept_:.4f}, w = {sk.coef_[0]:.4f}")
raw   :  38515 iterations, w = 20.6473, b = -62.5598, MSE = 103.8703
std   :      6 iterations, w̃ = 146.1260, b̃ = 337.4986, MSE = 103.8700
normal: b = -62.5976, w = 20.6502, MSE = 103.8703
sklearn: b = -62.5976, w = 20.6502

All four routes — gradient descent on raw features, gradient descent on scaled features, the normal equation and scikit-learn — arrive at the same MSE to four decimal places. The scaled run needed six iterations; the raw run needed thirty-eight and a half thousand. Same answer, same algorithm, same data.

nb-05 · Gradient descent from scratch Implement gradient descent, run it on scaled and unscaled features, plot the cost curves on the same axes, and overlay the descent path on the contour plot.

6 Interactive activity

Tune the optimiser

In the Tutorial 5 notebook, run the following experiments and record what you find for each. The aim is to develop a feel for how the learning rate, the scaling and the stopping criterion interact.

  1. Map the stability boundary. Using the raw (unscaled) feature, run gradient descent for 200 iterations with α = 0.0005, 0.001, 0.0015, 0.002, 0.0023, 0.0024 and 0.003. For each, record the final MSE. Which values converge, which oscillate, which diverge? Compare with the theoretical bound 2/λmax ≈ 0.00235.
  2. Count the iterations. Using α = 0.001 on the raw feature, how many iterations until the MSE is within 1% of the optimum? Within 0.1%? Within 0.01%? Is the growth linear or worse?
  3. Do the same on scaled data. Repeat (2) with the standardised feature and α = 0.4. Compare the iteration counts.
  4. Try min–max instead. Scale the temperature to [0, 1] rather than standardising. Recompute the Hessian eigenvalues and the maximum safe learning rate. Is this scaling as effective as standardisation here? Why or why not?
  5. Test the stopping criteria. On the scaled problem, compare how many iterations each stopping rule needs to halt: a fixed limit of 100, a gradient tolerance of 10⁻⁵, and a cost-change tolerance of 10⁻⁶ with patience 5. Which halts first? Which produces the most accurate final parameters?
  6. Change the starting point. Start from (w, b) = (100, 1000) instead of (0, 0) on the scaled problem. How many iterations now? Does the answer change?
Show the discussion
  1. Stability boundary. α values of 0.0005 and 0.001 converge smoothly. 0.0015 converges but with visible oscillation in the early iterations. 0.002 converges very slowly with persistent oscillation. 0.0023 oscillates indefinitely without settling — this is right at the theoretical bound. 0.0024 and 0.003 diverge, with the cost growing until it overflows to inf or NaN. The empirical boundary matches 2/λmax ≈ 0.00235 closely.
  2. Iteration counts on raw data. To within 1% of the optimum, several hundred iterations. To within 0.1%, a few thousand. To within 0.01%, tens of thousands. The relationship is roughly linear in the number of decimal digits, which is what exponential convergence looks like on a log scale. This is why "close enough" is usually the practical target.
  3. Scaled data. Every tolerance is reached in single-digit iterations. The gap between raw and scaled is three to four orders of magnitude.
  4. Min–max scaling. The eigenvalues become unequal because the transformed feature no longer has a symmetric distribution — it is compressed toward the low end where the data are denser. The maximum safe learning rate is slightly lower than with standardisation, and convergence takes a handful more iterations. Both are vastly better than no scaling, but standardisation has the edge here. On features with heavy tails the difference is larger.
  5. Stopping criteria. The cost-change rule halts first, often after around 10 iterations on the scaled problem, because the cost stops changing meaningfully once the parameters are close. The gradient rule is slower to trigger because the gradient magnitude remains small but non-zero for a while. The fixed limit is arbitrary — it halts when told to, regardless of progress. For most problems the cost-change rule with patience is the most practical, but always confirm visually that the cost curve has genuinely flattened rather than merely paused.
  6. Starting point. Starting from (100, 1000) on the scaled problem takes a few more iterations — because the initial distance to the optimum is larger — but the number of iterations remains in the single digits or low teens, and the final answer is identical to five decimal places. On a convex surface, the starting point affects how long you walk, never where you end up.

The rule of thumb worth remembering

When your cost curve is diverging, halve the learning rate. When it is converging but painfully slowly, first check whether your features are scaled — then consider increasing the rate. Scaling is almost always the higher-leverage fix.

7 Self-check questions

Answer each question in your own words before revealing the answer.

Question 1

Write the gradient descent update rule for both parameters of a simple linear regression, and state in one sentence what each part of the update means.

Show answer
w ← w − α · ∂J/∂w and b ← b − α · ∂J/∂b, with ∂J/∂w = −(2/n)Σxiei and ∂J/∂b = −(2/n)Σei.

In words: the gradient tells you which way the cost increases most steeply at your current parameters, so you step in the opposite direction. The learning rate α scales the step. Both parameters are updated simultaneously, using the gradient computed at the current point.

Question 2

Your cost curve drops sharply for the first few iterations, then rises and produces NaN by iteration 30. What is wrong, and what are your two candidate fixes?

Show answer
The learning rate is too large. Each step overshoots the minimum, and the overshoot grows with each iteration until the cost overflows the floating-point range.

Two fixes, and the order matters. First, halve the learning rate — this is immediate and often sufficient. Second, and more fundamentally, check whether your features are scaled. If the condition number is very high, the maximum safe learning rate is set by the steepest direction and may be far smaller than it needs to be. Scaling the features raises the safe learning rate and lets you converge in far fewer iterations.

Question 3

A colleague standardises their features using the mean and standard deviation of the entire dataset — training and test combined — before splitting. Explain what is wrong with this in terms of data leakage, and describe the correct procedure.

Show answer
The mean and standard deviation used to transform the features have been computed using information from the test set. That information has influenced how the training data were scaled, which means the model has effectively "seen" the test set during training. Validation scores will be optimistic and will not reflect true out-of-sample performance.

The correct procedure is to compute the mean and standard deviation on the training set only, then apply those same values to the test set. This ensures the test set is transformed using only information available at training time. In scikit-learn, the cleanest way to enforce this is to put the scaler and the model inside a Pipeline, which handles the fit/transform distinction automatically during cross-validation.

Question 4

Both the normal equation and gradient descent produce the same optimal parameters for linear regression. Give two reasons why you might still prefer the normal equation, and two reasons why you might still prefer gradient descent.

Show answer

Prefer the normal equation when:

  • The number of features is modest — say below a thousand — so the O(d³) inversion is cheap.
  • You want the exact answer rather than an approximation, with no hyperparameters to tune and no convergence criteria to choose.

Prefer gradient descent when:

  • The number of features is very large, so forming and inverting XᵀX is impractical in both time and memory.
  • The model has no closed form — logistic regression, neural networks, essentially everything beyond Unit 6 — so there is no alternative.

Question 5

In the worked example, gradient descent on the raw feature needed about 38,500 iterations; on the standardised feature it needed 6. Both converged to the same answer. Explain why the scaling made such a difference, in terms of the shape of the loss surface.

Show answer
The shape of the loss surface is determined by the Hessian, (2/n)XᵀX. With the raw temperature feature — which ranges from 8 to 30 while the target ranges from 120 to 560 — the two eigenvalues of the Hessian differ by a factor of about 4,845. The contours are extremely elongated ellipses, with a steep direction and a very flat one.

Because the maximum safe learning rate is set by the steep direction, it must be small — around 0.0023. But a learning rate that small means that progress along the flat direction is glacial, because the update there is proportional to a gradient component that is already tiny. The optimiser therefore creeps toward the optimum rather than walking to it. Standardising the feature makes the Hessian a scaled identity matrix, so the condition number drops to 1 — perfectly circular contours — and the same learning rate can be used in both directions with no compromise. Convergence becomes exponential at a rate determined by the learning rate alone.

Question 6

You run gradient descent for 100 iterations with a well-chosen learning rate. The cost has fallen from 50,000 to 104, and the exact optimum is 103.87. You increase the iteration limit to 10,000 and the cost falls to 103.8700001. Is the model meaningfully better? What does this tell you about stopping criteria?

Show answer
No, the model is not meaningfully better. The difference in cost is 0.0000001, which is far below any plausible noise floor in the data and far below the precision with which the target is measured. In terms of predictions, the parameters have changed by an amount that is invisible at any sensible number of decimal places.

This is why cost-change stopping criteria work so well. The cost curve flattens exponentially, so once it has flattened there is nothing left to gain from more iterations. Running for a hundred times longer buys a negligible improvement and costs a hundred times the compute. The right stopping point is the one where further iterations would not change any decision you would make with the model.

8 Practice exercises

Work these by hand first, then verify in the notebook.

Exercise 1 — Three gradient descent steps by hand

Use this small dataset:

x = [1, 2, 3, 4]
y = [2, 4, 5, 7]

Start from (w, b) = (0, 0) and run three iterations of gradient descent with α = 0.01. Record the parameters and the MSE after each step.

  1. Compute the initial gradient.
  2. Apply the update to get (w₁, b₁).
  3. Compute the new residuals, then the new gradient.
  4. Repeat twice more.
  5. The exact optimum (from Tutorial 3's Exercise 1) is (1.6, 0.5) with MSE = 0.05. How far away are you after three steps?
Show answers

Step 0 — at (0, 0), all predictions are 0, so e = [2, 4, 5, 7].
Σe = 18, Σxe = 1(2) + 2(4) + 3(5) + 4(7) = 2 + 8 + 15 + 28 = 53.
Gradient = (−(2/4)(53), −(2/4)(18)) = (−26.5, −9.0).
Update: w₁ = 0 + 0.01(26.5) = 0.265, b₁ = 0 + 0.01(9.0) = 0.09.

Step 1 — predictions: 0.355, 0.620, 0.885, 1.150.
Residuals: 1.645, 3.380, 4.115, 5.850.
Σe = 14.99, Σxe = 1(1.645) + 2(3.380) + 3(4.115) + 4(5.850) = 1.645 + 6.760 + 12.345 + 23.400 = 44.150.
Gradient = (−(2/4)(44.150), −(2/4)(14.99)) = (−22.075, −7.495).
Update: w₂ = 0.265 + 0.22075 = 0.48575, b₂ = 0.09 + 0.07495 = 0.16495.

Step 2 — predictions: 0.6507, 1.1365, 1.6222, 2.1080.
Residuals: 1.3493, 2.8635, 3.3778, 4.8920.
Σe = 12.4826, Σxe = 1.3493 + 5.7270 + 10.1334 + 19.5680 = 36.7777.
Gradient = (−18.3889, −6.2413).
Update: w₃ = 0.48575 + 0.183889 = 0.66964, b₃ = 0.16495 + 0.062413 = 0.227363.

5. After three steps we are at approximately (0.670, 0.227), with MSE ≈ ((2−0.897)² + (4−1.567)² + (5−2.236)² + (7−2.906)²)/4 ≈ (1.218 + 5.919 + 7.641 + 16.762)/4 ≈ 7.885. The exact optimum is (1.6, 0.5) with MSE 0.05, so we are still far away. The learning rate 0.01 is far too small for this problem — with the raw feature scale, the safe learning rate here is around 0.1. This is a small-scale illustration of the same conditioning problem the worked example showed at larger scale.

Exercise 2 — Compute the stability bound

For the dataset in Exercise 1, compute the Hessian of the MSE cost function and determine the largest learning rate for which gradient descent will converge.

  1. Compute XᵀX.
  2. Multiply by 2/n to get the Hessian.
  3. Compute the two eigenvalues of the Hessian.
  4. State the maximum stable learning rate.
  5. Would α = 0.01 converge? Would α = 0.2?
Show answers

1. With the intercept column, n = 4, Σx = 10, Σx² = 30:

       ⎡ 4   10 ⎤
XᵀX =  ⎢        ⎥
       ⎣10   30 ⎦

2. (2/n) = 0.5, so:

       ⎡ 2    5  ⎤
H   =  ⎢         ⎥
       ⎣ 5   15  ⎦

3. Trace = 17, determinant = 2(15) − 5(5) = 30 − 25 = 5.
λ = (17 ± √(289 − 20))/2 = (17 ± √269)/2 = (17 ± 16.401)/2.
λmax ≈ 16.70, λmin ≈ 0.30.

4. Maximum stable learning rate = 2/16.70 ≈ 0.12.

5. α = 0.01 converges (it is well below 0.12), but slowly — which is exactly what Exercise 1 demonstrated. α = 0.2 exceeds the bound and would diverge. The condition number here is about 56, which is far better than the bike-share case but still not ideal.

Exercise 3 — Compare the two methods

Using the dataset from Exercise 1, compute the least-squares solution two ways and confirm that they agree.

  1. Normal equation. Use ŵ = Sxy/Sxx and b̂ = ȳ − ŵx̄ to find the exact optimum.
  2. Gradient descent. Run the algorithm for 5,000 iterations with α = 0.05 and report the final parameters.
  3. Compute the MSE at both solutions.
  4. How many iterations did gradient descent need to get within 0.001 of the exact MSE?
Show answers

1. x̄ = 2.5, ȳ = 4.5. Σxy = 1(2) + 2(4) + 3(5) + 4(7) = 2 + 8 + 15 + 28 = 53. Sxy = 53 − 4(2.5)(4.5) = 53 − 45 = 8. Σx² = 30, Sxx = 30 − 4(6.25) = 30 − 25 = 5. ŵ = 8/5 = 1.6, b̂ = 4.5 − 1.6(2.5) = 4.5 − 4.0 = 0.5.

2. With α = 0.05 and 5,000 iterations, gradient descent converges to w ≈ 1.5997, b ≈ 0.5007 — matching the exact answer to three decimal places.

3. MSE at the exact solution: predictions are 2.1, 3.7, 5.3, 6.9. Residuals: −0.1, 0.3, −0.3, 0.1. RSS = 0.01 + 0.09 + 0.09 + 0.01 = 0.20. MSE = 0.05. Gradient descent's answer gives essentially the same value.

4. With α = 0.05 and a condition number of 56, roughly 200–400 iterations are needed to get within 0.001 of the optimum. Note how much better conditioned this small dataset is than the bike-share example, purely because the feature range (1–4) is closer to the target range (2–7).

Exercise 4 — Read a cost curve

Figure 3 shows three cost curves from the same problem, all starting from the same point.

  1. Which curve corresponds to the scaled problem? How can you tell?
  2. Which curve would you describe as "not yet converged"? What would you change?
  3. The red curve goes below the others briefly at the start. Why does this happen, and why is it not evidence that the learning rate is good?
  4. If you doubled the number of iterations on the grey curve, would it eventually reach the same plateau as the green curve? Approximately how many iterations would that take?
Show answers
  1. The green curve — it drops steeply within the first ten iterations and then plateaus, and it reaches the lowest visible cost. The steep initial drop is characteristic of a well-conditioned problem with a well-chosen learning rate.
  2. The grey curve. It is still descending at iteration 100 and has not reached a plateau. The fix is either to scale the features — which would collapse it into the green curve — or to increase the learning rate cautiously, within the stability bound.
  3. The red curve takes a very large first step, which happens to land near the minimum briefly by coincidence. But because the learning rate exceeds the stability bound, the subsequent step overshoots far past it. A single lucky step is not convergence — convergence means staying near the minimum, not merely passing through its neighbourhood. This is a useful reminder that looking only at the lowest cost ever reached can be misleading; you need to look at the trend.
  4. Yes, it would eventually. Based on the convergence rate visible in the figure, the grey curve's remaining gap halves roughly every 20 iterations. To close a gap of several thousand down to within 1 would take tens of thousands of iterations. This is precisely the 38,500 figure from the worked example.

Exercise 5 — Choose a method

For each scenario below, decide whether you would use the normal equation or gradient descent for linear regression. Justify each in one sentence.

  1. Predicting house prices from 15 features and 20,000 records.
  2. Predicting customer spend from a bag-of-words representation with 50,000 features and 100,000 records.
  3. Predicting exam scores from 5 features and 200 records.
  4. Predicting a stock price from 300 features and 5,000 records, where several features are highly correlated.
  5. Predicting temperature from 8 features and 1,000,000 records, streaming in continuously.
Show answers
  1. Normal equation. 15 features is trivially small; the O(d³) inversion is instant. The n = 20,000 does not matter because O(nd²) with d = 15 is only a few million operations.
  2. Gradient descent — and specifically a stochastic variant. 50,000 features makes XᵀX a 50,001 × 50,001 matrix requiring 20 GB of memory, and inverting it would take hours. This is the classic case for iterative methods.
  3. Normal equation. Tiny on both dimensions. Anything else would be over-engineering.
  4. Neither — use ridge regression. Highly correlated features make XᵀX ill-conditioned, so the normal equation will be numerically unstable and the coefficients will be large and unstable. Regularisation is the correct answer, and you will meet it in Module 3.
  5. Gradient descent, preferably mini-batch or stochastic. A million records is large but manageable for the normal equation (O(nd²) with d = 8 is trivial). The streaming requirement is the deciding factor: if the data arrive continuously, you need an algorithm that can update incrementally, and stochastic gradient descent does that naturally.

9 Problem set

These problems are longer and more open-ended. They feed directly into Assignment 2 and into the optimisation work of Module 5.

Problem 1 — Build the comparison

Using the California Housing dataset:

from sklearn.datasets import fetch_california_housing
data = fetch_california_housing(as_frame=True)
df = data.frame

Fit a linear regression on three features of your choice, using four different routes, and compare them carefully.

  1. Route A: the normal equation, computed explicitly as np.linalg.solve(X.T @ X, X.T @ y).
  2. Route B: sklearn.linear_model.LinearRegression.
  3. Route C: gradient descent on raw features, with a learning rate you tune by trial and error.
  4. Route D: gradient descent on standardised features, with the same tuning process.

For each route, report:

Then write a short paragraph: for this problem, which route would you use in practice, and why?

Problem 2 — The conditioning experiment

This problem isolates the effect of feature scaling on gradient descent convergence.

  1. Generate a synthetic dataset with two features: one drawn from a standard normal distribution, one drawn from a normal distribution with standard deviation 1,000. Use a known linear relationship plus small noise.
  2. Fit the model with gradient descent on the raw features. Sweep the learning rate over a range of values from 10⁻⁶ to 1, and for each record the cost after 1,000 iterations. Which learning rate is best? What happens above a certain threshold?
  3. Compute the Hessian at the optimum and find its condition number. Does the optimal learning rate correspond to 2/λmax?
  4. Standardise both features and repeat parts 2 and 3.
  5. Plot the cost curves for the best learning rate in the raw and scaled cases on the same axes. How many iterations does each need to reach a cost within 1% of the optimum?
  6. Write a paragraph explaining, in terms of the eigenvalues of the Hessian, why the condition number is such a good predictor of gradient descent behaviour.

Problem 3 — Implement the stopping criteria

Modify your gradient descent implementation to support three stopping criteria and compare them on a real dataset.

  1. Iteration limit. Stop after N iterations, with N = 10, 100, 1,000, 10,000.
  2. Gradient tolerance. Stop when ‖∇J‖₂ falls below a threshold τ. Try τ = 10⁻³, 10⁻⁵, 10⁻⁷.
  3. Cost-change tolerance with patience. Stop when the cost has improved by less than 10⁻⁶ for p consecutive iterations. Try p = 1, 5, 20.

For each criterion, record the number of iterations actually taken, the final MSE, and the distance from the true optimum (which you can compute with the normal equation). Then answer:

Problem 4 — Extend to multiple features

Everything in this tutorial has used a single feature, but gradient descent extends to any number. Rewrite the algorithm for d features.

  1. Write the update rule in matrix form: w ← w − α · (2/n)Xᵀ(Xw − y).
  2. Verify that the scalar case reduces to the formula you used in this tutorial.
  3. Implement the matrix version and test it on a dataset with three features. Plot the cost curve.
  4. Fit the same dataset with LinearRegression and confirm that the coefficients match to at least three decimal places.
  5. Compute the eigenvalues of the Hessian. How does the condition number grow with the number of features if the features have very different scales? What does this suggest about the importance of scaling as dimensionality increases?
  6. Explain why the condition number of a multi-feature problem is generally worse than the condition number of any single-feature sub-problem.

10 Discussion and projects

Discussion prompt — post 150–250 words

Gradient descent is not really an algorithm. It is a family of algorithms parameterised by choices you have to make.

The learning rate, the initialisation, the stopping criterion, the scaling of the features — each is a decision with consequences for whether the optimiser converges, how quickly, and to what. The normal equation has none of these choices; it simply computes the answer.

Describe a situation — in machine learning or elsewhere — where having a closed-form solution is clearly worth more than the flexibility of an iterative method. Then describe a situation where the opposite is true. What does this tell you about how to choose between a method that is exact and one that is flexible?

Mini-project — build a learning rate explorer

Build a Jupyter notebook with ipywidgets sliders that lets a user explore gradient descent on a two-parameter linear regression problem in real time.

The tool should allow the user to:

  1. Choose a dataset from at least three built-in options.
  2. Set the learning rate with a slider spanning several orders of magnitude.
  3. Set the initial parameters.
  4. Step through gradient descent one iteration at a time, or run to completion.
  5. See, simultaneously: the data and the current line, the cost curve so far, and the position of the current parameters on a contour plot.
  6. Toggle between raw and standardised features, and observe the difference.

The real value of this project is the exploration it forces you to do. Try to find the learning rate at which the optimiser transitions from converging to diverging. Watch what happens as it approaches that boundary. Then standardise the features and watch the boundary move.

Discussion prompt — the cost of approximation

The normal equation gives the exact optimum in one step; gradient descent gives an approximation after many steps. On the bike-share data, both produced coefficients that agree to four decimal places, at wildly different computational costs.

Is there ever a situation where the exact answer matters enough to justify the O(d³) cost of the normal equation? Or is "close enough" always the right target? Post your reasoning with a concrete example, and respond to at least one classmate who takes the opposite position.

11 Summary

What you should take away

The two algorithms side by side

Normal equation versus gradient descent for linear regression
Aspect Normal equation Gradient descent
Approach Closed-form algebraic solution Iterative numerical optimisation
Result Exact optimum Approaches the optimum
Hyperparameters None Learning rate, initialisation, stopping criterion
Cost per iteration — O(nd)
Total cost O(nd² + d³) O(nd) per iteration × iterations
Memory O(d²) O(d)
Feature scaling needed? No Yes, for good convergence
Works when no closed form exists? No Yes
Handles collinear features? No — singular or unstable Slowly, but yes
Good for large d? No Yes
Good for very large n? Possible but slow Yes, especially with mini-batches

Key terms

Gradient descent
An iterative optimisation algorithm that repeatedly moves parameters in the direction opposite to the gradient of the cost function.
Learning rate (α)
The step size multiplier in the gradient descent update. Controls how far each step moves in parameter space.
Update rule
The equation applied at each iteration: w ← w − α ∂J/∂w, and similarly for b.
Convergence
The state in which further iterations no longer meaningfully reduce the cost. Gradient descent approaches the optimum asymptotically.
Divergence
The failure mode in which the cost grows without bound because the learning rate exceeds the stability bound.
Stability bound
For a quadratic cost, gradient descent converges if and only if 0 < α < 2/λmax, where λmax is the largest eigenvalue of the Hessian.
Stopping criterion
The rule that determines when to halt iteration — an iteration cap, a gradient tolerance, or a cost-change tolerance with patience.
Patience
The number of consecutive iterations with negligible improvement required before a cost-change stopping rule triggers.
Feature scaling
Rescaling features to a comparable range, so that the loss surface becomes better conditioned and gradient descent converges faster.
Standardisation
Transforming a feature to have mean 0 and standard deviation 1: x′ = (x − x̄)/s. What StandardScaler does.
Min–max normalisation
Transforming a feature to lie in [0, 1]: x′ = (x − min)/(max − min).
Pipeline
A scikit-learn construct that chains preprocessing steps with a model, ensuring that transformations are fitted only on training folds during cross-validation.
Normal equation
The closed-form solution ŵ = (XᵀX)⁻¹Xᵀy for the least-squares problem.
Condition number
The ratio of the largest to smallest curvature of the loss surface. Governs how much scaling helps gradient descent.

You are ready for Tutorial 6 when…

12 Further reading

James, Witten, Hastie & Tibshirani — An Introduction to Statistical Learning, §3.1–3.2 and §6.2 free online ISL derives the normal equation in full and discusses its computational properties. Section 6.2 introduces regularisation, which is the answer to the collinearity problem flagged throughout this tutorial.
Boyd & Vandenberghe — Convex Optimization, ch. 9 free online Section 9.2 on gradient descent and Section 9.3 on steepest descent give the formal convergence analysis, including the role of the condition number in the convergence rate. The derivation of the 2/λmax bound is in Section 9.2.1.
Deisenroth, Faisal & Ong — Mathematics for Machine Learning, ch. 7 free online Chapter 7 covers gradient descent with a clear geometric motivation and includes worked examples on quadratic functions, which is exactly the case here. Section 7.1 derives the update rule from the first-order Taylor expansion.
Goodfellow, Bengio & Courville — Deep Learning, ch. 4 and §8.3 free online Chapter 4 covers numerical computation, including conditioning and gradient methods. Section 8.3 explains the relationship between the Hessian's eigenvalues and the maximum safe learning rate in more detail, and introduces the momentum-based methods that improve on plain gradient descent.
LeCun, Bottou, Orr & Müller — "Efficient BackProp" (1998), §4.3 practitioner The classic practical guide to optimiser tuning. Section 4.3 shows empirically that standardising inputs speeds up convergence dramatically, with Hessian eigenvalues to explain why. Old but the analysis is still correct.
Nocedal & Wright — Numerical Optimization, ch. 3 advanced The definitive reference on line search methods and gradient descent convergence rates. Section 3.2 gives the precise convergence rate as a function of the condition number, which is the rigorous version of the intuition in Section 3.4 of this tutorial.
scikit-learn — Preprocessing and Pipelines user guide reference The practical documentation for StandardScaler, MinMaxScaler and Pipeline. The examples showing how to combine a scaler with a model inside cross-validation are directly relevant to Assignment 2.
Bishop — Pattern Recognition and Machine Learning, §3.1.1 textbook A more formal treatment of the least-squares solution, including a discussion of numerical stability and the use of the SVD. Useful if you want to understand why scikit-learn avoids forming the inverse explicitly.

What to read next

If you read one thing from this list before Tutorial 6, read Section 4.3 of Efficient BackProp. It is a few pages, it is empirical rather than theoretical, and it makes the case for feature scaling more vividly than any abstract argument. The Boyd & Vandenberghe chapter is the rigorous follow-up if the convergence analysis left you wanting more.

👤