Skip to main content

GAM thin plate regression spline

Introduction​

While we have already implemented GAM using smoothers with only one predictor, thin plate regression splines are used to implement smoothers with one or more predictors. We follow the implementation in [1] closely here. For more theoretical treatments on the subject, please refer to [1] and [2].

The following information is on regression. However, the translation to other families is straight-forward. For regression, we aim to approximate the response YiY_i with f(xi)f(x_i). For the binomial family, we aim to approximate the log-odds p(xi)p(x_i) with f(xi)f(x_i).

A simple linear GAM model built using smoothers from multiple predictors​

Thin plate splines are used to estimate smooth functions of multiple predictor variables from noisy observations of the function at particular values of those predictors. Consider:

Yi=g(xi)+ϵiY_i = g(x_i) + \epsilon_i

where:

  • xiϵRdx_i\epsilon R^d,
  • dd is the number of predictors in the smooth function gg.

Thin plate spline smoothing estimates gg by finding function ff by minimizing:

∥y−f∥2+λJmd(f)  Equation 1{\parallel{y-f}\parallel}^2 + \lambda J_{md}(f) {\text{ }}{\text{ Equation 1}}

where:

  • y=[y1,y2,...,yn]Ty = [y_1,y_2,...,y_n]^T,
  • f=[f(x1),f(x2),...,f(xn)]Tf = [f(x_1),f(x_2),...,f(x_n)]^T,
  • Jmd(f)J_{md}(f) is a penalty measure of wiggliness of ff,
  • λ\lambda is the smoothing parameter (scale parameter) controlling the tradeoff between data fitting and smoothness of ff,
  • Jmd(f)=∫Rd∑γ1+γ2+...+γd=mm!γ1!γ2!...γd!(dmfdx1dx2...dxd)2dx1dx2...dxdJ_{md}(f) = {\int_{R^d}{\sum_{\gamma_1+\gamma_2+...+\gamma_d=m}{\frac{m!}{\gamma_1! \gamma_2!...\gamma_d!}{({\frac{d^mf}{dx_1dx_2...dx_d}})}^2}}}dx_1 dx_2...dx_d,
  • m=floor(d+12)+1m = floor(\frac{d+1}{2})+1.

The function ff that minimizes Equation 1 has the following form:

f^(x)=∑i=1nδiηmd(∥x−xi∥)+∑j=1Mαjϕj(x)  Equation 2{\hat{f}}(x) = {\sum_{i=1}^n}\delta_i \eta_{md}({\parallel{x-x_i}\parallel}) + {\sum_{j=1}^M}\alpha_j \phi_j (x) {\text{ }}{\text{ Equation 2}}

which is subject to the constraint TTδ=0T^T\delta = 0 where each element of TT is Tij=ϕj(xi)T_{ij} = \phi_j (x_i) and where:

  • M=(m+d−1)!d!(m−1)!M = {\frac{(m+d-1)!}{d!(m-1)!}};
  • ϕj\phi_j are the polynomial basis functions with order = 0,1,…,m-1;
Polynomial basis constraint
  • Γ(12−n)=(−4)nn!(2n)!π\Gamma ({\frac{1}{2}}-n) = {\frac{(-4)^nn!}{(2n)!}}{\sqrt \pi}.

However, Equation 2 cannot be implemented due to the fact that all the rows of the dataset are used in generating the function f^(x){\hat{f}}(x).

Knot-based approximation of f^(x){\hat{f}}(x)​

Instead of using all the data points in the training set, only a subset of knots are used in the approximation of f^(x){\hat{f}}(x) as follows:

f^(x)=∑i=1kδiηmd(∥x−xi∥)+∑j=1Mαjϕj(x)  Equation 3{\hat{f}}(x) = {\sum_{i=1}^k}\delta_i \eta_{md}({\parallel{x-x_i}\parallel})+{\sum_{j=1}^M}\alpha_j \phi_j (x) {\text{ }}{\text{ Equation 3}}

where kk is the number of knots. The coefficients δ=(δ1,δ2,...,δk)T\delta = (\delta_1,\delta_2,...,\delta_k)^T, α=(α1,α2,...,αM)T\alpha = (\alpha_1, \alpha_2,..., \alpha_M)^T can be obtained by minimizing the following objective function:

∥Y−Xβ∥2+λβTSβ Subject to Cβ=0{\parallel{Y-X\beta}\parallel}^2 + \lambda \beta^T S\beta {\text{ Subject to }} C\beta = 0

where:

  • βT=(δT,α2)\beta^T = (\delta^T , \alpha^2);
Chi matrix
  • SS is a (k+M)(k+M) by (k+M)(k+M) matrix with zeros everywhere except in its upper left kk by kk blocks where Sij=ηmd(∥xi∗−xj∗∥)S_{ij} = \eta_{md} ({\parallel{x_i^*-x_j^*}\parallel}) and xi∗,xj∗x_i^*,x_j^* are knots.
C matrix

Generation of XnmdX_{n_{md}}​

