III-08 · Least Squares and the Closed-Form Best Fit

Find the line the data can identify

Center the observations, apply the exact scalar best fit, and discover why constant inputs cannot identify a unique slope.

Chapters in Part II
  1. III-07 · Linear Regression
  2. III-08 · Least Squares and the Closed-Form Best Fit
  3. III-09 · Gradient Descent for Linear Regression
  4. III-10 · Multiple Linear Regression
  5. III-11 · Polynomial Features
  6. III-12 · Regularization
  7. III-13 · Logistic Regression
  8. III-14 · Decision Thresholds and Confusion Matrix
  9. III-15 · Precision, Recall, F1 and ROC
  10. III-16 · Multiclass Classification and Softmax Regression

Fit and optimization laboratory

Center → covariance → best line

Hand-authored toy observations, editable in this sheet. No seed and no measured population.

Observed and predicted targets on equal coordinate scalesCircles are training observations; squares are held-out observations; diamonds are predictions. Dashed vertical residuals join each observation to its prediction. Numeric values are in the following table.≈ −4.6≈ −4.6≈ 0≈ 0≈ 4.6≈ 4.6xy

● Training observation · ◇ Prediction · dashed residual. Axis domain adjusts to the data; x/y units share one scale.

Change the active experiment

Unique optimum for this scalar affine design. Best w ≈ 1.8, b ≈ 0.8. Sxx ≈ 10, Sxy ≈ 18.

X · augmented designSamples in rows; first column is 1.
5 × 2
W · coefficientsIntercept first, then feature weights.
2 × 1
y, predictions, residuals
5 × 1
NormalizationNo half factor; evaluation excludes penalties.
MSE / n

W = [≈ 0, ≈ 0.5]ᵀ

Live state

Data MSE
≈ 4.1
Optimal data MSE
≈ 0.08
y^=XW,r=y^−y\hat y=XW,\quad r=\hat y-y
Ldata=15∑iri2≈4.1L_{\mathrm{data}}=\frac1{5}\sum_i r_i^2\approx 4.1
w∗=SxySxx,b∗=yˉ−w∗xˉw_*=\frac{S_{xy}}{S_{xx}},\quad b_*=\bar y-w_*\bar x
Exact inputs; approximate computed results from the active state
IDxDesign rowTargetPredictionResidual
s1-2[1, -2]-3≈ −1≈ 2
s2-1[1, -1]-1≈ −0.5≈ 0.5
s30[1, 0]1≈ 0≈ −1
s41[1, 1]3≈ 0.5≈ −2.5
s52[1, 2]4≈ 1≈ −3
Change the controls first, then inspect the active equation, numeric rows and shapes. Exact inputs and approximate computed values are distinguished; Reset restores the hand-authored experiment.

Let the observations choose the line

Press Apply closed-form fit in the opening sheet. The weight changes from 0.50.5 to 1.81.8, the bias from 00 to 0.80.8, and the data MSE from 4.14.1 to 0.080.08. Edit one target and fit again. Every observation influences a single pair of coefficients; there is no private line for each row.

Now press Set constant x, then Apply closed-form fit. Setting the inputs changes the observations while preserving the current coefficients; applying the fit adopts the horizontal representative. The sheet reports a nonunique-slope explanation. A valid best prediction still exists, even when a unique weight does not. Numeric observation fields work with Enter, arrow keys and touch; Reset restores the original five observations.

The objective and its stationary conditions

For nn scalar inputs, choose an affine predictor and squared vertical residuals:

y^i=wxi+b,ri=y^i−yi,L(w,b)=1n∑iri2.\hat y_i=wx_i+b,\quad r_i=\hat y_i-y_i,\quad L(w,b)=\frac1n\sum_i r_i^2.

This is an optimization objective, not a procedure for finding it. “Least squares” describes what is minimized. A closed-form calculation and gradient descent can solve the same objective. Neither gives a guarantee of generalization to an unseen population.

Differentiating while holding the observations fixed gives

