Neural Mastery

Linear Regression, In Full Depth

Supervised Learning introduced linear regression in a couple of sentences. This page derives it properly — the cost function, the gradient step by step, the closed-form solution, and where gradient descent actually comes from — because "minimize squared error" means nothing until you've done the derivative by hand at least once.

Say you're guessing how well a friend will do on a test based on how many hours they studied. Plot "hours vs. score" for everyone you know and you'll see a cloud of dots, not a perfect line — real life is noisy, so no single line hits every point. Linear regression is just a precise way of picking one best line through that cloud: out of every line you could draw, it picks the one where the total "distance from the line, squared and added up across every point" is as small as possible. The squaring matters — it means one wildly-wrong guess hurts far more than several slightly-wrong ones.

Everything below is that same idea, made exact: the actual formula for "how wrong," and the actual math for finding the line that minimizes it.

Hypothesis
y^=wx+b\hat{y} = w \cdot x + b
Cost (MSE)
J(w,b)=1n(y^(i)y(i))2J(w,b) = \frac{1}{n}\sum(\hat y^{(i)} - y^{(i)})^2
Gradient
Jw=2n(y^(i)y(i))x(i)\frac{\partial J}{\partial w} = \frac{2}{n}\sum(\hat y^{(i)}-y^{(i)})x^{(i)}
Update Rule
wwαJww \leftarrow w - \alpha \frac{\partial J}{\partial w}
The whole page in four lines: the hypothesis produces a prediction, the cost function measures how wrong it is, the gradient says which direction reduces that cost, and the update rule is that direction applied to the weights -- every section below derives one of these four lines in full.

What Is Linear Regression?

In plain terms: linear regression finds the straight line that best fits a set of data points, so you can predict a continuous output from one or more inputs.

Take a familiar example: predicting a student's exam score from how many hours they studied. Plot each student as a point — hours studied on the x-axis, score on the y-axis — and you'll typically see a trend: more hours, generally, a higher score. Linear regression draws the single straight line through that cloud of points that fits it best.

1000hours studied9hexam score
Each point is one student; the line minimizes total squared vertical distance to every point.
Real least-squares fit on this data: ŷ = 6.4·x + 42.5. Try dragging your own fit in the Studio above and see how close you get to this real optimum.

You already know the equation for a line from algebra: y=mx+by = mx + b, where mm is the slope and bb is where the line crosses the y-axis. Linear regression uses the exact same equation, just with different letters and one important addition — a hat:

y^=wx+b\hat{y} = w \cdot x + b

The hat on y^\hat{y} (read "y-hat") matters: it means predicted value, not the actual observed value yy. Real data is noisy — two students who both studied 5 hours won't score identically — so the line can't pass through every point exactly. y^\hat{y} is the line's best estimate for a given xx; the gap between y^\hat{y} and the real yy for any point is called the residual, and it's exactly what the rest of this page is about minimizing.

Simple vs. multiple linear regression: the one-input example above is simple linear regression. Most real problems use several inputs at once — predicting a house's price from its size, location, and age, for instance — which is multiple linear regression: y^=w1x1+w2x2++wdxd+b\hat{y} = w_1 x_1 + w_2 x_2 + \cdots + w_d x_d + b. Section 1 below writes this generally using vectors so both cases are the same formula.

A warning worth internalizing early: a strong fit tells you xx and yy move together — it does not tell you xx causes yy. Students who study more may also be more motivated overall, and that motivation — not the studying itself — could be doing some of the work. Linear regression describes correlation; establishing causation requires a controlled experiment, not just a good fit.

Predict First
In the Gradient Descent Lab below, if you push the learning rate from the default up to its max (0.03), what happens?

Before the math below — try fitting the line yourself. Drag the slope and intercept and watch MSE respond live:

Interactive
Linear Regression Studio
Mode
Outlier
MSE 195.63ŷ = 3.0x + 45.0
A real dataset (noisy study-hours-vs-exam-score), real MSE, and real batch gradient descent -- running live as you drag, play, and step.

Notice how you had to guess-and-check to bring MSE down — nudge ww, see if it helped, nudge bb, see if it helped. That's exactly the problem gradient descent (Section 6) solves systematically: instead of guessing, compute the direction that reduces MSE fastest, from the data itself.

Everything above is the intuition. Everything below is the actual math that makes it work — precisely, not just approximately.

1. The Hypothesis

Linear regression assumes the target is a linear function of the input, plus noise:

