Generalized Additive Models › Formula

Michael BrenndoerferOctober 25, 202614 min read
Machine Learning from ScratchAll chapters
Part 5 of 15Linear and Generalized Linear Models
Chapter 39Generalized Additive Models › Formula
Reading section 4 of 10 · Formula · 14 minSection 4 of 10 · 14 min

Section 4 of 10 · 14 min read

Generalized Additive Models › FormulaSection view · Sections in this chapter4 / 10 ↓
Reading tools Definitions, links & notes

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Notes & comments

A GAM is best read as the answer to a design question: how do we let every predictor bend, yet keep the model as tractable and interpretable as a linear one? The formula answers that question in three interlocking layers, and each layer exists to solve a specific problem.

  1. An additive predictor built from smooths replaces the single straight line of linear regression with a sum of curves. This buys flexibility while preserving the "one term per predictor" structure that makes the model readable.
  2. A link function connects that additive predictor to the mean of the response, so that the same structure can serve a Gaussian, binary, or count outcome.
  3. A smoothness penalty discourages rough fitted curves. Identifiability constraints are also needed, and the amount of smoothing still requires selection.

It helps to see each problem before its formula. Picture two failure modes we must avoid. If we let the smooths be arbitrarily wiggly, they will thread through noise; if we over-restrict them, we are back to a straight line and lose the whole point. The penalty is the dial between these extremes. And if we ignore the scale of the response, the same smooths cannot respect a probability or a count. The link is what fixes the scale. With both ideas in place, the additive predictor is the piece that ties the predictors together. We build the layers in that order of need.

Once the basis is fixed, the additive predictor is linear in its unknown coefficients. For a Gaussian response with an identity link, fitting reduces to a quadratic penalized least-squares problem. Other response families generally require iterative likelihood fitting. The following steps distinguish the predictor, link, and fitting objective.

The additive predictor sums an intercept and one component per predictor for observation ii:

ηi=β0+f1(xi1)+f2(xi2)+⋯+fp(xip),\eta_i = \beta_0 + f_1(x_{i1}) + f_2(x_{i2}) + \cdots + f_p(x_{ip}),

where:

  • i=1,…,ni = 1, \dots, n: index over observations,
  • ηi\eta_i: the value of the additive predictor for observation ii,
  • β0\beta_0: an intercept shared by all observations,
  • pp: the number of predictors,
  • xijx_{ij}: the value of predictor jj for observation ii,
  • fjf_j: a smooth function of predictor jj, not necessarily linear.

If fj(x)=βjxf_j(x)=\beta_j x for every predictor, this becomes the predictor from Multiple Linear Regression. A GAM relaxes that restriction one term at a time: each fjf_j may curve, but the terms still add, so we can still read the model one predictor at a time.

Why insist on additivity instead of fitting one flexible surface f(x1,…,xp)f(x_1, \dots, x_p)? Additivity restricts the model to a smaller family of surfaces. The additive decomposition restricts the unknown surface to a sum of one-dimensional curves, whose coefficients are fitted jointly. That trade between flexibility within each predictor and additivity across predictors is the structural bet of a GAM, and it is exactly what we will exploit when we expand each fjf_j in a basis.

There is a second gap to close. The smooths naturally produce a real number, but the response need not live on the real line: a binary outcome is zero or one, while a finite logit predictor gives a fitted mean in (0,1)(0,1). A count mean need not be an integer; a log link keeps it positive. The link function bridges that gap:

g(μi)=ηi,μi=E[yi∣xi].g(\mu_i) = \eta_i, \qquad \mu_i = \mathbb{E}[y_i \mid \mathbf{x}_i].

Here gg is a monotone, differentiable link function. Its role is to connect the permissible mean to the additive predictor. The log and logit links map their mean domains onto the real line; an inverse link has a restricted predictor domain and requires corresponding constraints. Common choices are:

  • identity link g(μ)=μg(\mu) = \mu for Gaussian regression (this is the case we will spend most time on),
  • logit link g(μ)=log⁡(μ/(1−μ))g(\mu) = \log(\mu / (1 - \mu)) for binary responses,
  • log link g(μ)=log⁡(μ)g(\mu) = \log(\mu) for counts,
  • inverse link g(μ)=1/μg(\mu) = 1/\mu for gamma or inverse-Gaussian responses.