∂bL=2n∑i(wxi+b−yi)=0,∂wL=2n∑ixi(wxi+b−yi)=0.\partial_bL=\frac2n\sum_i(wx_i+b-y_i)=0, \qquad \partial_wL=\frac2n\sum_i x_i(wx_i+b-y_i)=0.

The first equation implies b=yˉ−wxˉb=\bar y-w\bar x. Substitute it into the second and group centered quantities:

Sxx=∑i(xi−xˉ)2,Sxy=∑i(xi−xˉ)(yi−yˉ),w∗Sxx=Sxy.S_{xx}=\sum_i(x_i-\bar x)^2,\quad S_{xy}=\sum_i(x_i-\bar x)(y_i-\bar y), \qquad w_*S_{xx}=S_{xy}.

When Sxx>0S_{xx}>0, the unique result is w∗=Sxy/Sxxw_*=S_{xy}/S_{xx} and b∗=yˉ−w∗xˉb_*=\bar y-w_*\bar x. Centering removes the intercept from the slope calculation. Geometrically the optimal residual column is orthogonal to both the input column and the column of ones. It is a projection onto the span of those two columns.

Work the five-row optimum

The original hand-authored inputs are [−2,−1,0,1,2][-2,-1,0,1,2] and targets [−3,−1,1,3,4][-3,-1,1,3,4]. There is no random seed, measurement claim or held-out set in this chapter. Here xˉ=0\bar x=0, yˉ=0.8\bar y=0.8, Sxx=10S_{xx}=10 and Sxy=18S_{xy}=18. Thus w∗=1.8w_*=1.8, b∗=0.8b_*=0.8.

xTargetOptimal predictionResidual
-2-3-2.80.2
-1-1-10
010.8-0.2
132.6-0.4
244.40.4

Both ∑ri\sum r_i and ∑xiri\sum x_ir_i are zero. The squared residual sum is 0.40.4, so MSE is 0.4/5=0.080.4/5=0.08. A positive optimal training error is expected because the last observation departs from the otherwise exact line 2x+12x+1.

The active design in this sheet is augmented: X=[1,x]∈Rn×2X=[\mathbf1,x]\in\mathbb R^{n\times2}, W=[b,w]T∈R2×1W=[b,w]^\mathsf T\in\mathbb R^{2\times1} and y∈Rn×1y\in\mathbb R^{n\times1}. This intercept-first notation is equivalent to III-07’s separate scalar bias; it does not alter its predictor or MSE normalization.

Zero variance is an identification failure

If every input equals cc, only the combination wc+bwc+b is constrained. Its best value is yˉ\bar y. Every pair satisfying wc+b=yˉwc+b=\bar y is a minimizer. We choose w=0,b=yˉw=0,b=\bar y as an explicitly labeled horizontal representative, not as a unique slope or the minimum norm parameter pair.

For two observations (2,1)(2,1) and (2,3)(2,3), both (w,b)=(0,2)(w,b)=(0,2) and (1,0)(1,0) predict 22 and have MSE 11. A single observation has the same ambiguity. Do not divide 0/00/0, invent an identifiable slope, or reject an otherwise meaningful prediction problem.

When every input equals x0x_0, the plot draws the chosen predictor over [x0−1,x0+1][x_0-1,x_0+1] so the line remains visible beyond the overlapping prediction markers. These are points on the displayed function, not additional observations or evidence identifying a slope. With a nonzero observed span, the drawing extends 0.150.15 beyond each observed endpoint.

Explore the assumptions

Fit, then edit one target without fitting again. The current line and current MSE reflect your displayed coefficients; “optimal data MSE” belongs to the newly computed optimum. Press fit to adopt it. Editing the data and optimizing the coefficients are different actions.

Change only one input away from a constant-input dataset and refit. The slope becomes identifiable, but a tiny input spread can make it extremely sensitive to target errors. A unique answer can still be poorly conditioned. Extrapolating far beyond the observed inputs magnifies that sensitivity.

Connections and checkpoints

