AI Knowledge Tree

Logistic regression

Build logistic regression from log-odds, write its log-likelihood, and fit it with IRLS or gradient descent.

Linear regression returns a real number; classification needs a probability. Logistic regression keeps the linear predictor but maps it to [0,1][0, 1] via the logistic (sigmoid) function. The motivation is the exponential-family machinery from A2: the logistic is the natural inverse link of the Bernoulli.

The model

For binary labels yi∈{0,1}y_i \in \{0, 1\},

Pr⁡(yi=1∣xi)=σ(xi⊤β),σ(z)=11+e−z.\Pr(y_i = 1 \mid x_i) = \sigma(x_i^\top \beta), \qquad \sigma(z) = \frac{1}{1 + e^{-z}}.

Equivalently, the log-odds are linear in the features:

log⁡Pr⁡(y=1∣x)Pr⁡(y=0∣x)=x⊤β.\log\frac{\Pr(y = 1 \mid x)}{\Pr(y = 0 \mid x)} = x^\top \beta.

This is the logit link. From the exponential-family lesson, the Bernoulli's natural parameter is the log-odds: the logistic does not just look natural, it falls out of the Bernoulli's algebra[1].

The link runs the whole real line into [0,1][0, 1], and a few points fix the scale. A score of 00 is the decision boundary, σ(0)=0.5\sigma(0) = 0.5. A score of ±2\pm 2 is already fairly confident (σ(2)≈0.88\sigma(2) \approx 0.88, σ(−2)≈0.12\sigma(-2) \approx 0.12), and ±4\pm 4 is nearly committed (σ(4)≈0.982\sigma(4) \approx 0.982). The coefficients act on the odds, not the probability: with the code block's β=(−0.5,1.5,−1.0,0.7)\beta = (-0.5, 1.5, -1.0, 0.7), raising feature 1 by one unit adds 1.51.5 to the score, which multiplies the odds by e1.5≈4.48e^{1.5} \approx 4.48; raising feature 2 by one unit subtracts 1.01.0, multiplying the odds by e−1.0≈0.37e^{-1.0} \approx 0.37. The same coefficient moves the probability by very different amounts depending on where the score starts: near the boundary the sigmoid is steep, out in the tails it is flat.

The log-likelihood

Treat each example as a Bernoulli trial with success probability pi=σ(xi⊤β)p_i = \sigma(x_i^\top\beta). The log-likelihood for one example is yilog⁡pi+(1−yi)log⁡(1−pi)y_i \log p_i + (1 - y_i)\log(1 - p_i); summing,

ℓ(β)=∑i=1nyilog⁡pi+(1−yi)log⁡(1−pi).\ell(\beta) = \sum_{i=1}^n y_i \log p_i + (1 - y_i)\log(1 - p_i).

Equivalently, since log⁡pi=log⁡σ(xi⊤β)\log p_i = \log\sigma(x_i^\top\beta) simplifies via log⁡σ(z)=−log⁡(1+e−z)\log\sigma(z) = -\log(1 + e^{-z}),

ℓ(β)=∑iyi xi⊤β−log⁡(1+exi⊤β).\ell(\beta) = \sum_i y_i\, x_i^\top\beta - \log(1 + e^{x_i^\top\beta}).

The negative of ℓ\ell is the cross-entropy loss.

Gradient and Hessian

The sigmoid has a clean derivative: σ′(z)=σ(z)(1−σ(z))\sigma'(z) = \sigma(z)(1 - \sigma(z)). Direct computation gives

∇ℓ=X⊤(y−p),∇2ℓ=−X⊤WX,\nabla \ell = X^\top(\mathbf{y} - \mathbf{p}), \qquad \nabla^2 \ell = -X^\top W X,

with W=diag⁡(pi(1−pi))W = \operatorname{diag}(p_i(1 - p_i)). The Hessian is negative semidefinite, so the log-likelihood is concave: any stationary point is a global maximum. There is no closed form (the gradient equation X⊤(y−σ(Xβ))=0X^\top(\mathbf{y} - \sigma(X\beta)) = 0 is nonlinear in β\beta), so fitting is iterative.

Newton's method as IRLS

Newton's method applied to maximizing ℓ\ell:

β(t+1)=β(t)+(X⊤W(t)X)−1X⊤(y−p(t)).\beta^{(t+1)} = \beta^{(t)} + (X^\top W^{(t)} X)^{-1} X^\top (\mathbf{y} - \mathbf{p}^{(t)}).

