Régularisation : alpha règle la force, l1_ratio le type

Un modèle qui récite ses données d’entraînement a besoin d’un frein. alpha en règle l’intensité, l1_ratio choisit entre Ridge et Lasso. Trop freiner nuit autant que pas assez.

8 min de lecturemachine-learningpythondata-science

Un étudiant qui apprend par cœur toutes les réponses des exercices corrigés est excellent sur les exercices corrigés, et perdu à l’examen. C’est exactement ce que fait un modèle sans frein :

text
Entraînement : 99 %
Test         : 65 %

La régularisation est ce frein. Elle dit au modèle : apprends les grandes tendances, ne complique pas ton explication.

Ce qu’elle fait réellement

Un modèle sans régularisation poursuit un seul objectif : réduire l’erreur sur les données d’entraînement. C’est ce que fait la régularisation qui change tout — elle en ajoute un second :

text
minimiser :   erreur sur l'entraînement  +  alpha × complexité du modèle

Le modèle doit désormais négocier. Un coefficient qui améliore un peu l’erreur mais coûte cher en complexité ne sera plus retenu. Et alpha est le taux de change entre les deux termes : il dit combien vaut une unité de complexité, exprimée en unités d’erreur.

C’est tout. Tout le reste — Ridge, Lasso, elastic net — découle de la façon dont on mesure cette « complexité ».

alpha : la force du frein

text
alpha petit   →  peu de frein  →  modèle libre
alpha grand   →  beaucoup de frein  →  modèle contraint

Les deux extrémités éclairent le mécanisme mieux que les valeurs intermédiaires.

alpha = 0 supprime le second terme : on retombe sur la régression sans pénalité, libre de surapprendre autant qu’elle veut.

alpha très grand rend la complexité si coûteuse que la meilleure stratégie devient de n’avoir aucun coefficient. Tous tombent à zéro, et le modèle prédit alors la moyenne de la cible, quelle que soit l’entrée. Son R² s’approche de zéro : il ne se trompe pas beaucoup, mais il n’explique rien.

Entre les deux, une courbe en U qu’il faut avoir en tête :

text
erreur

  │ ╲                                     ╱
  │  ╲         test                      ╱
  │   ╲___                          ____╱
  │       ╲________________________╱
  │  ╲
  │   ╲___
  │       ╲______________________________  entraînement
  └──────────────────────────────────────────→ alpha
     surapprentissage   bon      sous-apprentissage
                     compromis

L’erreur d’entraînement ne fait que monter quand alpha augmente : c’est normal, on bride le modèle. C’est l’erreur de test qui décrit un U, et son minimum est la valeur cherchée.

alphaEffetSymptôme
trop petitsurapprentissageexcellent à l’entraînement, mauvais en test
bien réglébon compromisles deux erreurs proches et basses
trop grandsous-apprentissageles deux erreurs élevées

Le diagnostic ne se lit donc pas sur un score seul, mais sur l’écart entre les deux — le même raisonnement que pour tout autre hyperparamètre de capacité.

l1_ratio : le type de frein

Il reste à choisir comment mesurer la complexité. Deux façons, aux conséquences très différentes :

  • L1 additionne les valeurs absolues des coefficients. Elle peut en mettre certains exactement à zéro, ce qui revient à supprimer des variables.
  • L2 additionne leurs carrés. Elle rétrécit les coefficients trop grands sans les annuler, et garde donc toutes les variables.

l1_ratio est le curseur entre les deux :

text
l1_ratio = 0     →  100 % L2   →  Ridge
l1_ratio = 0.5   →  50 % L1, 50 % L2
l1_ratio = 0.7   →  70 % L1, 30 % L2
l1_ratio = 1     →  100 % L1   →  Lasso

Ce que cela signifie : ElasticNet n’est pas un troisième modèle à côté de Ridge et Lasso, c’est le modèle qui les contient tous les deux. Les deux noms familiers sont les extrémités d’un même curseur.

En pratique, aux extrémités, on préfère Ridge et Lasso directement : ce sont des implémentations dédiées, mieux conditionnées numériquement, et l’intention est plus lisible dans le code. ElasticNet sert pour l’entre-deux.

Les deux boutons, ensemble

Dans scikit-learn, la fonction que minimise ElasticNet s’écrit ainsi :

text
1/(2n) × ||y − Xw||²  +  alpha × l1_ratio × ||w||₁  +  ½ × alpha × (1 − l1_ratio) × ||w||²

L’essentiel se lit sans être mathématicien : alpha multiplie les deux termes de pénalité, l1_ratio répartit le budget entre eux. C’est exactement la distinction qu’il faut retenir, et elle est inscrite dans la formule.

Donc ElasticNet(alpha=0.7, l1_ratio=0.7) déclare deux choses indépendantes : un frein plutôt appuyé, et un frein à dominante L1 qui va probablement annuler plusieurs variables.

Pourquoi L1 annule-t-elle, et pas L2 ?

L’intuition est géométrique. À budget de pénalité égal, la contrainte L1 dessine un losange dont les sommets sont posés sur les axes, tandis que la contrainte L2 dessine un cercle. La solution se trouve là où la surface d’erreur touche cette contrainte — et un losange se fait toucher par un coin, c’est-à-dire en un point où une coordonnée vaut exactement zéro.

