EVERYTHING AIAI engineering, made visual
0/18 complete
LESSON 02 · MACHINE LEARNING · BUILD

Find the line
that misses least.

ŷ = wx + b is the simplest model that learns from data — and its loss, MSE, is the template every training loop follows.

75 MIN · 8 CHAPTERSPREREQ · PHASE 1 + LESSON 01
FIG. 02 / SQUARED MISSES, SHRINKING
w 0.00 · b 0.00 · MSE 0.00best fit: w 1.61, b 1.98 · MSE 0.32
LESSON 02TYPE · BUILD~75 MINPREREQ · PHASE 1 · PHASE 2 LESSON 01ORIGINAL LESSON ↗
THE 60-SECOND VERSIONThen show me the line ↓
01 / ONE LINE, TWO KNOBS

Predict with ŷ = wx + b.

w is the slope — how much y changes when x grows by 1. b is the intercept — the prediction when x = 0. The model is not magic; it is a straight line with two numbers you are allowed to turn.

w = 2.5, b = 1 → x = 4 gives ŷ = 11
02 / SCORE THE MISS

MSE turns every miss into one number.

For each point, take the residual y − ŷ, square it, then average. Large misses dominate because 10² = 100, and the square is smooth, so the gradient exists everywhere. Training means finding the bottom of this bowl.

MSE = (1/n) Σ (ŷ − y)² → 0.48
03 / TWO WAYS TO THE BOTTOM

Solve it directly, or walk downhill.

The normal equation θ = (XᵀX)⁻¹Xᵀy jumps to the optimum in one exact step. Gradient descent takes many cheap steps against the gradient. Same bottom of the same bowl — different costs.

one exact leap ↔ ~500 small steps
MENTAL MODEL IN ONE SENTENCE

A model is a guess with knobs, a loss is the score of the guess, and training is turning the knobs to lower the score — linear regression is that whole loop with only two knobs.

By the end you will fit a line from data, compute MSE, RMSE, MAE and R² yourself, derive the closed-form solution, run gradient descent by hand, read a residual plot for the assumptions it breaks, and know when a straight line is the wrong tool.

THE MODEL

One line,
two knobs.

Linear regression assumes the relationship between input and output is a straight line — and asks you to choose the two numbers that make it miss the data as little as possible.

The model is ŷ = wx + b. Read it out loud: the prediction is the input, stretched by the weight w, then shifted by the bias b. The true value is y; the little hat on ŷ means “this is the model’s guess.” Learning linear regression is finding the w and b that make the guesses least wrong across every training example.

w is the slope: increase x by 1 and the prediction changes by w. If x is hours studied and y is a test score, w = 2.5 means each extra hour predicts a 2.5-point rise. b is the intercept: the prediction when x = 0. In context it can be nonsense (the price of a zero-square-foot house), but it is essential — it lets the line sit at the right height. You never drop b because it is awkward.

Worked example — plug in a number ŷ = 2.5x + 1 x = 4 → ŷ = 2.5·4 + 1 = 11 x = 0 → ŷ = 1 (the intercept) x = 5 → ŷ = 23/2 = 11.5 (+2.5 for one more unit of x) The line is the set of all predictions; the two knobs decide its tilt and its height. Nothing else is adjustable.

Drag the line, watch the error

Grab the line and pivot it around the data’s centre — or use the sliders. Every squared miss is drawn as a square, and the readout recomputes instantly.

ŷ = 1.00x + 0.00 MSE = 1.8000 RMSE = 1.3416 MAE = 1.0000 R² = -0.5000 biggest miss: point 2 (x = 2) y − ŷ = 2.00 (squared: 4.000) best fit: w = 0.60, b = 2.20 MSE = 0.4800

The squares are why MSE hates outliers: doubling a miss quadruples its square. Notice the best fit is the one where the misses stop pulling the line either way.

SymbolWhat it isIn the real world
xthe input (feature)hours studied, house size, dose…
ythe real, observed outputthe number you would like to predict
ŷthe model's predictionŷ = wx + b, read “y-hat”
wweight / slopehow much y moves per +1 of x
bbias / interceptthe prediction when x = 0

With several inputs, the same idea becomes ŷ = w₁x₁ + w₂x₂ + … + w_dx_d + b, written compactly as ŷ = wᵀx + b. Each weight says how much its own feature moves the prediction, holding the others fixed; the geometry is no longer a line but a hyperplane. Everything else in this lesson — the loss, the closed form, descent — works unchanged.

