PROMETHEUS OS
NATIVE AI OPERATING SYSTEM
[INIT] Prometheus OS loading...
[CORE] Neural interface initializing...
[AI] LLM router connecting...
[MEM] Memory graph loading...
[AGT] Agent chains on standby...
[SEC] Security protocols active...
[NET] Provider mesh established...
[UI] Workspace rendering...
[DONE] All systems nominal.
Welcome to PROMETHEUS OS.
READY · OFFLINE · SECURE · PRIVATE
🎨 THEME ENGINE
PROMETHEUS OS · 23 THEMES
Trial version: Dark + Light themes only.
Full theme access with PROMETHEUS OS license.
Dark theme active
Experience all 23 themes in PROMETHEUS OS

Linear and Logistic Regression

Intermediate 19 min· 4 exercises

Fit, interpret and regularise linear and logistic regression models, derive their training objectives, and extend logistic regression to many classes with softmax.

By the end of this lesson you will be able to
  • Derive the least-squares normal equation and implement linear regression with both a solver and gradient descent.
  • Interpret regression coefficients, odds ratios and R-squared, and state the assumptions behind them.
  • Explain the sigmoid, log-odds, decision boundary and log-loss of logistic regression and compute them by hand.
  • Compare ridge, lasso and elastic net regularisation and choose among them for a given dataset.
  • Extend binary logistic regression to multiclass problems using softmax or one-vs-rest.

Before this: The ML Workflow: Framing, Data, Splits and Leakage, Linear Algebra for AI, Calculus and Gradients: How Networks Learn

Linear and logistic regression are the workhorses of applied machine learning. They are the first models you should fit on almost any tabular problem, they are still used directly in credit scoring, medicine, advertising and economics because their coefficients can be read and audited, and their machinery (a weighted sum, a loss function, gradient descent, regularisation, softmax) is exactly what sits inside every neural network. If you understand this lesson, the output layer of a transformer holds no mysteries.

Intuition: the best straight edge

Imagine a scatter of points on graph paper and a ruler. Linear regression slides and tilts the ruler until the total squared vertical distance from the points to the ruler is as small as possible. Squaring makes large misses count much more than small ones, and it gives a smooth bowl-shaped error surface with one lowest point that we can find exactly.

Logistic regression uses the same ruler for a yes/no question. Instead of predicting the value directly, it computes a score along the ruler and squashes that score into a probability between 0 and 1. The ruler's line (where the probability is exactly 0.5) becomes a decision boundary separating the two classes.

Linear regression

The model

We have nn examples. Example ii has a feature vector xi∈Rdx_i \in \mathbb{R}^d and a real-valued target yiy_i. The model predicts

y^i=w⊤xi+b=w1xi1+⋯+wdxid+b\hat y_i = w^\top x_i + b = w_1 x_{i1} + \dots + w_d x_{id} + b

with weights (coefficients) w∈Rdw \in \mathbb{R}^d and intercept (bias) bb. It is convenient to append a constant 1 to every xix_i and fold bb into the weight vector; stacking all examples as rows gives a design matrix X∈Rn×(d+1)X \in \mathbb{R}^{n \times (d+1)} and the predictions y^=Xθ\hat y = X\theta, where θ=(b,w1,…,wd)\theta = (b, w_1, \dots, w_d).

Least squares

We choose θ\theta to minimise the mean squared error (MSE):

J(θ)=1n∑i=1n(yi−y^i)2=1n∥y−Xθ∥2.J(\theta) = \frac{1}{n}\sum_{i=1}^{n} (y_i - \hat y_i)^2 = \frac{1}{n}\lVert y - X\theta \rVert^2 .

Squared error is not arbitrary: if the targets are generated as yi=θ⊤xi+εiy_i = \theta^\top x_i + \varepsilon_i with independent Gaussian noise εi∼N(0,σ2)\varepsilon_i \sim \mathcal N(0, \sigma^2), minimising MSE is the same as maximising the likelihood of the data.

The normal equation

JJ is a convex quadratic in θ\theta, so its minimum is where the gradient is zero:

∇θJ=−2nX⊤(y−Xθ)=0⟹X⊤X θ=X⊤y.\nabla_\theta J = -\frac{2}{n} X^\top (y - X\theta) = 0 \quad\Longrightarrow\quad X^\top X\,\theta = X^\top y .

