20  Regularization

A mountain ridge in Japan.

20.1 Motivation

TipRegularization

Regularization is any technique that constrains model complexity to reduce overfitting and improve generalization. There are two main types:

  1. Loss function regularization adds a penalty term to the loss function (e.g., LASSO and Ridge regression penalize large coefficients). This is explicit.
  2. Algorithmic regularization constrains the model-fitting process itself without changing the loss function. This is implicit.

This chapter introduces loss function regularization in regression models like ridge regression, LASSO regression, and elastic net regression. Chapter 21.1 introduces algorithmic regularization through ensembled models.

20.2 Linear regression

Ordinary least squares (OLS) linear regression generates coefficients that minimize the sum of squared residuals.

\[\min(SSE) = \min\left(\sum_{i = 1}^n (y_i - \hat{y_i}) ^ 2\right)\]

It is impossible to solve for OLS when \(p > n\) where \(p\) is the number of predictors and \(n\) is the number of observations. Additionally, OLS is unstable when data contain multicollinearity.

Let’s fit a linear regression model on the PDB data set. Section 21.2 introduces the PDB data in more detail.

pdb_small <- read_csv(here::here("data", "pdb_small.csv"))

set.seed(20231114)
pdb_split <- initial_split(pdb_small, prop = 0.8)

pdb_train <- training(pdb_split)
lm_mod <- linear_reg() |>
  set_mode(mode = "regression") |>
  set_engine(engine = "lm")

lm_rec <- recipe(non_return_rate ~ ., data = pdb_train) |>
  add_role(State_name, County_name, Low_Response_Score, new_role = "id") |>
  step_rm(has_role("id"))

lm_wf <- workflow() |>
  add_model(spec = lm_mod) |>
  add_recipe(recipe = lm_rec)

lm_wf |>
  fit(data = pdb_train) |>
  extract_fit_parsnip() |>
  # vi() |>
  tidy() |>
  print(n = Inf)
# A tibble: 23 × 5
   term                           estimate   std.error statistic   p.value
   <chr>                             <dbl>       <dbl>     <dbl>     <dbl>
 1 (Intercept)                 25.9        0.109          238.   0        
 2 Renter_Occp_HU_ACS_13_17     0.000528   0.000160         3.30 9.55e-  4
 3 Pop_18_24_ACS_13_17          0.00142    0.000112        12.7  7.89e- 37
 4 Female_No_HB_ACS_13_17       0.000739   0.000367         2.02 4.39e-  2
 5 NH_White_alone_ACS_13_17    -0.000940   0.0000495      -19.0  3.84e- 80
 6 Pop_65plus_ACS_13_17        -0.00197    0.000149       -13.2  1.47e- 39
 7 Rel_Child_Under_6_ACS_13_17  0.00190    0.000355         5.34 9.17e-  8
 8 Males_ACS_13_17              0.000885   0.000147         6.01 1.92e-  9
 9 MrdCple_Fmly_HHD_ACS_13_17  -0.00267    0.000251       -10.7  1.81e- 26
10 Pop_25_44_ACS_13_17          0.00122    0.000142         8.56 1.13e- 17
11 Tot_Vacant_Units_ACS_13_17   0.00209    0.0000922       22.7  3.97e-113
12 College_ACS_13_17           -0.000345   0.0000738       -4.68 2.93e-  6
13 Med_HHD_Inc_ACS_13_17       -0.0000977  0.00000169     -57.9  0        
14 Pop_45_64_ACS_13_17          0.00173    0.000160        10.8  2.51e- 27
15 HHD_Moved_in_ACS_13_17       0.00387    0.000178        21.7  3.18e-104
16 Hispanic_ACS_13_17          -0.000372   0.0000532       -6.99 2.77e- 12
17 Single_Unit_ACS_13_17       -0.00263    0.0000800      -32.9  1.64e-235
18 Diff_HU_1yr_Ago_ACS_13_17   -0.000849   0.0000978       -8.68 4.23e- 18
19 Pop_5_17_ACS_13_17           0.00170    0.000145        11.7  9.76e- 32
20 NH_Blk_alone_ACS_13_17       0.000547   0.0000565        9.68 3.92e- 22
21 Sngl_Prns_HHD_ACS_13_17     -0.00432    0.000208       -20.7  3.62e- 95
22 Not_HS_Grad_ACS_13_17       -0.000210   0.000138        -1.53 1.26e-  1
23 Med_House_Value_ACS_13_17    0.00000928 0.000000224     41.4  0        

20.3 Regularization

TipRegularization/Penalization

To reduce the magnitude of fitted parameters (coefficients).

Regularization, or penalization, allows linear regression to work with very wide data, to generate stable estimates for data with multicollinearity, and to perform feature selection.

For regression, the idea is to add a penalty \(P\) to the optimization routine:

\[\min(SSE + P) = \min\left(\sum_{i = 1}^n (y_i - \hat{y_i}) ^ 2 + P\right)\]

20.4 Ridge Regression

\[\min(SSE + P) = \min\left(\sum_{i = 1}^n (y_i - \hat{y_i}) ^ 2 + \lambda\sum_{j = 1}^p\beta_j^2\right)\]

Ridge regression adds an L2 penalty (\(\lambda\sum_{j = 1}^p\beta_j^2\)) to the optimization routine. The model has one hyperparameter, \(\lambda\), which determines how much penalty to add. There is no penalty when \(\lambda = 0\) (just OLS).

All predictors should be centered and scaled (standardized) before estimation. Thus the coefficients will be per standard deviation of each predictor.

Ridge regression reduces coefficients but it does not eliminate coefficients.

20.4.1 Ridge regression reduces but does not eliminate coefficients

