Aller au contenu principal

Module 5 — Réglage des hyperparamètres à l'échelle

Le modèle du module 4 a été entraîné avec des hyperparamètres choisis à la main : n_estimators=400, max_depth=6, learning_rate=0.05. Sont-ils les bons ? Un balayage GridSearchCV local en aurait pour trois jours et un rendement décroissant. Vertex offre Vizier, un moteur d'optimisation bayésienne qui explore intelligemment un espace d'hyperparamètres en parallèle.

Vizier en une phrase

Vizier est l'optimiseur bayésien interne de Google — celui qui a réglé la latence de la recherche Google, l'énergie des data centers et les modèles publicitaires. Il est exposé dans Vertex sous deux formes : le HyperparameterTuningJob (le plus courant, une couche sur CustomTrainingJob) et une API Vizier autonome pour des cas non ML. Nous utilisons le premier.

Contrairement à une recherche aléatoire ou à une grille, Vizier construit à chaque essai un modèle probabiliste de la fonction objectif et choisit le prochain point à essayer là où l'espérance d'amélioration est maximale. Résultat pratique : sur un espace continu à cinq à dix dimensions, Vizier trouve en trente essais ce qu'une recherche aléatoire trouve en cent.

Rapporter la métrique depuis le code

Vizier ne lit pas les journaux : il lit une métrique que le code d'entraînement lui envoie explicitement. Une seule ligne :

import hypertune

hpt = hypertune.HyperTune()
hpt.report_hyperparameter_tuning_metric(
hyperparameter_metric_tag="aucpr_valid",
metric_value=score,
global_step=modele.get_booster().num_boosted_rounds(),
)

Le hyperparameter_metric_tag doit correspondre caractère par caractère à celui déclaré dans la spec du job. Une majuscule oubliée et Vizier considère tous les essais comme un échec sans erreur explicite — c'est la première cause de perte de temps sur ce module.

Notre script du module 4 s'enrichit également pour lire les hyperparamètres passés en argument :

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--n-estimators", type=int, default=400)
parser.add_argument("--max-depth", type=int, default=6)
parser.add_argument("--learning-rate", type=float, default=0.05)
parser.add_argument("--subsample", type=float, default=1.0)
args = parser.parse_args()

Déclarer l'espace de recherche

Côté carnet, on définit l'espace, la métrique et la stratégie :

from google.cloud import aiplatform
from google.cloud.aiplatform import hyperparameter_tuning as hpt

parametres = {
"n-estimators": hpt.IntegerParameterSpec(min=100, max=1500, scale="linear"),
"max-depth": hpt.IntegerParameterSpec(min=3, max=12, scale="linear"),
"learning-rate": hpt.DoubleParameterSpec(min=0.01, max=0.3, scale="log"),
"subsample": hpt.DoubleParameterSpec(min=0.5, max=1.0, scale="linear"),
}

metrique = {"aucpr_valid": "maximize"}

job_socle = aiplatform.CustomJob(
display_name="fraude-xgb-socle",
worker_pool_specs=[{
"machine_spec": {"machine_type": "c2-standard-8"},
"replica_count": 1,
"container_spec": {
"image_uri": "europe-west1-docker.pkg.dev/paiement-fraude-prod/vertex/fraude-xgb:2026-09-06",
},
}],
)

reglage = aiplatform.HyperparameterTuningJob(
display_name="fraude-xgb-reglage-2026-09-06",
custom_job=job_socle,
metric_spec=metrique,
parameter_spec=parametres,
max_trial_count=48,
parallel_trial_count=8,
search_algorithm=None, # bayesien par defaut
)

reglage.run(service_account="vertex-fraude-runner@paiement-fraude-prod.iam.gserviceaccount.com")

Quatre décisions structurent le job.

max_trial_count et parallel_trial_count

Le nombre d'essais total et le nombre en parallèle. Plus il y a de parallélisme, moins Vizier peut apprendre entre essais. L'extrême : 48 essais en parallèle équivaut presque à une recherche aléatoire, parce que le dernier lot est lancé avant que le premier n'ait rapporté. À l'inverse, 48 essais tous séquentiels prennent 48 fois la durée d'un essai.

L'équilibre pratique retenu chez nous est parallel_trial_count = max_trial_count / 6. Vizier a huit lots successifs pour apprendre ; le temps total est réduit d'un facteur huit par rapport à un tout-séquentiel. Sur c2-standard-8 (22 minutes par essai), le job entier dure environ trois heures.

L'échelle log pour les paramètres exponentiels

Le taux d'apprentissage varie sur trois ordres de grandeur utiles (0,001 à 1). Une échelle linéaire concentrerait 90 % des tirages entre 0,1 et 1, ce qui ignore la zone intéressante. L'échelle log distribue uniformément entre log(min) et log(max), donc autant de tirages dans chaque décade. Règle : échelle log pour tout ce qui est un taux, un coefficient de régularisation ou une taille de couche.

L'arrêt anticipé

Vizier peut décider d'arrêter un essai en cours s'il rapporte une métrique intermédiaire clairement moins bonne que les meilleurs essais précédents. Deux conditions pour l'activer : le code doit rapporter la métrique à plusieurs étapes (pas seulement à la fin), et la spec doit inclure enable_trial_early_stopping=True.

Pour XGBoost, on rapporte à chaque centaine d'arbres :

if boost_round % 100 == 0:
hpt.report_hyperparameter_tuning_metric(
hyperparameter_metric_tag="aucpr_valid",
metric_value=score_partiel,
global_step=boost_round,
)

L'économie est réelle : sur nos jobs, l'arrêt anticipé sauve environ 30 % du temps machine, en éteignant les mauvaises configurations tôt.

Lire les résultats

Une fois le job terminé, on récupère les essais triés :

essais = reglage.trials
meilleur = max(essais, key=lambda t: t.final_measurement.metrics[0].value)
print(meilleur.parameters)
print(meilleur.final_measurement.metrics[0].value)

Pour une inspection visuelle, la console Vertex affiche automatiquement un graphique en coordonnées parallèles : chaque essai est une ligne qui traverse les axes des hyperparamètres et de la métrique. Les motifs sautent aux yeux — « la métrique s'effondre dès que learning-rate dépasse 0,15 », « max-depth au-delà de 8 n'apporte plus rien ». C'est souvent plus instructif que le seul meilleur essai.

Le meilleur essai n'est pas toujours celui qu'on veut

Le meilleur essai peut avoir gagné 0,001 d'AUC-PR au prix d'une configuration extrême (1400 arbres, profondeur 12) qui prendra dix fois plus de mémoire au service. Souvent, l'essai au 95ᵉ percentile de score, mais deux fois moins coûteux, est le meilleur compromis pour la production. Vertex n'automatise pas ce choix — c'est votre métier.

En résumé

  • Vizier est un optimiseur bayésien : trente essais suffisent souvent là où une recherche aléatoire en demande cent.
  • Le code d'entraînement doit rapporter la métrique avec hypertune, sous un tag identique à celui de la spec.
  • Parallélisme trop grand = recherche aléatoire : viser parallel_trial_count ≈ max_trial_count / 6.
  • Échelle log pour les taux, la régularisation, les tailles de couche — jamais linéaire.
  • L'arrêt anticipé exige plusieurs rapports de métrique et coupe les mauvaises pistes tôt, gain de 30 % typique.

Module suivant : nous inscrivons le meilleur modèle au registre, avec version, alias et évaluation attachée.