Rewriting, define the working response zi=xi⊤β(t)+(yi−pi(t))/(pi(t)(1−pi(t)))z_i = x_i^\top \beta^{(t)} + (y_i - p_i^{(t)}) / (p_i^{(t)}(1 - p_i^{(t)})). Each Newton step is then a weighted least-squares fit β(t+1)=(X⊤WX)−1X⊤Wz\beta^{(t+1)} = (X^\top W X)^{-1} X^\top W \mathbf{z}, hence the name iteratively reweighted least squares (IRLS)[2]. Quadratic local convergence, the same as Newton on any smooth convex objective.

For large nn or sparse features, plain gradient descent (or its variants from A3) is simpler and often faster per epoch.

Regularization

Adding L2 gives ridge logistic regression: ℓ(β)−λ2∥β∥2\ell(\beta) - \frac{\lambda}{2}\lVert\beta\rVert^2. Adding L1 gives L1 logistic regression for sparse classifiers. Both are MAP under a Gaussian or Laplace prior on β\beta, and both are fit with the same gradient/proximal machinery as their regression cousins.

Logistic regression: the sigmoid maps a linear score to a probability (left), giving a linear decision boundary at P = 0.5 with smooth probability contours around it (right).
Logistic regression: the sigmoid maps a linear score to a probability (left), giving a linear decision boundary at P = 0.5 with smooth probability contours around it (right).

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)
n, d = 1000, 4
X = np.column_stack([np.ones(n), rng.normal(size=(n, d - 1))])
beta_true = np.array([-0.5, 1.5, -1.0, 0.7])
p_true = 1 / (1 + np.exp(-X @ beta_true))
y = (rng.random(n) < p_true).astype(float)

def sigmoid(z): return 1 / (1 + np.exp(-z))

def fit_irls(X, y, n_steps=10):
    beta = np.zeros(X.shape[1])
    for _ in range(n_steps):
        p = sigmoid(X @ beta)
        W = p * (1 - p) + 1e-8
        z = X @ beta + (y - p) / W
        beta = np.linalg.solve(X.T @ (W[:, None] * X), X.T @ (W * z))
    return beta

def fit_gd(X, y, lr=0.1, n_steps=2000):
    beta = np.zeros(X.shape[1])
    for _ in range(n_steps):
        p = sigmoid(X @ beta)
        beta += lr * X.T @ (y - p) / len(y)
    return beta

beta_irls = fit_irls(X, y)
beta_gd = fit_gd(X, y)
print("IRLS beta:", np.round(beta_irls, 3))
print("GD   beta:", np.round(beta_gd, 3))
print("true     :", beta_true)

# Predictions
p_hat = sigmoid(X @ beta_irls)
acc = np.mean((p_hat > 0.5) == y)
print(f"accuracy: {acc:.3f}")

# Log-likelihood is concave: Hessian -X^T W X is NSD
W = p_hat * (1 - p_hat)
H = -X.T @ (W[:, None] * X)
print("Hessian eigvals all <= 0:", np.all(np.linalg.eigvalsh(H) <= 1e-10))

IRLS converges in a handful of steps; gradient descent reaches the same solution more slowly; the Hessian's eigenvalues are nonpositive, confirming concavity.

Exercises

Exercise 1. A fitted model has β=(−0.5,1.5,−1.0,0.7)\beta = (-0.5, 1.5, -1.0, 0.7) (the first entry is the intercept). For a feature vector x=(1,1,0,0)x = (1, 1, 0, 0) (the leading 1 is the intercept term), compute the linear score, the predicted probability σ(x⊤β)\sigma(x^\top\beta), and the odds, by hand.

Solution

Step 1. The score is the dot product x⊤β=1⋅(−0.5)+1⋅1.5+0⋅(−1.0)+0⋅0.7=1.0x^\top\beta = 1\cdot(-0.5) + 1\cdot 1.5 + 0\cdot(-1.0) + 0\cdot 0.7 = 1.0.

Step 2. Apply the sigmoid:

σ(1.0)=11+e−1.0=11+0.3679=11.3679≈0.7311.\sigma(1.0) = \frac{1}{1 + e^{-1.0}} = \frac{1}{1 + 0.3679} = \frac{1}{1.3679} \approx 0.7311.

Step 3. The odds are p1−p=0.73110.2689≈2.718\frac{p}{1 - p} = \frac{0.7311}{0.2689} \approx 2.718, which equals e1.0e^{1.0}, the exponentiated score. This is the log-odds reading: the score is the log of the odds, so the odds are escoree^{\text{score}}.

