Régression pénalisée en R : ridge, lasso et elastic net avec glmnet

Rétractez les coefficients, sélectionnez les variables et maîtrisez la multicolinéarité — réglée honnêtement par validation croisée

Un guide pratique de la régression pénalisée (régularisée) en R : ridge (L2), lasso (L1) et elastic net. Découvrez comment chacune rétracte les coefficients, pourquoi le lasso en met certains exactement à zéro (sélection de variables), et comment régler la pénalité lambda par validation croisée. Traité de deux façons — le workflow tidymodels moderne avec parsnip + glmnet, et la voie classique glmnet::cv.glmnet — avec des graphiques de chemins de coefficients et de courbe de validation croisée expliqués en langage clair. Sur les données Boston intégrées.

Date de publication

25 juin 2026

Modifié

7 juillet 2026

AstuceL’essentiel à retenir
  • La régression pénalisée (alias régularisation ou rétraction) ajoute une pénalité sur la taille des coefficients à la régression des moindres carrés ordinaires. Elle rétracte les prédicteurs faibles vers zéro — le remède aux trop-nombreuses-variables, à la multicolinéarité et au surapprentissage.
  • Le ridge (L2) rétracte chaque coefficient vers zéro mais les conserve tous. Le lasso (L1) rétracte et met certains coefficients exactement à zéro — il fait donc de la sélection de variables. L’elastic net mélange les deux (le mixture entre 0 et 1).
  • L’ampleur de la rétraction tient en un seul paramètre de réglage, lambda (penalty). λ = 0 correspond aux moindres carrés ordinaires ; à mesure que λ grandit, les coefficients se rétractent vers zéro. Vous choisissez λ par validation croisée.
  • Standardisez d’abord les prédicteurs — les pénalités agissent sur la taille des coefficients, donc les prédicteurs doivent être à une échelle commune (step_normalize(), ou le standardize = TRUE intégré de glmnet).
  • Deux voies, un même résultat : le workflow tidymodels moderne (linear_reg(penalty = tune(), mixture =) |> set_engine("glmnet")) réglé avec tune_grid(), ou le classique glmnet::cv.glmnet() — qui vous fournit lambda.min (erreur de validation croisée la plus faible) et lambda.1se (le modèle le plus simple à une erreur standard près).
  • Lisez les deux lambdas : lambda.min prédit le mieux ; lambda.1se est plus agressif (plus de zéros, modèle plus simple) au prix d’une infime perte de précision — souvent le meilleur choix quand vous voulez un modèle interprétable.
Obtenez le livre — Machine Learning Essentials (PDF)

Introduction

La régression des moindres carrés ordinaires a un point faible : quand vous avez beaucoup de prédicteurs — surtout plus de prédicteurs que d’observations, ou des prédicteurs corrélés entre eux — ses estimations de coefficients deviennent instables et elle surapprend, ajustant le bruit d’entraînement et prédisant mal les nouvelles données.

La régression pénalisée corrige cela en ajoutant une contrainte au problème d’ajustement : une pénalité sur la taille des coefficients. Le modèle n’est plus libre de rendre un coefficient aussi grand que les données d’entraînement le suggèrent — les grands coefficients lui coûtent, donc il les rétracte vers zéro, ne gardant que le signal qui mérite vraiment sa place. On appelle aussi cela régularisation ou rétraction, et c’est l’un des moyens les plus fiables de construire un modèle qui prédit bien hors échantillon.

Cette leçon couvre les trois chevaux de bataille — ridge, lasso et elastic net — ce que chacun fait à vos coefficients, et comment choisir honnêtement la pénalité par validation croisée. Nous le traitons de deux façons : le workflow tidymodels moderne (parsnip + glmnet), et la voie classique glmnet::cv.glmnet() que la plupart des utilisateurs de R apprennent en premier. Pour le contexte plus large du machine learning, voir construire des modèles avec tidymodels et validation croisée ; pour l’alternative de sélection de variables qui retire les prédicteurs un à un, voir régression pas à pas.

NoteQuand y recourir

Utilisez la régression pénalisée quand vous avez beaucoup de prédicteurs, des prédicteurs corrélés (multicolinéarité), ou un modèle qui obtient de bons scores à l’entraînement mais de mauvais sur de nouvelles données. Recourez au lasso quand vous soupçonnez que seule une poignée de prédicteurs comptent vraiment (vous voulez un modèle parcimonieux, interprétable) ; au ridge quand de nombreux prédicteurs contribuent chacun un peu ; et à l’elastic net quand les prédicteurs sont corrélés et que vous voulez tout de même une sélection.