Multiple-feature numeric check — house prices (units: dollars and ft²) ŷ = 50·size + 10,000·bedrooms − 1,000·age + 50,000 A 2,000 ft², 3-bedroom, 10-year-old house: ŷ = 50·2,000 + 10,000·3 − 1,000·10 + 50,000 = 100,000 + 30,000 − 10,000 + 50,000 = 170,000 Reading the weights: one extra bedroom adds $10,000; one extra year of age subtracts $1,000; one extra ft² adds $50 — all else equal. Note how much the units matter: compare 50 per ft² with 10,000 per bedroom before deciding which feature "matters more".
Quick check

A model learns ŷ = 3x + 7. What does the number 7 say?

MEAN SQUARED ERROR

Every miss becomes
one honest number.

A model is only as good as its score. Mean squared error takes the vertical miss of every point, squares it, and averages — turning a whole scatter plot into one number you can minimize.

For one point, the residual is y − ŷ: how far the actual value sits above or below the line. Some residuals are positive, some negative, so we square them before averaging — otherwise they would cancel and a terrible line could score zero. The cost is MSE = (1/n) · Σ(ŷ − y)². Notice the order inside the square: (ŷ − y)² equals (y − ŷ)², so either convention gives the same number; only the sign of the unsquared residual depends on the order.

Why square instead of taking absolute values? Two reasons from the source. First, big misses hurt disproportionately: an error of 10 contributes 100 to the sum, not 10 — one bad prediction can dominate the score. Second, the square is smooth and differentiable everywhere, so its derivative exists at every point and optimization has a clear downhill direction. There is a price: MSE is measured in y², and it is very sensitive to outliers. That trade-off is a decision, not an accident — RMSE and MAE, later in this lesson, are the other sides of it.

xyŷ = 0.60x + 2.20
Every square has side equal to one residual, so its area is that residual squared. MSE is the average area. On these five points the best line leaves MSE = 0.48.
Numeric check on the lesson's five points (1,2), (2,4), (3,5), (4,4), (5,5) Candidate line ŷ = 1x + 1: predictions: 2, 3, 4, 5, 6 residuals y − ŷ: 0, 1, 1, −1, −1 squares: 0, 1, 1, 1, 1 → MSE = 4/5 = 0.8 All-zero line ŷ = 0: squares: 4, 16, 25, 16, 25 → MSE = 86/5 = 17.2 Least-squares line ŷ = 0.6x + 2.2: predictions: 2.8, 3.4, 4.0, 4.6, 5.2 squares: 0.64, 0.36, 1.0, 0.36, 0.04 → MSE = 2.4/5 = 0.48 Same data, three lines, one score each. 0.48 is the smallest possible.
Why the bowl is convex (and why that is good news)

MSE as a function of w and b is a quadratic: expand the square and every term is either constant or a square of a linear expression. A sum of squares is never negative and curves upward, so the surface is a bowl — a convex paraboloid. A bowl has a single bottom, which means gradient descent cannot get trapped in a false valley: any downhill path ends at the global minimum. Most models you meet later are not this friendly, which is why linear regression is the right place to learn the training loop.

MSE(w, b) = (1/n) Σ (w·xᵢ + b − yᵢ)² = A w² + 2C wb + b² − 2D w − 2E b + F A = mean(x²) = 11 C = mean(x) = 3 D = mean(xy) = 13.2 E = mean(y) = 4 F = mean(y²) = 17.2 MSE(w, b) = 11w² + 6wb + b² − 26.4w − 8b + 17.2 At the best fit (0.6, 2.2): 3.96 + 7.92 + 4.84 − 15.84 − 17.6 + 17.2 = 0.48 ✓

The loss bowl, in (w, b) space

Every point on this plane is a candidate line; the rings are equal-MSE contours and the dot marks the minimum. Click anywhere to drop a start, then watch gradient descent slide to the bottom.

-0.70 , 5.80 MSE 3.9500 minimum: w = 0.60, b = 2.20 min MSE = 0.4800 start MSE = 3.9500 the path always turns perpendicular to the contour it stands on; lr sets step length.

Try lr = 0.005 (slow crawl), 0.03 (steady), then 0.08 (fast zigzag). The bowl shape is the same at every scale — only the step size changes how you travel it.

Quick check

Model A misses by exactly 1 on all 10 points. Model B misses by 2 on 5 points and by 0 on the other 5. Both have MAE = 1. Which has the larger MSE?

SOLVE IT DIRECTLY

Set the slope to zero.
Land on the answer.

Because MSE is a bowl, its bottom is exactly where both partial derivatives vanish. Solving those two equations gives a formula for w and b — no iteration, no learning rate, one leap.