These are the normal equations. If X⊤XX^\top X is invertible (no feature is an exact linear combination of others),

θ^=(X⊤X)−1X⊤y.\hat\theta = (X^\top X)^{-1} X^\top y .

In code, never form the inverse explicitly; call a least-squares solver (np.linalg.lstsq), which uses numerically stable QR or SVD decompositions. Solving costs roughly O(nd2+d3)O(nd^2 + d^3), which is fine for thousands of features but not millions.

Gradient descent

For very large nn or dd, or when no closed form exists (as in logistic regression), we use gradient descent: start from some θ\theta and repeatedly step downhill,

θ←θ−η ∇θJ(θ),\theta \leftarrow \theta - \eta \,\nabla_\theta J(\theta),

where the learning rate η>0\eta > 0 sets the step size. Too large and the iterates diverge; too small and progress crawls. Stochastic gradient descent (SGD) estimates the gradient from a small random batch of examples, trading noise for much cheaper steps. Gradient descent converges much faster when features are on similar scales, which is one reason to standardise them.

Worked example: three points

Data: (x,y)=(1,2),(2,3),(3,5)(x, y) = (1, 2), (2, 3), (3, 5). With one feature, the least-squares solution has the familiar form

w=∑i(xi−xˉ)(yi−yˉ)∑i(xi−xˉ)2,b=yˉ−wxˉ.w = \frac{\sum_i (x_i - \bar x)(y_i - \bar y)}{\sum_i (x_i - \bar x)^2}, \qquad b = \bar y - w\bar x .
  • Means: xˉ=2\bar x = 2, yˉ=10/3≈3.333\bar y = 10/3 \approx 3.333.
  • Numerator: (−1)(2−3.333)+(0)(3−3.333)+(1)(5−3.333)=1.333+0+1.667=3(-1)(2 - 3.333) + (0)(3 - 3.333) + (1)(5 - 3.333) = 1.333 + 0 + 1.667 = 3.
  • Denominator: 1+0+1=21 + 0 + 1 = 2.
  • So w=1.5w = 1.5 and b=3.333−1.5×2=0.333b = 3.333 - 1.5 \times 2 = 0.333.

Predictions are $1.833, 3.333, 4.833,residuals, residuals 0.167, -0.333, 0.167$.

One step of gradient descent from w=b=0w = b = 0 with η=0.1\eta = 0.1, for comparison: the residuals y^−y\hat y - y are (−2,−3,−5)(-2, -3, -5), so ∂J/∂w=23∑(y^i−yi)xi=23(−2−6−15)=−15.33\partial J/\partial w = \frac{2}{3}\sum (\hat y_i - y_i)x_i = \frac{2}{3}(-2 - 6 - 15) = -15.33 and ∂J/∂b=23(−10)=−6.67\partial J/\partial b = \frac{2}{3}(-10) = -6.67. The update gives w=1.533w = 1.533, b=0.667b = 0.667: already close to the answer, and further steps converge to (1.5,0.333)(1.5, 0.333).

Measuring fit: R2R^2

The coefficient of determination compares the model's squared error with that of always predicting the mean:

R2=1−SSESST=1−∑i(yi−y^i)2∑i(yi−yˉ)2.R^2 = 1 - \frac{\text{SSE}}{\text{SST}} = 1 - \frac{\sum_i (y_i - \hat y_i)^2}{\sum_i (y_i - \bar y)^2}.

In the example, SSE=0.0278+0.1111+0.0278=0.1667\text{SSE} = 0.0278 + 0.1111 + 0.0278 = 0.1667 and SST=1.778+0.111+2.778=4.667\text{SST} = 1.778 + 0.111 + 2.778 = 4.667, so R2=1−0.1667/4.667=0.964R^2 = 1 - 0.1667/4.667 = 0.964: the line explains about 96% of the variance in yy. On training data R2R^2 never decreases when you add features, so judge it on held-out data, where it can even be negative (worse than predicting the mean).

Assumptions, and which ones matter