Les données

Nous utilisons le jeu de données intégré Boston du package MASS : 506 banlieues de Boston, avec pour objectif de prédire medv — la valeur médiane du logement (en milliers de dollars) — à partir de 13 prédicteurs socio-économiques et structurels (taux de criminalité crim, nombre moyen de pièces rm, ratio élèves–enseignant ptratio, % population de statut inférieur lstat, etc.). Plusieurs de ces prédicteurs sont corrélés, ce qui est exactement le cadre où la pénalisation prouve son utilité.

Nous séparons les données en un jeu d’entraînement (80 %, pour l’ajustement et le réglage) et un jeu de test (20 %, mis de côté pour juger honnêtement le modèle final). Nous stratifions sur medv pour que les deux jeux aient une distribution de sortie similaire, et fixons une graine pour la reproductibilité :

library(rsample)
data("Boston", package = "MASS")

# 506 Boston suburbs: predict median home value (medv) from 13 predictors
set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)
test_data  <- testing(split)

c(train = nrow(train_data), test = nrow(test_data), predictors = ncol(Boston) - 1)
     train       test predictors 
       403        103         13 
head(Boston, 4)
     crim zn indus chas   nox    rm  age    dis rad tax ptratio  black lstat
1 0.00632 18  2.31    0 0.538 6.575 65.2 4.0900   1 296    15.3 396.90  4.98
2 0.02731  0  7.07    0 0.469 6.421 78.9 4.9671   2 242    17.8 396.90  9.14
3 0.02729  0  7.07    0 0.469 7.185 61.1 4.9671   2 242    17.8 392.83  4.03
4 0.03237  0  2.18    0 0.458 6.998 45.8 6.0622   3 222    18.7 394.63  2.94
  medv
1 24.0
2 21.6
3 34.7
4 33.4

Environ 400 banlieues pour apprendre, 100 mises de côté pour faire office de juge, et 13 prédicteurs à rétracter.

Le workflow tidymodels : ridge, lasso, elastic net

Dans tidymodels, les trois méthodes sont une seule spécification de modèle avec deux boutons. parsnip::linear_reg() prend un penalty (c’est lambda — combien rétracter) et un mixture (le mélange de l’elastic net) :

  • mixture = 0ridge pur (pénalité L2)
  • mixture = 1lasso pur (pénalité L1)
  • 0 < mixture < 1elastic net (par ex. 0.5 est un mélange moitié-moitié)

Le moteur est "glmnet". Les méthodes pénalisées exigent des prédicteurs standardisés (la pénalité agit sur la taille des coefficients, donc les prédicteurs doivent partager une échelle), aussi le recipe les normalise-t-il d’abord — c’est l’étape de feature engineering dont tout modèle pénalisé a besoin.

Régler la pénalité par validation croisée (lasso)

Nous ne devinons pas lambda — nous laissons la validation croisée le choisir. Marquez penalty = tune(), construisez une grille de valeurs candidates, et notez chacune par une validation croisée à 10 plis sur le jeu d’entraînement. mixture = 1 en fait un lasso :

library(rsample)
library(recipes)
library(parsnip)
library(workflows)
library(tune)
library(dials)
library(yardstick)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)

# Standardize predictors — penalized methods require a common scale
rec <- recipe(medv ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())

# Lasso: mixture = 1, penalty left to be tuned
lasso_spec <- linear_reg(penalty = tune(), mixture = 1) |>
  set_engine("glmnet")

lasso_wf <- workflow() |>
  add_recipe(rec) |>
  add_model(lasso_spec)

# 10-fold CV on the training set, over a grid of lambda values
set.seed(123)
folds <- vfold_cv(train_data, v = 10)
grid  <- grid_regular(penalty(range = c(-3, 0)), levels = 50)   # 10^-3 to 10^0

set.seed(123)
lasso_tuned <- tune_grid(
  lasso_wf, resamples = folds, grid = grid,
  metrics = metric_set(rmse, rsq)
)

# The lambda with the lowest cross-validated RMSE
best_lambda <- select_best(lasso_tuned, metric = "rmse")
best_lambda
# A tibble: 1 × 2
  penalty .config         
    <dbl> <chr>           
1   0.001 pre0_mod01_post0

tune_grid() a validé par validation croisée chaque lambda candidat ; select_best() renvoie celui dont l’erreur de validation croisée est la plus faible. Ici la meilleure pénalité est petite (≈ 0.001) — sur ce jeu de données une touche légère prédit le mieux, mais comme nous le verrons, une pénalité un peu plus lourde achète un modèle bien plus simple pour presque aucun coût en précision.

