Régression Lasso : Guide Complet — Principes, Exemples et Implémentation Python

Régression Lasso : Guide Complet — Principes, Exemples et Implémentation Python

Régression Lasso : Guide Complet — Principes, Exemples et Implémentation Python

La régression lasso est un algorithme d’apprentissage supervisé qui combine la prédiction linéaire avec une pénalité de régularisation L1. Contrairement à la régression Ridge qui réduit les coefficients sans jamais les annuler complètement, la régression lasso produit des solutions creuses (sparse) : elle force certains coefficients à devenir exactement zéro, réalisant ainsi une sélection automatique de variables. C’est cette propriété unique qui fait du Lasso un outil indispensable dès que l’on travaille avec des données en grande dimension.

La régression lasso, dont le nom provient de l’acronyme anglais Least Absolute Shrinkage and Selection Operator, a été introduite par Robert Tibshirani en 1996. Elle s’est imposée comme l’une des techniques fondamentales du Machine Learning, particulièrement dans les domaines où le nombre de caractéristiques (features) dépasse largement le nombre d’observations. Dans ce guide complet, nous explorerons les fondements mathématiques, l’intuition géométrique, l’implémentation pratique en Python, et les applications concrètes de cet algorithme essentiel.


Principe mathématique

La fonction objectif du Lasso

Soit un jeu de données composé de $n$ observations et $p$ variables explicatives. Notons $X \in \mathbb{R}^{n \times p}$ la matrice des features, $y \in \mathbb{R}^n$ le vecteur des cibles, et $w \in \mathbb{R}^p$ le vecteur des coefficients à estimer. La régression lasso résout le problème d’optimisation suivant :

$$
\hat{w}^{\text{lasso}} = \underset{w}{\arg\min} \left( \frac{1}{2n} |y – Xw|_2^2 + \alpha |w|_1 \right)
$$

