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 ŷ = 1102 / 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.4803 / 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.
01
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.
Symbol
What it is
In the real world
x
the input (feature)
hours studied, house size, dose…
y
the real, observed output
the number you would like to predict
ŷ
the model's prediction
ŷ = wx + b, read “y-hat”
w
weight / slope
how much y moves per +1 of x
b
bias / intercept
the 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?
02
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.
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?
03
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
Start from MSE = (1/n) Σ (w·xᵢ + b − yᵢ)².
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̄, ȳ).
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.
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
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 Xθ.
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.
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.
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 equation
Gradient descent
Hyperparameters
none
learning rate, step count
Cost
one O(n·d² + d³) solve
O(n·d) per step, repeated
Answer
exact (up to floating point)
approximate, approaches the exact one
Large feature count d
fails — d³ inversion is brutal
fine
Huge row count n
builds all of XᵀX anyway
mini-batches keep memory small
Collinear features
XᵀX is singular; no inverse
still limps; regularization helps
04
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
Write the loss as a sum of squared errors: MSE = (1/n) Σ eᵢ², where eᵢ = w·xᵢ + b − yᵢ.
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ᵢ.
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.
Variant
Gradient from
Updates
Strength
Weakness
Batch
all n points
1 update / epoch
smoothest, most stable
slow; memory grows with n
Stochastic (SGD)
1 point
n updates / epoch
fast early progress, escapes shallow traps
noisy; can bounce around the minimum
Mini-batch
32–256 points
n/batch updates / epoch
vectorizes well on GPUs; the default
one more dial to tune
05
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 shows
What it means
What to do
Random cloud around zero
the model captured the trend; nothing structured is left
nothing — keep it
Curve (U or arch)
the true relationship bends; a line is the wrong shape
add 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 residual
an outlier or a high-leverage point owns part of the fit
check 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?
06
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. R² 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.
Metric
Value
Units
What it punishes
MSE
0.800
y²
Squares every miss — big misses dominate, outliers explode it.
RMSE
0.894
y
√MSE, back in the target's units — the typical-sized miss.
MAE
0.800
y
Every miss counts linearly; robust to a single outlier.
R²
0.333
—
1 − SS_res/SS_tot: the share of variance your line explains.
baseline MSE
1.200
y²
Predicting ȳ = 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
07
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) ** 2for 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) ** 2for t, p in zip(y, preds))
ss_tot = sum((t - y_mean) ** 2for t in y)
return1 - 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?
08
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.
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.
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.
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 product — Multiply 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)
transpose — Swap rows and columns. Xᵀ is the design matrix flipped so its columns become the features' rows. (Phase 1, Lesson 02)
matrix inverse — The 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)
derivative — The instantaneous slope of a function: how much the loss changes per tiny change of one parameter. (Phase 1, Lesson 04)
gradient — The list of partial derivatives of the loss with respect to every parameter. Training steps in the opposite direction. (Phase 1, Lesson 04)
convex function — A 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 scaling — Standardize each feature (subtract its mean, divide by its standard deviation) so no column dwarfs the others. (Phase 1, Lesson 15)
train/test split — Hold out part of the data before fitting, so you measure generalization instead of memorization. (Phase 2, Lesson 09)
regularization — Add 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.