Foundations of Regression

Lesson 1

Linear regression

The line of best fit and how gradient descent finds it

Linear regression, lines, SSR, and gradient descent

Understanding how a line is represented in linear regression

Let's start with a simple example:

xy
22
48
511
100296

Here, we're not trying to classify anything, we're predicting a continuous value. This is regression, not classification.

Real-world analogy

Let's say x is the size of a house in square yards, and y is the price of the house. We're trying to learn a function that maps size → price.


The equation of a line (1D input)

When there is only one input feature, the relationship is modeled as:

y=mx+cy = mx + c

Or more generally, in machine learning terms:

y=wx+by = wx + b

w (or m)

The weight or slope

b (or c)

The bias or intercept


Moving beyond 1D: multiple input features

What happens when we have more than one input?

  • X₁ = size (sq. yards)
  • X₂ = location score
  • X₃ = city weight

The equation becomes:

y=w1x1+w2x2+w3x3+by = w_1 x_1 + w_2 x_2 + w_3 x_3 + b

In compact vector form (weights w ∈ ℝⁿ, input x ∈ ℝⁿ):

y=wx+by = \mathbf{w}^\top \mathbf{x} + b

This is still a linear model, but it's now a hyperplane in higher dimensions:

2 input features → 2D hyperplane (a line in feature space cross-sections)

3 input features → 3D hyperplane

N input features → N-dimensional hyperplane

Each wᵢ is a parameter that the model learns during training.


Visualizing a line

Consider the equation:

y=3x+(4)y = 3x + (-4)

This is a classic slope-intercept form of a line:

Slope (m) = 3
Intercept (c) = −4

It represents a straight line in 2D space, and helps build intuition before moving into higher-dimensional regression.


Sum of squared residuals (SSR) in linear regression and how it helps find optimal coefficients

1. Understanding residuals

In linear regression, our goal is to find the best-fitting line:

y=mx+cy = mx + c

where:

  • y is the predicted output.
  • x is the input.
  • m (slope) and c (intercept) are the coefficients we want to optimize.

In general, this equation represents the equation of a line in 2D space where:

  • m is the slope (how much y changes when x increases).
  • c is the y-intercept (the value of y when x = 0).

This equation defines a straight line because:

Linear relationship

The output y changes proportionally with x, meaning it follows a straight-line trend.

First-degree polynomial

The highest power of x is 1, making it a linear function.

Linear regression: an informal explanation

Linear regression is a technique that helps us find the best straight line through a set of data points. Let me explain how it works in a more structured and clear way.

What are we trying to do?

We want to find a line with the equation Y = mx + c (where m is the slope and c is the y-intercept) that best fits our data.

Our process

1

Start with initial m and c (m = 1, c = 1)

2

Predict with those values

3

Compare to true y; compute errors

4

Update m and c to reduce errors

5

Repeat until satisfactory

Our dataset

Training data:

Pointxy
122
248
3511
4100296
5200596

Testing data (with current prediction errors):

xpredicted ytrue yerror
51140149−9
55170161+9
151400449−49
201550599−49

The improvement process

We start with m = 1 and c = 1, which gives us poor predictions. For example, with these values, when x = 51, we get y = 52 (which is far from the true value of 149).

So we begin the iterative process:

  • Calculate how wrong our predictions are
  • Adjust m slightly
  • Adjust c slightly
  • Test if our predictions improved
  • Repeat

Each iteration makes our line fit the data better. After multiple iterations, our m and c values will converge to the optimal values that minimize the errors across all data points.

The pattern in our data suggests m should be close to 3 and c should be around 0, which would give us Y ≈ 3x. This matches what we can observe: when x doubles, y approximately triples (e.g., x = 100 → y = 296, x = 200 → y = 596).

This step-by-step refinement is the essence of the gradient descent algorithm commonly used in machine learning.


Residuals and SSR

For each data point (xᵢ, yᵢ), the residual is the difference between the actual value yᵢ and the predicted value:

Error / Residual=truepredicted\text{Error / Residual} = \text{true} - \text{predicted} Residuali=yiy^i\text{Residual}_i = y_i - \hat{y}_i

With prediction ŷᵢ = m xᵢ + c (same line as y = mx + c):

Residuali=yi(mxi+c)\text{Residual}_i = y_i - (m x_i + c)

Since some residuals may be positive and others negative, we square them to measure the total error. Sum of squared errors (SSR):