où :

  • $\frac{1}{2n} |y – Xw|_2^2$ est le terme de perte quadratique (RSS — Residual Sum of Squares), identique à la régression linéaire ordinaire. Il mesure l’erreur de prédiction sur les données d’entraînement.
  • $|w|1 = \sum |w_j|$}^{p est la norme L1 du vecteur des coefficients. C’est la somme des valeurs absolues de chaque coefficient.
  • $\alpha \geq 0$ est l’hyperparamètre de régularisation qui contrôle le compromis entre l’ajustement aux données (fidélité) et la parcimonie du modèle (sparsité).

Pourquoi la norme L1 produit-elle des zéros exacts ?

C’est ici que réside la différence fondamentale entre le Lasso (norme L1) et le Ridge (norme L2). Pour comprendre ce phénomène, il faut examiner la géométrie des deux approches.

Formulation contrainte équivalente. Le problème Lasso peut se reformuler sous forme contrainte :

$$
\underset{w}{\arg\min} \ |y – Xw|_2^2 \quad \text{sous la contrainte} \quad |w|_1 \leq t
$$

Dans un espace à 2 dimensions (deux coefficients $w_1$ et $w_2$), la région définie par $|w|_1 \leq t$ a la forme d’un diamant (ou losange) dont les sommets se trouvent sur les axes. Les courbes de niveau du RSS, quant à elles, sont des ellipses centrées autour des coefficients de la régression linéaire ordinaire (OLS).

Intersection aux sommets. La solution optimale se trouve au premier point de contact entre une ellipse RSS et le diamant L1. Comme le diamant possède des coins pointus situés exactement sur les axes, il est très fréquent que le point de contact se produise sur un sommet ou une arête — ce qui signifie qu’au moins un coefficient est exactement égal à zéro. Cette géométrie explique la propriété de sélection de variables : le Lasso identifie automatiquement les features pertinentes (coefficients non nuls) et élimine les features inutiles (coefficients exactement nuls).

Cas particulier : solution analytique à une variable

Dans le cas univarié ($p = 1$), avec une feature $x$ normalisée (moyenne nulle, variance unitaire), la solution Lasso admet une forme explicite appelée opérateur de seuillage doux (soft-thresholding) :

$$
\hat{w}_j = \text{sign}(z_j) \cdot \max(|z_j| – \alpha, 0)
$$

où $z_j$ est le coefficient OLS correspondant. Cette formule montre clairement que si $|z_j| \leq \alpha$, alors $\hat{w}_j = 0$ : le coefficient est annulé. C’est la manifestation algébrique de la propriété de sparsité.


Intuition — Comment le comprendre ?

Le Lasso comme sélection automatique de variables

Imaginez que vous travaillez avec un jeu de données comportant 1 000 variables explicatives : âge, revenu, localisation, dozens de mesures biologiques, etc. Vous suspectez que seules 20 ou 30 de ces variables sont réellement utiles pour prédire votre cible. La régression linéaire ordinaire utiliserait les 1 000 variables, ce qui entraînerait un surajustement (overfitting) massif et un modèle illisible.

La régression lasso résout ce problème élégamment. En augmentant progressivement le paramètre $\alpha$, le Lasso force les coefficients des variables les moins informatives à devenir exactement zéro. À la fin du processus, il ne reste que les variables « importantes » — celles dont les coefficients sont non nuls. Le Lasso a sélectionné automatiquement les meilleures features, sans que vous ayez besoin de tester manuellement des combinaisons.

L’effet de $\alpha$ sur la sélection

Le paramètre $\alpha$ agit comme un bouton de sensibilité :

  • $\alpha = 0$ : Aucun terme de régularisation. Le Lasso se réduit à la régression linéaire ordinaire. Tous les coefficients sont potentiellement non nuls.
  • $\alpha$ petit : Quelques coefficients très faibles sont annulés. Le modèle reste proche de l’OLS tout en commençant à éliminer le bruit.
  • $\alpha$ modéré : Un nombre significatif de coefficients devient nul. Le modèle est parcimonieux et généralise mieux.
  • $\alpha$ très grand : Presque tous les coefficients sont nuls. Le modèle sous-ajuste (underfitting) et manque de capacité prédictive.

Le choix optimal de $\alpha$ se fait généralement par validation croisée, en cherchant la valeur qui minimise l’erreur sur des données non vues.

Lasso vs Ridge : quand choisir quoi ?

Le choix entre Lasso et Ridge dépend de la structure sous-jacente de vos données :

  • Privilégiez le Lasso lorsque vous pensez que seules quelques variables sont véritablement importantes (hypothèse de sparsité). Le Lasso sélectionnera ces variables et éliminera les autres.
  • Privilégiez le Ridge lorsque vous pensez que toutes (ou presque toutes) les variables contribuent à la prédiction, chacune avec un effet modeste (typiquement le cas en génomique polygénique ou en traitement d’images).
  • Considérez l’Elastic Net lorsque vos variables sont fortement corrélées entre elles — un cas où le Lasso seul tend à sélectionner une seule variable au hasard parmi un groupe corrélé.

Implémentation Python — Exemple complet

Installation des dépendances

pip install scikit-learn numpy matplotlib

Code complet

# Etape 1: Generation de donnees synthetiques en haute dimension

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Lasso, LassoCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

np.random.seed(42)

n_samples = 200       # Nombre d'observations
n_features = 100      # Nombre total de variables (haute dimension)
n_informative = 5     # Seulement 5 variables sont reellement informatives

# Creation de la matrice X avec des features correlees
X = np.random.randn(n_samples, n_features)

# Construction du vrai vecteur de coefficients : seuls 5 sont non nuls
w_true = np.zeros(n_features)
w_true[:n_informative] = np.array([3.0, -2.5, 1.8, -1.2, 0.7])

# Generation de la cible avec un bruit gaussien
noise = np.random.randn(n_samples) * 0.5
y = X @ w_true + noise

print(f"Jeu de donnees : {n_samples} echantillons, {n_features} features")
print(f"Variables reellement informatives : {n_informative}")
print(f"Coefficients vrais : {w_true[:n_informative]}")

# Etape 2: Separation train / test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# Etape 3: Ajustement du Lasso pour differentes valeurs d'alpha
alphas = np.logspace(-4, 2, 50)  # 50 valeurs de 0.0001 a 100
coef_paths = []                   # Stocke les coefficients pour chaque alpha
n_nonzero = []                    # Nombre de coefficients non nuls
train_scores = []                 # Score R2 sur le train
test_scores = []                  # Score R2 sur le test

for alpha in alphas:
    # Creation et ajustement du modele Lasso
    lasso = Lasso(alpha=alpha, max_iter=10000, random_state=42)
    lasso.fit(X_train, y_train)

    # Stockage des resultats
    coef_paths.append(lasso.coef_)
    n_nonzero.append(np.count_nonzero(lasso.coef_))

    # Evaluation des performances
    y_pred_train = lasso.predict(X_train)
    y_pred_test = lasso.predict(X_test)
    train_scores.append(r2_score(y_train, y_pred_train))
    test_scores.append(r2_score(y_test, y_pred_test))

coef_paths = np.array(coef_paths)

# Etape 4: Visualisation des chemins de regularisation
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Graphique de gauche : evolution des coefficients en fonction de log(alpha)
ax1 = axes[0]
for j in range(n_informative):
    ax1.plot(np.log10(alphas), coef_paths[:, j], linewidth=2,
             label=f"Coefficient {j} (vrai = {w_true[j]})")
# Tracage des coefficients des variables non informatives (en gris)
for j in range(n_informative, n_features):
    ax1.plot(np.log10(alphas), coef_paths[:, j], color='gray',
             alpha=0.15, linewidth=0.5)
ax1.axvline(0, color='red', linestyle='--', alpha=0.7, label='alpha = 1')
ax1.set_xlabel('log10(alpha)', fontsize=12)
ax1.set_ylabel('Valeur du coefficient', fontsize=12)
ax1.set_title('Chemins de regularisation - Regression Lasso', fontsize=13)
ax1.legend(fontsize=8, loc='upper left')
ax1.grid(True, alpha=0.3)

# Graphique de droite : nombre de features selectionnees vs alpha
ax2 = axes[1]
ax2.semilogx(alphas, n_nonzero, 'b-o', linewidth=2, markersize=4)
ax2.axhline(n_informative, color='red', linestyle='--',
            label=f'Vrai nombre ({n_informative})')
ax2.set_xlabel('Alpha (echelle logarithmique)', fontsize=12)
ax2.set_ylabel('Nombre de coefficients non nuls', fontsize=12)
ax2.set_title('Selection de variables en fonction de alpha', fontsize=13)
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('lasso_coefficient_paths.png', dpi=150, bbox_inches='tight')
plt.show()

# Etape 5: LassoCV - selection automatique d'alpha par validation croisee
print("\n" + "="*60)
print("LassoCV - Selection automatique d'alpha par validation croisee")
print("="*60)

lasso_cv = LassoCV(
    alphas=None,          # L'algo teste automatiquement un eventail d'alphas
    cv=5,                 # Validation croisee a 5 plis
    max_iter=10000,
    random_state=42,
    n_jobs=-1             # Utilisation de tous les coeurs CPU
)
lasso_cv.fit(X_train, y_train)

alpha_optimal = lasso_cv.alpha_
print(f"Alpha optimal selectionne : {alpha_optimal:.6f}")
print(f"Nombre de coefficients non nuls : {np.count_nonzero(lasso_cv.coef_)}")
print(f"Score R2 (train) : {lasso_cv.score(X_train, y_train):.4f}")
print(f"Score R2 (test)  : {r2_score(y_test, lasso_cv.predict(X_test)):.4f}")

# Affichage des coefficients estimes vs coefficients vrais
fig, ax = plt.subplots(figsize=(10, 5))
indices = np.arange(n_informative + 3)  # Affiche les 5 vrais + 3 faux positifs max
ax.bar(indices - 0.2, w_true[indices], width=0.4,
       label='Coefficients vrais', color='steelblue', alpha=0.8)
ax.bar(indices + 0.2, lasso_cv.coef_[indices], width=0.4,
       label='Coefficients LassoCV', color='coral', alpha=0.8)
ax.set_xticks(indices)
ax.set_xticklabels([f'Feature {i}' for i in indices])
ax.set_ylabel('Valeur du coefficient', fontsize=12)
ax.set_title('Comparaison : coefficients vrais vs estimes par LassoCV', fontsize=13)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('lasso_coefficients_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

# Etape 6: Analyse detaillee - tableau recapitulatif
print("\n" + "="*60)
print("Tableau recapitulatif des coefficients pour alpha optimal")
print("="*60)
print(f"{'Index':<8} {'Vrai':>10} {'Lasso':>10} {'Selectionne':>14}")
print("-" * 44)
for j in range(n_features):
    selected = "Oui" if lasso_cv.coef_[j] != 0 else "Non"
    print(f"{j:<8} {w_true[j]:>10.4f} {lasso_cv.coef_[j]:>10.4f} {selected:>14}")

# Etape 7: Courbe performances train/test
fig, ax = plt.subplots(figsize=(8, 5))
ax.semilogx(alphas, train_scores, 'b-o', markersize=3, linewidth=1.5,
            label='Score R2 (train)')
ax.semilogx(alphas, test_scores, 'r-s', markersize=3, linewidth=1.5,
            label='Score R2 (test)')
ax.axvline(alpha_optimal, color='green', linestyle='--',
           label=f'Alpha optimal ({alpha_optimal:.4f})')
ax.set_xlabel('Alpha (echelle logarithmique)', fontsize=12)
ax.set_ylabel('Score R2', fontsize=12)
ax.set_title('Performance du Lasso en fonction de alpha', fontsize=13)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lasso_perf_vs_alpha.png', dpi=150, bbox_inches='tight')
plt.show()

print("\nAnalyse terminee !")

Explication étape par étape

Étape 1 — Données synthétiques. Nous créons un problème délibérément difficile : 200 observations pour 100 variables, dont seulement 5 sont informatives. C’est exactement le scénario dans lequel le Lasso excelle lorsque $p$ est grand mais que le vrai modèle est sparse.

Étape 2 — Parcours d’alphas. Nous testons 50 valeurs de $\alpha$ espacées logarithmiquement de 0,0001 à 100. Pour chaque valeur, nous entraînons un modèle Lasso et enregistrons les coefficients obtenus, le nombre de features sélectionnées et les scores $R^2$.

Étape 3 — LassoCV. Au lieu de choisir $\alpha$ manuellement, LassoCV effectue automatiquement une validation croisée pour trouver la valeur optimale. C’est la méthode recommandée en pratique.

Étape 4 — Comparaison des coefficients. Le tableau récapitulatif montre pour chaque variable le coefficient vrai, le coefficient estimé par LassoCV, et si la variable a été sélectionnée (coefficient non nul).

Hyperparamètres

Hyperparamètre Rôle Valeurs typiques Remarques
alpha Intensité de la pénalisation L1 1e-4 à 10 Plus alpha est grand, plus le modèle force des coefficients à zéro.
fit_intercept Ajoute une ordonnée à l’origine True (défaut), False À laisser sur True sauf si les données sont déjà centrées et l’intercept inutile.
max_iter Nombre maximal d’itérations 1000 à 10000 À augmenter si l’optimisation ne converge pas.
tol Tolérance de convergence 1e-4 (défaut), 1e-6 Une valeur plus faible donne une solution plus précise mais rallonge le calcul.
precompute Utilise une matrice de Gram précalculée False, True, matrice Peut accélérer l’entraînement quand $n > p$.
selection Choix des coordonnées en descente 'cyclic', 'random' 'random' peut accélérer la convergence sur certains jeux de données.

Recommandation pratique. Dans la grande majorité des cas, utilisez LassoCV plutôt que Lasso. La validation croisée réduit le risque de choisir une pénalisation trop faible (surapprentissage) ou trop forte (sous-ajustement).


Avantages et limites

Avantages

  • Sélection automatique de variables — Le Lasso identifie et conserve uniquement les features pertinentes en forçant les autres coefficients à zéro exact. C’est son atout majeur : un modèle qui fait à la fois de la prédiction et de la sélection de variables.
  • Modèles interprétables — En éliminant les variables inutiles, le Lasso produit des modèles plus simples et plus faciles à interpréter, ce qui est crucial en recherche scientifique et en prise de décision métier.
  • Efficacité computationnelle — L’algorithme de descente de coordonnées (coordinate descent) utilisé par scikit-learn est très rapide, même pour des milliers de variables.
  • Résistance au surajustement — La régularisation L1 réduit la variance du modèle, améliorant la capacité de généralisation, particulièrement en haute dimension ($p > n$).
  • Pas de seuil arbitraire — Contrairement aux méthodes de sélection pas-à-pas (stepwise selection), le Lasso ne repose pas sur des seuils statistiques arbitraires (p-valeurs). La sélection émerge naturellement de l’optimisation.

Limites

  • Mauvaise gestion des features corrélées — Lorsque plusieurs variables sont fortement corrélées, le Lasso tend à en sélectionner une seule de manière arbitraire et à annuler les autres. Cela peut conduire à une sélection instable : une légère modification des données change la variable retenue. Pour remédier à ce problème, l’Elastic Net combine les pénalités L1 et L2.
  • Limite de sélection à $n$ variables — Le Lasso ne peut sélectionner au plus que $n$ variables (où $n$ est le nombre d’observations). Si $p \gg n$, le Lasso sature : il ne peut pas aller au-delà de $n$ features sélectionnées. L’Elastic Net lève cette limitation.
  • Sensibilisation à l’échelle des features — Comme toutes les méthodes de régularisation, le Lasso est sensible à l’échelle des variables. Il est impératif de standardiser les features (centrer-réduire) avant l’application du Lasso.
  • Pas de solution analytique fermée — Contrairement au Ridge qui admet une solution en forme close ($\hat{w} = (X^T X + \alpha I)^{-1} X^T y$), le Lasso nécessite un algorithme itératif (descente de coordonnées), ce qui le rend légèrement plus lent.
  • Biais d’estimation — Le Lasso sous-estime systématiquement l’amplitude des coefficients non nuls (biais de shrinkage). Sur les coefficients importants, ce biais peut être significatif. Une approche en deux étapes (Lasso pour la sélection, puis OLS sur les features retenues) peut atténuer ce problème.

Cas d’usage

1. Génomique et biologie moléculaire

C’est l’application phare du Lasso. En génomique, on mesure l’expression de dizaines de milliers de gènes ($p \approx 20\,000$) sur un nombre limité de patients ($n \approx 100$ à $500$). La plupart des gènes n’ont aucun lien avec la maladie étudiée. Le Lasso identifie automatiquement le petit sous-ensemble de gènes pertinents pour la prédiction (par exemple, le statut tumoral ou la réponse à un traitement). Cette capacité de sélection en ultra-haute dimension fait du Lasso un outil standard en bio-informatique.

2. Traitement du langage naturel (NLP) et classification de textes

La vectorisation de textes (TF-IDF, Bag-of-Words) produit des représentations extrêmement creuses avec des dizaines de milliers de mots/features par document. La régression lasso permet d’identifier les termes les plus discriminants pour une tâche de classification (spam/ham, analyse de sentiment, catégorisation thématique) tout en ignorant le vocabulaire non pertinent. Le modèle résultant est non seulement plus performant mais aussi interprétable : on peut lire directement quels mots contribuent à la prédiction.

3. Finance quantitative et scoring de crédit

Dans le scoring de crédit, les institutions financières disposent de centaines de variables descriptives sur chaque emprunteur (revenu, historique bancaire, endettement, âge, profession, etc.). Le Lasso aide à construire des modèles de prédiction de défaut parcimonieux, ne retenant que les variables qui apportent réellement de l’information prédictive. Ces modèles sont plus robustes, plus faciles à auditer par les régulateurs, et moins sujets au surajustement.

4. Économétrie et sciences sociales

En économétrie, les chercheurs travaillent souvent avec un grand nombre de covariables potentielles (démographiques, géographiques, temporelles) et doivent identifier les déterminants réels d’un phénomène économique. Le Lasso, utilisé comme outil de sélection préalable, aide à construire des modèles économétriques plus fiables en éliminant les variables parasites. Des variantes comme le Lasso adaptatif et le Lasso post-sélection sont couramment employées dans la littérature économétrique moderne.


Voir aussi


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.