La courbe de validation croisée : erreur en fonction de lambda

Le graphique le plus utile de la régression pénalisée est l’erreur de validation croisée en fonction de lambda. Il montre tout le compromis biais–variance en une seule image :

library(ggplot2)

# Collect the CV metric for every candidate lambda
lasso_metrics <- collect_metrics(lasso_tuned)
rmse_curve    <- subset(lasso_metrics, .metric == "rmse")

ggplot(rmse_curve, aes(x = penalty, y = mean)) +
  geom_line(color = "#3a86d4", linewidth = 1) +
  geom_point(color = "#3a86d4", size = 1.5) +
  geom_vline(xintercept = best_lambda$penalty, linetype = "dashed", color = "gray40") +
  scale_x_log10() +
  labs(
    x = expression("Penalty " * lambda * " (log scale)"),
    y = "Cross-validated RMSE",
    title = "Choosing the penalty by cross-validation",
    subtitle = "Error is flat for light penalties, then climbs as lambda over-shrinks (dashed = best lambda)"
  ) +
  theme_minimal()

Cross-validated RMSE plotted against the lasso penalty lambda on a log scale for the Boston data: error is flat and low for small penalties, then rises sharply as the penalty grows large and shrinks the coefficients too far.

Lisez-le de gauche à droite. Pour un petit lambda (à gauche), la pénalité mord à peine — le modèle est proche des moindres carrés ordinaires et l’erreur est faible mais un peu variable. À mesure que lambda grandit (à droite), les coefficients sont forcés vers zéro ; passé un certain point le modèle est trop contraint (sous-apprentissage) et l’erreur grimpe fortement. La ligne en tirets marque le vainqueur de la validation croisée — le juste milieu entre le surapprentissage (trop peu de pénalité) et le sous-apprentissage (trop).

Le chemin des coefficients : voyez le lasso sélectionner les variables

L’autre graphique caractéristique est le chemin des coefficients — chaque coefficient tracé à mesure que lambda augmente. C’est là que le comportement déterminant du lasso est visible : les coefficients ne font pas que se rétracter, ils atteignent exactement zéro et y restent. Un prédicteur dont le chemin est tombé à zéro a été retiré du modèle.

library(glmnet)
library(rsample)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)

# glmnet wants a numeric predictor matrix (no intercept column) + an outcome vector
x <- model.matrix(medv ~ ., train_data)[, -1]
y <- train_data$medv

# Fit lasso (alpha = 1) over a whole sequence of lambdas; standardize = TRUE by default
lasso_full <- glmnet(x, y, alpha = 1, standardize = TRUE)

plot(lasso_full, xvar = "lambda", label = TRUE)
title("Lasso coefficient paths (Boston)", line = 2.5)

Lasso coefficient-path plot for the Boston data: each coloured line is one predictor's standardized coefficient as the L1 penalty (lambda) increases from left to right; lines fan in toward zero and several reach exactly zero, so those predictors are selected out of the model.

Chaque courbe est un prédicteur. En lisant de droite à gauche (pénalité croissante → décroissante) : à une forte pénalité (à l’extrême droite) presque tous les coefficients sont nuls ; à mesure que la pénalité se relâche, les prédicteurs « s’allument » un à un à peu près dans leur ordre d’importance. Le nombre le long de l’axe supérieur indique combien de prédicteurs restent non nuls à ce lambda. C’est la sélection de variables qui se produit en continu — la fonction phare que le lasso possède et que le ridge n’a pas.

NoteLe ridge a l’air différent — il n’atteint jamais zéro

Relancez le chemin avec alpha = 0 (ridge) et les lignes glissent ensemble vers zéro mais ne le touchent jamais — le ridge conserve chaque prédicteur, simplement plus petit. Cette unique différence visuelle est toute l’histoire : le lasso sélectionne, le ridge ne fait que rétracter. L’elastic net (0 < alpha < 1) se situe entre les deux.

Finaliser et évaluer sur le jeu de test

Nous choisissons le meilleur lambda, finalisons le workflow, et faisons last_fit() — ajustement sur toutes les données d’entraînement et évaluation une seule fois sur le jeu de test intact. Le tableau ordonné des coefficients montre quels prédicteurs ont survécu :

library(rsample)
library(recipes)
library(parsnip)
library(workflows)
library(tune)
library(dials)
library(yardstick)
library(broom)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)

rec        <- recipe(medv ~ ., data = train_data) |> step_normalize(all_numeric_predictors())
lasso_spec <- linear_reg(penalty = tune(), mixture = 1) |> set_engine("glmnet")
lasso_wf   <- workflow() |> add_recipe(rec) |> add_model(lasso_spec)

