Capstone: linear regression three ways
One line, fitted exactly with the normal equation, approximately with gradient descent, and as a maximum-likelihood problem. All three land in the same place, and that is the lesson.
1. The idea
This capstone fits one line three ways, using nearly every module so far. The data are ten points, the model is ŷ = wx + b, and the loss is the mean squared error.
First, exactly. Put a column of ones next to x to make the design matrix X, and the best parameters solve the normal equation XᵀXθ = Xᵀ𝐲: matrices from Module 2, and setting the gradient to zero from Module 4. Second, by gradient descent: start anywhere and walk downhill (Module 6). Third, by maximum likelihood: assume each y is wx + b plus Gaussian noise, and pick the most likely w and b (Module 5). The first gets the answer in one solve, the second creeps up on it, and the third turns out to be the same problem as the first.
That agreement is the point. Squared error isn't an arbitrary choice: it is exactly what you minimize when you believe the noise is Gaussian. And the closed form that works here doesn't exist for neural networks, which is why everything at scale uses the second method.
2. Try it: watch descent catch up with the exact answer
The dashed line is the normal equation's exact answer. Gradient descent starts from a flat line at 0; run it and compare the numbers.
3. The math
4. The code (python)
import numpy as np
x = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0])
y = np.array([1.9, 2.3, 3.4, 3.6, 4.4, 5.1, 5.3, 6.2, 6.8, 7.1])
X = np.column_stack([x, np.ones_like(x)]) # one row per point: [x, 1]
# 1. exactly: the normal equation
theta_exact = np.linalg.solve(X.T @ X, X.T @ y)
print(theta_exact.round(4)) # [1.1891 1.34 ]
# 2. gradient descent
theta = np.zeros(2)
for _ in range(5000):
grad = -2 / len(x) * X.T @ (y - X @ theta)
theta = theta - 0.05 * grad
print(theta.round(4)) # [1.1891 1.34 ]
# 3. maximum likelihood with Gaussian noise (sigma = 0.3), searched on a grid of (w, b)
W, B = np.meshgrid(np.linspace(1.0, 1.4, 401), np.linspace(1.0, 1.7, 701))
R = y - (W[..., None] * x + B[..., None])
log_lik = np.sum(-0.5 * (R / 0.3) ** 2 - np.log(0.3 * np.sqrt(2 * np.pi)), axis=-1)
i = np.unravel_index(log_lik.argmax(), log_lik.shape)
print(round(W[i], 3), round(B[i], 3)) # 1.189 1.345. You’ll see this in
- Core Course chapter 7: the training loop
- Linear regression in the catalog
- Reference notes: linear algebra and matrix calculus the matrix form of this gradient
6. Check yourself
Finished this chapter?
What changed in ML architectures this week
New models worth knowing, benchmark moves, and one architecture explained in plain words. Free, once a week, unsubscribe anytime.