The data matrix XX consists of two parts: X=[Xnmd:T]X = [X_{n_{md}}:T]. First, we will generate XnmdX_{n_{md}}, which consists of the distance measure part. XnmdX_{n_{md}} is nn by kk in dimension, and the ijthij^{th} element is calculated as:

ijth element formula

Generation of penalty matrix SS

Note that the penalty matrix S=Xnmd∗S=X_{n_{md}}^*. It is the distance measure calculated using only the knot points.

Generation of the polynomial basis​

Let dd be the number of predictors included in the thin plate regression smoother, and let m−1m-1 be the highest degree of the polynomial basis function used. We can calculate mm from dd by using the formula m=floor(d+12)+1m=floor(\frac{d+1}{2})+1. The total number of polynomial basis function MM is determined by the formula M=(d+m−1d)=(d+m−1)d!(m−1)!M={{d+m-1} \choose {d}} = {\frac{(d+m-1)}{d!(m-1)!}}. We will illustrate how this is done with two examples:

Polynomial basis for d=2d=2

In this case, m=floor(2+12)+1=2m=floor({\frac{2+1}{2}})+1=2 and M=(2+2−12)=3M={{2+2-1} \choose {2}} = 3. The size of the polynomial basis is 3, and the polynomial basis consists of polynomials of degrees 0 and 1. When the two predictors are set as x1,x2x_1,x_2, the polynomial basis will consist of 1,x1,x21,x_1,x_2. TT consists of one column of ones, predictor x1x_1, and predictor x2x_2. The size of TT is nn by 33.

Polynomial basis for d=4d=4

In this case, m=floor(4+12)+1=3m=floor({\frac{4+1}{2}})+1=3 and M=(4+3−14)=15M={{4+3-1} \choose {4}}=15. The size of the polynomial basis is 15, and the polynomial basis consists of polynomials of degrees 0, 1, and 2. The four predictors are x1,x2,x3,x4x_1,x_2,x_3,x_4. TT consists of:

  • one zero degree polynomial: one column of ones;
  • four degree one polynomials: x1,x2,x3,x4x_1,x_2,x_3,x_4;
  • ten degree 2 polynomials: x12,x22,x32,x42,x1x2,x1x3,x1x4,x2x3,x2x4,x3x4x_1^2, x_2^2, x_3^2, x_4^2, x_1x_2, {x_1}{x_3}, {x_1}{x_4}, {x_2}{x_3}, {x_2}{x_4}, {x_3}{x_4}.

The size of TT is nn by 1515. The size of the polynomial basis grows rapidly as the number of predictors increase in the thin plate regression smoother.

Generation of TT

Remember that TT is defined as Tij=ϕj(xi)T_{ij} = \phi_j (x_i). Therefore, TT is of size nn by MM. However, T∗T_* is only evaluated at the knots chosen by the user. Hence, by using the example of d=2d=2 and letting the two predictors be x1,x2x_1,x_2, TT contains:

T matrix

Absolving the constraint via matrix transformation​

The constraint Cβ=0C\beta =0 is equivalent to T∗Tδ=0T_*^T\delta =0 and is MM by kk. The following transformations are applied:

  • Generate the QR decomposition of CTC^T (which is equivalent to the QR decomposition of T∗T_*). Therefore, rewrite T∗=UPT_* =UP where UU is kk by MM, and PP is MM by MM;
  • Next, generate an orthogonal basis ZcsZ_{cs} which is kk by (k−M)(k-M), and ZcsZ_{cs} is orthogonal to UU. This will force the condition that k>M+1k>M+1 in setting the number of knots.
  • ZcsZ_{cs} is easily generated by first generating the (k−M)(k-M) random vector. Next, use Gram-Schmidt to make the random vectors orthogonal to UU and to each other.
  • Set δ=Zcsδcs\delta =Z_{cs}\delta_{cs} and rewrite βT=((Zcsδcs)T,αT)\beta^T =((Z_{cs}\delta_{cs})^T,\alpha^T).

Let's also:

  • decompose XX into two parts as X=[Xnmd:T]X=[X_{n_{md}}:T] where XnmdX_{n_{md}} is nn by kk and TT is nn by MM;
X decomposition into parts

Let's rewrite the new objective with this decomposition:

Decomposed objective function

Note that ZcsTXnmd∗ZcsZ_{cs}^TX_{n_{md}}^*Z_{cs} is (k−M)(k-M) by (k−M)(k-M).

Sum-to-zero constraints implementation​

This will follow the Identifiability constraints rules for GAM. Let XX be the model matrix that contains the basis functions of one predictor variable; the sum-to-zero constraints require that 1Tfp=0=1TXβ1^Tf_p=0=1^TX\beta where β\beta contains the coefficients relating to the basis functions of that particular predictor column. The idea is to create a kk by (k−1)(k-1) matrix ZZ such that β=Zβz\beta =Z\beta_z, then 1TXβ=01^TX\beta =0 for any βz\beta_z. ZZ is generated by using the Householder transform. Please refer to [3] for details. Therefore, we have βCS=ZβZ\beta_{CS}=Z\beta_Z. Rewrite the objective function again and we will have