A minimum of a smooth bowl has a flat tangent: the derivative is zero in every direction. So take the derivative of MSE with respect to b, set it to zero, and do the same for w. Two equations, two unknowns. The algebra (open the derivation below) collapses into a formula you can compute with a calculator.

Derivation: the one-feature closed form, with two numeric checks
  1. Start from MSE = (1/n) Σ (w·xᵢ + b − yᵢ)².
  2. Differentiate with respect to b: ∂MSE/∂b = (2/n) Σ (w·xᵢ + b − yᵢ). Set it to zero: n·b + w·Σxᵢ − Σyᵢ = 0, so b = ȳ − w·x̄. The best line always passes through the data’s centre of mass (x̄, ȳ).
  3. Differentiate with respect to w: ∂MSE/∂w = (2/n) Σ (w·xᵢ + b − yᵢ)·xᵢ. Substitute b = ȳ − w·x̄ and simplify: w = Σ(xᵢ − x̄)(yᵢ − ȳ) / Σ(xᵢ − x̄)². In words: covariance of x and y over variance of x — how much they move together, scaled by how much x moves at all.
Worked example A — the five lesson points (1,2), (2,4), (3,5), (4,4), (5,5) x̄ = 3, ȳ = 4 numerator Σ(xᵢ−x̄)(yᵢ−ȳ) = (−2)(−2) + (−1)(0) + 0 + (1)(0) + (2)(1) = 4 + 0 + 0 + 0 + 2 = 6 denominator Σ(xᵢ−x̄)² = 4 + 1 + 0 + 1 + 4 = 10 w = 6/10 = 0.6 b = ȳ − w·x̄ = 4 − 0.6·3 = 2.2 → ŷ = 0.6x + 2.2 predictions 2.8, 3.4, 4.0, 4.6, 5.2 → MSE = 2.4/5 = 0.48 Second worked example B — four points (0,1), (1,3), (2,2), (3,5) x̄ = 1.5, ȳ = 2.75 numerator = (−1.5)(−1.75) + (−0.5)(0.25) + (0.5)(−0.75) + (1.5)(2.25) = 2.625 − 0.125 − 0.375 + 3.375 = 5.5 denominator = 2.25 + 0.25 + 0.25 + 2.25 = 5 w = 5.5/5 = 1.1, b = 2.75 − 1.1·1.5 = 1.1 → ŷ = 1.1x + 1.1 squares: 0.01 + 0.64 + 1.69 + 0.36 = 2.70 → MSE = 0.675 SS_tot = 8.75, so R² = 1 − 2.7/8.75 ≈ 0.691

Notice the two examples give the same recipe with different numbers: compute two means, two sums, one division. That is the entire training loop for a one-feature model, done by hand.

Derivation: the matrix form, θ = (XᵀX)⁻¹Xᵀy, with a numeric check
  1. Stack the data: each row of the design matrix X is [xᵢ, 1] — one column of inputs plus a column of ones that multiplies the bias. The parameter vector is θ = [w, b] and all predictions at once are .
  2. The loss is (1/n)‖Xθ − y‖². Its gradient with respect to θ is (2/n)·Xᵀ(Xθ − y). Set it to zero: XᵀXθ = Xᵀy — these are the normal equations.
  3. Multiply by the inverse: θ = (XᵀX)⁻¹Xᵀy. XᵀX is (d+1)×(d+1) and Xᵀy is (d+1)×1, so the whole solve is a small linear system, no matter how many rows n you have. The ones column is what makes b fall out of the same formula.
Numeric check with the five lesson points ⎡ 1 1 ⎤ ⎡ 2 ⎤ X = ⎢ 2 1 ⎥ y = ⎢ 4 ⎥ … ⎣ 5 1 ⎦ ⎣ 5 ⎦ XᵀX = ⎡ Σx² Σx ⎤ = ⎡ 55 15 ⎤ Xᵀy = ⎡ Σxy ⎤ = ⎡ 66 ⎤ ⎣ Σx n ⎦ ⎣ 15 5 ⎦ ⎣ Σy ⎦ ⎣ 20 ⎦ det(XᵀX) = 55·5 − 15·15 = 275 − 225 = 50 (XᵀX)⁻¹ = (1/50) ⎡ 5 −15 ⎤ ⎣ −15 55 ⎦ θ = (1/50) ⎡ 5·66 − 15·20 ⎤ = (1/50) ⎡ 330 − 300 ⎤ = ⎡ 0.6 ⎤ ⎣ −15·66 + 55·20 ⎦ ⎣ −990 + 1100 ⎦ ⎣ 2.2 ⎦ w = 0.6, b = 2.2 ✓ same answer as the one-feature formula.