For a Gaussian GAM with identity link, the model predicts the conditional mean directly. For a binary GAM with logit link, the model predicts the log-odds of success, and each fjf_j describes how the log-odds change as a smooth function of xjx_j. For a count GAM with log link, each fjf_j describes how the log expected count changes.

Notice the clean division of labor this creates: the smooths describe shape on the scale of η\eta, and the link describes scale. That separation is what allows one spline-and-penalty mechanism to serve radically different response types without being redesigned for each.

We are foreshadowing here because the later chapter Generalized Linear Models develops the link-function framework in full. For this chapter the important point is that the same additive machinery, namely smooths, penalties, and penalized least-squares solves, extends to suitable GLM families and links through penalized likelihood, with additional iterative fitting (iterative reweighting, which we will sketch but not build from scratch).

Spline bases and smoothness penalties

To compute the fit using finitely many coefficients, we represent each smooth fjf_j with a spline basis:

fj(x)=∑k=1Kjβjk bjk(x),f_j(x) = \sum_{k=1}^{K_j} \beta_{jk} \, b_{jk}(x),

where:

  • KjK_j is the number of basis functions we allocate to predictor jj,
  • bjk(x)b_{jk}(x) is the kk-th basis function, a known deterministic function of xx,
  • βjk\beta_{jk} are unknown coefficients we will estimate.

We use B-spline basis functions: low-degree piecewise polynomials with local support. Natural cubic spline bases are another option, but their individual basis functions need not have local support. Taken together, they form a flexible family of curves. The dimension KjK_j controls the maximum possible wiggliness: with more basis functions we can represent more complex shapes.

A mental picture makes the next step natural. Each bjkb_{jk} is a localized bump, and the fitted smooth is a weighted sum of those bumps, with the weights given by the coefficients βjk\beta_{jk}. Increasing KjK_j adds more bumps and therefore more potential detail, but it also adds more coefficients that can conspire to fit noise.

Substituting the basis expansion into the additive predictor gives

ηi=β0+∑j=1p∑k=1Kjβjk bjk(xij).\eta_i = \beta_0 + \sum_{j=1}^p \sum_{k=1}^{K_j} \beta_{jk} \, b_{jk}(x_{ij}).

This is now linear in the coefficients βjk\beta_{jk}. If we stack all coefficients into a single vector β\boldsymbol\beta and expand each predictor into its basis, the model becomes η=Bβ\boldsymbol\eta = \mathbf{B}\boldsymbol\beta for an augmented design matrix B\mathbf{B} whose columns are the basis functions evaluated at each observation.

We now reach the second problem hinted at earlier. With enough independent basis columns, a spline can interpolate a finite sample at distinct inputs. Fitting noise that closely can hurt predictions on new observations. Too small a basis can instead exclude real structure. Rather than solve the flexibility problem by choosing the basis size, we keep the basis generous and control curvature directly. We penalize it with a quadratic penalty on the (second) differences of neighboring coefficients:

Pen(fj)=λj∑k=3Kj(βjk−2βj,k−1+βj,k−2)2.\text{Pen}(\mathbf{f}_j) = \lambda_j \sum_{k=3}^{K_j} \left( \beta_{jk} - 2\beta_{j,k-1} + \beta_{j,k-2} \right)^2.

This is the coefficient-difference penalty used in P-splines. Classical P-splines pair it with regularly spaced B-splines; our later Patsy example uses quantile knots with the same coefficient-difference regularizer, so its smoothing is tied to that parameterization. Each difference measures curvature in coefficient-index space. For smooth coefficient sequences on a fine, uniform knot grid with compatible boundary treatment, appropriate knot-spacing scaling makes this a discrete approximation to a derivative penalty. It is not generally identical to integrating the squared second derivative of the curve. Squaring and summing accumulates a measure of total curvature, so the penalty grows when the fitted coefficients oscillate from one basis function to the next. The parameter λj≥0\lambda_j \ge 0 controls how strongly that penalty acts:

  • λj=0\lambda_j = 0 gives an unpenalized spline, which can be as wiggly as the basis allows;
  • λj→∞\lambda_j \to \infty forces the coefficient sequence to be affine in its index; the resulting curve depends on the knot arrangement;
  • moderate λj\lambda_j interpolates between the two, giving the data room to bend but discouraging noise.