set.seed(123); folds <- vfold_cv(train_data, v = 10)
grid <- grid_regular(penalty(range = c(-3, 0)), levels = 50)
set.seed(123)
lasso_tuned <- tune_grid(lasso_wf, resamples = folds, grid = grid,
                         metrics = metric_set(rmse, rsq))
best_lambda <- select_best(lasso_tuned, metric = "rmse")

# Finalize with the chosen lambda, fit on train, score ONCE on test
final_fit <- finalize_workflow(lasso_wf, best_lambda) |> last_fit(split)
collect_metrics(final_fit)
# A tibble: 2 × 4
  .metric .estimator .estimate .config        
  <chr>   <chr>          <dbl> <chr>          
1 rmse    standard       4.88  pre0_mod0_post0
2 rsq     standard       0.714 pre0_mod0_post0

Lisez les chiffres (la performance honnête et hors échantillon de votre modèle) :

  • RMSE ≈ 4.9 — une prédiction typique de la valeur médiane du logement se trompe d’environ 4 900 $ (la sortie est en milliers de dollars). Plus bas, mieux c’est ; c’est le nombre sur lequel comparer les modèles.
  • R² ≈ 0.71 — le modèle explique environ 71 % de la variance de la valeur médiane du logement sur des banlieues qu’il n’a jamais vues pendant l’ajustement. Solide pour un modèle linéaire sur ces données.

Et les coefficients survivants (à l’échelle standardisée, donc directement comparables en taille) :

library(broom)

# The coefficients of the finalized lasso model
tidy(extract_fit_parsnip(final_fit$.workflow[[1]]))
# A tibble: 14 × 3
   term        estimate penalty
   <chr>          <dbl>   <dbl>
 1 (Intercept)  22.5      0.001
 2 crim         -0.709    0.001
 3 zn            0.794    0.001
 4 indus        -0.0682   0.001
 5 chas          0.837    0.001
 6 nox          -1.93     0.001
 7 rm            2.98     0.001
 8 age           0.0236   0.001
 9 dis          -2.82     0.001
10 rad           2.36     0.001
11 tax          -1.75     0.001
12 ptratio      -2.02     0.001
13 black         0.893    0.001
14 lstat        -3.58     0.001

Les plus grands coefficients — lstat (% population de statut inférieur), rm (pièces), dis (distance aux centres d’emploi) — sont les prédicteurs sur lesquels le modèle s’appuie le plus. Comme nous avons réglé sur une pénalité légère ici, peu de coefficients ont été poussés entièrement à zéro ; la section suivante montre comment une pénalité un peu plus lourde achète un modèle véritablement parcimonieux.

La voie classique : glmnet::cv.glmnet

La plupart des utilisateurs de R rencontrent la régression pénalisée à travers glmnet directement. cv.glmnet() fait la validation croisée pour vous et renvoie deux lambdas utiles. C’est la voie qui vaut la peine d’être connue parce qu’elle rend explicite le choix entre lambda.min et lambda.1se :

library(glmnet)
library(rsample)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)
test_data  <- testing(split)

# glmnet works on a numeric matrix + outcome vector
x      <- model.matrix(medv ~ ., train_data)[, -1]
y      <- train_data$medv
x_test <- model.matrix(medv ~ ., test_data)[, -1]

# Lasso (alpha = 1) with built-in 10-fold cross-validation
set.seed(123)
cv_lasso <- cv.glmnet(x, y, alpha = 1)   # standardize = TRUE by default

c(lambda.min = cv_lasso$lambda.min, lambda.1se = cv_lasso$lambda.1se)
 lambda.min  lambda.1se 
0.006976771 0.347235533 

Deux lambdas, deux philosophies :

  • lambda.min — le lambda dont l’erreur de validation croisée est la plus faible. Prédit le mieux, mais conserve plus de prédicteurs.
  • lambda.1se — le plus grand lambda dont l’erreur reste à une erreur standard près du minimum. Un peu plus d’erreur, mais un modèle plus simple, pénalisé plus agressivement (plus de zéros). Le choix conventionnel quand vous valorisez l’interprétabilité et la robustesse.

Regardez les coefficients à lambda.1se — la pénalisation comme sélection de variables sous vos yeux :

library(glmnet)
library(rsample)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)
x <- model.matrix(medv ~ ., train_data)[, -1]
y <- train_data$medv
set.seed(123); cv_lasso <- cv.glmnet(x, y, alpha = 1)

# Coefficients at the simpler 1-SE model: dots are predictors dropped (= zero)
coef(cv_lasso, s = "lambda.1se")
14 x 1 sparse Matrix of class "dgCMatrix"
              lambda.1se
(Intercept) 16.513093237
crim        -0.007284334
zn           .          
indus        .          
chas         2.606472873
nox         -4.135548263
rm           4.443731663
age          .          
dis         -0.331671833
rad          .          
tax          .          
ptratio     -0.798716237
black        0.006918224
lstat       -0.492410327

Plusieurs prédicteurs — zn, indus, age, rad, tax — affichent un . : leur coefficient est exactement zéro, donc le lasso les a retirés du modèle. De 13 prédicteurs à une poignée, automatiquement. Ce modèle plus parcimonieux est plus facile à expliquer et généralise souvent tout aussi bien.

Maintenant évaluons les deux lambdas sur le jeu de test pour voir le compromis en chiffres :

library(glmnet)
library(rsample)
library(yardstick)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)
test_data  <- testing(split)
x      <- model.matrix(medv ~ ., train_data)[, -1]
y      <- train_data$medv
x_test <- model.matrix(medv ~ ., test_data)[, -1]
set.seed(123); cv_lasso <- cv.glmnet(x, y, alpha = 1)

# Predict at each lambda, then score on the held-out test set
pred_min <- as.vector(predict(cv_lasso, newx = x_test, s = "lambda.min"))
pred_1se <- as.vector(predict(cv_lasso, newx = x_test, s = "lambda.1se"))

data.frame(
  lambda      = c("lambda.min", "lambda.1se"),
  RMSE        = c(rmse_vec(test_data$medv, pred_min), rmse_vec(test_data$medv, pred_1se)),
  R2          = c(rsq_vec(test_data$medv, pred_min),  rsq_vec(test_data$medv, pred_1se))
)
      lambda     RMSE        R2
1 lambda.min 4.879740 0.7139875
2 lambda.1se 5.322084 0.6667434

lambda.min donne le RMSE le plus bas (≈ 4.9, R² ≈ 0.71) — la meilleure prédiction. lambda.1se coûte un peu de précision (RMSE ≈ 5.3, R² ≈ 0.67) mais en échange vous obtenez le modèle bien plus simple ci-dessus. Voilà la décision centrale de la régression pénalisée : combien de précision êtes-vous prêt à échanger contre de la simplicité ? La validation croisée vous remet les deux options ; vous choisissez selon votre objectif.

Ridge et elastic net : changez un seul argument

Tout ce qui précède était du lasso (mixture = 1 / alpha = 1). Les deux autres méthodes changent un seul argument — le workflow est par ailleurs identique :

library(rsample)
library(recipes)
library(parsnip)
library(workflows)
library(tune)
library(dials)
library(yardstick)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)
rec        <- recipe(medv ~ ., data = train_data) |> step_normalize(all_numeric_predictors())
set.seed(123); folds <- vfold_cv(train_data, v = 10)
grid <- grid_regular(penalty(range = c(-3, 0)), levels = 50)

# Helper: tune a penalized model at a given mixture, return test-set RMSE / R2
score_penalized <- function(mixture) {
  spec <- linear_reg(penalty = tune(), mixture = mixture) |> set_engine("glmnet")
  wf   <- workflow() |> add_recipe(rec) |> add_model(spec)
  set.seed(123)
  tuned <- tune_grid(wf, resamples = folds, grid = grid, metrics = metric_set(rmse, rsq))
  best  <- select_best(tuned, metric = "rmse")
  m     <- collect_metrics(finalize_workflow(wf, best) |> last_fit(split))
  data.frame(RMSE = m$.estimate[m$.metric == "rmse"],
             R2   = m$.estimate[m$.metric == "rsq"])
}

rbind(
  Ridge       = score_penalized(0),     # L2 only — keeps all predictors
  `Elastic net` = score_penalized(0.5), # L1 + L2 blend
  Lasso       = score_penalized(1)      # L1 only — selects predictors
)
                RMSE        R2
Ridge       5.008403 0.7031116
Elastic net 4.880683 0.7139031
Lasso       4.879740 0.7139875

Sur les données Boston les trois sont quasi identiques en précision (RMSE ≈ 4.9–5.0, R² ≈ 0.70–0.71) — ce qui est courant, et en soi une conclusion utile. Quand la précision prédictive est à égalité, préférez le modèle le plus simple : le lasso ou l’elastic net, qui ont retiré des prédicteurs (par ex. age), plutôt que le ridge, qui garde les 13. Toutes choses égales par ailleurs, optez pour le modèle le plus simple.

NoteLequel devrais-je utiliser ?
  • Lasso quand vous pensez que seuls quelques prédicteurs comptent — il donne un modèle parcimonieux et interprétable.
  • Ridge quand de nombreux prédicteurs contribuent chacun un peu et que vous voulez tous les conserver (il gère aussi élégamment la multicolinéarité en répartissant la charge entre prédicteurs corrélés).
  • Elastic net quand les prédicteurs sont corrélés et que vous voulez tout de même une sélection — il regroupe les prédicteurs corrélés au lieu d’en choisir un arbitrairement (la faiblesse du lasso). Réglez aussi mixture si vous hésitez : laissez la validation croisée choisir le mélange.

Les moindres carrés ordinaires choisissent les coefficients β qui minimisent la somme des carrés des résidus, \(RSS\). La régression pénalisée ajoute une pénalité sur les tailles des coefficients :

\[ \hat{\beta} = \arg\min_{\beta} \; RSS + \lambda \, P(\beta), \]

où λ ≥ 0 contrôle la force de la pénalité et \(P(\beta)\) est le terme de pénalité. Les trois méthodes ne diffèrent que par \(P\) :

\[ P_{ridge} = \sum_{j} \beta_j^2 \quad (\ell_2), \qquad P_{lasso} = \sum_{j} \lvert \beta_j \rvert \quad (\ell_1), \]

\[ P_{enet} = \alpha \sum_{j} \lvert \beta_j \rvert + (1-\alpha)\sum_{j} \beta_j^2 . \]

La pénalité L2 (ridge) est lisse, donc elle rétracte les coefficients mais ne les force jamais exactement à zéro. La pénalité L1 (lasso) a un coin à zéro, et c’est ce coin qui lui permet de mettre des coefficients exactement à zéro — la raison géométrique pour laquelle le lasso sélectionne les variables et le ridge non. L’elastic net mélange les deux avec \(\alpha\) (= mixture) : \(\alpha = 1\) est le lasso, \(\alpha = 0\) est le ridge. Dans tous les cas \(\lambda\) (= penalty) est le cadran choisi par validation croisée ; λ = 0 retrouve les moindres carrés ordinaires. Parce que la pénalité compare les tailles des coefficients, les prédicteurs doivent d’abord être standardisés — sinon un prédicteur mesuré en petites unités serait pénalisé plus lourdement du seul fait de son échelle.

Vous venez de caret ? La correspondance

Si vous avez appris la régression pénalisée avec le train(method = "glmnet") de caret, les idées se transposent directement — seuls les verbes changent. tidymodels fait de la pénalité et du mixture des paramètres de réglage explicites :

caret tidymodels
train(..., method = "glmnet") linear_reg(penalty = tune(), mixture = tune()) \|> set_engine("glmnet")
tuneGrid = expand.grid(alpha = 0, lambda = ...) mixture = 0 (ridge) + une grille penalty()
tuneGrid = expand.grid(alpha = 1, lambda = ...) mixture = 1 (lasso) + une grille penalty()
tuneLength = 10 grid_regular(penalty(), levels = ...) (ou grid = 10)
trControl = trainControl("cv", number = 10) vfold_cv(v = 10) + tune_grid()
model$bestTune select_best(tuned, metric = "rmse")
resamples(models) pour comparer fit_resamples() / collect_metrics() sur des plis partagés

alpha dans glmnet/caret est mixture dans parsnip ; lambda est penalty. Tout le reste est le même enchaînement split → recipe → tune → finalize que vous connaissez déjà.

🟢 Avec un agent IA

Vous avez un modèle avec trop de prédicteurs corrélés ? Demandez à Prova « ajuste un lasso à ces données, règle la pénalité par validation croisée, et dis-moi quels prédicteurs il conserve » — elle répond avec du code tidymodels + glmnet que vous pouvez exécuter sur vos propres données, puis vous aide à lire le chemin des coefficients et à choisir lambda.min vs lambda.1se. The runtime is the judge. Ask Prova →

Problèmes courants

  • Vous avez oublié de standardiser les prédicteurs. Les pénalités agissent sur la taille des coefficients, donc un prédicteur en grandes unités est injustement moins rétracté. Mettez toujours step_normalize(all_numeric_predictors()) dans le recipe (ou comptez sur le standardize = TRUE par défaut de glmnet). Les coefficients sont tout de même rapportés à l’échelle d’origine, donc ils restent interprétables.
  • glmnet renvoie une erreur sur des facteurs ou un x non numérique. glmnet() a besoin d’une matrice de modèle numérique. Construisez-la avec model.matrix(y ~ ., data)[, -1] (le [, -1] retire la colonne d’ordonnée à l’origine), ce qui transforme aussi les facteurs en variables indicatrices. Dans un recipe tidymodels, ajoutez step_dummy(all_nominal_predictors()).
  • Votre « meilleur » lambda se situe au bord de la grille. Cela signifie que la grille est trop étroite — élargissez penalty(range = c(...)) (elle est sur une échelle log10) jusqu’à ce que la courbe de validation croisée montre un minimum clair à l’intérieur de la plage, et non à une extrémité.
  • Deux exécutions donnent des lambdas légèrement différents. La répartition des plis de validation croisée est aléatoire. Appelez set.seed() avant vfold_cv() / cv.glmnet() pour un résultat reproductible.

Foire aux questions

Utilisez glmnet avec alpha = 0 : cv.glmnet(x, y, alpha = 0) pour choisir la pénalité par validation croisée, puis glmnet(x, y, alpha = 0, lambda = cv$lambda.min) pour le modèle final. En tidymodels : linear_reg(penalty = tune(), mixture = 0) |> set_engine("glmnet") dans un workflow avec step_normalize(), réglé par tune_grid(). Le ridge rétracte chaque coefficient mais conserve tous les prédicteurs.

Comme le ridge mais avec alpha = 1 (glmnet) ou mixture = 1 (tidymodels). cv.glmnet(x, y, alpha = 1) valide la pénalité par validation croisée ; coef() à lambda.1se montre quels prédicteurs le lasso a conservés et lesquels il a mis exactement à zéro. Contrairement au ridge, le lasso effectue une sélection de variables — il retire les prédicteurs faibles du modèle.

L’elastic net mélange les pénalités L1 (lasso) et L2 (ridge). Dans glmnet, fixez alpha entre 0 et 1 (par ex. alpha = 0.5). Dans tidymodels, utilisez linear_reg(penalty = tune(), mixture = tune()) et laissez tune_grid() chercher à la fois la pénalité et le mixture. C’est le meilleur choix quand les prédicteurs sont corrélés et que vous voulez tout de même une sélection.

La régression pénalisée (régularisée) ajoute une pénalité sur la taille des coefficients à la régression des moindres carrés ordinaires, rétractant les prédicteurs faibles vers zéro. Utilisez-la quand vous avez beaucoup de prédicteurs, des prédicteurs corrélés (multicolinéarité), ou un modèle qui surapprend — la pénalisation stabilise les estimations et prédit généralement mieux sur de nouvelles données. Le ridge, le lasso et l’elastic net sont les trois variantes standard.

glmnet est le package R standard pour la régression et la classification pénalisées. glmnet(x, y, alpha, lambda) ajuste le ridge (alpha = 0), le lasso (alpha = 1), ou l’elastic net (entre les deux) sur une séquence de pénalités ; cv.glmnet() ajoute une validation croisée intégrée et renvoie lambda.min (erreur de validation croisée la plus faible) et lambda.1se (le modèle le plus simple à une erreur standard près). Il a besoin d’une matrice de prédicteurs numérique et standardise les prédicteurs par défaut.

Les deux viennent de cv.glmnet(). lambda.min est la pénalité dont l’erreur de validation croisée est la plus faible — elle prédit le mieux mais conserve plus de prédicteurs. lambda.1se est la plus grande pénalité dont l’erreur reste à une erreur standard près du minimum — un modèle un peu moins précis mais plus simple, plus régularisé (plus de coefficients mis à zéro). Choisissez lambda.min pour la précision brute, lambda.1se pour un modèle plus simple et plus robuste.

Testez votre compréhension

En utilisant les données Boston (MASS), construisez un modèle ridge (mixture = 0) à la manière tidymodels : séparez 80/20 stratifié sur medv, normalisez les prédicteurs, réglez penalty par validation croisée à 10 plis, et rapportez le RMSE et le R² du jeu de test. Comment se compare-t-il au lasso de la leçon — et le ridge retire-t-il des prédicteurs ?

La forme est identique au workflow du lasso — changez seulement mixture = 1 en mixture = 0. Vous aurez besoin de rsample, recipes, parsnip, workflows, tune, dials et yardstick. Pour vérifier les prédicteurs retirés, regardez le tidy() de l’ajustement finalisé — le ridge les conservera tous (aucun zéro).

library(rsample); library(recipes); library(parsnip)
library(workflows); library(tune); library(dials); library(yardstick)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)

rec        <- recipe(medv ~ ., data = train_data) |> step_normalize(all_numeric_predictors())
ridge_spec <- linear_reg(penalty = tune(), mixture = 0) |> set_engine("glmnet")
ridge_wf   <- workflow() |> add_recipe(rec) |> add_model(ridge_spec)

set.seed(123); folds <- vfold_cv(train_data, v = 10)
grid <- grid_regular(penalty(range = c(-3, 0)), levels = 50)
set.seed(123)
tuned <- tune_grid(ridge_wf, resamples = folds, grid = grid, metrics = metric_set(rmse, rsq))
best  <- select_best(tuned, metric = "rmse")

collect_metrics(finalize_workflow(ridge_wf, best) |> last_fit(split))

Vous obtiendrez un RMSE autour de 5.0 et un R² autour de 0.70 — essentiellement la même précision que le lasso. La différence clé est dans les coefficients : le ridge conserve les 13 prédicteurs (aucun mis à zéro), tandis que le lasso en a retiré plusieurs. Quand la précision est à égalité, le modèle lasso/elastic net est le plus simple, le préférable.

Vérification rapide. Vous ajustez un lasso et un ridge aux mêmes données et ils prédisent aussi bien l’un que l’autre, mais le modèle lasso a cinq coefficients exactement à zéro tandis que le modèle ridge n’en a aucun. Quel modèle expédieriez-vous, et pourquoi ?

Expédiez le modèle lasso. Quand deux modèles prédisent aussi bien, préférez le plus simple : les cinq zéros du lasso signifient qu’il utilise moins de prédicteurs, donc il est plus facile à expliquer, moins coûteux en collecte de données, et moins susceptible de surapprendre par la suite. Le ridge conserve chaque prédicteur, ce qui ajoute de la complexité sans gain de précision ici. Toutes choses égales par ailleurs, optez pour le modèle le plus simple.

Conclusion

Vous avez vu la régression pénalisée de bout en bout sur les données Boston : le ridge (L2) rétracte chaque coefficient mais les conserve tous ; le lasso (L1) rétracte et met certains exactement à zéro, faisant de la sélection de variables ; l’elastic net mélange les deux. Vous avez réglé la pénalité lambda honnêtement par validation croisée — à la fois la manière moderne (linear_reg(penalty = tune(), mixture =) |> set_engine("glmnet") + tune_grid()) et la voie classique cv.glmnet() — lu la courbe d’erreur de validation croisée et le chemin des coefficients, et pesé lambda.min (meilleure prédiction) contre lambda.1se (modèle plus simple). La règle pratique : standardisez d’abord, laissez la validation croisée choisir lambda, et quand la précision est à égalité, expédiez le modèle le plus simple.

Ensuite, découvrez l’autre approche de sélection de variables dans régression pas à pas, ou revisitez le workflow sur lequel cette leçon s’appuie dans construire des modèles avec tidymodels.

Leçons connexes

Vous préférez un livre ? Machine Learning Essentials est disponible en PDF téléchargeable — toutes les leçons de cette série, hors ligne et à vous.

Obtenir le livre (PDF) →Les leçons restent gratuites en ligne.
Cette page vous a-t-elle été utile ?

Prouvez que vous savez le faire. Maîtrisez toute la série Sélection de modèles en R — suivez votre parcours, construisez des projets et obtenez un certificat.

Commencer gratuitement →

Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.

dès 15 $/mois facturé annuellement

Passer à Pro →

✓ Vous êtes Pro — continuez. The runtime is the judge.

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

Cette leçon est reproductible : chaque coefficient, métrique et figure a été produit par le code montré — copiez n’importe quel bloc et exécutez-le pour les reproduire. The runtime is the judge.

Références

  • James, G., Witten, D., Hastie, T., & Tibshirani, R. (2014). An Introduction to Statistical Learning, with Applications in R. Springer. (Chapitre 6 — Sélection et régularisation des modèles linéaires.)
  • Friedman, J., Hastie, T., & Tibshirani, R. (2010). Regularization Paths for Generalized Linear Models via Coordinate Descent. Journal of Statistical Software, 33(1). (Le package glmnet.)
  • Kuhn, M., & Silge, J. Tidy Modeling with R. En ligne, gratuit (CC BY-NC-SA).

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {Régression pénalisée en R : ridge, lasso et elastic net avec
    glmnet},
  date = {2026-06-25},
  url = {https://www.datanovia.com/learn/machine-learning/model-selection/penalized-regression},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Régression pénalisée en R : ridge, lasso et elastic net avec glmnet.” 2026. June 25. https://www.datanovia.com/learn/machine-learning/model-selection/penalized-regression.