Least-squares geometry comes from Book II’s orthogonal projection. III-09 solves this same objective iteratively, and III-10 replaces the scalar slope division with a rank-aware design solve. In the canonical Python, closed_form uses centered NumPy columns. A separate Fraction oracle eliminates the intercept through pair differences and independently confirms the optimum. Computed values are approximate displays; exact input text and underlying coefficients retain binary64 precision.

You should now be able to derive the scalar optimum, check residual orthogonality, distinguish current coefficients from the best fit, and explain why zero variance identifies predictions but not a unique slope.

Execute the actual Python and NumPy

The imported source below is the runnable file used in canonical comparisons. Read the named mechanism from this chapter; the other functions coordinate the same Part experiment. From the book root:

.venv/bin/python -B code/python/p02/fit_reference.py
code/python/p02/fit_reference.py
"""III-08–12: centered fits, frozen regression updates, small SVD and explicit penalties.

Run from the book root: .venv/bin/python -B code/python/p02/fit_reference.py
Rows are samples; W and y are columns. Intercept is first in augmented X/W.
"""
import json
import math
import numpy as np
from linear_regression import CANONICAL_REGRESSION, derive_regression, regression_trace

RCOND = 1e-10
POLYNOMIAL_DATA = {
    "trainX": [-1, -.6, -.2, .2, .6, 1], "trainY": [1.1, .46, 0, .16, .26, 1.2],
    "heldX": [-.8, -.4, 0, .4, .8], "heldY": [.64, .16, 0, .16, .64],
}


def finite(values, name):
    if not np.isfinite(values).all():
        raise ValueError(f"{name} must remain finite")
    return values


def design(X, y):
    # Validate before coercion: booleans, strings and ragged rows are not numeric data.
    if not isinstance(X, (list, tuple)) or not 1 <= len(X) <= 64:
        raise ValueError("X requires 1–64 rows")
    if not isinstance(X[0], (list, tuple)) or not 1 <= len(X[0]) <= 7:
        raise ValueError("X requires 1–7 columns")
    d = len(X[0])
    for row in X:
        if not isinstance(row, (list, tuple)) or len(row) != d:
            raise ValueError("X must be rectangular")
        for v in row:
            if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v):
                raise ValueError("X must contain finite numbers")
    column(y, len(X), "Targets")
    return finite(np.array(X, dtype=float), "X"), np.array(y, dtype=float).reshape(-1, 1)


def column(values, size, name):
    if not isinstance(values, (list, tuple)) or len(values) != size:
        raise ValueError(f"{name} requires {size} entries (a column)")
    if any(isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v) for v in values):
        raise ValueError(f"{name} must contain finite numbers")
    return np.array(values, dtype=float).reshape(-1, 1)


def linear(X, y, weights):
    A, target = design(X, y)
    W = column(weights, A.shape[1], "Weights")
    with np.errstate(over="ignore", invalid="ignore", under="ignore"):
        prediction = finite(A @ W, "Predictions")
        residual = finite(prediction - target, "Residuals")
        mse = float(finite(np.sum(residual * residual) / len(A), "MSE"))
        gradient = finite(2 / len(A) * A.T @ residual, "Gradient")
    n, d = A.shape
    return {"predictions": prediction[:, 0].tolist(), "residuals": residual[:, 0].tolist(),
            "mse": mse, "gradient": gradient[:, 0].tolist(),
            "shapes": {"X": [n, d], "W": [d, 1], "y": [n, 1], "prediction": [n, 1], "gradient": [d, 1]}}