The penalty is deliberately a local statement about neighboring coefficients. It does not insist that the whole curve be flat; it only makes sharp reversals from one coefficient to the next expensive. That is why a moderate λj\lambda_j still permits broad bends (like a sinusoid) while a huge one suppresses the penalized directions. An affine coefficient sequence need not produce a globally straight line with quantile knots and repeated boundary knots.

PythonIn [3]
CollapseExpand
k_coef = 16
index = np.arange(k_coef)
D_pen = np.diff(np.eye(k_coef), n=2, axis=0)
S_pen = D_pen.T @ D_pen
coef_sequences = {
    "constant": np.ones(k_coef),
    "affine": 0.6 * index - 2.0,
    "sinusoid": 1.5 * np.sin(2.0 * np.pi * index / (k_coef - 1)),
    "alternating": np.where(index % 2 == 0, 1.0, -1.0),
}
penalty_values = {
    name: float(np.sum((D_pen @ coef) ** 2))
    for name, coef in coef_sequences.items()
}
VisualizationOut [4]
CollapseExpand
Four coefficient patterns with annotated second-difference penalties.
Second-difference penalty across four coefficient patterns. Constant and affine sequences incur zero penalty because they lie in the null space, while the alternating sequence pays the most, showing how the penalty charges for oscillation.

In matrix form the penalty for smooth jj can be written as

Pen(fj)=λj βj⊤D⊤D βj=λj βj⊤Sj βj,\text{Pen}(\mathbf{f}_j) = \lambda_j \, \boldsymbol\beta_j^\top \mathbf{D}^\top \mathbf{D} \, \boldsymbol\beta_j = \lambda_j \, \boldsymbol\beta_j^\top \mathbf{S}_j \, \boldsymbol\beta_j,

where:

  • βj=(βj1,…,βjKj)⊤\boldsymbol\beta_j = (\beta_{j1}, \dots, \beta_{jK_j})^\top collects the smooth coefficients,
  • D\mathbf{D} is the (Kj−2)×Kj(K_j - 2) \times K_j second-difference matrix, each row of the form (0,…,0,1,−2,1,0,…,0)(0, \dots, 0, 1, -2, 1, 0, \dots, 0),
  • Sj=D⊤D\mathbf{S}_j = \mathbf{D}^\top \mathbf{D} is a Kj×KjK_j \times K_j symmetric positive semidefinite matrix that encodes the penalty.

Putting the pieces together, the Gaussian GAM is fit by minimizing

L(β)=∑i=1n(yi−β0−∑j=1p∑k=1Kjβjk bjk(xij))2+∑j=1pλj βj⊤Sjβj.\mathcal{L}(\boldsymbol\beta) = \sum_{i=1}^n \left( y_i - \beta_0 - \sum_{j=1}^p \sum_{k=1}^{K_j} \beta_{jk} \, b_{jk}(x_{ij}) \right)^2 + \sum_{j=1}^p \lambda_j \, \boldsymbol\beta_j^\top \mathbf{S}_j \boldsymbol\beta_j.

Read the two terms as a negotiation. The first term asks the curve to explain the data; the second asks it to stay smooth. The smoothing parameters λj\lambda_j set the exchange rate between those demands.

In matrix notation, augmenting B\mathbf{B} with a leading column of ones for the intercept and stacking the pp coefficient blocks,

L(β)=∥y−Bβ∥22+β⊤Sβ,\mathcal{L}(\boldsymbol\beta) = \| \mathbf{y} - \mathbf{B}\boldsymbol\beta \|_2^2 + \boldsymbol\beta^\top \mathbf{S} \boldsymbol\beta,