Le développement complet, avec le comportement de chacune sur des variables corrélées, est dans quelle différence entre régularisation L1 et L2.

La condition qu’on oublie : normaliser

La pénalité porte sur la taille des coefficients. Or un coefficient dépend de l’unité de sa variable : passer un montant des euros aux centimes divise son coefficient par cent, sans rien changer au modèle.

Conséquence : sans mise à l’échelle, régulariser revient à pénaliser plus fortement les variables exprimées dans de petites unités. C’est un choix arbitraire déguisé en méthode, et il fausse silencieusement le résultat.

python
from sklearn.linear_model import ElasticNet
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

modele = make_pipeline(
    StandardScaler(),
    ElasticNet(alpha=0.7, l1_ratio=0.7, random_state=42),
)

modele.fit(train_x, train_y)

Le Pipeline n’est pas un raffinement de style : il garantit que la mise à l’échelle est calculée sur l’entraînement seul et appliquée telle quelle au test. Normaliser sur l’ensemble des données avant de découper est une fuite de données classique, qui gonfle les scores sans améliorer le modèle.

Choisir alpha sans le deviner

Ces deux valeurs ne se règlent pas au jugement. Deux approches, par ordre d’effort.

La version directe, qui cherche alpha et l1_ratio par validation croisée :

python
from sklearn.linear_model import ElasticNetCV

modele = make_pipeline(
    StandardScaler(),
    ElasticNetCV(
        l1_ratio=[0.1, 0.5, 0.7, 0.9, 0.95, 1.0],
        cv=5,
        random_state=42,
    ),
)

Deux détails valent d’être notés. Les valeurs de l1_ratio sont resserrées vers 1 — c’est la recommandation usuelle, parce que la sensibilité du modèle est plus forte du côté Lasso. Et alpha n’est pas fourni : la classe engendre elle-même une grille logarithmique adaptée aux données, ce qui est presque toujours meilleur qu’une grille écrite à la main.

La version générale, quand la régularisation n’est qu’un réglage parmi d’autres, passe par une recherche sur le pipeline entier avec des échelles logarithmiques — la méthode est détaillée dans comment régler ses hyperparamètres.

Attention au piège du paramètre inversé

Dans Ridge, Lasso et ElasticNet, alpha augmente la régularisation. Dans LogisticRegression et SVC, le paramètre s’appelle C et fait l’inverse : un C petit régularise fort.

C’est la confusion la plus fréquente sur ce sujet, et elle conduit à chercher dans la mauvaise direction pendant une heure. Le moyen mnémotechnique : alpha est le prix de la complexité, C est la confiance accordée aux données.

Le cas concret d’un alpha trop grand

Voici une application immédiate. Dans le script d’entraînement instrumenté avec MLflow, le modèle tourne avec alpha=0.7 sur les données de qualité du vin, et son R² plafonne autour de 0,1.

Ce n’est ni un bug ni une limite du jeu de données : c’est un alpha trop grand. Le frein est si serré que les coefficients ont été écrasés, et le modèle s’approche de la prédiction constante décrite plus haut. Descendre alpha vers 0,01 ou le laisser choisir par ElasticNetCV change complètement le résultat.

C’est la meilleure raison d’avoir un journal d’expériences : sans comparaison entre plusieurs alpha, un R² de 0,1 ressemble à une conclusion sur les données, alors que ce n’est qu’une conclusion sur un réglage.

La régularisation n’est pas réservée aux modèles linéaires

Le mot désigne une idée, pas une formule. Contraindre un modèle pour qu’il généralise mieux prend une forme différente dans chaque famille :

FamilleSa régularisation
Régression linéairealpha, L1 et L2
Arbre de décisionmax_depth, min_samples_leaf
Forêt aléatoiretaille minimale des feuilles, échantillonnage
Gradient boostingarrêt anticipé, taux d’apprentissage faible
Réseau de neuronesdropout, décroissance des poids, arrêt anticipé

Reconnaître ce point commun fait gagner beaucoup de temps : devant un modèle qui surapprend, la question n’est jamais « quelle astuce appliquer », mais « quel levier de contrainte cette famille m’offre-t-elle ».

Ce qu’il faut retenir

Régulariser, c’est mettre un frein au modèle pour l’empêcher d’apprendre ses données par cœur.

text
alpha     dit COMBIEN on régularise
l1_ratio  dit COMMENT c'est réparti entre L1 et L2

Trois réflexes. Vérifier que les variables sont mises à l’échelle dans un pipeline, sans quoi la pénalité pénalise des unités. Se rappeler que trop de frein est aussi mauvais que pas assez, et que le diagnostic se lit sur l’écart entre l’erreur d’entraînement et celle de test. Et ne jamais choisir alpha au jugement quand une validation croisée le fait mieux.

Pour aller plus loin : la géométrie de L1 et L2 pour le pourquoi, et le suivi des expériences pour garder la trace des valeurs essayées.

Continuer sur le même sujet