def _least_norm_validated(A, target):
    # Trusted Ridge augmentation may add ≤7 rows beyond the 64 public observation rows.
    d = A.shape[1]
    scale = float(np.max(np.abs(A)))
    if scale == 0:
        return {"weights": [0.] * d, "rank": 0, "singularValues": [0.] * d, "cutoff": 0., "condition": None}
    B = A / scale
    V = np.eye(d)
    for sweep in range(100):
        rotated = False
        for p in range(d):
            for q in range(p + 1, d):
                a, b, c = float(B[:, p] @ B[:, p]), float(B[:, q] @ B[:, q]), float(B[:, p] @ B[:, q])
                if a == 0 or b == 0 or abs(c) <= 1e-14 * math.sqrt(a) * math.sqrt(b):
                    continue
                tau = (b - a) / (2 * c)
                t = 1. if tau == 0 else math.copysign(1., tau) / (abs(tau) + math.hypot(1., tau))
                cs, sn = 1 / math.hypot(1., t), t / math.hypot(1., t)
                for matrix in (B, V):
                    left, right = matrix[:, p].copy(), matrix[:, q].copy()
                    matrix[:, p], matrix[:, q] = cs * left - sn * right, sn * left + cs * right
                rotated = True
        if not rotated:
            break
    else:
        raise ValueError("SVD did not converge in 100 sweeps; no fit returned")
    norms = np.array([math.hypot(*B[:, j]) for j in range(d)])
    cutoff = RCOND * float(np.max(norms))
    weights = np.zeros(d)
    for j in range(d):
        if norms[j] > cutoff:
            coefficient = float((B[:, j] / norms[j]) @ target[:, 0]) / norms[j] / scale
            weights += V[:, j] * coefficient
    finite(weights, "Fitted weights")
    residual = finite(A @ weights.reshape(-1, 1) - target, "Fitted residuals")
    finite(np.sum(residual * residual) / len(A), "Fitted MSE")
    values = sorted(finite(norms * scale, "Singular values").tolist(), reverse=True)
    rank = int(np.sum(norms > cutoff))
    condition = values[0] / values[-1] if rank == d else None
    return {"weights": weights.tolist(), "rank": rank, "singularValues": values,
            "cutoff": cutoff * scale, "condition": condition}


def least_norm(X, y):
    A, target = design(X, y)
    return _least_norm_validated(A, target)


def closed_form(samples):
    derive_regression({"samples": samples, "weight": 0., "bias": 0., "learningRate": .1})
    x = np.array([r["x"] for r in samples], dtype=float)
    y = np.array([r["y"] for r in samples], dtype=float)
    with np.errstate(over="ignore", invalid="ignore", under="ignore"):
        mean_x, mean_y = float(finite(np.mean(x), "Mean x")), float(finite(np.mean(y), "Mean y"))
        dx, dy = finite(x - mean_x, "Centered x"), finite(y - mean_y, "Centered y")
        sxx, sxy = float(finite(dx @ dx, "Sxx")), float(finite(dx @ dy, "Sxy"))
    if sxx == 0 and any(v != x[0] for v in x):
        raise ValueError("Input variance underflowed; slope is numerically unresolved")
    weight = 0. if sxx == 0 else sxy / sxx
    bias = mean_y - weight * mean_x
    mse = derive_regression({"samples": samples, "weight": weight, "bias": bias, "learningRate": .1})["mse"]
    return {"weight": weight, "bias": bias, "meanX": mean_x, "meanY": mean_y, "sxx": sxx, "sxy": sxy, "mse": mse, "unique": sxx != 0}


def descent(state):
    result = derive_regression(state)  # Frozen III-07 is the predictor, gradient and update authority.
    x = np.array([r["x"] for r in state["samples"]])
    a, b, c = 2 * float(np.mean(x * x)), 2 * float(np.mean(x)), 2.
    maximum = (a + c + math.hypot(a - c, 2 * b)) / 2
    factor = 1 - state["learningRate"] * maximum
    behavior = ("undamped oscillation" if factor == -1 else "divergence") if factor <= -1 else ("convergent oscillation" if factor < 0 else "convergence without fast-mode sign flips")
    return {**result, "lambdaMax": maximum, "criticalRate": 2 / maximum, "factor": factor, "behavior": behavior}


def polynomial_design(x, degree):
    if type(degree) is not int or not 1 <= degree <= 5:
        raise ValueError("Degree must be an integer from 1 to 5")
    column(x, len(x), "Polynomial input")
    return finite(np.array([[v ** j for j in range(degree + 1)] for v in x]), "Polynomial features").tolist()