fit_ridge <- function(data, penalty) {
  
  ridge_rec <- recipe(non_return_rate ~ ., data = data) |>
    add_role(State_name, County_name, Low_Response_Score, new_role = "id") |>
    step_rm(has_role("id")) |>
    step_normalize(all_predictors())
  
  ridge_mod <- linear_reg(penalty = penalty, mixture = 0) |>
    set_mode(mode = "regression") |>
    set_engine(engine = "glmnet")  
  
  ridge_wf <- workflow() |>
    add_recipe(recipe = ridge_rec) |>
    add_model(spec = ridge_mod)
  
  ridge_wf |>
    fit(data = data) |>
    extract_fit_parsnip() |>
    tidy() |>
    mutate(penalty = penalty)
  
}

ridge_fit <- seq(0, 50, 1) |>
  map_dfr(.f = ~ fit_ridge(pdb_train, .x))

ggplot() +
  geom_line(
    data = filter(ridge_fit, term != "(Intercept)"),
    mapping = aes(penalty, estimate, group = term),
    alpha = 0.4
  ) +
  geom_point(
    data = filter(ridge_fit, term != "(Intercept)", penalty == 0),
    mapping = aes(penalty, estimate),
    color = "red"
  )

The red points show the estimated coefficients when \(\lambda = 0\), which is OLS on the standardized predictors. Each gray line traces how one coefficient changes as the penalty increases. The LASSO and elastic net plots below use the same layout.

Note that regularized regression models expect standardized predictors (mean = 0 and standard deviation = 1). This changes the starting estimated parameters.

20.5 LASSO Regression

\[\min(SSE + P) = \min\left(\sum_{i = 1}^n (y_i - \hat{y_i}) ^ 2 + \lambda\sum_{j = 1}^p|\beta_j|\right)\]

Least Absolute Shrinkage and Selection Operator (LASSO) regression adds an L1 penalty (\(\lambda\sum_{j = 1}^p|\beta_j|\)) to the optimization routine. The model has one hyperparameter, \(\lambda\), which determines how much penalty to add. There is no penalty when \(\lambda = 0\) (just OLS).

All predictors should be centered and scaled (standardized) before estimation. Thus the coefficients will be per standard deviation of each predictor.

LASSO regression can regularize coefficients all the way to zero.

20.5.1 LASSO regression eliminates coefficients

fit_lasso <- function(data, penalty) {
  
  lasso_rec <- recipe(non_return_rate ~ ., data = data) |>
    add_role(State_name, County_name, Low_Response_Score, new_role = "id") |>
    step_rm(has_role("id")) |>
    step_normalize(all_predictors())
  
  lasso_mod <- linear_reg(penalty = penalty, mixture = 1) |>
    set_mode(mode = "regression") |>
    set_engine(engine = "glmnet")  
  
  lasso_wf <- workflow() |>
    add_recipe(recipe = lasso_rec) |>
    add_model(spec = lasso_mod)
  
  lasso_wf |>
    fit(data = data) |>
    extract_fit_parsnip() |>
    tidy() |>
    mutate(penalty = penalty)
  
}

lasso_fit <- seq(0, 3.5, 0.1) |>
  map_dfr(.f = ~ fit_lasso(pdb_train, .x))

ggplot() +
  geom_line(
    data = filter(lasso_fit, term != "(Intercept)"),
    mapping = aes(penalty, estimate, group = term),
    alpha = 0.4
  ) +
  geom_point(
    data = filter(lasso_fit, term != "(Intercept)", penalty == 0),
    mapping = aes(penalty, estimate),
    color = "red"
  )

20.6 Elastic Net Regression

\[\min(SSE + P) = \min\left(\sum_{i = 1}^n (y_i - \hat{y_i}) ^ 2 + \lambda_1\sum_{j = 1}^p\beta_j^2 + \lambda_2\sum_{j = 1}^p|\beta_j|\right)\]

Elastic net regression combines ridge regression and LASSO regression. It has two hyperparameters, \(\lambda_1\) and \(\lambda_2\). Sometimes the hyperparameters are \(\lambda\) and mixture, which determines how much of \(\lambda\) to apply to each penalty (e.g., mixture = 0 is ridge regression and mixture = 1 is LASSO regression).

All predictors should be centered and scaled (standardized) before estimation. Thus the coefficients will be per standard deviation of each predictor.

Elastic net regression can perform feature selection, but in a less dramatic fashion than LASSO regression.

20.6.1 Elastic net blends ridge regression and LASSO regression

This example is contrained to make \(\lambda_1\) = \(\lambda_2\).

fit_enet <- function(data, penalty) {

  enet_rec <- recipe(non_return_rate ~ ., data = data) |>
    add_role(State_name, County_name, Low_Response_Score, new_role = "id") |>
    step_rm(has_role("id")) |>
    step_normalize(all_predictors())

  enet_mod <- linear_reg(penalty = penalty, mixture = 0.5) |>
    set_mode(mode = "regression") |>
    set_engine(engine = "glmnet")

  enet_wf <- workflow() |>
    add_recipe(recipe = enet_rec) |>
    add_model(spec = enet_mod)

  enet_wf |>
    fit(data = data) |>
    extract_fit_parsnip() |>
    tidy() |>
    mutate(penalty = penalty)

}

enet_fit <- seq(0, 7, 0.2) |>
  map_dfr(.f = ~ fit_enet(pdb_train, .x))

ggplot() +
  geom_line(
    data = filter(enet_fit, term != "(Intercept)"),
    mapping = aes(penalty, estimate, group = term),
    alpha = 0.4
  ) +
  geom_point(
    data = filter(enet_fit, term != "(Intercept)", penalty == 0),
    mapping = aes(penalty, estimate),
    color = "red"
  )