where S\mathbf{S} is the block-diagonal matrix with zero in the intercept block and λjSj\lambda_j \mathbf{S}_j in the block for smooth jj. After imposing centering, the following matrix formulas use the free coefficients and the correspondingly transformed design and penalty; the symbol β\boldsymbol\beta now denotes those identifiable coordinates. Setting the gradient of L\mathcal{L} with respect to β\boldsymbol\beta equal to zero and rearranging (exactly as we did for OLS in Ordinary Least Squares (OLS)) gives the closed-form solution

β^=(B⊤B+S)−1B⊤y.\hat{\boldsymbol\beta} = \left( \mathbf{B}^\top \mathbf{B} + \mathbf{S} \right)^{-1} \mathbf{B}^\top \mathbf{y}.

This is identical in form to ridge regression, with S\mathbf{S} playing the role of a feature-specific ridge penalty. The inverse exists only when the design identifies every direction left unpenalized: ker⁡(B)∩ker⁡(S)={0}\ker(\mathbf{B}) \cap \ker(\mathbf{S}) = \{0\}. Indeed, for any coefficient direction v\mathbf{v}, v⊤(B⊤B+S)v=∥Bv∥2+v⊤Sv\mathbf{v}^\top(\mathbf{B}^\top\mathbf{B}+\mathbf{S})\mathbf{v}=\|\mathbf{B}\mathbf{v}\|^2+\mathbf{v}^\top\mathbf{S}\mathbf{v}. A positive smoothing parameter alone cannot remove an unpenalized constant ambiguity. The inverse formula therefore applies after identifiability constraints; in code we solve the system rather than form an inverse. The fitted curve for predictor jj is f^j(x)=∑kβ^jk bjk(x)\hat f_j(x) = \sum_k \hat\beta_{jk} \, b_{jk}(x), and the full fitted predictor is η^i=β0+∑jf^j(xij)\hat\eta_i = \beta_0 + \sum_j \hat f_j(x_{ij}).

For many family and link choices, such as Bernoulli responses with a logit link or Poisson counts with a log link, the penalized likelihood is non-quadratic. These models are commonly fitted by iterative reweighted least squares (IRLS), which replaces one least-squares solve with a sequence of penalized weighted least-squares solves. The likelihood and link jointly determine the objective; a nonlinear inverse link alone does not determine whether it is quadratic. A common Fisher-scoring update is

β^(t+1)=(B⊤W(t)B+S)−1B⊤W(t)z(t),\hat{\boldsymbol\beta}^{(t+1)} = \left( \mathbf{B}^\top \mathbf{W}^{(t)} \mathbf{B} + \mathbf{S} \right)^{-1} \mathbf{B}^\top \mathbf{W}^{(t)} \boldsymbol{z}^{(t)},

where:

  • tt: the IRLS iteration index
  • W(t)\mathbf{W}^{(t)}: the diagonal matrix of GLM working weights at iteration tt, with entries depending on the variance function and link derivative
  • z(t)\boldsymbol{z}^{(t)}: the working response at iteration tt, formed from the current linear predictor and the link
  • S\mathbf{S}: the same block-diagonal penalty matrix as in the Gaussian case