Linear regression fits any data. The assumptions matter for whether the fit is good and whether standard errors and p-values are trustworthy:

  1. Linearity: the expected target is linear in the features (after any transformations you apply). Check residual-vs-fitted plots for curves.
  2. Independent errors: violated by time series and repeated measurements; standard errors become too small.
  3. Constant error variance (homoscedasticity): violated when errors grow with the prediction, common for prices and counts; a log transform of yy often helps.
  4. Normal errors: needed only for exact small-sample inference; irrelevant for prediction.
  5. No perfect multicollinearity: if features are exact linear combinations, X⊤XX^\top X is singular. Strong (not perfect) correlation makes individual coefficients unstable even when predictions are fine.

For pure prediction, only (1) truly matters, and it is checked by validation error. For explanation, all of them do.

Interpreting coefficients

wjw_j is the expected change in y^\hat y for a one-unit increase in feature jj, holding the other features fixed. Three cautions:

  • Units matter. A coefficient of 0.001 on income in dollars is not "small". Standardise features (subtract the mean, divide by the standard deviation) to compare magnitudes; then wjw_j is the change per standard deviation.
  • "Holding others fixed" may be impossible in reality when features are correlated (you cannot raise square footage while holding room count fixed), and correlated features can split or flip each other's coefficients.
  • Coefficients describe association, not causation. A positive coefficient on "number of firefighters" for fire damage does not mean firefighters cause damage.

Logistic regression

From scores to probabilities

For binary labels yi∈{0,1}y_i \in \{0, 1\} we want P(y=1∣x)P(y = 1 \mid x). A linear score z=w⊤x+bz = w^\top x + b can be any real number, so we pass it through the sigmoid (logistic) function:

σ(z)=11+e−z,p^=σ(w⊤x+b).\sigma(z) = \frac{1}{1 + e^{-z}}, \qquad \hat p = \sigma(w^\top x + b).

σ\sigma maps (−∞,∞)(-\infty, \infty) to (0,1)(0, 1), with σ(0)=0.5\sigma(0) = 0.5 and σ(−z)=1−σ(z)\sigma(-z) = 1 - \sigma(z).

Odds and log-odds

The odds of an event with probability pp are p/(1−p)p/(1-p); a probability of 0.8 is odds of 4 (to 1). Inverting the sigmoid shows what logistic regression really models:

log⁡p^1−p^=w⊤x+b.\log\frac{\hat p}{1 - \hat p} = w^\top x + b .

The log-odds (logit) are linear in the features. So a one-unit increase in xjx_j adds wjw_j to the log-odds, which multiplies the odds by ewje^{w_j} (the odds ratio). With wj=0.7w_j = 0.7, each unit multiplies the odds by e0.7≈2.01e^{0.7} \approx 2.01. Note that this is not a fixed change in probability: the same odds ratio moves pp from 0.5 to 0.67, but from 0.95 to only 0.97.

The decision boundary

Predicting class 1 when p^≥0.5\hat p \ge 0.5 is the same as predicting it when w⊤x+b≥0w^\top x + b \ge 0. The boundary w⊤x+b=0w^\top x + b = 0 is a straight line (a hyperplane in dd dimensions), so logistic regression is a linear classifier. Curved boundaries require transformed features (squares, interactions, splines). The 0.5 threshold is a choice: when false negatives cost more than false positives, lower it.

Log-loss

The squared error would make the optimisation non-convex here. Instead we maximise the likelihood ∏ip^i yi(1−p^i)1−yi\prod_i \hat p_i^{\,y_i}(1-\hat p_i)^{1-y_i}, which is equivalent to minimising the log-loss (binary cross-entropy):

L(w,b)=−1n∑i=1n[ yilog⁡p^i+(1−yi)log⁡(1−p^i)].L(w, b) = -\frac{1}{n}\sum_{i=1}^{n} \Big[\, y_i \log \hat p_i + (1 - y_i)\log(1 - \hat p_i) \Big].

It punishes confident mistakes severely: predicting p^=0.01\hat p = 0.01 for a true positive costs −log⁡0.01=4.6-\log 0.01 = 4.6, while p^=0.5\hat p = 0.5 costs only 0.690.69. LL is convex, and its gradient has a strikingly simple form,

