Logistic Regression and Generalized Linear Models
Logistic regression models a binary outcome’s conditional probability and can turn that probability into a class decision using a threshold. Viewing it as a generalized linear model (GLM) connects its likelihood, loss, and fitting procedure with regression models for other outcomes. We will follow that connection through binary labels, counts, and counts observed over different exposure times.
The three pieces of a GLM
For observation i, a GLM models the conditional mean \(\mu_i=E[Y_i\mid x_i]\). The linear predictor \(\eta_i=x_i^\top\beta\) combines the input columns and coefficients; an intercept is included through a column of ones. A link function connects the mean to that predictor: \(g(\mu_i)=\eta_i\), or \(\mu_i=g^{-1}(\eta_i)\). The response distribution describes variation around the mean and is drawn from an exponential family, which includes the examples in the table. The design can still contain transformed inputs and interactions; “linear” refers to the coefficients in the predictor.
| Target | Distribution | Link used here | Model |
|---|---|---|---|
| a real number | Normal | identity | linear regression |
| a binary label | Bernoulli | logit \(\log\frac{p}{1-p}\) | logistic regression |
| a count | Poisson | log | Poisson regression |
| a count with extra spread | negative binomial | log | negative binomial regression |
| a positive skewed amount | Gamma | log | Gamma regression |
For binary data, the logit is \(\log(p/(1-p))\) and its inverse is the sigmoid. A predictor value of 0 gives probability 0.5; \(\eta=\log2\) gives odds 2 and probability 2/3. A log link has inverse \(\mu=\exp(\eta)\), so \(\eta=\log3\) gives expected count 3. A predicted count mean can be noninteger even though each observed count is an integer. A canonical link ties the linear predictor to the distribution’s natural parameter in its exponential-family form. The table lists common choices: log is canonical for Poisson, but not for Gamma or the usual fixed-dispersion negative-binomial family. A finite log predictor gives a positive mean; numerical overflow and underflow still require care.
Where the loss comes from
For conditionally independent binary observations with modeled probabilities \(p_i\), the log-likelihood is \(\sum_i[y_i\log p_i+(1-y_i)\log(1-p_i)]\). Negating it gives the summed binary cross-entropy. Using the mean instead of the sum has the same unpenalized minimizer. For one observed positive label with predicted probability 0.8, the loss is \(-\log0.8\approx0.223\); predicting 0.2 gives about 1.609. This likelihood interpretation does not guarantee that a fitted probability model is correctly specified.
A Gaussian response with constant variance gives squared error up to a positive scale and a term independent of the mean parameters. Poisson deviance is twice the difference between the saturated-model log-likelihood (allowing a separate mean for each observation) and the fitted-model log-likelihood, so minimizing it gives the same unpenalized coefficient fit as maximizing the Poisson likelihood. Deviance and negative log-likelihood are not numerically identical. Losses can also be chosen for their prediction target or decision costs; Loss Functions and Surrogates discusses those interpretations.
How it is actually fitted
General logistic regression requires numerical optimization. One method is iteratively reweighted least squares (IRLS). For the Bernoulli logit model, set \(w_i=p_i(1-p_i)\) and \(z_i=\eta_i+(y_i-p_i)/w_i\), then fit z by weighted least squares. This gives a Newton step for the unpenalized likelihood when the weights are positive and the design has full rank. It is one fitting method; scikit-learn’s default solver uses L-BFGS. The small example below rejects tiny weights and a rank-deficient solve rather than hiding them with clipping. A production solver also needs safeguards for difficult steps.
In the code, Xd is the design matrix including the intercept, and b is the current coefficient estimate. At the initial predictor value 0, p=0.5 and w=0.25, so a positive observation has working response z=0+(1−0.5)/0.25=2. Each row gets weight w and working response z; multiplying the design and response by the square root of the weight turns weighted least squares into an ordinary least-squares solve.
import numpy as np
from sklearn.linear_model import LogisticRegression
from scipy.special import expit
rng = np.random.default_rng(0)
n = 2000
X = rng.normal(size=(n, 3))
Xd = np.column_stack([np.ones(n), X])
beta_true = np.array([-0.5, 1.2, -0.8, 0.4])
y = (rng.random(n) < 1 / (1 + np.exp(-Xd @ beta_true))).astype(float)
b = np.zeros(4)
for it in range(30):
eta = Xd @ b
p = expit(eta)
w = p * (1 - p)
if np.min(w) < 1e-12:
raise RuntimeError("Tiny IRLS weights: inspect separation or scaling")
z = eta + (y - p) / w
root_w = np.sqrt(w)
b_new, _, rank, _ = np.linalg.lstsq(root_w[:, None] * Xd, root_w * z, rcond=None)
if rank != Xd.shape[1]:
raise RuntimeError("IRLS design is rank deficient")
if np.abs(b_new - b).max() < 1e-10:
b = b_new
break
b = b_new
else:
raise RuntimeError("IRLS did not converge within 30 iterations")
assert np.max(np.abs(Xd.T @ (expit(Xd @ b) - y))) < 1e-7
sk = LogisticRegression(C=np.inf, tol=1e-10, max_iter=5000).fit(X, y)
print(f"converged in {it + 1} iterations")
print("IRLS ", np.round(b, 6))
print("sklearn", np.round(np.r_[sk.intercept_, sk.coef_[0]], 6))
print("true ", beta_true)
np.testing.assert_allclose(b, np.r_[sk.intercept_, sk.coef_[0]], atol=1e-6, rtol=0)
# converged in 6 iterations
# IRLS [-0.484414 1.112975 -0.746429 0.458875]
# sklearn [-0.484414 1.112975 -0.746429 0.458875]
# true [-0.5 1.2 -0.8 0.4]
The loop reports convergence only after its coefficient-change criterion is met. It also checks the likelihood gradient and compares coefficients with a tightly converged scikit-learn fit. Both coefficient vectors agree to the displayed six decimal places here and pass an absolute-tolerance check of 1e-6. This is a numerical check for this dataset. Newton’s local quadratic convergence needs a sufficiently close starting point and a smooth objective with a nonsingular Hessian; convexity alone does not guarantee rapid or global convergence. Separation and ill-conditioning can make this iteration fail.
The fitted coefficients differ from the generating coefficients because a finite sample has random outcomes. The solver comparison helps separate numerical disagreement from sampling variation here. Standard errors quantify sampling uncertainty under assumptions; a coefficient close to its generating value in one run does not by itself validate those uncertainty estimates.
Separation
Under complete separation, the unpenalized logistic likelihood has a supremum but no finite coefficient vector attaining it. Scaling a separating direction makes every fitted class probability approach its observed label. Not every coefficient must diverge along every fitting path. A numerical optimizer can stop at large finite values because its tolerance is satisfied, even though a finite unpenalized maximum does not exist.
import numpy as np
from sklearn.linear_model import LogisticRegression
X = np.array([[-2.0], [-1.0], [1.0], [2.0]])
y = np.array([0, 0, 1, 1]) # perfectly separable
for C in (1e0, 1e2, 1e4, 1e8):
m = LogisticRegression(C=C, tol=1e-10, max_iter=100_000).fit(X, y)
print(f"C={C:8.0e} coef {m.coef_[0, 0]:9.4f}"
f" P(y=1 | x=1) {m.predict_proba([[1.0]])[0, 1]:.8f}")
# C= 1e+00 coef 1.0066 P(y=1 | x=1) 0.73235312
# C= 1e+02 coef 3.9453 P(y=1 | x=1) 0.98102172
# C= 1e+04 coef 7.8441 P(y=1 | x=1) 0.99960810
# C= 1e+08 coef 16.3200 P(y=1 | x=1) 0.99999992
Each finite C in this table still specifies a penalized fit. In this two-class, full-rank example, positive L2 regularization gives a finite optimum; increasing C weakens that penalty and allows a larger coefficient. The limiting unpenalized problem has no finite solution. The code uses a tighter tolerance than the default so a very weak penalty is less likely to be confused with early numerical stopping. Report the penalty and convergence settings along with a coefficient of this size.
Separation can occur in small samples, high-dimensional designs, or rare categorical levels, and it can also arise from a genuinely deterministic relationship. Investigate prediction-time availability if a feature perfectly predicts the outcome, but separation alone is not evidence of leakage. Large coefficients, convergence warnings, and very extreme probabilities are clues; probabilities need not round to exactly 0 or 1 for separation to be present.
Scikit-learn applies regularization by default, supporting finite and more stable fits in many settings. The unpenalized comparisons here use C=np.inf with the current API. Removing the penalty does not ensure that a finite maximum exists or that the optimizer found it. Bias-reducing approaches such as Firth logistic regression provide another option under appropriate design conditions; they do not resolve leakage or identify a causal effect.
A log-linear mean for counts
import numpy as np
from sklearn.linear_model import LinearRegression, PoissonRegressor
rng = np.random.default_rng(2)
n = 4000
x = rng.normal(size=(n, 2))
counts = rng.poisson(np.exp(0.5 + 0.8 * x[:, 0] - 0.4 * x[:, 1]))
ols = LinearRegression().fit(x, counts)
poisson = PoissonRegressor(alpha=0).fit(x, counts)
print(f"OLS coef {np.round(ols.coef_, 4)} intercept {ols.intercept_:.4f}")
print(f"Poisson coef {np.round(poisson.coef_, 4)} intercept {poisson.intercept_:.4f}")
print(f"true [0.8, -0.4] intercept 0.5")
print(f"OLS predicts a negative count for {np.mean(ols.predict(x) < 0):.4f} of rows"
f", min {ols.predict(x).min():.3f}")
print(f"Poisson minimum prediction {poisson.predict(x).min():.4f}")
# OLS coef [ 1.9393 -1.0111] intercept 2.4533
# Poisson coef [ 0.7939 -0.4025] intercept 0.5010
# true [0.8, -0.4] intercept 0.5
# OLS predicts a negative count for 0.1320 of rows, min -6.450
# Poisson minimum prediction 0.0449
The fitted Poisson coefficients are close to the generating log-mean coefficients in this sample. OLS fits an additive mean on the original scale, so its coefficients do not estimate those same parameters. The example compares representations of a log-linear mean; it does not imply that every count prediction task requires a full Poisson distributional assumption.
OLS produces negative predicted means for 13.2% of these training rows, down to −6.45. That is incompatible with a count mean. The log-link model produces positive means by construction in exact arithmetic. Positivity alone does not establish a correct mean model, calibrated uncertainty, or good held-out performance; this code reports fitted values on the data used for estimation.
For a Poisson response, conditional variance equals the conditional mean at fixed inputs. Overall sample variance can exceed the overall mean simply because the means vary across inputs, even in this correctly generated Poisson example. Extra conditional dispersion can invalidate Poisson-based standard errors. Under a correctly specified log mean and suitable regularity conditions, Poisson quasi-likelihood can still consistently estimate the mean parameters; this is not a finite-sample unbiasedness guarantee. Alternatives include appropriate robust covariance estimates or a justified negative-binomial variance model, often \(\mu+\alpha\mu^2\). Neither repairs a wrong mean automatically.
References
- Kosmidis and contributors. brglm2: bias reduction for generalized linear models
- scikit-learn documentation. Poisson regression and non-normal loss
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