SSR=i=1n(yi(mxi+c))2\text{SSR} = \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)^2

This is also called the cost function or loss function:

J(m,c)=i=1n(yi(mxi+c))2J(m, c) = \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)^2

Drag m and c below. Each red line is a residual (true − predicted), and each red square is that residual squared, so its area is the squared error. The SSR is the total area of all the squares. Try to shrink it as much as you can, and notice how a single far-off point (a big square) dominates the total.

Line over one feature; residual squares
xy

y = 1.0x + 1.0

SSR = 49.0

total area of the red squares — smaller is a better fit


2. Finding the optimal coefficients using gradient descent

The best values for m and c are the ones that minimize SSR. We use gradient descent to iteratively adjust m and c until we reach the optimal values.

Gradient descent steps

1

Initialize m and c with random values.

2

Compute the cost J(m, c) using the SSR formula.

3

Compute the gradients (partial derivatives):

The derivative of J with respect to m:

Jm=2i=1n(yi(mxi+c))xi\frac{\partial J}{\partial m} = -2 \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)\, x_i

The derivative of J with respect to c:

Jc=2i=1n(yi(mxi+c))\frac{\partial J}{\partial c} = -2 \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)
4

Update m and c using the learning rate η (eta):

mmηJm,ccηJcm \leftarrow m - \eta\,\frac{\partial J}{\partial m}, \qquad c \leftarrow c - \eta\,\frac{\partial J}{\partial c}
5

Repeat steps 2–4 until the cost J(m, c) stops decreasing (i.e., convergence is reached).

Where do these gradient formulas come from?

Step 3 plugged in two derivatives. Here is where they come from. Start from the cost, one squared residual per point:

J(m,c)=i=1n(yi(mxi+c))2J(m, c) = \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)^2

Nudging the slope m. We ask: how does the cost change if we wiggle m a little? Differentiate each squared term:

Jm=i=1n2(yi(mxi+c))(xi)\frac{\partial J}{\partial m} = \sum_{i=1}^{n} 2\,\bigl(y_i - (m x_i + c)\bigr)\cdot(-x_i)

Two pieces show up:

  • the 2 × residual comes from differentiating the square (the derivative of x² is 2x),
  • the −xᵢ comes from the chain rule: inside the bracket, the only part that depends on m is −m·xᵢ, whose derivative with respect to m is −xᵢ.

Tidying up:

Jm=2i=1nxi(yi(mxi+c))\frac{\partial J}{\partial m} = -2 \sum_{i=1}^{n} x_i \bigl(y_i - (m x_i + c)\bigr)

Nudging the intercept c. Same logic, but now the only part inside the bracket that depends on c is +c, whose derivative is 1, so the chain-rule factor is just −1:

Jc=i=1n2(yi(mxi+c))(1)=2i=1n(yi(mxi+c))\frac{\partial J}{\partial c} = \sum_{i=1}^{n} 2\,\bigl(y_i - (m x_i + c)\bigr)\cdot(-1) = -2 \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)

So the two gradients you drop into the update rule are exactly:

Jm=2i=1nxi(yi(mxi+c)),Jc=2i=1n(yi(mxi+c))\frac{\partial J}{\partial m} = -2 \sum_{i=1}^{n} x_i \bigl(y_i - (m x_i + c)\bigr), \qquad \frac{\partial J}{\partial c} = -2 \sum_{i=1}^{n} \bigl(y_i - (m x_i + c)\bigr)

3. Intuition behind gradient descent

Imagine standing on a mountain and trying to find the lowest point.

With every iteration:

  • Compute the direction and magnitude in which you want to move in order to reduce the loss and make your parameters better.
  • Thus we say that with every step in the training process, the model is getting better and better and better.

Watch it happen below: the line starts from a random slope and intercept, then gradient descent nudges m and c step by step until the line settles into the best fit and the SSR bottoms out. Hit Randomise & retrain to drop a fresh random start and watch it converge again.

xy
SSR (the loss J)15.8
slope m
3.20
intercept c
-2.00
step 0

The view above watches the line move through the data. The view below watches the same process from the other side: the loss landscape, where the ball rolls downhill and the learning rate η sets the step size.

Loss curve over a single weight w
learning rate η0.060
too slowconvergesovershoots

Analogy map

SSR / J

Landscape of the mountain

Gradients

Direction to descend faster

Learning rate η

Step size, not too big, not too small