For independent GLM observations, a common working response is zi=ηi+(yi−μi)g′(μi)z_i=\eta_i+(y_i-\mu_i)g'(\mu_i), where g′(μi)g'(\mu_i) is the derivative of the link at the current fitted mean. The corresponding working weight is wi=1/[Var⁡(yi∣xi){g′(μi)}2]w_i=1/[\operatorname{Var}(y_i\mid\mathbf{x}_i)\{g'(\mu_i)\}^2]. The response linearizes the mean relationship locally, while the weights account for conditional variance and the link's slope. These expressions require valid interior means, finite positive variances, and nonzero link derivatives. A common dispersion factor can instead be absorbed into the penalty convention. The resulting Fisher-scoring iteration may need damping and convergence checks; the displayed update is not a guarantee of convergence.

We can retain the same chosen coefficient penalty when changing the response family and link, while changing the working weights and response. Each IRLS step couples the additive coefficients through W(t)\mathbf{W}^{(t)}, and a well-defined solution requires the smooth coefficients to satisfy the identifiability conditions described next.

Mathematical properties

The difference-penalized Gaussian model has several properties worth stating:

  • The second-difference matrix has a two-dimensional null space: constant and affine coefficient sequences have zero second differences. With arbitrary knot spacing this statement concerns coefficients, not a guarantee of exact linearity in the predictor.
  • A constant can be transferred between a smooth and the intercept without changing predictions. We impose ∑ifj(xij)=0\sum_i f_j(x_{ij})=0 on the training observations for each component. This is a constraint during fitting, rather than merely subtracting a mean from a plotted curve afterward.
  • With basis and smoothing parameters fixed, the Gaussian fit is linear in the responses. Its total effective degrees of freedom are tr⁡(H)\operatorname{tr}(\mathbf{H}), where H=B(B⊤B+S)−1B⊤\mathbf{H}=\mathbf{B}(\mathbf{B}^{\top}\mathbf{B}+\mathbf{S})^{-1}\mathbf{B}^{\top} for the identifiable design. This counts the intercept too; a centered component does not generally have degrees of freedom ranging from KjK_j to 22.

The matrix H\mathbf{H} maps observed responses to fitted values: y^=Hy\hat{\mathbf{y}}=\mathbf{H}\mathbf{y}. With the basis and smoothing parameters fixed, its diagonal entry HiiH_{ii} measures how sensitive fitted value ii is to observed response ii. The trace, tr⁡(H)\operatorname{tr}(\mathbf{H}), sums these diagonal entries to give an effective parameter count. For an unpenalized, full-column-rank least-squares fit, it equals the number of fitted coefficients. Smoothing shrinks the penalized directions, so the effective count can be fractional.

The next figure isolates one unconstrained, full-rank, 12-column spline basis with no separate intercept. Its total effective degrees of freedom approach the two unpenalized coefficient directions as smoothing grows. This is a separate demonstration from the centered two-feature model, where the intercept and each constraint affect the count.

PythonIn [5]
CollapseExpand
import numpy as np
import patsy

edf_x = np.linspace(-3.0, 3.0, 120)
edf_basis = patsy.dmatrix(
    "0 + bs(edf_x, df=12, degree=3, include_intercept=True, "
    "lower_bound=-3, upper_bound=3)",
    {"edf_x": edf_x},
    return_type="matrix",
)
edf_B = np.asarray(edf_basis)
edf_D = np.diff(np.eye(edf_B.shape[1]), n=2, axis=0)
edf_S = edf_D.T @ edf_D
edf_lambdas = np.logspace(-4, 4, 80)
edf_values = np.empty_like(edf_lambdas)
for i, lam in enumerate(edf_lambdas):
    edf_A = edf_B.T @ edf_B + lam * edf_S
    edf_values[i] = np.trace(np.linalg.solve(edf_A, edf_B.T @ edf_B))
VisualizationOut [6]
CollapseExpand
Effective degrees of freedom fall from twelve toward two as lambda increases on a log scale.
Effective degrees of freedom for one penalized cubic B-spline smooth across a log-spaced grid of smoothing parameters. The curve declines from near the full basis dimension and approaches two degrees of freedom as lambda grows because constant and affine coefficient directions are unpenalized.

For independent errors of variance σ2\sigma^2 and fixed smoothing parameters, write A=B⊤B+S\mathbf{A}=\mathbf{B}^{\top}\mathbf{B}+\mathbf{S}. Then Cov⁡(β^)=σ2A−1B⊤BA−1\operatorname{Cov}(\hat{\boldsymbol\beta})=\sigma^2\mathbf{A}^{-1}\mathbf{B}^{\top}\mathbf{B}\mathbf{A}^{-1}. Here Cov⁡\operatorname{Cov} collects the sampling covariances of the estimated free coefficients. Its diagonal entries are their variances; their square roots give standard errors conditional on the fixed design and smoothing choices. The factor A−1B⊤\mathbf{A}^{-1}\mathbf{B}^{\top} maps response noise to coefficient noise. These conditional sampling variances do not include smoothing-selection uncertainty or remove smoothing bias.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy