Early Stopping
L’early stopping (arrêt précoce) est une technique de régularisation qui interrompt l’entraînement d’un modèle de machine learning quand sa performance sur un jeu de validation cesse de s’améliorer, empêchant ainsi l’overfitting sans modifier l’architecture ni la fonction de perte.
C’est probablement la technique de régularisation la plus simple et la plus universellement applicable en deep learning. Le principe : pendant l’entraînement, on surveille une métrique sur un jeu de validation séparé (typiquement la loss). Tant que cette métrique s’améliore, on continue. Dès qu’elle stagne ou se dégrade pendant un certain nombre d’epochs (la « patience »), on arrête l’entraînement et on restaure les poids du meilleur epoch. Le modèle résultant est celui qui généralise le mieux, capturé au moment optimal avant que l’overfitting ne commence.
- Catégorie
- Régularisation / Contrôle de l’entraînement
- Objectif
- Prévenir l’overfitting en arrêtant au moment optimal
- Mécanisme
- Monitore la validation loss, arrête après N epochs sans amélioration
- Paramètres clés
- Patience, min_delta, métrique monitorée, restore_best_weights
- Compatible avec
- Tout modèle itératif (réseaux de neurones, gradient boosting, etc.)
- Keras
tf.keras.callbacks.EarlyStopping- PyTorch
- Implémentation manuelle (pas de callback natif)
Comment fonctionne l’early stopping
L’intuition derrière l’early stopping repose sur un phénomène bien documenté en machine learning : au début de l’entraînement, la loss d’entraînement et la loss de validation diminuent ensemble (le modèle apprend des patterns utiles). Après un certain nombre d’epochs, la loss d’entraînement continue de diminuer mais la loss de validation commence à augmenter. Ce point de divergence marque le début de l’overfitting : le modèle commence à mémoriser le bruit des données d’entraînement au lieu d’apprendre des patterns généralisables.
L’early stopping capture le modèle juste avant cette divergence. Le processus concret :
1. Initialisation. On définit une patience (ex. 10 epochs), un seuil minimum d’amélioration (min_delta, ex. 0.001), et la métrique à surveiller (ex. validation loss).
2. Monitoring à chaque epoch. Après chaque epoch, on évalue la métrique sur le jeu de validation. Si elle s’est améliorée d’au moins min_delta par rapport au meilleur score enregistré, on sauvegarde les poids du modèle et on réinitialise le compteur de patience.
3. Décompte de patience. Si la métrique ne s’améliore pas, on incrémente le compteur de patience. L’entraînement continue tant que le compteur n’a pas atteint la valeur de patience.
4. Arrêt et restauration. Quand le compteur atteint la patience, l’entraînement s’arrête. Si restore_best_weights est activé (fortement recommandé), les poids sont restaurés à ceux du meilleur epoch, pas ceux du dernier epoch.
Les paramètres de l’early stopping
Patience
La patience est le nombre d’epochs consécutives sans amélioration avant l’arrêt. C’est le paramètre le plus important. Une patience trop faible (2-3) risque d’arrêter prématurément si la validation loss fluctue naturellement. Une patience trop élevée (50+) peut laisser l’entraînement tourner longtemps après le point optimal, gaspillant du calcul.
Valeurs recommandées : 5 à 10 pour la plupart des tâches de deep learning. Pour le fine-tuning de LLM (peu d’epochs au total), une patience de 2 à 3 suffit. Pour les modèles de vision entraînés sur des centaines d’epochs, une patience de 10 à 20 est courante.
Des études empiriques montrent que des patience plus élevées nécessitent plus d’epochs pour atteindre le meilleur résultat, mais produisent parfois de meilleurs modèles car elles permettent au learning rate schedule de se déployer pleinement avant l’arrêt.
Min Delta (seuil d’amélioration)
Le seuil minimum d’amélioration pour considérer qu’un epoch a « progressé ». Si min_delta = 0.001, une amélioration de la validation loss de 0.500 à 0.4995 ne compte pas (delta = 0.0005 < 0.001). Cela évite de continuer l'entraînement pour des améliorations microscopiques qui ne se traduisent pas en gains réels.
Valeur par défaut recommandée : 0 (toute amélioration compte). Si votre loss est bruitée, augmentez à 0.001 ou 0.01 pour filtrer le bruit.
Métrique monitorée
La métrique la plus courante est la validation loss (val_loss). C’est le choix le plus robuste car la loss est directement ce que l’optimiseur cherche à minimiser. Pour les tâches de classification, vous pouvez aussi monitorer la validation accuracy, le F1-score ou l’AUC. Attention à la direction : la loss doit diminuer, l’accuracy doit augmenter.
Restore Best Weights
Ce paramètre restaure les poids du modèle à ceux de l’epoch avec la meilleure métrique, plutôt que de garder les poids du dernier epoch (qui est, par définition, patience epochs après le meilleur). Activez-le systématiquement, il n’y a aucune raison de ne pas le faire.
Implémentation
Keras / TensorFlow
Keras fournit un callback natif, ce qui rend l’implémentation triviale :
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
early_stop = EarlyStopping(
monitor="val_loss",
patience=10,
min_delta=0.001,
restore_best_weights=True,
verbose=1
)
checkpoint = ModelCheckpoint(
"best_model.keras",
monitor="val_loss",
save_best_only=True
)
model.fit(
X_train, y_train,
validation_data=(X_val, y_val),
epochs=1000, # Nombre max élevé, l'early stopping gère l'arrêt
callbacks=[early_stop, checkpoint]
)
PyTorch (implémentation manuelle)
PyTorch ne fournit pas de callback d’early stopping natif. Voici une implémentation propre :
class EarlyStopping:
def __init__(self, patience=10, min_delta=0.0):
self.patience = patience
self.min_delta = min_delta
self.counter = 0
self.best_loss = float("inf")
self.best_weights = None
self.should_stop = False
def step(self, val_loss, model):
if val_loss = self.patience:
self.should_stop = True
def restore(self, model):
if self.best_weights is not None:
model.load_state_dict(self.best_weights)
# Utilisation
early_stop = EarlyStopping(patience=10)
for epoch in range(1000):
train_loss = train_one_epoch(model, train_loader)
val_loss = evaluate(model, val_loader)
early_stop.step(val_loss, model)
if early_stop.should_stop:
print(f"Early stopping at epoch {epoch}")
early_stop.restore(model)
break
Hugging Face Trainer
Le Trainer de Hugging Face supporte l’early stopping via un callback dédié :
from transformers import TrainingArguments, EarlyStoppingCallback
training_args = TrainingArguments(
output_dir="./output",
evaluation_strategy="epoch", # Évaluer à chaque epoch
save_strategy="epoch", # Sauvegarder à chaque epoch
load_best_model_at_end=True, # Restaurer le meilleur modèle
metric_for_best_model="eval_loss",
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],
)
L’early stopping comme régularisation implicite
L’early stopping n’est pas juste un « truc pratique » pour économiser du temps. C’est une forme de régularisation théoriquement fondée. En arrêtant l’entraînement tôt, on sélectionne un modèle avec des poids de magnitude plus faible (l’optimiseur n’a pas eu le temps de faire croître les poids). Les modèles arrêtés tôt se comportent de façon similaire aux modèles régularisés par L2 (weight decay) : dans les deux cas, les poids sont contraints à rester petits.
La différence avec le L2 explicite est que l’early stopping n’introduit pas de terme de pénalité dans la loss. Il agit indirectement en limitant le nombre d’itérations d’optimisation. C’est pourquoi l’early stopping est souvent combiné avec d’autres régularisations (dropout, weight decay) : elles agissent sur des mécanismes différents et leurs effets sont complémentaires.
Pièges courants
Fluctuations de la validation loss
La validation loss n’est pas monotone : elle fluctue d’un epoch à l’autre, surtout avec de petits jeux de validation ou des petits batch sizes. Une patience trop faible peut provoquer un arrêt prématuré pendant une fluctuation temporaire. Solutions : augmenter la patience, augmenter la taille du jeu de validation, utiliser un min_delta > 0, ou lisser la métrique sur une fenêtre de N epochs.
Overfitting du jeu de validation
Si vous utilisez le même jeu de validation pour l’early stopping ET pour le tuning d’hyperparamètres, vous risquez de surajuster vos hyperparamètres à ce jeu de validation. En théorie, vous devriez avoir trois splits : train, validation (pour l’early stopping), et test (pour l’évaluation finale). En pratique, la validation croisée atténue ce problème.
Interaction avec le learning rate schedule
Certains learning rate schedules (cosine decay, warm restarts) incluent des phases où la loss augmente temporairement avant de diminuer à nouveau. L’early stopping peut arrêter l’entraînement pendant une de ces phases, avant que le schedule ait eu le temps de produire ses effets. Solutions : augmenter la patience pour couvrir au moins un cycle complet du schedule, ou désactiver l’early stopping et utiliser un nombre fixe d’epochs quand le schedule est bien calibré.
Oubli du restore_best_weights
Sans restauration des meilleurs poids, le modèle final est celui du dernier epoch (qui est, par définition, le pire des patience derniers epochs, sinon l’entraînement ne se serait pas arrêté). C’est une erreur courante qui dégrade silencieusement les résultats.
Quand utiliser (et ne pas utiliser) l’early stopping
Utilisez-le pour le fine-tuning de modèles (BERT, LLM, vision), les tâches de classification/régression classiques, le gradient boosting (XGBoost supporte nativement l’early stopping), et tout entraînement où le nombre optimal d’epochs est inconnu.
Ne l’utilisez pas (ou avec prudence) pour le pré-entraînement de LLM (le nombre de tokens est fixé par les scaling laws, pas par la validation loss), quand le learning rate schedule est finement calibré et doit se compléter, ou quand vous n’avez pas de jeu de validation représentatif.
Pour le pré-entraînement de LLM, la pratique standard est de fixer le budget en tokens (ex. 2T tokens) et d’entraîner exactement ce budget, avec un learning rate schedule cosine calibré pour atteindre zéro à la fin. L’early stopping n’a pas de sens dans ce contexte car le modèle est conçu pour utiliser exactement ce budget.
Early stopping dans le gradient boosting
XGBoost, LightGBM et CatBoost supportent nativement l’early stopping via le paramètre early_stopping_rounds. Le principe est identique : l’entraînement s’arrête quand la métrique de validation ne s’améliore plus après N rounds de boosting. C’est la méthode standard pour déterminer le nombre optimal d’arbres dans un ensemble gradient boosting.
import xgboost as xgb
model = xgb.XGBClassifier(
n_estimators=10000, # Maximum très élevé
early_stopping_rounds=50, # Patience de 50 rounds
eval_metric="logloss"
)
model.fit(
X_train, y_train,
eval_set=[(X_val, y_val)],
verbose=100
)
print(f"Best iteration: {model.best_iteration}")
Pour XGBoost, l’early stopping est tellement standard qu’il est rare de voir un entraînement sans. Le nombre optimal d’arbres varie énormément selon le dataset (de 100 à 10 000+), ce qui rend l’early stopping indispensable.
Variantes et extensions de l’early stopping
Lissage par moyenne mobile
Quand la validation loss est très bruitée (petit jeu de validation, modèle instable), monitorer la loss brute peut provoquer des arrêts prématurés. Une solution est de lisser la métrique avec une moyenne mobile exponentielle (EMA) sur les N derniers epochs. Au lieu de comparer l’epoch courant au meilleur historique, on compare la moyenne lissée des K derniers epochs. Cela filtre les fluctuations aléatoires et produit un signal plus fiable.
Cooldown period
Certaines implémentations ajoutent un « cooldown » après une amélioration : une période pendant laquelle le compteur de patience ne s’incrémente pas, même si la métrique ne s’améliore pas. Cela donne au modèle le temps de « digérer » la dernière amélioration avant de reprendre le monitoring. Keras supporte cette option via cooldown dans le ReduceLROnPlateau (qui est un cousin de l’early stopping, appliqué au learning rate plutôt qu’à l’arrêt).
ReduceLROnPlateau : l’alternative à l’arrêt
Plutôt que d’arrêter l’entraînement quand la validation stagne, on peut réduire le learning rate. C’est l’approche ReduceLROnPlateau : quand la métrique ne s’améliore pas pendant patience epochs, le learning rate est divisé par un facteur (typiquement 2 ou 10). L’entraînement continue avec un pas plus fin, ce qui permet parfois de trouver un meilleur minimum.
En pratique, ReduceLROnPlateau et early stopping sont souvent combinés : on réduit d’abord le learning rate (1 à 3 fois), puis on arrête si même un learning rate réduit ne produit plus d’amélioration. C’est un pipeline en deux niveaux qui extrait le maximum de chaque run d’entraînement.
# Combinaison ReduceLROnPlateau + EarlyStopping en Keras
from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping
reduce_lr = ReduceLROnPlateau(
monitor="val_loss",
factor=0.5, # Divise le LR par 2
patience=5, # Après 5 epochs sans amélioration
min_lr=1e-7
)
early_stop = EarlyStopping(
monitor="val_loss",
patience=15, # Patience plus grande que ReduceLR
restore_best_weights=True
)
model.fit(..., callbacks=[reduce_lr, early_stop])
Early stopping dans le tuning d’hyperparamètres
Le concept d’early stopping s’étend au tuning d’hyperparamètres. Optuna et Ray Tune utilisent le « pruning » (terme qui signifie early stopping dans ce contexte) pour arrêter les essais d’hyperparamètres peu prometteurs avant la fin de l’entraînement. Si un essai montre des performances intermédiaires en dessous de la médiane des essais précédents, il est abandonné et les ressources sont allouées à de nouveaux essais. L’algorithme Hyperband formalise cette idée avec un schedule d’allocation de ressources optimal.
Avec Optuna, le pruning s’active simplement :
import optuna
def objective(trial):
lr = trial.suggest_float("lr", 1e-5, 1e-2, log=True)
model = create_model(lr=lr)
for epoch in range(100):
train_loss = train_epoch(model)
val_loss = evaluate(model)
# Rapporter la métrique intermédiaire
trial.report(val_loss, epoch)
# Pruning : abandonner si non prometteur
if trial.should_prune():
raise optuna.TrialPruned()
return val_loss
study = optuna.create_study(
direction="minimize",
pruner=optuna.pruners.MedianPruner()
)
study.optimize(objective, n_trials=100)
Ce pruning peut réduire le temps total de tuning de 50% ou plus, car les mauvaises configurations sont éliminées après quelques epochs au lieu de consommer le budget complet. C’est l’application la plus impactante de l’early stopping au-delà de l’entraînement classique, et elle est devenue standard dans tout workflow de Bayesian Optimization pour le deep learning.
Questions fréquentes sur l’early stopping
Quelle patience choisir pour le fine-tuning d’un LLM ?
Pour le fine-tuning d’un LLM (typiquement 3 à 10 epochs au total), une patience de 2 à 3 epochs suffit. Si la validation loss ne s’améliore pas pendant 2 epochs consécutives sur un entraînement de 5 epochs, il est très probable que le modèle a atteint son optimum. Avec Hugging Face Trainer, utilisez EarlyStoppingCallback(early_stopping_patience=3) et load_best_model_at_end=True.
L’early stopping remplace-t-il le dropout et le weight decay ?
Non, il les complète. Le dropout et le weight decay agissent pendant l’entraînement en modifiant la dynamique d’apprentissage (suppression aléatoire de neurones, pénalisation des grands poids). L’early stopping agit après coup en sélectionnant le meilleur moment pour s’arrêter. Les trois techniques ciblent l’overfitting par des mécanismes différents et sont régulièrement combinées.
Faut-il toujours monitorer la validation loss ?
C’est le choix le plus robuste, mais pas toujours optimal. Pour une tâche de classification déséquilibrée, le F1-score ou l’AUC sur la validation peut être plus pertinent que la loss. Pour la détection d’objets, le mAP est préférable. Choisissez la métrique qui reflète le mieux votre objectif réel, pas nécessairement la loss brute.
Peut-on utiliser l’early stopping avec la cross-validation ?
Oui. Appliquez l’early stopping indépendamment dans chaque fold. Le nombre d’epochs optimal sera différent pour chaque fold (car les données de validation changent). Le nombre final d’epochs peut être la moyenne des epochs de chaque fold, ou chaque fold peut simplement utiliser son propre nombre optimal. Scikit-learn, XGBoost et LightGBM gèrent bien ce scénario.
L’early stopping est-il utile pour les modèles pré-entraînés ?
Pour le pré-entraînement from scratch (GPT, Llama, DeepSeek V3), non. Le budget en tokens est fixé à l’avance par les scaling laws et le modèle est conçu pour l’utiliser entièrement, avec un learning rate schedule calibré pour atteindre zéro à la fin du budget. L’early stopping n’a pas de sens dans ce contexte. Pour le fine-tuning de modèles pré-entraînés, oui absolument. Le nombre optimal d’epochs de fine-tuning dépend du dataset, de la tâche et du learning rate, et l’early stopping est le meilleur moyen de le déterminer automatiquement sans gaspiller de ressources GPU.