Exercise 2. Derive the gradient ∇ℓ=X⊤(y−p)\nabla\ell = X^\top(\mathbf{y} - \mathbf{p}) of the log-likelihood, starting from ℓ(β)=∑iyixi⊤β−log⁡(1+exi⊤β)\ell(\beta) = \sum_i y_i x_i^\top\beta - \log(1 + e^{x_i^\top\beta}) and using σ′(z)=σ(z)(1−σ(z))\sigma'(z) = \sigma(z)(1 - \sigma(z)).

Solution

Step 1. Differentiate one example's contribution with respect to β\beta. The first term yixi⊤βy_i x_i^\top\beta has gradient yixiy_i x_i. For the second term, with zi=xi⊤βz_i = x_i^\top\beta,

∇βlog⁡(1+ezi)=ezi1+ezi xi=σ(zi) xi=pi xi,\nabla_\beta \log(1 + e^{z_i}) = \frac{e^{z_i}}{1 + e^{z_i}}\, x_i = \sigma(z_i)\, x_i = p_i\, x_i,

using ez1+ez=11+e−z=σ(z)\frac{e^z}{1 + e^z} = \frac{1}{1 + e^{-z}} = \sigma(z) and the chain rule ∇βzi=xi\nabla_\beta z_i = x_i.

Step 2. Combine the two terms for example ii:

∇β(yizi−log⁡(1+ezi))=yixi−pixi=(yi−pi) xi.\nabla_\beta\big(y_i z_i - \log(1 + e^{z_i})\big) = y_i x_i - p_i x_i = (y_i - p_i)\, x_i.

Step 3. Sum over examples. Writing the residuals yi−piy_i - p_i as the vector y−p\mathbf{y} - \mathbf{p} and stacking the xix_i as rows of XX,

∇ℓ=∑i(yi−pi) xi=X⊤(y−p).\nabla\ell = \sum_i (y_i - p_i)\, x_i = X^\top(\mathbf{y} - \mathbf{p}).

The gradient is the design matrix applied to the prediction residuals, the same shape as the OLS gradient with p\mathbf{p} replacing XβX\beta.

Exercise 3 (code). The log-likelihood is concave, so it has a unique maximum. Implement the negative log-likelihood and its gradient, then verify the gradient is correct against a finite-difference approximation at a random β\beta. Confirm the maximum absolute difference is below 10−510^{-5}.

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
n, d = 200, 4
X = np.column_stack([np.ones(n), rng.normal(size=(n, d - 1))])
beta_true = np.array([-0.5, 1.5, -1.0, 0.7])
p_true = 1 / (1 + np.exp(-X @ beta_true))
y = (rng.random(n) < p_true).astype(float)

def sigmoid(z): return 1 / (1 + np.exp(-z))

def neg_log_lik(beta):
    z = X @ beta
    # log(1 + e^z) computed stably as max(z,0) + log(1 + e^{-|z|})
    log1pexp = np.maximum(z, 0) + np.log1p(np.exp(-np.abs(z)))
    return -np.sum(y * z - log1pexp)

def grad(beta):
    return -X.T @ (y - sigmoid(X @ beta))   # negative of X^T(y - p)

beta0 = rng.normal(size=d)
g_analytic = grad(beta0)

# Central finite differences
eps = 1e-6
g_numeric = np.zeros(d)
for j in range(d):
    step = np.zeros(d); step[j] = eps
    g_numeric[j] = (neg_log_lik(beta0 + step) - neg_log_lik(beta0 - step)) / (2 * eps)

max_diff = np.abs(g_analytic - g_numeric).max()
assert max_diff < 1e-5
print("max |analytic - numeric| gradient:", round(float(max_diff), 9))

The analytic gradient X⊤(y−p)X^\top(\mathbf{y} - \mathbf{p}) matches the finite-difference estimate to well under 10−510^{-5}, confirming the derivation in Exercise 2 and the formula used inside IRLS.

The next lesson generalizes to multi-class with the softmax.


References

  1. [1]Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning (2nd ed.), §4.4. Springer. doi:10.1007/978-0-387-84858-7. Logistic regression and its log-odds link.↩
  2. [2]McCullagh, P., & Nelder, J. A. (1989). Generalized Linear Models (2nd ed.), §4.4. Chapman & Hall. IRLS as the standard fitting algorithm.↩