y^=wx+b\hat{y} = w \cdot x + b

For multiple features, xx becomes a vector xRd\mathbf{x} \in \mathbb{R}^d and ww a weight vector wRd\mathbf{w} \in \mathbb{R}^d (see Linear Algebra):

y^=wTx+b\hat{y} = \mathbf{w}^T \mathbf{x} + b

ww (the weights) and bb (the bias/intercept) are the parameters we need to learn — the entire rest of this page is about finding the w,bw, b that make y^\hat{y} match the real data yy as closely as possible.

Why is it called "linear"? Not because the input has to appear to the first power — because the model is linear in its parameters. y^=w1x2+w2x+b\hat{y} = w_1 x^2 + w_2 x + b is still linear regression (fit x2x^2 as just another input column, and it's identical math to fitting any other feature) — only y^=w1xw2\hat{y} = w_1 x^{w_2}, where a parameter multiplies or exponentiates another parameter, would break linearity. This distinction matters in practice: polynomial regression is a linear model in disguise, solvable with everything on this page unchanged.

2. The Cost Function (Mean Squared Error)

We need a number that measures "how wrong" a given (w,b)(w, b) is across the whole dataset of nn examples:

J(w,b)=1ni=1n(y^(i)y(i))2=1ni=1n(wx(i)+by(i))2J(w, b) = \frac{1}{n} \sum_{i=1}^{n} \left( \hat{y}^{(i)} - y^{(i)} \right)^2 = \frac{1}{n} \sum_{i=1}^{n} \left( w x^{(i)} + b - y^{(i)} \right)^2

Why squared error, specifically? Three reasons that all matter:

  • It's differentiable everywhere (unlike absolute error, which has a sharp corner at zero — its derivative is undefined right where the error is smallest, which complicates gradient-based optimization).
  • It penalizes large errors disproportionately more than small ones, which is often the right behavior — being off by 10 should hurt more than 10x being off by 1.
  • It's exactly the loss function that falls out of assuming Gaussian-distributed noise and doing Maximum Likelihood Estimation (see Probability & Statistics — MLE vs MAP) — minimizing MSE is finding the maximum-likelihood parameters under that assumption, not an arbitrary choice.

JJ is convex in (w,b)(w, b) (see Calculus & Optimization — Convexity) — there's exactly one minimum, no false valleys to get stuck in. That's what makes everything below work cleanly.

Try it yourself: implement mean squared error from scratch, against real test cases.

3. Deriving the Gradient, Step by Step

To minimize JJ, we need Jw\dfrac{\partial J}{\partial w} and Jb\dfrac{\partial J}{\partial b} — the direction that increases the loss fastest, so we can step in the opposite direction (see Calculus & Optimization — Gradients).

Start with a single example, error e(i)=y^(i)y(i)=(wx(i)+b)y(i)e^{(i)} = \hat{y}^{(i)} - y^{(i)} = (w x^{(i)} + b) - y^{(i)}, and its squared contribution to the cost, (e(i))2\left(e^{(i)}\right)^2. Apply the chain rule:

w(e(i))2=2e(i)e(i)w=2e(i)x(i)\frac{\partial}{\partial w}\left(e^{(i)}\right)^2 = 2 e^{(i)} \cdot \frac{\partial e^{(i)}}{\partial w} = 2 e^{(i)} \cdot x^{(i)}

because e(i)w=w(wx(i)+by(i))=x(i)\dfrac{\partial e^{(i)}}{\partial w} = \dfrac{\partial}{\partial w}\left(w x^{(i)} + b - y^{(i)}\right) = x^{(i)} — everything else in e(i)e^{(i)} is constant with respect to ww.

Similarly, e(i)b=1\dfrac{\partial e^{(i)}}{\partial b} = 1, so:

b(e(i))2=2e(i)\frac{\partial}{\partial b}\left(e^{(i)}\right)^2 = 2 e^{(i)}

Now sum across all nn examples and divide by nn (matching the 1n\frac{1}{n} in JJ):

Jw=2ni=1n(y^(i)y(i))x(i)Jb=2ni=1n(y^(i)y(i))\frac{\partial J}{\partial w} = \frac{2}{n} \sum_{i=1}^{n} \left( \hat{y}^{(i)} - y^{(i)} \right) x^{(i)} \qquad\qquad \frac{\partial J}{\partial b} = \frac{2}{n} \sum_{i=1}^{n} \left( \hat{y}^{(i)} - y^{(i)} \right)

Reading the intuition directly off the formula: if a prediction is too high (y^(i)>y(i)\hat{y}^{(i)} > y^{(i)}), the gradient is positive, so the update step (below) decreases ww — pulling the prediction back down. If the estimate is already close to correct, the error term is small, so the gradient is small and the update barely moves ww at all. The gradient is literally "error times input" — the size of the correction is proportional to both how wrong you are and how much that particular input contributed to the prediction.

4. Vectorized Form

Looping over nn examples in Python is slow. Stack all examples into a matrix XRn×dX \in \mathbb{R}^{n \times d} (one row per example) and all targets into yRn\mathbf{y} \in \mathbb{R}^n. The predictions for the whole dataset at once:

y^=Xw+b\hat{\mathbf{y}} = X\mathbf{w} + b

And the gradient across the entire dataset, as a single matrix expression:

wJ=2nXT(y^y)\nabla_w J = \frac{2}{n} X^T (\hat{\mathbf{y}} - \mathbf{y})

This is the exact same formula as Section 3 — XTX^T (transpose, see Linear Algebra) is what turns "multiply each error by its matching input and sum" into one matrix multiplication. This is why frameworks are fast: a GPU does this single matrix multiply in parallel instead of looping example by example.

5. The Closed-Form Solution (Normal Equation)

Because JJ is convex, we can skip iterating entirely and solve for the exact minimum directly: set the gradient to zero and solve.

wJ=0        XT(Xwy)=0        XTXw=XTy\nabla_w J = 0 \;\;\Rightarrow\;\; X^T(X\mathbf{w} - \mathbf{y}) = 0 \;\;\Rightarrow\;\; X^TX\mathbf{w} = X^T\mathbf{y}

w=(XTX)1XTy\mathbf{w} = (X^TX)^{-1}X^T\mathbf{y}

This is the normal equation — it gives the exact optimal weights in one shot, no learning rate, no iterations. So why doesn't everyone just use this?

Because (XTX)1(X^TX)^{-1} is expensive and sometimes doesn't exist. Computing a matrix inverse costs roughly O(d3)O(d^3) where dd is the number of features (see Algorithms & Data Structures) — fine for a handful of features, prohibitive for thousands. And if features are linearly dependent (multicollinearity), XTXX^TX isn't invertible at all. This is exactly why gradient descent — next section — is the practical default at any real scale, even though the normal equation is "more exact."

6. Gradient Descent

Instead of solving in one step, take small repeated steps in the direction that reduces the cost:

wwαJwbbαJbw \leftarrow w - \alpha \frac{\partial J}{\partial w} \qquad\qquad b \leftarrow b - \alpha \frac{\partial J}{\partial b}

where α\alpha is the learning rate (see Calculus & Optimization). The full loop:

initw,bCompute ŷCompute J(w,b)Compute ∂J/∂w, ∂J/∂bUpdate w, brepeat until J stops decreasing
w, b start at some initial value (often zero or small random values), then the loop runs.
Click a stage. This loop is exactly what the Studio's Gradient Descent Lab mode runs live, one visible step at a time.

Because JJ is convex, this loop is guaranteed to converge to the global minimum (not just a minimum) as long as α\alpha is small enough — no local-minima concerns here, unlike the non-convex loss surfaces in Deep Learning.

Switch the Studio above to Gradient Descent Lab mode to watch this loop run for real: real J/w\partial J/\partial w and J/b\partial J/\partial b computed every step, a live MSE surface with the descent path drawn on it, and a step log with the actual numbers. Push the learning rate slider too high and watch it diverge instead of converge — a direct, visceral answer to "why does the learning rate matter so much."

Try it yourself: implement one gradient descent step from scratch, against real test cases.

7. Closed-Form vs. Gradient Descent

J(w,b) contoursstart (w₀, b₀)global minimum
— Normal equation: one jump, O(d³)— Gradient descent: iterative, O(nd) per step
Both paths start in the same place and land on the identical minimum -- convexity guarantees there's only one to find. The difference is entirely in how they get there, not where they end up.
Normal equationGradient descent
ResultExact minimum in one stepApproximate, improves each iteration
CostO(d3)O(d^3) from the matrix inverseO(nd)O(nd) per iteration
Scales to many features?No — becomes impractical past a few thousand featuresYes
Needs a learning rate?NoYes — and a badly chosen one breaks convergence
Requires XTXX^TX invertible?YesNo

In practice: normal equation for small, low-dimensional problems where you want the exact answer with no tuning; gradient descent (or a variant — see Adaptive Optimizers) for anything larger, and universally in deep learning where a closed form doesn't exist at all.

8. Assumptions Behind Linear Regression

The model — and its statistical guarantees — rely on a few assumptions worth knowing explicitly, since violating them is a common real-world failure mode:

  • Linearity: the true relationship between features and target is (approximately) linear.
  • Independence of errors: residuals aren't correlated with each other (violated by time-series data with autocorrelation, for instance).
  • Homoscedasticity: the variance of the residuals is constant across all values of xx — not, say, growing larger for bigger predictions.
  • Normally distributed residuals: needed specifically for the statistical validity of confidence intervals and hypothesis tests on the coefficients (see Probability & Statistics), not for the point predictions themselves.
  • No severe multicollinearity: highly correlated features make XTXX^TX near-singular, causing the normal equation's coefficients to become unstable and hard to interpret.

How to actually check these: plot the residuals (actual − predicted) against xx. A healthy fit looks like random scatter centered on zero, with no pattern and roughly constant spread:

0hours studied9hresidual
residual = actual − predicted, for the same fit shown above
Random scatter, centered on zero, no visible pattern, roughly constant spread across x -- this is what a healthy fit's residuals look like. A curve or a funnel shape here would signal a violated assumption.

If you instead saw a curve (points systematically above zero for small/large xx and below for the middle, or vice versa), that would signal a nonlinear relationship — linearity violated. If the spread visibly widens or narrows across xx, that's heteroscedasticity — homoscedasticity violated. This one plot is the fastest real-world check for two of the five assumptions above.

9. Minimal Implementation

The entire training loop from Section 6, in plain NumPy — no framework, so every line maps directly to a formula above:

import numpy as np

def fit_linear_regression(X, y, lr=0.01, epochs=1000):
    n, d = X.shape
    w = np.zeros(d)
    b = 0.0

    for _ in range(epochs):
        y_hat = X @ w + b                  # Section 1: predictions
        error = y_hat - y
        dw = (2 / n) * X.T @ error         # Section 4: vectorized gradient
        db = (2 / n) * np.sum(error)
        w -= lr * dw                       # Section 6: gradient descent update
        b -= lr * db

    return w, b

Compare this against the closed-form solution directly:

def fit_normal_equation(X, y):
    return np.linalg.inv(X.T @ X) @ X.T @ y   # Section 5

For a small, well-conditioned dataset, both should converge to (nearly) the same ww — a good way to sanity-check that a from-scratch gradient descent implementation is actually correct.

Engineering note: np.linalg.inv(X.T @ X) @ X.T @ y above is the right way to learn the normal equation, but it's not what production numerical code actually does. Explicitly forming and inverting XTXX^TX is numerically less stable than solving the linear system directly — real implementations (scikit-learn's dense least-squares solver included) use SVD or QR decomposition instead (see Linear Algebra — SVD), which avoid explicitly computing an inverse at all. Know the formula; don't ship it literally.