Closed form vs gradient descent: the race

The dashed line is the exact least-squares answer, computed in one step. The moving line is descent walking downhill from a bad start. Watch the MSE curve fall as it chases the optimum.

closed form (1 leap): w = 0.60, b = 2.20 MSE = 0.4800 cost: one (XᵀX)⁻¹ solve gradient descent (700 steps max): w -0.60 · b 5.50 · MSE 3.4500 with lr = 0.050, descent is within 0.0001 of the minimum after 292 steps.

Push lr toward 0.09 — past this bowl’s stability edge — and watch the loss climb instead of fall: the steps overshoot and the numbers explode. Shrink it to 0.005 and the curve crawls. The closed form never has this dial — but it pays O(d³) in features instead.

Normal equationGradient descent
Hyperparametersnonelearning rate, step count
Costone O(n·d² + d³) solveO(n·d) per step, repeated
Answerexact (up to floating point)approximate, approaches the exact one
Large feature count dfails — d³ inversion is brutalfine
Huge row count nbuilds all of XᵀX anywaymini-batches keep memory small
Collinear featuresXᵀX is singular; no inversestill limps; regularization helps
WALK DOWNHILL

Follow the slope,
one step at a time.

When a formula is out of reach, calculus still tells you which way is downhill. Gradient descent turns that direction into a simple loop — and the loop scales to models with billions of knobs.

The gradient of MSE answers two questions about the point where you currently stand: which way is uphill, and how steep is it. Training does the obvious thing: step the opposite way, by an amount proportional to the slope. For ŷ = wx + b with MSE, the two partial derivatives are almost embarrassingly simple — each one is an average of the errors, weighted by x for the slope:

∂MSE/∂w = (2/n) · Σ (ŷᵢ − yᵢ) · xᵢ ∂MSE/∂b = (2/n) · Σ (ŷᵢ − yᵢ) update: w ← w − lr · ∂MSE/∂w b ← b − lr · ∂MSE/∂b In words: if the predictions are too high, both gradients are positive, so the update subtracts and pulls them down. If too low, it pushes up. The learning rate lr converts a slope into a step length.
Derivation: where those gradients come from (chain rule), with a first step computed by hand
  1. Write the loss as a sum of squared errors: MSE = (1/n) Σ eᵢ², where eᵢ = w·xᵢ + b − yᵢ.
  2. For one term, the chain rule gives ∂(eᵢ²)/∂w = 2·eᵢ·∂eᵢ/∂w and ∂eᵢ/∂w = xᵢ, so the term contributes 2·eᵢ·xᵢ. Average the terms: ∂MSE/∂w = (2/n) Σ eᵢ·xᵢ.
  3. Same route with respect to b: ∂eᵢ/∂b = 1, so ∂MSE/∂b = (2/n) Σ eᵢ. The 2 is a constant — it changes the gradient’s length, not its direction, so some implementations absorb it into the learning rate.
One step by hand — five points, start at w = 0, b = 0, lr = 0.01 predictions: 0, 0, 0, 0, 0 errors ŷ − y: −2, −4, −5, −4, −5 Σ eᵢ·xᵢ = (−2)(1) + (−4)(2) + (−5)(3) + (−4)(4) + (−5)(5) = −2 − 8 − 15 − 16 − 25 = −66 ∂MSE/∂w = (2/5)(−66) = −26.4 ∂MSE/∂b = (2/5)(−20) = −8.0 w ← 0 − 0.01·(−26.4) = 0.264 b ← 0 − 0.01·(−8.0) = 0.08 MSE falls from 17.2 to 10.49 in one step. The slope is steep at the start, so a small lr already moves a long way. A longer run at lr = 0.05: step 100: w = 0.695, b = 1.857, MSE = 0.501 step 500: w = 0.600, b = 2.200, MSE = 0.480 ← the exact optimum Batch descent needed ~500 steps for what the closed form did in one solve. At two knobs that looks silly; at a million knobs, one step of descent is the only thing that fits in memory.

The loss bowl, in (w, b) space

Every point on this plane is a candidate line; the rings are equal-MSE contours and the dot marks the minimum. Click anywhere to drop a start, then watch gradient descent slide to the bottom.

-0.70 , 5.80 MSE 3.9500 minimum: w = 0.60, b = 2.20 min MSE = 0.4800 start MSE = 3.9500 the path always turns perpendicular to the contour it stands on; lr sets step length.

Try lr = 0.005 (slow crawl), 0.03 (steady), then 0.08 (fast zigzag). The bowl shape is the same at every scale — only the step size changes how you travel it.