∥Y−XCSβCS∥2+λ(βCS)TSβCS=∥Y−XCSZβz∥2+{\parallel{Y-X_{CS}\beta_{CS}}\parallel}^2+\lambda(\beta_{CS})^TS\beta_{CS} = {\parallel{Y-X_{CS}Z\beta_z}\parallel}^2+

λ(βZ)TZTSCSZβZ=∥Y−XZβz∥2+λ(βZ)TSZβZ\lambda(\beta_Z)^TZ^TS_{CS}Z\beta_Z = {\parallel{Y-X_Z\beta_z}\parallel}^2+\lambda (\beta_Z)^TS_Z\beta_Z

and we will be solving for βZ\beta_Z. Then, we will obtain βCS=Zβz\beta_{CS}=Z\beta_z. Last, we will obtain the original β\beta by multiplying the part of the coefficeints not corresponding to the polynomial basis with ZCSZ_{CS} like βT=((ZCSδCS)T,αT)\beta^T =((Z_{CS}\delta_{CS})^T,\alpha^T).

Specifying GAM columns​

Two ways to specify GAM columns for thin plate regression are available. Following the below example, gam_columns can be specified as:

For R:

  • gam_col1 <- list("C11", c("C12","C13"), c("C14", "C15", "C16"), "C17", "C18") or
  • gam_col1 <- list(c("C11"), c("C12","C13"), c("C14", "C15", "C16"), c("C17"), c("C18"))

For Python:

  • gam_col1 = ["C11",["C12","C13"],["C14","C15","C16"],"C17","C18"] or
  • gam_col1 = [["C11"],["C12","C13"],["C14","C15","C16"],["C17"],["C18"]]

When using a grid search, the GAM columns are specified inside of the subspaces hyperparameter. Otherwise, the gam_column parameter is entered on its own when building a GAM model.

Normal GAM model​

#Import the train and test datasets:
train <- h2o.importFile("https://s3.amazonaws.com/h2o-public-test-data/smalldata/glm_test/gaussian_20cols_10000Rows.csv")
test <- h2o.importFile("https://s3.amazonaws.com/h2o-public-test-data/smalldata/glm_test/gaussian_20cols_10000Rows.csv")

# Set the factors:
train$C1 <- h2o.asfactor(train$C1)
train$C2 <- h2o.asfactor(train$C2)
test$C1 <- h2o.asfactor(test$C1)
test$C2 <- h2o.asfactor(test$C2)

# Set the predictors, response, & GAM columns:
predictors <- c("C1", "C2")
response = "C21"
gam_col1 <- list("C11", c("C12","C13"), c("C14", "C15", "C16"), "C17", "C18")

# Build and train the model:
gam_model <- h2o.gam(x = predictors, y = response,
gam_columns = gam_col1, training_frame = train,
validation_frame = test, family = "gaussian",
lambda_search = TRUE)

# Retrieve the coefficients:
coefficients <- h2o.coef(gam_model)
# Import the train dataset:
h2o_data <- h2o.importFile("https://s3.amazonaws.com/h2o-public-test-data/smalldata/gam_test/synthetic_20Cols_gaussian_20KRows.csv")

# Set the factors:
h2o_data$response <- h2o.asfactor(h2o_data$response)
h2o_data$C3 <- h2o.asfactor(h2o_data$C3)
h2o_data$C7 <- h2o.asfactor(h2o_data$C7)
h2o_data$C8 <- h2o.asfactor(h2o_data$C8)
h2o_data$C10 <- h2o.asfactor(h2o_data$C10)

# Set the predictors and response:
xL <- c("c_0", "c_1", "c_2", "c_3", "c_4", "c_5", "c_6", "c_7", "c_8",
"c_9", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10")
yR = "response"

# Set up the search criteria and hyperparameters:
search_criteria <- list()
search_criteria$strategy <- 'RandomDiscrete'
search_criteria$seed <- 1
hyper_parameters <- list()
hyper_parameters$lambda = c(1, 2)
subspace <- list()
subspace$scale <- list(c(0.001, 0.001, 0.001), c(0.002, 0.002, 0.002))
subspace$num_knots <- list(c(5, 10, 12), c(6, 11, 13))
subspace$bs <- list(c(1, 1, 1), c(0, 1, 1))
subspace$gam_columns <- list(list("c_0", c("c_1", "c_2"), c("c_3", "c_4", "c_5")), list("c_1", c("c_2", "c_3"), c("c_4", "c_5", "c_6")))
hyper_parameters$subspaces <- list(subspace)

# Build and train the grid:
gam_grid = h2o.grid("gam", grid_id="GAMModel1", x=xL, y=yR,
training_frame=h2o_data, family='binomial',
hyper_params=hyper_parameters, search_criteria=search_criteria)

# Retrieve the coefficients:
coefficients <- h2o.coef(gam_grid)

References​

  1. Simon N. Wood, Generalized Additive Models An Introduction with R, Texts in Statistical Science, CRC Press, Second Edition.
  1. T.J. Hastie, R.J. Tibshirani, Generalized Additive Models, Chapman and Hall, First Edition, 1990.
  1. Wendy C Wong, Gam.doc.

Feedback