10. When Not to Use Linear Regression

The assumptions in Section 8 aren't just theory — violate them badly enough and the model quietly gives you confident, wrong answers. Reach for something else when:

SymptomTry instead
Relationship is strongly nonlinearTrees / boosting (Decision Trees, Random Forest), or a nonlinear model
Severe multicollinearity (unstable, hard-to-interpret coefficients)Ridge Regression
You need automatic feature selection (many irrelevant features)Lasso Regression
Complex feature interactionsBoosting (XGBoost/LightGBM/CatBoost)
Target changes over time (autocorrelated errors)Specialized time-series forecasting
Residuals show a clear pattern against y^\hat{y}The functional form is wrong — revisit linearity before tuning anything else

Why Ridge comes right after this page: Section 8 flagged multicollinearity as an assumption violation — in practice, it's one of the most common ones. When features are correlated, XTXX^TX becomes near-singular, and the normal equation's solution becomes wildly sensitive to small changes in the data: tiny noise in yy can flip a coefficient's sign or blow up its magnitude, even though predictions stay reasonable. Ridge Regression fixes exactly this by penalizing large weights, trading a little bias for a lot less variance in the coefficients.


Next: Ridge Regression — what happens when we penalize large weights — and Model Evaluation & Metrics for how to judge whether the resulting model is actually good.

Last updated Sep 5, 2026Edit this pageReport an issue
← Previous
Supervised Learning
Next →
Ridge Regression, In Full Depth