VariantGradient fromUpdatesStrengthWeakness
Batchall n points1 update / epochsmoothest, most stableslow; memory grows with n
Stochastic (SGD)1 pointn updates / epochfast early progress, escapes shallow trapsnoisy; can bounce around the minimum
Mini-batch32–256 pointsn/batch updates / epochvectorizes well on GPUs; the defaultone more dial to tune
READ THE RESIDUALS

The score says how much.
Residuals say what kind.

Two models can share an R² and fail completely differently. Plotting what the model missed — against x — is how you hear the data complain before you trust the number.

The residual of a point is y − ŷ: the vertical distance between the data and the line. In a strict statistical sense the “error” is the unobservable noise, and the residual is its observed stand-in — for this lesson the distinction matters less than the habit: plot the residuals against x and look at the shape. A model that has captured everything it can leaves residuals with no remaining pattern: roughly constant spread, centered on zero, no bends, no loners.

Any visible shape is a message. A curve means the trend bends and a straight line cannot follow. A fan means the model’s certainty should vary with x. A single distant residual means one row has more influence than the rest. None of these show up in a single summary number — which is exactly why the plot is not optional.

Residual plots: the model’s confession

The left panel shows the data and the least-squares line; the right panel plots every residual against x. Switch datasets and read the shape: random noise, a curve, a funnel, or one point owning the fit.

A straight trend with small, patternless wobble. Residuals scatter around zero with constant width — this is what you want to see. w = 2.01 b = 1.02 MSE = 0.024 MAE = 0.137 R² = 0.999 largest |residual| = 0.26

A good fit does not mean a good model: the clean line and the fan both score high R², but only one has residuals you can trust to be independent. Look at the shape, not just the score.

What the residual plot showsWhat it meansWhat to do
Random cloud around zerothe model captured the trend; nothing structured is leftnothing — keep it
Curve (U or arch)the true relationship bends; a line is the wrong shapeadd x², or transform x/y
Fan (spread grows with x)the variance is not constant (heteroscedasticity)weighted least squares, log y, or report prediction intervals
One isolated large residualan outlier or a high-leverage point owns part of the fitcheck the row; use a robust loss if the outlier is real
Quick check

A residual plot shows a clear U shape: residuals are negative at both ends of the x-range and positive in the middle. What is the diagnosis?

JUDGE THE FIT

Four scores,
four different questions.

MSE is what training minimizes. RMSE, MAE and R² are what people report — and they disagree in exactly the situations that matter. Knowing which question each one asks is the skill.

All four metrics start from the same residuals, but weigh them differently. MSE squares and averages: the training objective. RMSE = √MSE undoes the squaring, so it is back in the units of y — “the typical miss, with big misses getting extra weight.” MAE averages absolute misses: the plain-language “average error,” indifferent to whether a miss is small or huge. divides your squared error by the squared error of predicting ȳ, and turns the comparison into a unit-free fraction: how much of the target’s variation your model explains.

RMSE = √( (1/n) Σ (ŷᵢ − yᵢ)² ) units: same as y MAE = (1/n) Σ |ŷᵢ − yᵢ| units: same as y R² = 1 − SS_res / SS_tot unit-free, ≤ 1 Numeric check on the five lesson points with the best fit ŷ = 0.6x + 2.2 squares: 0.64, 0.36, 1.00, 0.36, 0.04 SS_res = 2.40 MSE = 2.40 / 5 = 0.48 RMSE = √0.48 ≈ 0.693 MAE = (0.8 + 0.6 + 1.0 + 0.6 + 0.2) / 5 = 3.2 / 5 = 0.64 ȳ = 4 → SS_tot = (2−4)² + 0 + (5−4)² + 0 + (5−4)² = 6 R² = 1 − 2.40/6 = 0.60 (the line explains 60% of the variation) Baseline — always predict ȳ = 4 (ŷ = 0x + 4): squares: 4, 0, 1, 0, 1 → MSE = 6/5 = 1.2, R² = 1 − 6/6 = 0 So this line cuts the mean-only error by 60%, and every metric agrees on that arithmetic. They disagree only on how much the biggest miss should count.
Where R² comes from: the variance budget

For a model with an intercept, the total squared deviation of y from its mean splits exactly into two piles: what the model explains and what it misses.

SS_tot = SS_res + SS_reg Σ(yᵢ − ȳ)² = Σ(yᵢ − ŷᵢ)² + Σ(ŷᵢ − ȳ)² On the five points: 6 = 2.40 + 3.60 ✓ R² = 1 − SS_res/SS_tot = SS_reg/SS_tot = 3.60/6 = 0.60 R² = 1.0 perfect R² = 0.0 ties the mean R² = 0.5 half the variation explained R² < 0.0 worse than the mean — possible on held-out data

The decomposition needs the intercept. If you force the line through the origin, the budget stops adding up and R² can look terrible for a model that predicts fine. Another reason not to drop b casually.

The metric console

Move the line and watch four scores disagree. Then switch on the outlier: one distant point can own MSE while MAE barely flinches. Every value is computed from the listed points.

MetricValueUnitsWhat it punishes
MSE0.800Squares every miss — big misses dominate, outliers explode it.
RMSE0.894y√MSE, back in the target's units — the typical-sized miss.
MAE0.800yEvery miss counts linearly; robust to a single outlier.
0.3331 − SS_res/SS_tot: the share of variance your line explains.
baseline MSE1.200Predicting ȳ = 4.00 every time — the R² = 0 reference.

MSE is the squared scale, RMSE and MAE are in y-units, R² is unit-free. Never compare MSE across datasets with different y scales — compare R² or RMSE.

ŷ = 1.00x + 1.00 points: 5 Σ(ŷ − y)² = 4.000 MSE = that sum ÷ 5 biggest miss: x = 2 y − ŷ = 1.00 squared = 1.000 best fit here: w = 0.60, b = 2.20 R² at best fit = 0.600
WHEN IT FAILS

A straight line
has conditions.

Linear regression predicts whenever the trend is roughly linear — but its confidence intervals and coefficients only mean what the textbook says when a short list of assumptions holds.

As a prediction machine, linear regression is happy with any roughly linear trend. As a statistical model — one whose coefficient estimates and confidence intervals you quote — it assumes more:

  • Linearity: the average of y is a straight-line function of the features (or can be made so with transforms like log or x²).
  • Independence: one row tells you nothing about the next. Time series, repeated measurements, and grouped data break this visibly.
  • Constant variance: the size of the misses does not change with x. The fan dataset in the last lab breaks it.
  • No extreme influence: no single row (or tiny set of them) drags the line. An outlier that is far in x is especially powerful.
  • Normal residuals — only needed for the classical confidence intervals, not for the fit itself. With enough data, the central limit theorem softens this one.

When the relationship bends, the fix is not a fancier optimizer — it is a better set of features. Polynomial regression is the smallest such fix: feed the model x, x², x³, … and it is still linear regression, because the model is linear in the weights. The curve below is the whole trade-off in one dial.

Polynomial regression is still linear regression ŷ = w₁x + w₂x² + w₃x³ + b numeric check with ŷ = 0.4x³ − 1.0x² + 0.5x + 2: x = 1 → 0.4 − 1.0 + 0.5 + 2 = 1.9 x = 2 → 3.2 − 4.0 + 1.0 + 2 = 2.2 The expression is nonlinear in x but linear in (w₁, w₂, w₃, b), so the same normal equation, the same MSE bowl and the same descent apply — only the feature columns got richer.

Bend the line: the degree dial

A polynomial is still linear regression — you just feed it x, x², x³ … as features. Turn the degree up: training fit always improves, but held-out points eventually get worse.

degree 1 → 2 coefficients train R² = 0.6604 RMSE = 2.324 test R² = 0.6618 RMSE = 1.868 gap = -0.0014 Underfitting: a line cannot bend.

Degree 3 matches the true curve; degree 10 threads every training point and dives between them. More capacity is not more skill — it is more rope.

With multiple features, one practical failure is scale: if size runs 0–3,000 and bedrooms run 1–5, the MSE bowl is an elongated canyon and descent zigzags. Standardize features first (subtract mean, divide by standard deviation). Then the learned weights are comparable across features — a big |w| means that feature moves the prediction a lot per standard deviation, not just per raw unit.

The two listings below are the same model twice: once written out as the training loop, and once as the production call. Run both on the same data and the coefficients agree to several decimal places — the library is not doing different mathematics, it is doing the same mathematics with better numerical care and more edge-case handling. Use scikit-learn in production; write the loop once yourself to understand what it is doing.

From scratch — gradient descentpython
import random

TRUE_W, TRUE_B = 3.0, 7.0
random.seed(42)
X = [random.uniform(0, 10) for _ in range(100)]
y = [TRUE_W * x + TRUE_B + random.gauss(0, 2.0) for x in X]


class LinearRegression:
    def __init__(self, learning_rate=0.01):
        self.w, self.b, self.lr = 0.0, 0.0, learning_rate

    def predict(self, X):
        return [self.w * x + self.b for x in X]

    def compute_cost(self, X, y):
        preds = self.predict(X)
        return sum((p - t) ** 2 for p, t in zip(preds, y)) / len(y)

    def compute_gradients(self, X, y):
        preds = self.predict(X)
        n = len(y)
        dw = (2 / n) * sum((p - t) * x for p, t, x in zip(preds, y, X))
        db = (2 / n) * sum(p - t for p, t in zip(preds, y))
        return dw, db

    def fit(self, X, y, epochs=1000):
        for _ in range(epochs):
            dw, db = self.compute_gradients(X, y)
            self.w -= self.lr * dw
            self.b -= self.lr * db
        return self

    def r_squared(self, X, y):
        preds = self.predict(X)
        y_mean = sum(y) / len(y)
        ss_res = sum((t - p) ** 2 for t, p in zip(y, preds))
        ss_tot = sum((t - y_mean) ** 2 for t in y)
        return 1 - ss_res / ss_tot


model = LinearRegression(learning_rate=0.005).fit(X, y)
print(f"learned y = {model.w:.4f}x + {model.b:.4f}")   # ≈ 3x + 7
print(f"R^2 = {model.r_squared(X, y):.4f}")
Roughly 30 lines: predict, cost, gradients, update. Every training loop you meet later is a scaled-up version of this.
The same thing — scikit-learnpython
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

rng = np.random.default_rng(42)
X = rng.uniform(0, 10, (100, 1))
y = 3.0 * X.squeeze() + 7.0 + rng.normal(0, 2.0, 100)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

lr = LinearRegression().fit(X_train, y_train)
print(f"w = {lr.coef_[0]:.4f}, b = {lr.intercept_:.4f}")   # close to 3, 7
print(f"test R^2 = {r2_score(y_test, lr.predict(X_test)):.4f}")
print(f"test MSE = {mean_squared_error(y_test, lr.predict(X_test)):.4f}")

# scaling matters for descent (sklearn's closed form already handles it)
scaled = StandardScaler().fit_transform(X_train)
ridge = Ridge(alpha=1.0).fit(scaled, y_train)
print(f"ridge coef: {ridge.coef_[0]:.4f}  (shrunk toward zero)")
The library adds stability (QR/SVD instead of an explicit inverse), scaling tools, and the metrics. The mathematics is identical.
Quick check

The residuals get wider as x grows: a funnel opening to the right. Which assumption is being violated?

CHECK YOURSELF

Six questions.
Then the terms worth keeping.

Answer before you look. The learning-rate, R² and overfitting questions are exactly the ones that decide whether a real training run succeeds.

0 / 6 answered · 0 correct

01What does the learning rate control in gradient descent?

02What does R² = 0 mean for a regression model?

03Why is feature scaling important for gradient descent in multiple linear regression?

04The normal equation gives optimal weights directly. Why would you prefer gradient descent instead?

05A degree-10 polynomial regression fits training data perfectly (R² = 1.0) but scores R² = 0.3 on test data. What should you do?

06Why does MSE square the errors instead of adding their absolute values?

Key terms, demystified

Click a card to swap the lazy description for what it actually means.

Exercises from the lesson