The learning rate η controls the step size: we don't want to move too fast (overshooting) or too slow (taking forever to converge).


4. How SSR minimization leads to optimal coefficients

  • High SSR means the line is a bad fit (large prediction errors).
  • Gradient descent tweaks m and c iteratively to reduce SSR.
  • As SSR decreases, the regression line gets closer to the actual data points.
  • Eventually, when the gradients become small enough, we've reached the best possible m and c.

5. Alternative: normal equation

Instead of using gradient descent, there's a direct mathematical way to find optimal coefficients. In matrix form, with design matrix X and target vector Y, and parameter vector θ (here θ = [c, m]ᵀ when the model is y = mx + c in column form):

θ=(XX)1XY\boldsymbol{\theta} = (\mathbf{X}^\top \mathbf{X})^{-1} \mathbf{X}^\top \mathbf{Y}

Trade-off: This method is computationally expensive for large datasets (matrix inverse cost), which is why iterative methods like gradient descent are often preferred at scale.


Python
First run loads the Python runtime (~10 MB) — takes ~5–10 seconds. Subsequent runs are instant.

Implementing linear regression from scratch

Before the full multi-feature version, here's the gradient-descent loop in its simplest form — walk it stage by stage. Press Run to compile the code in your browser and watch the line glide onto the data as the matching lines light up.

Hit Run — the code executes in your browser (Pyodide) while the animation glides through each stage and the matching lines light up. Traffic-lights act per panel: green/yellow expand it to a big view, red closes it.

The data
Initialize
Forward pass
Residuals
Loss (MSE)
Gradients
Update
linear_regression.py · read-only
import numpy as np # 1. the data — features X, targets yX = np.array([0.5, 1.3, 2.1, ... , 9.4])y = np.array([2.1, 2.5, 3.0, ... , 8.6])X = (X - X.mean()) / X.std()          # standardize # 2. initialize the parametersw, b = 0.0, 0.0lr, epochs = 0.15, 80n = len(X) for epoch in range(epochs):    # 3. forward pass — predict    y_pred = w * X + b    # 4. residuals    error = y_pred - y    # 5. loss — mean squared error    loss = np.mean(error ** 2)    # 6. gradients    dw = (2/n) * np.sum(error * X)    db = (2/n) * np.sum(error)    # 7. update — step down the gradient    w -= lr * dw    b -= lr * db
First Run loads the Python runtime (~10 MB, 5–10 s); then runs are instant. Code is read-only.
visualizer.live
Stage
The data
1 / 7
Loss curve · MSE per epoch
current loss
epoch
0
weight w
0.000
bias b
0.000
grad dw
grad db
lr
0.15
Data: 13 points and targets. We standardize X (mean 0, std 1) so gradient descent is well-conditioned.

The playground above fit a toy line. Now let's scale up to ten features, and rather than paste code you carry off to a notebook, run and tweak the whole thing right here.

To prove the algorithm actually works, we plant a known answer: we build a dataset of 442 samples and 10 features from a fixed set of "true" weights plus noise, then check whether gradient descent can rediscover those weights from the data alone. The train/test split, the loop, and the gradients we just derived are all hand-written NumPy.

Python
First run loads the Python runtime (~10 MB) — takes ~5–10 seconds. Subsequent runs are instant.

Run it. Within a few hundred epochs the learned weights snap onto the planted true_w (including the two zeros, the features that genuinely carry no signal), the bias lands near 1.5, and the test MSE settles near the noise floor of 0.25. That is the whole of linear regression: the same residuals, the same gradients, the same update rule you watched animate at the top of this lesson, now learning eleven parameters from ten-dimensional data with nothing but NumPy.

Now make it yours. Set lr = 1.0 and watch the loss blow up to nan (steps too big); drop it to 0.05 and watch convergence crawl. Push noise_level to 5 and see the recovered weights drift away from the truth. Break it, then fix it.

Check your understanding

1 / 10

In the equation y = wx + b, what does 'b' represent?


Practice it yourself

You watched the line fit itself. Now build it from scratch in the browser, with real Python and hidden tests, no setup. These four Easy drills retrace the lesson end to end — predict → residual → SSR → one step — so every piece of gradient descent passes through your own fingers.

Test your understanding

Prof is ready

Prof will ask you questions about Linear regression: lines, SSR, and gradient descent — not explain it. You'll be surprised what you don't know until you have to say it.

Finished this lesson?

Read through the lesson first (0/20s).