∇wL=1n∑i=1n(p^i−yi) xi,∂L∂b=1n∑i=1n(p^i−yi),\nabla_w L = \frac{1}{n} \sum_{i=1}^{n} (\hat p_i - y_i)\, x_i, \qquad \frac{\partial L}{\partial b} = \frac{1}{n}\sum_{i=1}^{n} (\hat p_i - y_i),

"prediction minus target, times input", the same shape as for linear regression. There is no closed-form solution, so we use iterative solvers (gradient descent, Newton's method, L-BFGS). If the classes are perfectly separable, the unregularised weights grow without bound; regularisation fixes this.

Worked example

A model has w=2w = 2, b=−3b = -3 for a single feature. For x=2x = 2: z=2(2)−3=1z = 2(2) - 3 = 1, so p^=σ(1)=1/(1+e−1)=0.731\hat p = \sigma(1) = 1/(1 + e^{-1}) = 0.731.

  • If the true label is y=1y = 1, the log-loss for this example is −log⁡0.731=0.313-\log 0.731 = 0.313.
  • If y=0y = 0, it is −log⁡(1−0.731)=−log⁡0.269=1.313-\log(1 - 0.731) = -\log 0.269 = 1.313.
  • The odds are 0.731/0.269=e1=2.720.731/0.269 = e^{1} = 2.72. Increasing xx by 1 multiplies the odds by e2=7.39e^{2} = 7.39, to 20.120.1, i.e. p^=20.1/21.1=0.953\hat p = 20.1/21.1 = 0.953 (check: σ(3)=0.953\sigma(3) = 0.953).
  • The decision boundary is at 2x−3=02x - 3 = 0, i.e. x=1.5x = 1.5.

Regularisation: ridge, lasso, elastic net

With many features, few examples or correlated features, least-squares and maximum-likelihood weights become large and unstable, and the model overfits. Regularisation adds a penalty on the size of the weights. For a data loss Loss(w)\text{Loss}(w) (MSE or log-loss) and strength λ≥0\lambda \ge 0:

Ridge (L2):Loss(w)+λ∥w∥22=Loss(w)+λ∑jwj2\text{Ridge (L2):}\quad \text{Loss}(w) + \lambda \lVert w \rVert_2^2 = \text{Loss}(w) + \lambda \sum_j w_j^2 Lasso (L1):Loss(w)+λ∥w∥1=Loss(w)+λ∑j∣wj∣\text{Lasso (L1):}\quad \text{Loss}(w) + \lambda \lVert w \rVert_1 = \text{Loss}(w) + \lambda \sum_j |w_j| Elastic net:Loss(w)+λ[ρ∥w∥1+1−ρ2∥w∥22],0≤ρ≤1.\text{Elastic net:}\quad \text{Loss}(w) + \lambda\Big[\rho \lVert w\rVert_1 + \tfrac{1-\rho}{2}\lVert w \rVert_2^2\Big], \quad 0 \le \rho \le 1 .

The intercept is not penalised. (Exact scaling constants differ between textbooks and libraries; scikit-learn's Ridge minimises ∥y−Xw∥2+α∥w∥2\lVert y - Xw\rVert^2 + \alpha\lVert w\rVert^2 without dividing by nn, while its Lasso divides the squared error by 2n2n.)

  • Ridge shrinks all weights smoothly toward zero and has a closed form, w^=(X⊤X+λI)−1X⊤y\hat w = (X^\top X + \lambda I)^{-1}X^\top y on centred data. Adding λI\lambda I makes the matrix invertible even with collinear features. Among correlated features, ridge tends to share weight between them.
  • Lasso drives some weights exactly to zero, performing feature selection. Geometrically, the L1 constraint region is a diamond whose corners lie on the axes, and the optimum often lands on a corner. Among a group of highly correlated features, lasso tends to pick one somewhat arbitrarily.
  • Elastic net mixes both: sparse like lasso, but stable with correlated groups like ridge.

Regularisation penalises all weights equally, so features must be standardised first, or the penalty will hit small-unit features harder. Choose λ\lambda by cross-validation. In scikit-learn, linear regressors use alpha for λ\lambda, but LogisticRegression uses C, the inverse strength: small C means strong regularisation. scikit-learn's LogisticRegression applies L2 regularisation with C=1.0 by default, which surprises people expecting a plain maximum-likelihood fit.

Multiclass classification

For K>2K > 2 classes there are two standard approaches.

One-vs-rest (OvR) trains KK binary classifiers, class kk against all others, and predicts the class with the highest score. Simple and parallel, but the KK probabilities are not jointly normalised.

Multinomial (softmax) regression gives each class its own weight vector wkw_k and bias bkb_k, computes scores zk=wk⊤x+bkz_k = w_k^\top x + b_k, and normalises them with the softmax:

P(y=k∣x)=softmax⁡(z)k=ezk∑j=1Kezj.P(y = k \mid x) = \operatorname{softmax}(z)_k = \frac{e^{z_k}}{\sum_{j=1}^{K} e^{z_j}} .

Training minimises the cross-entropy −1n∑ilog⁡P(y=yi∣xi)-\frac{1}{n}\sum_i \log P(y = y_i \mid x_i), which reduces to the log-loss when K=2K = 2. For example, scores z=(2.0,1.0,0.1)z = (2.0, 1.0, 0.1) give ez=(7.39,2.72,1.11)e^{z} = (7.39, 2.72, 1.11), summing to 11.21, so the probabilities are (0.659,0.242,0.099)(0.659, 0.242, 0.099); if the true class is the first, the loss is −log⁡0.659=0.417-\log 0.659 = 0.417. This exact computation is the final layer of nearly every neural network classifier, including the next-token prediction of language models. Current versions of scikit-learn's LogisticRegression use the multinomial formulation for multiclass problems with its default solver; wrap a model in OneVsRestClassifier if you specifically want OvR.

Code

First, three ways to fit the same linear regression, which should agree:

import numpy as np
from sklearn.linear_model import LinearRegression

rng = np.random.default_rng(0)
n = 200
x = rng.uniform(0, 10, size=(n, 1))
y = 3.0 + 2.0 * x[:, 0] + rng.normal(0, 1.0, size=n)   # true b=3, w=2

# Normal equation (use lstsq/solve, never an explicit inverse)
X = np.hstack([np.ones((n, 1)), x])                       # add bias column
theta_ne = np.linalg.lstsq(X, y, rcond=None)[0]

# Batch gradient descent on MSE
theta = np.zeros(2)
lr = 0.01
for _ in range(5000):
    grad = (2 / n) * X.T @ (X @ theta - y)
    theta -= lr * grad

sk = LinearRegression().fit(x, y)
print("normal eq :", theta_ne.round(3))
print("grad desc :", theta.round(3))
print("sklearn   :", round(sk.intercept_, 3), sk.coef_.round(3))
print("R^2       :", round(sk.score(x, y), 3))

All three recover b≈3.05b \approx 3.05, w≈1.98w \approx 1.98 (close to the true 3 and 2; the difference is noise), with R2≈0.97R^2 \approx 0.97.

Next, regularised regression on scikit-learn's diabetes dataset (442 patients, 10 features, target is a disease progression score):

import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet

X, y = load_diabetes(return_X_y=True)
models = {
    "OLS":        LinearRegression(),
    "Ridge a=10": Ridge(alpha=10),
    "Lasso a=1":  Lasso(alpha=1.0),
    "ENet a=1":   ElasticNet(alpha=1.0, l1_ratio=0.5),
}
for name, m in models.items():
    pipe = make_pipeline(StandardScaler(), m)
    r2 = cross_val_score(pipe, X, y, cv=5, scoring="r2").mean()
    coef = pipe.fit(X, y)[-1].coef_
    print(f"{name:11s} CV R2={r2:.3f}  zero coefs={np.sum(coef == 0)}/10")

Typical output: all of OLS, ridge and lasso reach a cross-validated R2R^2 of about 0.48, but lasso does it with 3 of the 10 coefficients set exactly to zero, a simpler model at no cost. The elastic net at this alpha is over-regularised (R2≈0.46R^2 \approx 0.46): the strength must be tuned (for example with LassoCV, RidgeCV or ElasticNetCV), not guessed.

Finally, logistic regression with interpretable coefficients:

import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import log_loss, accuracy_score

data = load_breast_cancer()
X_tr, X_te, y_tr, y_te = train_test_split(
    data.data, data.target, test_size=0.25, stratify=data.target, random_state=0)

clf = make_pipeline(StandardScaler(), LogisticRegression(C=1.0, max_iter=1000))
clf.fit(X_tr, y_tr)
p = clf.predict_proba(X_te)[:, 1]
print("test log-loss: %.3f  accuracy: %.3f" % (log_loss(y_te, p), accuracy_score(y_te, p > 0.5)))

coef = clf[-1].coef_[0]
top = np.argsort(np.abs(coef))[::-1][:3]
for i in top:   # odds ratio per +1 standard deviation of the feature
    print(f"{data.feature_names[i]:22s} w={coef[i]:+.2f}  odds x{np.exp(coef[i]):.2f}")

This reaches about 0.96 test accuracy with a log-loss near 0.09. Because features were standardised, each coefficient is per standard deviation. In this dataset class 1 is benign, so the negative weights (for example on "radius error", about −1.2-1.2, odds ratio about 0.30) mean larger values make benign less likely: each extra standard deviation cuts the odds of benign to roughly a third. Remember the caveat from earlier: many of these 30 features are strongly correlated (radius, perimeter and area measure nearly the same thing), so individual coefficients should be interpreted cautiously.

Common pitfalls

  • Not scaling before regularising, so the penalty depends on arbitrary units.
  • Forgetting that LogisticRegression is regularised by default and that C is an inverse strength.
  • Reading coefficients causally, or comparing raw coefficients across features with different units.
  • Interpreting coefficients of strongly correlated features individually; they can split, shrink or flip sign.
  • Reporting training R2R^2 as evidence of predictive power.
  • Using linear regression for a binary target, which yields "probabilities" below 0 or above 1.
  • Treating 0.5 as the only threshold; choose the threshold from the costs of errors.
  • Computing the inverse (X⊤X)−1(X^\top X)^{-1} explicitly instead of using a stable solver.

In practice

  • Fit a regularised linear or logistic model as your first serious baseline on every tabular problem. It trains in seconds, rarely fails badly and tells you whether the signal is roughly linear.
  • Standardise numeric features and one-hot encode categoricals inside a pipeline; tune alpha or C on a logarithmic grid by cross-validation.
  • Use L1 or elastic net when you want a sparse, explainable model; ridge when you want stability with many correlated features.
  • For heavily skewed targets (prices, durations), model log⁡y\log y; for counts, consider Poisson regression (PoissonRegressor).
  • Logistic regression's probabilities are usually reasonably calibrated, which is why it remains standard in credit scoring and clinical risk scores; check calibration anyway (covered in model-evaluation-and-metrics).
  • When the linear baseline is clearly beaten by tree ensembles (next lesson), that tells you the signal involves interactions and non-linearities, which you can either engineer as features or leave to a more flexible model.

Key takeaways

  • Least squares has a closed-form solution from the normal equations, but gradient descent scales to problems where it does not.
  • Logistic regression models log-odds as a linear function, so each coefficient multiplies the odds by e to the power of that coefficient.
  • Log-loss heavily penalises confident wrong predictions and has the simple gradient (prediction minus target) times input.
  • Ridge shrinks weights smoothly, lasso sets some exactly to zero, and elastic net combines both; all require standardised features and a tuned strength.
  • Softmax with cross-entropy generalises logistic regression to many classes and is the output layer of most neural classifiers.
  • Coefficients describe associations conditional on the other features, not causes, and are unstable when features are correlated.

Terms in this lesson

Least squares
Choosing model parameters to minimise the sum of squared differences between predictions and targets.
Normal equations
The linear system X^T X theta = X^T y whose solution gives the least-squares parameters.
Learning rate
The step size multiplying the gradient in each gradient-descent update.
R-squared
One minus the ratio of the model's squared error to the squared error of predicting the mean.
Sigmoid
The function 1/(1+e^(-z)) that maps any real number to a probability between 0 and 1.
Log-odds (logit)
The logarithm of p/(1-p); logistic regression models it as a linear function of the features.
Log-loss
The negative average log-probability a model assigns to the true labels, also called binary cross-entropy.
Regularisation
A penalty on model complexity, such as the size of the weights, added to the loss to reduce overfitting.
Softmax
A function that turns a vector of K scores into K positive probabilities summing to one by exponentiating and normalising.

Check your understanding

  1. 1.

    A logistic regression model has coefficient 0.5 on feature x. What happens when x increases by 2 units, others fixed?

  2. 2.

    You have 200 features, many of them irrelevant, and you want a model that uses only a small subset. Which regulariser best fits this goal?

  3. 3.

    A model predicts p = 0.2 for an example whose true label is 1. What is its log-loss contribution (natural log)?

  4. 4.

    Which statement about the normal equation is correct?

  5. 5.

    Your linear regression has training R^2 = 0.93 and cross-validated R^2 = 0.41. What is the most likely diagnosis and fix?

Exercises

Work each problem before opening the solution.

  1. Exercise 1Core

    Fit a least-squares line by hand to the points (0,1),(1,3),(2,4),(3,6)(0, 1), (1, 3), (2, 4), (3, 6). Report ww, bb, the residuals, SSE, SST and R2R^2.

    Show solution

    Means: xˉ=1.5\bar x = 1.5, yˉ=3.5\bar y = 3.5.

    Numerator ∑(xi−xˉ)(yi−yˉ)=(−1.5)(−2.5)+(−0.5)(−0.5)+(0.5)(0.5)+(1.5)(2.5)=3.75+0.25+0.25+3.75=8\sum (x_i - \bar x)(y_i - \bar y) = (-1.5)(-2.5) + (-0.5)(-0.5) + (0.5)(0.5) + (1.5)(2.5) = 3.75 + 0.25 + 0.25 + 3.75 = 8.

    Denominator ∑(xi−xˉ)2=2.25+0.25+0.25+2.25=5\sum (x_i - \bar x)^2 = 2.25 + 0.25 + 0.25 + 2.25 = 5.

    w=8/5=1.6w = 8/5 = 1.6, b=3.5−1.6×1.5=1.1b = 3.5 - 1.6 \times 1.5 = 1.1.

    Predictions: $1.1, 2.7, 4.3, 5.9.Residuals. Residuals y - \hat y:: -0.1, 0.3, -0.3, 0.1$.

    SSE=0.01+0.09+0.09+0.01=0.20\text{SSE} = 0.01 + 0.09 + 0.09 + 0.01 = 0.20. SST=6.25+0.25+0.25+6.25=13\text{SST} = 6.25 + 0.25 + 0.25 + 6.25 = 13.

    R2=1−0.20/13=0.985R^2 = 1 - 0.20/13 = 0.985.

  2. Exercise 2Core

    A logistic regression predicts exam success from hours studied: w=0.8w = 0.8, b=−4b = -4. (a) At how many hours is P(pass)=0.5P(\text{pass}) = 0.5? (b) What is P(pass)P(\text{pass}) for 7 hours? (c) By what factor do the odds change per extra hour? (d) A student who studied 7 hours failed; what is the log-loss for that example?

    Show solution

    (a) P=0.5P = 0.5 when z=0.8h−4=0z = 0.8h - 4 = 0, so h=5h = 5 hours.

    (b) z=0.8×7−4=1.6z = 0.8 \times 7 - 4 = 1.6; σ(1.6)=1/(1+e−1.6)=1/(1+0.2019)=0.832\sigma(1.6) = 1/(1 + e^{-1.6}) = 1/(1 + 0.2019) = 0.832.

    (c) The odds are multiplied by e0.8=2.23e^{0.8} = 2.23 per hour.

    (d) True label y=0y = 0, so loss =−log⁡(1−0.832)=−log⁡(0.168)=1.78= -\log(1 - 0.832) = -\log(0.168) = 1.78 - a large loss, because the model was fairly confident and wrong.

  3. Exercise 3Core

    Implement binary logistic regression with batch gradient descent in numpy on the standardised breast cancer dataset (load_breast_cancer). Report the training log-loss and accuracy, and compare accuracy with scikit-learn's LogisticRegression using a very large C (almost no regularisation).

    Show solution
    import numpy as np
    from sklearn.datasets import load_breast_cancer
    from sklearn.preprocessing import StandardScaler
    from sklearn.linear_model import LogisticRegression
    
    X, y = load_breast_cancer(return_X_y=True)
    X = StandardScaler().fit_transform(X)
    n, d = X.shape
    
    def sigmoid(z):
        return 1 / (1 + np.exp(-z))
    
    w, b, lr = np.zeros(d), 0.0, 0.1
    for step in range(5000):
        p = sigmoid(X @ w + b)
        grad_w = X.T @ (p - y) / n          # gradient of mean log-loss
        grad_b = np.mean(p - y)
        w -= lr * grad_w
        b -= lr * grad_b
    
    p = np.clip(sigmoid(X @ w + b), 1e-12, 1 - 1e-12)
    loss = -np.mean(y * np.log(p) + (1 - y) * np.log(1 - p))
    print(f"numpy GD: train log-loss {loss:.4f}, accuracy {np.mean((p > 0.5) == y):.3f}")
    
    sk = LogisticRegression(C=1e6, max_iter=10000).fit(X, y)   # C huge = almost no penalty
    print(f"sklearn : accuracy {sk.score(X, y):.3f}")
    

    A typical run gives a training log-loss of about 0.05 and accuracy about 0.99 for the numpy version, and about 0.99 for scikit-learn. They differ slightly because 5,000 gradient steps have not fully converged, while the nearly unregularised scikit-learn solver pushes further toward the (almost separable) optimum. These are training numbers; on held-out data an unregularised model would typically do no better, and often worse, than the default C=1.

  4. Exercise 4Challenge

    (a) Derive the closed-form ridge solution w^=(X⊤X+λI)−1X⊤y\hat w = (X^\top X + \lambda I)^{-1} X^\top y for centred data with loss ∥y−Xw∥2+λ∥w∥2\lVert y - Xw \rVert^2 + \lambda \lVert w \rVert^2. (b) Explain why it exists even when X⊤XX^\top X is singular. (c) Implement it in numpy on load_diabetes with λ=10\lambda = 10 and verify it matches sklearn.linear_model.Ridge(alpha=10), including the intercept.

    Show solution

    (a) J(w)=(y−Xw)⊤(y−Xw)+λw⊤wJ(w) = (y - Xw)^\top (y - Xw) + \lambda w^\top w. Gradient: ∇J=−2X⊤(y−Xw)+2λw\nabla J = -2X^\top(y - Xw) + 2\lambda w. Setting it to zero: X⊤Xw+λw=X⊤yX^\top X w + \lambda w = X^\top y, i.e. (X⊤X+λI)w=X⊤y(X^\top X + \lambda I) w = X^\top y, giving w^=(X⊤X+λI)−1X⊤y\hat w = (X^\top X + \lambda I)^{-1} X^\top y. JJ is strictly convex for λ>0\lambda > 0, so this is the unique minimum.

    (b) X⊤XX^\top X is positive semi-definite, so its eigenvalues are ≥0\ge 0. Adding λI\lambda I raises every eigenvalue by λ>0\lambda > 0, making the matrix positive definite and therefore invertible, even with collinear features.

    (c) Scikit-learn does not penalise the intercept, so centre XX and yy, solve for ww, then recover b=yˉ−xˉ⊤wb = \bar y - \bar x^\top w:

    import numpy as np
    from sklearn.datasets import load_diabetes
    from sklearn.linear_model import Ridge
    
    X, y = load_diabetes(return_X_y=True)
    alpha = 10.0
    Xc = X - X.mean(axis=0)
    yc = y - y.mean()
    w = np.linalg.solve(Xc.T @ Xc + alpha * np.eye(X.shape[1]), Xc.T @ yc)
    b = y.mean() - X.mean(axis=0) @ w
    
    sk = Ridge(alpha=alpha).fit(X, y)
    print(np.allclose(w, sk.coef_), np.isclose(b, sk.intercept_))   # True True
    

    Both checks print True. As a sanity check on shrinkage, the norm of the ridge weight vector here is about 146, versus about 1,378 for the unregularised least-squares solution on the same centred data.

Further reading

Go further

Sources

Fact-checked and last reviewed 2026-11-05

Stay in the loop
Prometheus OS™ · Native AI OS · Built for the future © 2025 - 2026