Three problems with fully worked answers — the cubic one has every number you need to reproduce it.

  1. Implement batch gradient descent, stochastic gradient descent (SGD) and mini-batch gradient descent on the five-point dataset x = [1, 2, 3, 4, 5], y = [2, 4, 5, 4, 5]. Start from w = 0, b = 0 with lr = 0.01 and compare the first update each method takes. Which converges fastest? Which has the smoothest cost curve?
    Show one worked answer

    Start MSE(0, 0) = mean(y²) = 17.2, and the true optimum is w = 0.6, b = 2.2 with MSE = 0.48. Batch takes the gradient over all five points: dw = (2/5)·Σ(ŷ−y)x = (2/5)(−66) = −26.4 and db = (2/5)(−20) = −8, so the first step lands at (0.264, 0.08) and MSE drops to 10.49. SGD uses one point at a time: if the first point happens to be (5, 5), the gradient is (2·(−5)·5, 2·(−5)) = (−50, −10) and the step jumps to (0.5, 0.1) — a big, noisy correction; starting with (1, 2) instead gives only (0.04, 0.04). Mini-batch of the first two points: gradients (−10, −6) → (0.1, 0.06). So per-update, SGD moves fastest (it sees 5 updates per epoch) but its cost curve zigzags; batch is the smoothest but pays for a full pass per update; mini-batch (32–256 examples in practice) is the usual compromise because it vectorizes well on GPUs. Run each to convergence and compare cost curves, not single steps: with lr = 0.05, batch descent reaches (0.600, 2.200) in a few hundred steps.

  2. Generate data from a cubic curve (y = 0.4x³ − x² + 0.5x + 2 plus noise). Fit polynomials of degree 1, 3 and 10 on the training points below. Compare training and test R². At what degree does overfitting become obvious?
    Show one worked answer

    Train on these eleven noisy points: (−2.40, −9.94), (−1.92, −6.18), (−1.44, −1.64), (−0.96, 1.04), (−0.48, 1.04), (0.00, 2.60), (0.48, 1.25), (0.96, 2.21), (1.44, 2.54), (1.92, 1.60), (2.40, 3.37). Hold out seven test points from the same curve, e.g. (−2.1, −6.81), (−1.3, −1.62), (−0.5, 1.75), (0.3, 1.72), (1.1, 2.27), (1.9, 1.78), (2.35, 3.09). Scale x to [0, 1] before fitting (numerical stability), then solve the normal equations for each degree. Results: degree 1 → train R² = 0.66, test R² = 0.66 (underfit: a line cannot bend); degree 3 → train R² = 0.98, test R² = 0.99 (the true shape, noise aside); degree 10 → train R² = 0.9999, test R² ≈ −0.6 (negative: worse than predicting the mean). Overfitting is already obvious at degree 10: training error keeps falling while test performance collapses to worse-than-mean. The pattern, not the exact decimals, is the lesson — with 11 points, a degree-10 curve has enough knobs to wiggle through every training point, and the wiggle is noise.

  3. Implement Lasso regression (L1 penalty αΣ|wᵢ|) and compare it with Ridge (L2 penalty αΣwᵢ²). Why does L1 drive some weights exactly to zero while L2 only shrinks them?
    Show one worked answer

    Take one weight with no data pressure (∂MSE/∂w = 0), start at w = 0.3, lr = 0.1 and α = 1. Ridge subtracts 2αw each step: w ← w(1 − 0.2), so 0.3 → 0.24 → 0.192 → 0.154 → … — a geometric decay that approaches zero but never reaches it. Lasso applies the soft-threshold operator: shrink by lr·α = 0.1 and clamp at zero, so 0.3 → 0.2 → 0.1 → 0 exactly, and it stays there. With real data gradients the same thing happens: whenever a feature's gradient is smaller than α, L1's threshold snaps its weight to exactly zero, producing a sparse model that ignores features; L2's derivative 2αw vanishes as w approaches zero, so weak weights become tiny but stay alive. That is why L1 selects features and L2 just stabilizes them. (Code detail: use soft-thresholding or coordinate descent for L1; a naive subgradient step oscillates around zero instead of landing on it.)

Terms this lesson borrows from later lessons (or outside)

You do not need to master these here. Each one gets a proper treatment in its own lesson; the one-line meaning is enough to keep reading. Orange dotted underlines in the prose point back to this list.

  • dot productMultiply matching coordinates and sum. Building XᵀX and Xᵀy is nothing but dot products between columns of the design matrix. (Phase 1, Lesson 02)
  • transposeSwap rows and columns. Xᵀ is the design matrix flipped so its columns become the features' rows. (Phase 1, Lesson 02)
  • matrix inverseThe matrix that undoes another. The normal equation divides by XᵀX using its inverse — which is why collinear features break it. (Phase 1, Lesson 02)
  • derivativeThe instantaneous slope of a function: how much the loss changes per tiny change of one parameter. (Phase 1, Lesson 04)
  • gradientThe list of partial derivatives of the loss with respect to every parameter. Training steps in the opposite direction. (Phase 1, Lesson 04)
  • convex functionA function shaped like a bowl with a single bottom: any downhill path reaches the global minimum. MSE is convex in w and b. (Phase 1, Lesson 18)
  • feature scalingStandardize each feature (subtract its mean, divide by its standard deviation) so no column dwarfs the others. (Phase 1, Lesson 15)
  • train/test splitHold out part of the data before fitting, so you measure generalization instead of memorization. (Phase 2, Lesson 09)
  • regularizationAdd a penalty on weight size to the loss (Ridge adds λΣwᵢ²). It shrinks weights and fights overfitting. (Phase 2, later lesson)
KEEP GOING

A picture is a start.
Practice is the rest.

This lesson is a port of an open course. Everything here traces back to it — and the next step is running the code yourself.

Lesson text adapted from AI Engineering from Scratch (Phase 02, Lesson 02) and the Math Foundations Notebook reference build. The loss-surface explorer, the closed-form-vs-descent race, the residual diagnostics lab, the metric console, the polynomial playground, every numeric check, and the worked exercise answers are original to this page. Every lab runs in your browser.