A minimodule on linear regression, from Victorian curiosity to the engine of modern AI
In 1885, Francis Galton was studying the height of parents and their children. He noticed something curious, tall parents tended to have tall children, but those children were usually a little shorter than their parents. Short parents had short children, but slightly taller ones. Everything seemed to drift back toward the average.
He called this phenomenon "regression to the mean", and the name stuck, long after the concept grew into something far more powerful.
What Galton was really doing, without modern language for it, was drawing a line through a cloud of points. A line that best captured the relationship between two things: parent height and child height. That line is what we now call a regression line, and the method he used to find it inherited the name. We still call it regression today.
At its heart, linear regression is finding the best straight line through a set of data points. Given two variables, say, hours studied and exam score, or house size and price, we want to describe how one changes as the other changes. That line has a simple form:
The challenge: how do we find the best values of \(w_0\) and \(w_1\)?
"Best" needs a definition. The most natural one: the best line is the one that minimizes the total prediction error between predictions and actual data points. We call these differences residuals.
To avoid positive and negative errors cancelling each other out, we square them and take the average. This gives us the Mean Squared Error (MSE):
The beautiful thing about this problem is that it has an exact, closed-form solution. Using calculus, we set the derivatives of \(L\) with respect to \(w_0\) and \(w_1\) to zero and solve. The result is:
Where \(\bar{x}\) and \(\bar{y}\) are simply the averages of \(x\) and \(y\). The numerator of \(w_1\) measures how \(x\) and \(y\) move together; the denominator measures how much \(x\) varies on its own.
In matrix form, useful when we have many input variables, this becomes even more compact:
Plug in your data, compute the formula, and you immediately get the optimal line. No guessing, no iteration, just math.
Now let's shift perspective. What if, instead of solving the formula directly, we taught a computer to find the best line through trial and error? This is where linear regression becomes the simplest example of a machine learning algorithm.
The setup has five ingredients:
That "downhill direction" is given by the gradient, the partial derivatives of \(L\):
At each step, we update the weights by moving against the gradient (since we want to go downhill):
Where \(\alpha\) (alpha) is the learning rate, the size of each step. Too large, and you overshoot the valley. Too small, and it takes forever to get there.
You and a partner each get your own weight space, a 2D plane of possible \((w_0, w_1)\) pairs. Click anywhere in your panel to test that combination. Dots glow red when you are close to a good fit and blue when you are far. After each guess you'll see Warmer or Colder compared to your own previous attempt. Ten turns total, alternating. Lowest MSE wins.
For linear regression, gradient descent is overkill, we have the exact formula. But the real power is that this same loop:
data → model → loss → gradient descent → stopping criterion
…is the engine behind every neural network, every large language model, every modern AI system. The architecture changes. The loss function changes. The gradients get more complex. But the loop stays the same. Linear regression is where that story begins.
The game is written in Python using Pygame, so you can open main.py and modify it directly. Try changing the number of turns, the range of weights, or the amount of noise in the dataset, even small tweaks can reveal a lot about how the loss landscape behaves. The source code is available here:
https://github.com/i-yam/passt/tree/main/modules/hot_or_cold