Module 6 — Réglage automatique des hyperparamètres
Les modules 4 et 5 ont livré des tâches d'entraînement qui prennent 3 à 6 minutes et exposent une métrique validation:auc. Ce module confie à SageMaker le soin d'essayer méthodiquement des combinaisons d'hyperparamètres pour maximiser cette métrique — l'équivalent de GridSearchCV de scikit-learn, mais parallélisé, bayésien et facturable à la minute.
Le problème que le tuner résout
Sur le fil rouge, XGBoost expose une demi-douzaine d'hyperparamètres influents : max_depth, eta, subsample, min_child_weight, gamma, num_round. Chacun vaut typiquement dix valeurs raisonnables. Explorer la grille complète coûterait 10⁶ tâches, soit 5 000 heures d'instance à 0,46 $/h — infaisable.
Les bonnes stratégies évitent cette explosion :
| Stratégie | Principe | Quand |
|---|---|---|
Grid | Toutes les combinaisons d'une grille | Petites dimensions (≤ 3 hyperparamètres) |
Random | Tirage aléatoire dans les plages | Défaut simple, très bon rapport qualité/prix |
Bayesian | Utilise les résultats passés pour guider les prochains | Le meilleur quand chaque tâche est coûteuse |
Hyperband | Alloue plus de temps aux configurations prometteuses | Réseaux profonds avec courbes d'apprentissage |
Sur XGBoost où chaque tâche prend 3 minutes, Bayesian gagne face à Random en environ 30 % d'appels en moins pour atteindre la même AUC. C'est celle que le fil rouge utilise.
Un job de réglage, ligne par ligne
from sagemaker.tuner import HyperparameterTuner, IntegerParameter, ContinuousParameter
plages = {
"num_round": IntegerParameter(50, 300),
"max_depth": IntegerParameter(3, 10),
"eta": ContinuousParameter(0.01, 0.3, scaling_type="Logarithmic"),
"subsample": ContinuousParameter(0.5, 1.0),
"min_child_weight": IntegerParameter(1, 10),
"gamma": ContinuousParameter(0.0, 5.0),
}
tuner = HyperparameterTuner(
estimator=estimateur, # de module 4
objective_metric_name="validation:auc",
objective_type="Maximize",
hyperparameter_ranges=plages,
metric_definitions=[
{"Name": "validation:auc", "Regex": r"validation-auc:(\S+)"},
],
max_jobs=60, # 60 tâches au total
max_parallel_jobs=6, # 6 en parallèle
strategy="Bayesian",
early_stopping_type="Auto",
base_tuning_job_name="resiliation-xgb-tune",
)
tuner.fit({
"train": "s3://resiliation-donnees/prepare/v3/train.parquet",
"validation": "s3://resiliation-donnees/prepare/v3/valid.parquet",
})
Quatre décisions structurent ce code.
Les plages : Integer pour les valeurs entières, Continuous pour les réelles, Categorical pour les listes fixes. Sur eta, l'échelle Logarithmic est décisive : eta a un effet perceptif exponentiel, aller de 0,01 à 0,3 en linéaire concentrerait l'exploration sur les valeurs élevées.
max_jobs=60, max_parallel_jobs=6 : 60 tâches valent typiquement 100 % du gain accessible ; au-delà, le rendement décroche. Le parallélisme accélère l'exécution mais dégrade légèrement la stratégie bayésienne, qui a moins d'informations passées pour choisir. Un ratio de 10:1 (60 tâches, 6 en parallèle) est un bon compromis.
objective_metric_name="validation:auc" : doit correspondre exactement à un metric_definitions, sinon le tuner échoue silencieusement en ne recevant aucune valeur et déclare toutes les tâches équivalentes.
early_stopping_type="Auto" : SageMaker arrête une tâche dès qu'il devient clair qu'elle ne battra pas le meilleur résultat courant. Sur XGBoost avec 60 tâches, l'économie observée est de 25 à 40 % du temps total.
Le coût, chiffré
60 tâches × 4 minutes en moyenne × ml.m5.2xlarge (0,46 $/h) = 1,84 $ en instances à la demande. En Spot avec point de reprise : environ 0,55 $. La lecture n'est pas triviale : ces montants sont la facture du réglage, à ne pas confondre avec le prix d'une tâche unique. Le facteur 60 se paie une fois par cycle d'ingénierie ; c'est bon marché pour un gain d'AUC souvent supérieur au demi-point.
Sur le fil rouge, un premier réglage part de la ligne de base eta=0,1, max_depth=5, num_round=200 (AUC = 0,802 en validation) et remonte à eta=0,073, max_depth=6, num_round=140 (AUC = 0,821). Ce demi-point d'AUC vaut environ 200 clients sauvés par mois selon la matrice de coût métier ; le retour sur les 1,84 $ est infini à l'échelle mensuelle.
Lire les résultats sans se noyer
Après la fin, deux vues suffisent :
resultats = tuner.analytics().dataframe() # une ligne par tâche
resultats = resultats.sort_values("FinalObjectiveValue", ascending=False)
print(resultats[["max_depth", "eta", "num_round", "FinalObjectiveValue"]].head(10))
meilleure = tuner.best_training_job()
print("Meilleure tâche :", meilleure)
La console SageMaker affiche également un graphique interactif : une projection en deux dimensions (eta en abscisse, max_depth en ordonnée, FinalObjectiveValue en couleur) permet de voir immédiatement les régions denses — là où le tuner a concentré son effort après quelques itérations. Une zone dense et brillante indique un optimum robuste ; un optimum isolé au milieu d'un plateau sombre est suspect (surajustement du tuner à un tirage particulier).
Redémarrer un job pour affiner
Une fois la région prometteuse identifiée, on peut relancer un tuner avec des plages resserrées autour de l'optimum trouvé. Cette approche à deux passes coûte 25 % plus cher qu'un tuner unique bien dimensionné, mais donne souvent un ou deux dixièmes de point d'AUC de plus :
plages_2 = {
"num_round": IntegerParameter(100, 180),
"max_depth": IntegerParameter(5, 7),
"eta": ContinuousParameter(0.05, 0.10, scaling_type="Logarithmic"),
}
Sur des systèmes matures, cette itération manuelle est remplacée par un tuner à budget adaptatif géré par le pipeline (module 9), qui décide seul de l'affinage.
Le piège du calibrage sur la validation
Le tuner optimise sur validation. Une AUC de 0,821 en validation n'est pas l'AUC attendue en production ; c'est une borne supérieure biaisée, parce que l'on a fait 60 essais sur cette même partition. Le vrai chiffre attendu est celui du fichier test.parquet, jamais vu par le tuner.
Sur le fil rouge, test donne 0,814 — un demi-point de moins que la validation, mais toujours au-dessus de la ligne de base à 0,802. Le trou entre validation et test est la surestimation induite par le tuner ; sans partition de test, on la découvre en production, ce qui est trop tard.
Fixer max_depth=10 alors que le bruit du jeu et la régularisation ne le supportent pas produit un surajustement systématique : toutes les tâches remontent une AUC train très haute et une AUC validation médiocre. Le tuner « bat » pourtant la ligne de base à chaque essai, sans améliorer la production. Autre piège : eta linéaire avec plage 0,001 à 0,3 — le tuner passe 55 essais dans la moitié droite parce que le tirage est équidistant, et rate les optima autour de 0,03. L'échelle Logarithmic est le seul remède. Ces deux erreurs sont explicitement thématisées dans la banque d'examen.
Enchaîner avec les modules suivants
tuner.best_estimator() renvoie un estimateur configuré avec les meilleurs hyperparamètres et pointant sur le meilleur model.tar.gz. Cet objet se déploie tel quel :
meilleur_estimateur = tuner.best_estimator()
point_de_terminaison = meilleur_estimateur.deploy( # module 7
initial_instance_count=1,
instance_type="ml.m5.large",
)
Ce lien direct entre tuner.best_estimator() et deploy() est ce qui rend la chaîne SageMaker cohérente : le meilleur modèle du réglage devient sans étape supplémentaire le modèle servi.
En résumé
- Le réglage automatique parallélise et guide l'exploration des hyperparamètres ;
Bayesiangagne 30 % d'appels surRandomquand chaque tâche coûte cher. - Les plages utilisent
Integer,ContinuousouCategorical; l'échelleLogarithmicest indispensable pour des paramètres à effet exponentiel commeeta. - La métrique objectif doit correspondre à un
metric_definitionsdéjà déclaré ; sans cela, le tuner échoue en silence. early_stopping_type="Auto"économise 25 à 40 % du temps ; un jeu de test distinct est le seul garde-fou contre la surestimation induite par le réglage survalidation.
Module suivant : servir le meilleur modèle en temps réel avec un point de terminaison HTTPS, puis en variante sans serveur qui ne facture que lorsqu'une requête arrive.