def polynomial_fit(samples, degree):
    closed_form(samples)
    X, y = polynomial_design([r["x"] for r in samples], degree), [r["y"] for r in samples]
    solve = least_norm(X, y)
    return {**solve, "train": linear(X, y, solve["weights"]), "held": linear(polynomial_design(POLYNOMIAL_DATA["heldX"], degree), POLYNOMIAL_DATA["heldY"], solve["weights"]), "X": X}


def regularized(X, y, lam, kind, penalize_intercept):
    A, target = design(X, y)
    if isinstance(lam, bool) or not isinstance(lam, (int, float)) or not math.isfinite(lam) or not 0 <= lam <= 10 or kind not in ("l1", "l2") or type(penalize_intercept) is not bool:
        raise ValueError("Penalty requires L1/L2, lambda in [0,10] and an explicit intercept policy")
    if not np.all(A[:, 0] == 1):
        raise ValueError("First design column must be the intercept ones")
    n, d = A.shape
    mask = np.array([j != 0 or penalize_intercept for j in range(d)])
    iterations = 0
    if kind == "l2" or lam == 0:
        extra = np.diag(np.full(d, math.sqrt(n * lam)))[mask] if lam > 0 else np.empty((0, d))
        solve = _least_norm_validated(np.vstack([A, extra]), np.vstack([target, np.zeros((len(extra), 1))]))
        weights = np.array(solve["weights"])
    else:
        weights = np.zeros(d)
        for iterations in range(1, 3001):
            old = weights.copy()
            for j in range(d):
                partial = target[:, 0] - A @ weights + A[:, j] * weights[j]
                rho, curvature = float(A[:, j] @ partial / n), float(A[:, j] @ A[:, j] / n)
                threshold = lam / 2 if mask[j] else 0
                weights[j] = 0 if curvature == 0 else math.copysign(max(0, abs(rho) - threshold), rho) / curvature
            finite(weights, "L1 weights")
            if np.max(np.abs(weights - old)) < 1e-12:
                break
    result = linear(X, y, weights.tolist())
    penalty = float(finite(lam * np.sum((weights ** 2 if kind == "l2" else np.abs(weights))[mask]), "Penalty"))
    gradient = np.array(result["gradient"])
    kkt = []
    for j, w in enumerate(weights):
        g = gradient[j]
        kkt.append(abs(g) if not mask[j] else abs(g + 2 * lam * w) if kind == "l2" else max(0, abs(g) - lam) if w == 0 else abs(g + lam * np.sign(w)))
    residual = float(max(kkt))
    return {**result, "weights": weights.tolist(), "penalty": penalty, "objective": float(finite(result["mse"] + penalty, "Objective")), "kkt": residual, "iterations": iterations, "converged": residual <= 1e-8}


def demonstration():
    samples = CANONICAL_REGRESSION["samples"]
    poly = [dict(id=f"s{i+1}", x=x, y=POLYNOMIAL_DATA["trainY"][i]) for i, x in enumerate(POLYNOMIAL_DATA["trainX"])]
    return {"closedForm": closed_form(samples), "descent": descent(CANONICAL_REGRESSION),
            "descentTrace": regression_trace(CANONICAL_REGRESSION, 4),
            "multiple": least_norm([[1, -1, 0], [1, 0, 1], [1, 1, 0], [1, 2, 1]], [-1, 0, 3, 4]),
            "polynomial": {str(d): polynomial_fit(poly, d) for d in (1, 2, 5)},
            "regularization": {kind: regularized([[1, r["x"]] for r in samples], [r["y"] for r in samples], .5, kind, False) for kind in ("l1", "l2")}}


if __name__ == "__main__":
    print(json.dumps(demonstration(), indent=2, allow_nan=False))

Five graded investigations

With Sxx = 10 and Sxy = 18, what is the optimal slope?
For mean x = 0, mean y = 0.8 and slope 1.8, what is the intercept?
All inputs equal 2 and mean target is 2. Which statement is correct?
Optimal squared errors sum to 0.4 over five observations. What is MSE?
Does a closed-form training optimum establish low error on new data?