Régler les hyperparamètres
- Leçon 19
- 65 min
- Module 6 : Régler et finaliser le modèle
Leçon 19 sur 27 · Apprentissage automatique : du problème à un modèle fiableRégler les hyperparamètres
1. Comprendre l'apprentissage automatique
2. Cadrer le problème de prédiction
3. Évaluer les modèles supervisés
4. Créer les premiers modèles
5. Essayer d'autres familles de modèles
6. Régler et finaliser le modèle
7. Comprendre et choisir le modèle
8. Préparer la passation du modèle
Ce que vous saurez faire
Choisissez les réglages du modèle à partir de résultats, sans deviner ni utiliser les données de test. Gardez ensuite le réglage le plus simple qui égale le meilleur. Un score « meilleur » dont le gain est inférieur au bruit n’est pas meilleur.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient toutes les cellules de code, les tâches et les vérifications de cette leçon, avec les consignes. Il exécute aussi la recherche aléatoire et la simulation du réglage sur les données de test. Ses cellules de configuration installent pandas et scikit-learn, puis téléchargent les données d’exercice et ticket_model.py. Il fonctionne donc sans le projet local. Chaque recherche prend environ 15 secondes.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les trois jeux de données et leurs rôles, dans Données d’entraînement, de validation et de test : trois jeux de données, trois rôles, et la validation croisée, dans la même leçon.
- La précision moyenne, dans Le compromis entre précision et rappel : la précision moyenne.
- Le sous-apprentissage, le surapprentissage et le réglage
Cde la régression logistique, dans Contrôler la complexité. - Pourquoi un modèle plus complexe doit justifier sa place, dans Les méthodes d’ensemble : le coût de la complexité.
- L’erreur-type comme taille habituelle d’une erreur due au hasard, dans Estimer l’incertitude du cours Mathématiques et statistiques essentielles pour l’IA.
Ce qu’il vous faut sur votre ordinateur (seulement pour le travail local; le carnet n’en a pas besoin)
- Le dossier de projet
~/projects/ticket-model, avec son.venvactif et les données dansdata/, de Définir une référence de base : préparer votre projet. Si un fichier de données manque ou a changé, exécutezpython get_data.py --reset. ticket_model.pyavec 11 caractéristiques, de Sélectionner les caractéristiques utiles : modifier ticket_model.py. Pour le récupérer :curl -fsSL -O https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C05/project/ticket_model.py(curl.exesous Windows).experiment_log.csvavec les lignes E01 à E18, de Sélectionner les caractéristiques utiles : l’écrire dans le journal d’expériences. Cette leçon ajoute E19 à E21. S’il manque, les cellules de configuration du carnet réécrivent les lignes.
Testé sous macOS avec Python 3.14.6, pandas 3.0.6 et scikit-learn 1.9.1. Les commandes Windows ne sont pas testées. Le carnet a été testé dans Jupyter.
Le four n’apprend pas
Section intitulée « Le four n’apprend pas »Vous préparez un gâteau avec une nouvelle recette. La recette indique quels ingrédients mélanger, et le mélange devient le gâteau. Mais deux nombres ne sont pas dans le bol : la température du four et le temps de cuisson. Le gâteau ne les choisit pas et ne les apprend pas pendant la cuisson. Vous les réglez avant de commencer.
L’emballage indique « 180 °C pendant 40 minutes ». C’est un point de départ raisonnable pour un four typique, pas une promesse pour le vôtre. Si votre four chauffe trop, les bords du gâteau brûlent. Si vous le sortez trop tôt, le centre reste cru. Une pâtissière attentive essaie donc quelques réglages, goûte chaque gâteau et garde le réglage qui fonctionne. Elle le note aussi pour ne pas avoir à deviner la prochaine fois.
Un modèle d’apprentissage automatique possède aussi ces deux types de nombres. Vous les avez rencontrés dans Contrôler la complexité. Vous y avez choisi un réglage, la profondeur d’un arbre, par validation croisée. Cette leçon explique l’idée au complet, puis choisit plusieurs réglages à la fois, de façon honnête.
Paramètres et hyperparamètres
Section intitulée « Paramètres et hyperparamètres »Quand vous appelez fit(), le modèle apprend des nombres à partir des billets de soutien d’entraînement. Ces nombres appris sont les paramètres du modèle. Pour la régression logistique, ce sont les coefficients (un poids par colonne préparée) et l’ordonnée à l’origine. Pour un arbre de décision, ce sont les questions à chaque division et les valeurs dans les feuilles. Personne ne les saisit. Les données les déterminent.
D’autres nombres doivent être fixés avant le début de fit(). Ce sont les hyperparamètres. Le préfixe « hyper » signifie « au-dessus » : ils se situent au-dessus des paramètres et contrôlent leur apprentissage. Le C de la régression logistique en est un. Il règle la force avec laquelle l’ajustement maintient les coefficients petits, comme vous l’avez vu dans Contrôler la complexité. La profondeur maximale d’un arbre en est un autre. Le nombre d’arbres d’une forêt et le taux d’apprentissage du gradient boosting (amplification par gradient) en sont aussi.
Vous pouvez voir les deux types dans le modèle final. Comme dans chaque carnet de ce cours, pd désigne pandas et np désigne NumPy. Les cellules de configuration du carnet les importent.
from ticket_model import FEATURES, TARGET, build_pipeline, load
train, valid = load("train"), load("valid")pipeline = build_pipeline().fit(train[FEATURES], train[TARGET])model = pipeline.named_steps["model"]print("parameters learned by fit():", model.coef_.size, "coefficients and 1 intercept")print("hyperparameters chosen before:", {name: model.get_params()[name] for name in ("C", "max_iter", "class_weight")})La sortie :
parameters learned by fit(): 28 coefficients and 1 intercepthyperparameters chosen before: {'C': 1.0, 'max_iter': 1000, 'class_weight': None}Les 11 caractéristiques deviennent 28 colonnes après la préparation. Chaque catégorie obtient sa propre colonne. L’outil d’imputation ajoute une colonne pour chaque variable numérique qui contient des valeurs manquantes. L’ajustement a donc appris 28 coefficients. Par exemple, team_account a obtenu −1,02 et prior_escalations_90d a obtenu +0,43. Les hyperparamètres ne viennent pas du tout des données. C = 1.0 est la valeur par défaut de scikit-learn, et max_iter = 1000 est celle que définit ticket_model.py.
Description textuelle du diagramme
Un flux de gauche à droite (de haut en bas sur un téléphone). Deux cases alimentent une case nommée fit (ajustement). La première est Hyperparamètres : « vous les choisissez avant l’ajustement : C = 1, max_iter = 1000 ». La deuxième est le fichier de données train.csv, avec 16 939 billets. Une flèche part de fit vers Paramètres : « l’ajustement les apprend à partir des données : 28 coefficients et 1 ordonnée à l’origine ». De là, une flèche mène à Scores pour de nouveaux billets.
| Modèle | Paramètres (appris par fit()) |
Quelques hyperparamètres (choisis avant) |
|---|---|---|
| Régression logistique | Un coefficient par colonne préparée et l’ordonnée à l’origine | C, class_weight |
| Arbre de décision | La question à chaque division, la valeur dans chaque feuille | max_depth, min_samples_leaf |
| Forêt aléatoire | Toutes les divisions de tous les arbres | n_estimators (nombre d’arbres), max_depth, min_samples_leaf |
| Gradient boosting | Toutes les divisions de tous les arbres et leurs poids | learning_rate, max_depth, max_iter (nombre d’arbres) |
Pourquoi les valeurs par défaut ne sont qu’une estimation
Section intitulée « Pourquoi les valeurs par défaut ne sont qu’une estimation »Chaque hyperparamètre de scikit-learn a une valeur par défaut. Les auteurs de la bibliothèque ont choisi chaque valeur pour qu’elle fonctionne raisonnablement sur de nombreux jeux de données. Mais vos données forment un seul jeu, et la valeur par défaut n’en sait rien.
Vous avez déjà vu ce qu’une valeur par défaut peut faire. DecisionTreeClassifier n’a pas de limite de profondeur par défaut. Dans Contrôler la complexité, cet arbre a mémorisé les billets d’entraînement : AP d’entraînement de 1,000, AP en validation croisée de 0,137. Un arbre limité à une profondeur de 6 a atteint une AP en validation croisée de 0,258. Pour les arbres, le réglage par défaut était donc le pire de tous les réglages essayés. Pour le gradient boosting, le réglage par défaut a obtenu une AP en validation croisée de 0,316. Une profondeur de 3 avec un taux d’apprentissage de 0,05 a obtenu 0,327. Une valeur par défaut peut convenir ou être loin de la meilleure. Vous ne pouvez pas le savoir sans mesurer.
La raison est le compromis du module 5. Un réglage qui rend le modèle trop simple cause du sous-apprentissage. Il manque des tendances réelles, comme un gâteau sorti trop tôt. Un réglage qui le rend trop complexe cause du surapprentissage. Il apprend le bruit des billets d’entraînement, comme un gâteau laissé trop longtemps au four. Le meilleur réglage se situe entre les deux, et sa position dépend des données. Le réglage consiste à le trouver à partir de résultats : essayer plusieurs valeurs, mesurer chacune équitablement et choisir.
Omar demande : « Un expert ne peut pas simplement nous donner la bonne valeur? » Un expert peut vous proposer une plage raisonnable à essayer. La bonne valeur dans cette plage dépend de ces billets. Seule une mesure permet donc de la trouver.
Comment comparer les réglages honnêtement
Section intitulée « Comment comparer les réglages honnêtement »Pour choisir entre les réglages, il vous faut un score pour chacun. Quelles données donnent ce score? Pensez aux trois jeux de Données d’entraînement, de validation et de test.
- Pas le score d’entraînement. Un modèle qui mémorise gagne sur les billets qui lui ont servi à apprendre. Si vous choisissez selon le score d’entraînement, vous choisissez l’arbre sans limite (AP d’entraînement de 1,000). C’est le pire sur de nouveaux billets.
- Pas les données de test. Les données de test sont l’examen final scellé. Si elles aident à choisir, leur score cesse d’être une estimation honnête. Le cas d’échec à la fin de cette leçon montre l’ampleur du problème.
- La validation croisée sur les données d’entraînement. La validation croisée (CV) divise les billets d’entraînement en 5 plis, ajuste sur 4 et évalue le cinquième. Elle répète cela 5 fois. Chaque score provient de billets que le modèle ajusté n’a pas vus. Vous obtenez 5 scores, ce qui montre aussi leur dispersion. Vous l’avez appris dans Validation croisée.
Le réglage comporte donc cinq étapes :
- Choisissez quelques valeurs pour chaque hyperparamètre que vous voulez régler.
- Évaluez chaque réglage avec la même validation croisée sur les données d’entraînement et la même métrique.
- Choisissez un réglage avec une règle qui tient compte du bruit (la règle d’une erreur-type, ci-dessous).
- Vérifiez une seule fois le réglage choisi sur les données de validation.
- Écrivez le résultat dans le journal d’expériences.
Le cours utilise toujours StratifiedKFold(5, shuffle=True, random_state=0). « Stratifié » signifie que chaque pli reçoit la même proportion d’escalades, environ 12 %. Chaque score de pli mesure donc la même tâche. Voici le pipeline final avec cette validation croisée :
from sklearn.model_selection import StratifiedKFold, cross_val_score
cv = StratifiedKFold(5, shuffle=True, random_state=0)fold_scores = cross_val_score(build_pipeline(), train[FEATURES], train[TARGET], cv=cv, scoring="average_precision")print("AP per fold:", fold_scores.round(3))print(f"mean {fold_scores.mean():.4f}, std {fold_scores.std():.4f}, standard error {fold_scores.std() / 5 ** 0.5:.4f}")AP per fold: [0.325 0.321 0.347 0.364 0.34 ]mean 0.3393, std 0.0156, standard error 0.0070Le même pipeline obtient des scores de 0,321 à 0,364, uniquement parce que les plis contiennent des billets différents. La moyenne des 5 scores est plus stable que chacun d’eux. À quel point? L’erreur-type de la moyenne est std ÷ √5 = 0,0156 ÷ 2,24 = 0,0070. C’est la taille habituelle de l’erreur due au hasard dans la moyenne, comme dans le cours de mathématiques. Gardez ce nombre en tête : deux réglages dont les moyennes diffèrent de moins d’environ 0,007 sont impossibles à départager.
Pourquoi le mois de validation reste important
Section intitulée « Pourquoi le mois de validation reste important »L’étape 4 vérifie le réglage choisi sur les données de mai, le jeu de validation. Pourquoi, si la validation croisée l’a déjà évalué? Parce que les 22 mois d’entraînement précèdent tous la nouvelle politique de garantie du 1er mai 2026. Mai vient après. Priya compare le score moyen du modèle, son estimation moyenne du taux d’escalade, au taux réel :
from sklearn.model_selection import cross_val_predict
oof = cross_val_predict(build_pipeline(), train[FEATURES], train[TARGET], cv=cv, method="predict_proba")[:, 1]scores = pipeline.predict_proba(valid[FEATURES])[:, 1]print(f"train, out of fold: mean score {oof.mean():.3f}, escalation rate {train[TARGET].mean():.3f}")print(f"valid (May 2026): mean score {scores.mean():.3f}, escalation rate {valid[TARGET].mean():.3f}")monthly = train.groupby(train["created_at"].dt.strftime("%Y-%m"))[TARGET].mean()print(f"train months: escalation rate from {monthly.min():.3f} to {monthly.max():.3f}")train, out of fold: mean score 0.117, escalation rate 0.117valid (May 2026): mean score 0.120, escalation rate 0.155train months: escalation rate from 0.095 to 0.139cross_val_predict donne à chaque billet d’entraînement un score hors pli : celui du modèle de pli qui ne l’a pas vu. Sur les données d’entraînement, le score moyen égale le taux. En mai, 15,5 % des billets font l’objet d’une escalade, plus que durant tout mois d’entraînement. Le modèle estime encore ce taux à 12,0 %. La validation croisée ne peut pas montrer cela, car chaque pli précède le changement. Les deux jeux ont donc deux rôles. La validation croisée sur les données d’entraînement choisit : elle contient 1 983 escalades, donc ses moyennes sont stables. Le mois de validation vérifie le choix dans les nouvelles conditions. Avec seulement 183 escalades, il est trop petit pour classer dix réglages. La prochaine leçon utilise mai pour une autre tâche : le seuil.
Le schéma montre tout le flux de travail. Cette leçon réalise les deux premières étapes. La prochaine leçon traite du seuil et du test unique.
Description textuelle du diagramme
Le schéma est un flux de gauche à droite (de haut en bas sur un téléphone). train.csv (16 939 billets, de juillet 2024 à avril 2026) mène à Validation croisée à 5 plis sur les données d’entraînement, avec GridSearchCV et 10 réglages. La validation croisée mène à Choisir les réglages, où la règle d’une erreur-type choisit la régression logistique avec C = 1. Cela mène à Seuil sur les données de validation, qui reçoit aussi valid.csv (1 179 billets, mai 2026). Pour une capacité de 20 %, le seuil est de 0,19. Le seuil et test.csv (1 002 billets, juin 2026) mènent à Tester une fois, avec evaluate.py. La dernière case est le Rapport : précision moyenne de 0,345, rappel de 0,446 et aucun changement ensuite. Sur le schéma large, une flèche pointillée du rapport vers Choisir les réglages se termine par une croix. Elle porte la mention « aucun retour ».
Recherche par grille et recherche aléatoire
Section intitulée « Recherche par grille et recherche aléatoire »De retour dans la cuisine. Vous voulez essayer trois températures (160, 180 et 200 °C) et trois temps de cuisson (30, 40 et 50 minutes). Si vous préparez un gâteau pour chaque combinaison, vous préparez 3 × 3 = 9 gâteaux. Ce plan est une grille : chaque valeur d’un réglage avec chaque valeur de l’autre. Il est facile à planifier et à lire.
Une grille grandit vite. Avec un troisième réglage, comme la hauteur de la grille du four, à trois valeurs, vous préparez 27 gâteaux. Avec cinq valeurs pour chacun des trois réglages, vous en préparez 125. En apprentissage automatique, chaque « gâteau » représente 5 ajustements de validation croisée. Le coût d’une grille se multiplie donc avec chaque hyperparamètre ajouté.
Il y a un deuxième problème. Souvent, seuls un ou deux réglages comptent beaucoup, et les autres changent à peine le résultat. Supposons que la température compte et que la hauteur de la grille ne compte pas. Une grille de 9 gâteaux essaie seulement 3 températures différentes, chacune trois fois. Une recherche aléatoire choisit 9 combinaisons au hasard dans les plages que vous donnez. Les 9 gâteaux ont alors des températures différentes. Vous apprenez donc beaucoup plus sur le réglage important, pour le même coût. Bergstra et Bengio l’ont montré en 2012 (« Recherche aléatoire pour l’optimisation des hyperparamètres », Journal of Machine Learning Research 13).
Description textuelle du diagramme
Il y a deux panneaux : « Grille : 9 réglages » et « Aléatoire : 9 réglages » (superposés sur un téléphone). Dans chaque panneau, un carré montre les réglages essayés. La direction horizontale représente le réglage important, et la direction verticale représente un réglage peu important. Sous chaque carré, de courts traits montrent les valeurs du réglage important. Au-dessus de chaque carré, une courbe montre le score selon le réglage important. Elle est plate et basse, avec un sommet un peu à droite du milieu.
Dans le panneau de la grille, les neuf points sont disposés en 3 sur 3. Ils couvrent donc seulement trois valeurs du réglage important. Le meilleur, marqué en ambre, se trouve sur la pente sous le sommet. Dans le panneau aléatoire, les neuf points sont dispersés et couvrent donc neuf valeurs différentes. Le meilleur, en ambre, se trouve près du sommet.
Lequel devriez-vous utiliser? Utilisez une grille pour régler un ou deux hyperparamètres avec quelques valeurs chacun. Vous voyez alors chaque combinaison, par exemple les quatre valeurs de C. Utilisez une recherche aléatoire s’il y a trois hyperparamètres ou plus, ou si une valeur peut être n’importe où dans une plage. Un taux d’apprentissage entre 0,01 et 0,3 en est un exemple. Vous fixez le budget (le nombre de réglages) à l’avance, ce qui garde le coût sous contrôle.
Exemple détaillé : régler avec GridSearchCV
Section intitulée « Exemple détaillé : régler avec GridSearchCV »GridSearchCV exécute la même validation croisée pour chaque configuration d’une grille, puis les classe. La grille de Priya comprend 4 régressions logistiques et 6 modèles de boosting (amplification par gradient) : 10 configurations × 5 plis = 50 entraînements. Avant de poursuivre, faites une prédiction : quelle configuration arrivera en premier?
from sklearn.ensemble import HistGradientBoostingClassifierfrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import GridSearchCV
grid = [ {"model": [LogisticRegression(max_iter=1000)], "model__C": [0.01, 0.1, 1, 10]}, {"model": [HistGradientBoostingClassifier(random_state=0)], "model__max_depth": [2, 3, None], "model__learning_rate": [0.05, 0.1]},]search = GridSearchCV(build_pipeline(), grid, cv=cv, scoring="average_precision", n_jobs=-1)search.fit(train[FEATURES], train[TARGET])print("settings:", len(search.cv_results_["params"]))settings: 10Chaque partie de l’appel a un rôle :
| Lignes | Pourquoi |
|---|---|
"model": [...] |
La dernière étape du pipeline s’appelle model dans ticket_model.py. La grille la remplace par chaque modèle de la liste. |
"model__C": [...] |
Deux traits de soulignement permettent d’accéder à l’intérieur d’une étape : le C de l’étape model. |
| Une liste de deux dictionnaires | Deux grilles : C seulement pour la régression logistique, profondeur et taux d’apprentissage seulement pour le boosting. |
cv=cv, scoring="average_precision" |
Les mêmes plis et la même métrique pour chaque configuration permettent une comparaison équitable. |
n_jobs=-1 |
Utiliser tous les cœurs du processeur. Cela prend environ 15 secondes. |
Le pipeline conserve la mise à l’échelle pour les modèles de boosting. Les arbres divisent les données selon l’ordre des valeurs. La mise à l’échelle ne change donc rien pour eux.
Une courte fonction utilitaire nomme chaque configuration. Les résultats forment ensuite un tableau, avec les meilleurs en premier :
def describe(params): """A short name for one setting of the grid.""" if isinstance(params["model"], LogisticRegression): return f"logistic, C={params['model__C']}" return f"boosting, rate {params['model__learning_rate']}, depth {params['model__max_depth']}"
results = pd.DataFrame(search.cv_results_)results["setting"] = results["params"].map(describe)results["se"] = results["std_test_score"] / 5 ** 0.5table = results[["setting", "mean_test_score", "std_test_score", "se", "rank_test_score"]].sort_values("rank_test_score")print(table.round(4).to_string(index=False)) setting mean_test_score std_test_score se rank_test_score boosting, rate 0.1, depth 2 0.3424 0.0178 0.0080 1 logistic, C=10 0.3394 0.0157 0.0070 2 logistic, C=1 0.3393 0.0156 0.0070 3 logistic, C=0.1 0.3391 0.0154 0.0069 4 boosting, rate 0.05, depth 2 0.3379 0.0179 0.0080 5 logistic, C=0.01 0.3375 0.0168 0.0075 6 boosting, rate 0.1, depth 3 0.3367 0.0153 0.0069 7 boosting, rate 0.05, depth 3 0.3362 0.0147 0.0066 8boosting, rate 0.05, depth None 0.3212 0.0138 0.0062 9 boosting, rate 0.1, depth None 0.3188 0.0101 0.0045 10Tomás pointe la première ligne : « Le boosting gagne. Mettons-le en production. » Priya pose une autre question : 0,3424 est-il vraiment meilleur que 0,3393?
La règle d’une erreur-type
Section intitulée « La règle d’une erreur-type »Retournons encore dans la cuisine. Cinq amis goûtent deux gâteaux et donnent des notes sur 10. Le gâteau A obtient une moyenne de 8,4 et le gâteau B, de 8,3. Mais les notes du même gâteau varient d’un demi-point d’un ami à l’autre. Une différence de 0,1 est bien plus petite que cette variation. Les amis ne peuvent donc pas vraiment distinguer les gâteaux. Si le gâteau B est beaucoup plus facile à préparer, un pâtissier raisonnable choisit le gâteau B.
La règle d’une erreur-type transforme cette idée en calcul :
- Prenez la meilleure moyenne : 0,3424.
- Soustrayez son erreur-type : 0,3424 − 0,0080 = 0,3344.
- Toute configuration qui atteint ou dépasse 0,3344 est à égalité avec la meilleure.
- Parmi les configurations à égalité, choisissez la plus simple.
Le graphique l’illustre. Chaque barre représente la moyenne d’une configuration ± une erreur-type. La ligne pointillée indique la limite.
Description textuelle du graphique
Dix lignes sur un axe allant de 0,31 à 0,355, avec une ligne verticale pointillée à la limite de 0,3344. Chaque ligne présente un point à la moyenne et une barre de ± une erreur-type. À partir du haut : boosting avec un taux de 0,1 et une profondeur de 2, le meilleur, à 0,3424 (0,3344 à 0,3504), en orange. Régression logistique avec C = 10 : 0,3394. Régression logistique avec C = 1, celle choisie, à 0,3393 (0,3323 à 0,3463), en bleu. Régression logistique avec C = 0,1 : 0,3391. Boosting avec un taux de 0,05 et une profondeur de 2 : 0,3379. Régression logistique avec C = 0,01 : 0,3375. Boosting avec un taux de 0,1 et une profondeur de 3 : 0,3367. Boosting avec un taux de 0,05 et une profondeur de 3 : 0,3362. Ces huit points sont tous à droite de la ligne pointillée. Les deux dernières lignes représentent le boosting sans limite de profondeur, aux taux de 0,05 et de 0,1. Elles se trouvent à 0,3212 et à 0,3188, bien à gauche de la ligne.
Huit des dix configurations atteignent ou dépassent 0,3344. Seuls les deux modèles de boosting sans limite de profondeur sont clairement moins bons. La régression logistique avec C = 1 est à 0,0031 sous la meilleure : moins d’une demi-erreur-type.
Décision de Priya : la régression logistique avec la valeur par défaut C = 1,0. Elle est à égalité avec la meilleure et c’est le modèle le plus simple parmi ceux à égalité. Ses résultats varient moins entre les plis (écart-type de 0,0156 contre 0,0178). On peut expliquer ses coefficients à Grace (module 7). Elle est aussi plus petite, plus rapide et plus facile à vérifier. C’est le coût de la complexité du module 5, mais à l’inverse. Les quatre valeurs de C donnent des résultats à moins de 0,002 les uns des autres. Régler C ne change donc rien sur ces données. Dites-le honnêtement dans un rapport, plutôt que de présenter un « C réglé ».
Puis vient l’étape 4 : vérifiez le choix une seule fois sur le mois de validation, aux côtés de la configuration arrivée en premier.
from sklearn.metrics import average_precision_score
best_boosting = build_pipeline(HistGradientBoostingClassifier(learning_rate=0.1, max_depth=2, random_state=0))best_boosting.fit(train[FEATURES], train[TARGET])for name, fitted in [("boosting, rate 0.1, depth 2", best_boosting), ("logistic, C=1", pipeline)]: print(f"{name:28s} valid AP {average_precision_score(valid[TARGET], fitted.predict_proba(valid[FEATURES])[:, 1]):.3f}")boosting, rate 0.1, depth 2 valid AP 0.395logistic, C=1 valid AP 0.391La différence sur mai est de 0,004. L’AP de mai varie d’environ 0,03 par hasard. Mai confirme donc l’égalité entre les deux configurations. C’est le même pipeline qu’avant la recherche. La recherche n’a pas trouvé de meilleur modèle. Elle a montré, avec des preuves, qu’aucune configuration n’est clairement meilleure. C’est un résultat utile.
Tomás n’est pas convaincu : « Alors pourquoi ne pas garder le boosting? Il était premier. » Parce qu’être « premier » avec un écart inférieur au bruit ne veut pas dire être « meilleur ». Le modèle plus simple donne les mêmes résultats, et il coûte moins cher à tous les autres égards. Une leçon ultérieure, Quand un modèle simple est meilleur, revient sur ce choix.
La recherche aléatoire en code
Section intitulée « La recherche aléatoire en code »RandomizedSearchCV fonctionne comme GridSearchCV. Mais vous lui donnez une plage ou une liste pour chaque hyperparamètre, ainsi qu’un budget, n_iter. Priya donne trois hyperparamètres au boosting. Une grille avec 5 valeurs pour chacun nécessiterait 125 configurations. Elle en essaie 10 au hasard :
from scipy.stats import loguniformfrom sklearn.model_selection import RandomizedSearchCV
space = {"model__learning_rate": loguniform(0.01, 0.3), "model__max_depth": [2, 3, 4, 6, None], "model__min_samples_leaf": [20, 50, 100, 200]}random_search = RandomizedSearchCV(build_pipeline(HistGradientBoostingClassifier(random_state=0)), space, n_iter=10, cv=cv, scoring="average_precision", random_state=0, n_jobs=-1)random_search.fit(train[FEATURES], train[TARGET])found = random_search.cv_results_for i in found["rank_test_score"].argsort(): p = found["params"][i] print(f"rate {p['model__learning_rate']:.3f} depth {p['model__max_depth']!s:4} min leaf {p['model__min_samples_leaf']:3}" f" CV AP {found['mean_test_score'][i]:.4f} se {found['std_test_score'][i] / 5 ** 0.5:.4f}")loguniform(0.01, 0.3) tire des taux d’apprentissage entre 0,01 et 0,3, aussi souvent près de 0,01 que de 0,1. random_state=0 rend les 10 tirages identiques à chaque exécution. Faites d’abord une prédiction : la meilleure configuration aléatoire dépassera-t-elle la régression logistique de plus d’une erreur-type?
rate 0.208 depth 2 min leaf 20 CV AP 0.3441 se 0.0077rate 0.233 depth 2 min leaf 50 CV AP 0.3437 se 0.0086rate 0.259 depth 2 min leaf 100 CV AP 0.3418 se 0.0083rate 0.091 depth 2 min leaf 200 CV AP 0.3393 se 0.0087rate 0.065 depth 2 min leaf 200 CV AP 0.3385 se 0.0080rate 0.185 depth 3 min leaf 200 CV AP 0.3366 se 0.0082rate 0.050 depth 3 min leaf 200 CV AP 0.3343 se 0.0076rate 0.037 depth 2 min leaf 50 CV AP 0.3330 se 0.0079rate 0.090 depth None min leaf 200 CV AP 0.3307 se 0.0062rate 0.059 depth 6 min leaf 100 CV AP 0.3302 se 0.0061Avec le même budget de 10 configurations, la recherche aléatoire a trouvé une configuration de boosting légèrement meilleure que la grille : 0,3441 contre 0,3424. Elle a essayé 10 taux d’apprentissage différents. Les meilleurs sont plus élevés que les deux valeurs de la grille. Les meilleures configurations ont toutes une profondeur de 2. La profondeur compte donc, et une faible profondeur est préférable. Mais appliquez la règle : 0,3441 − 0,0077 = 0,3364. La régression logistique avec C = 1 (0,3393) dépasse cette limite. Il y a toujours égalité, et la décision ne change pas.
Consigner dans le journal des expériences
Section intitulée « Consigner dans le journal des expériences »Priya ajoute trois lignes : la meilleure configuration de la grille (E19), la meilleure valeur de C (E20) et la meilleure configuration aléatoire (E21). Elle rejette les trois, car chacune est à égalité avec E15. Une recherche qui ne change rien mérite quand même ses lignes. Elle prouve que le modèle simple est un choix mesuré, pas un choix par paresse.
best = table.iloc[0]c_10 = table.set_index("setting").loc["logistic, C=10"]top = found["rank_test_score"].argmin()log_experiment("E19", "Grid search best: gradient boosting, learning_rate=0.1, max_depth=2", best["mean_test_score"], best["std_test_score"], 0.395, "reject", "Within one standard error (0.008) of E15, and more complex.")log_experiment("E20", "E15 with C=10 (the best C; C=0.01 to 10 are within 0.002)", c_10["mean_test_score"], c_10["std_test_score"], None, "reject", "No real difference from C=1, so keep the default.")log_experiment("E21", "Random search best: gradient boosting, learning_rate=0.208, max_depth=2, min_samples_leaf=20", found["mean_test_score"][top], found["std_test_score"][top], None, "reject", "Less than one standard error (0.0077) above E15.")show_log()Les dernières lignes de la sortie :
E19 Grid search best: gradient boosting, learning_rate=0.1, max_depth=2 0.342 0.018 0.395 rejectE20 E15 with C=10 (the best C; C=0.01 to 10 are within 0.002) 0.339 0.016 rejectE21 Random search best: gradient boosting, learning_rate=0.208, max_depth=2, min_samples_leaf=20 0.344 0.017 rejectE19 contient valid_ap parce que Priya l’a vérifié une seule fois sur mai pour confirmer l’égalité. E15 reste le modèle à conserver.
Exercice guidé : lancer la recherche et appliquer la règle
Section intitulée « Exercice guidé : lancer la recherche et appliquer la règle »Utilisez le carnet (notebook) Jupyter, sections 4 et 5.
- Exécutez les cellules de préparation et les sections 1 à 3. Vous devriez voir
mean 0.3393, std 0.0156, standard error 0.0070(moyenne, écart-type, erreur-type) dans la section 2. - Prédisez quelle configuration arrivera en premier et notez votre prédiction.
- Exécutez la recherche par grille dans la section 4. Vous devriez voir
settings: 10(10 configurations) après environ 15 secondes. - Exécutez la cellule du tableau. La première ligne devrait être
boosting, rate 0.1, depth 2(boosting, taux 0,1, profondeur 2), avec 0,3424. - Dans la section 5, définissez
limitcomme la meilleure moyenne moins son erreur-type. - Définissez
tiedcomme la liste des configurations detabledont la moyenne atteint ou dépasselimit. - Exécutez la cellule de vérification. Elle affiche
Check passed(vérification réussie) quand la liste est correcte. - Choisissez la configuration la plus simple dans
tiedet expliquez pourquoi en une phrase.
Afficher la réponse
best = table.iloc[0]limit = best["mean_test_score"] - best["se"]tied = table.loc[table["mean_test_score"] >= limit, "setting"].tolist()print(round(limit, 4), tied)0.3344 ['boosting, rate 0.1, depth 2', 'logistic, C=10', 'logistic, C=1', 'logistic, C=0.1', 'boosting, rate 0.05, depth 2', 'logistic, C=0.01', 'boosting, rate 0.1, depth 3', 'boosting, rate 0.05, depth 3']Huit configurations sont à égalité. La plus simple est la régression logistique. Parmi ses quatre valeurs de C, gardez la valeur par défaut, C = 1. Les quatre résultats sont à moins de 0,002, et la valeur par défaut ne nécessite aucune explication. Une bonne phrase : « La régression logistique avec C = 1 est à 0,0031 sous la meilleure configuration, soit moins d’une erreur-type (0,0080). La différence vient du bruit, et le modèle plus simple est plus facile à expliquer et à vérifier. »
À vous : classer selon l’aire sous la courbe ROC
Section intitulée « À vous : classer selon l’aire sous la courbe ROC »Changez une seule chose : classez la même grille selon l’aire sous la courbe ROC plutôt que la précision moyenne. Dans la section 9 du carnet, définissez scoring_name = "roc_auc" et exécutez la cellule. Avant de l’exécuter, faites une prédiction : la décision change-t-elle? La régression logistique avec C = 1 reste-t-elle à moins d’une erreur-type de la meilleure?
Afficher la réponse
setting mean_test_score se rank_test_scoreboosting, rate 0.1, depth 2 0.7719 0.0026 1boosting, rate 0.1, depth 3 0.7699 0.0018 2 logistic, C=1 0.7696 0.0013 3 logistic, C=0.1 0.7696 0.0013 4TrueLa même configuration de boosting arrive en premier (0,7719, erreur-type de 0,0026). La régression logistique avec C = 1 est à 0,0023 sous ce résultat : moins d’une erreur-type. La décision ne change pas. L’aire sous la courbe ROC a des erreurs-types plus petites ici, mais les écarts entre configurations sont tout aussi petits. Une autre métrique peut changer l’ordre d’un tableau. Choisissez donc la métrique avant la recherche, comme le module 3 l’enseignait. Jamais après avoir vu quelle métrique fait gagner votre modèle préféré.
Régler sur les données de test
Section intitulée « Régler sur les données de test »Erreur courante
Régler sur les données de test
Symptôme : Tomás dit : « On a des données de test. Essayons les 10 configurations dessus et gardons la meilleure. C’est juste un nombre de plus. » Le meilleur score de test dépasse toute estimation honnête. Il le présente comme le résultat du modèle.
Cause : quand vous choisissez une configuration parce qu’elle obtient le meilleur score sur des données, son score dépend en partie du hasard. Le score choisi est donc trop élevé. Priya le montre sans toucher aux vraies données de test. Elle divise au hasard les 1 179 billets de soutien de mai en deux moitiés, 200 fois. La moitié A joue le rôle des données de test sur lesquelles Tomás fait ses réglages. La moitié B représente de nouveaux billets :
truth = valid[TARGET].to_numpy()rng = np.random.default_rng(0)reported, on_new, fixed = [], [], []for draw in range(200): order = rng.permutation(len(truth)) half_a, half_b = order[:589], order[589:] ap_a = {name: average_precision_score(truth[half_a], s[half_a]) for name, s in may_scores.items()} chosen = max(ap_a, key=ap_a.get) reported.append(ap_a[chosen]) on_new.append(average_precision_score(truth[half_b], may_scores[chosen][half_b])) fixed.append(average_precision_score(truth[half_b], may_scores["logistic, C=1"][half_b]))print(f"reported (best of 10 on half A): {np.mean(reported):.3f}")print(f"the same setting on half B: {np.mean(on_new):.3f}")print(f"logistic, C=1 on half B: {np.mean(fixed):.3f}")reported (best of 10 on half A): 0.411the same setting on half B: 0.389logistic, C=1 on half B: 0.399may_scores contient les scores de mai des 10 configurations, chacune entraînée sur les données d’entraînement (section 8 du carnet). La meilleure des 10 sur la moitié A semble 0,022 meilleure que la même configuration sur de nouveaux billets. C’est environ trois erreurs-types de validation croisée. Et la configuration choisie ainsi ne fait pas mieux sur de nouveaux billets que la régression logistique avec C = 1, choisie à l’avance (0,399).
Correction : faites tous les choix avant le test : les configurations par validation croisée sur les données d’entraînement, le seuil sur les données de validation. Consultez ensuite les données de test une seule fois, dans la prochaine leçon, et rapportez leur résultat. Si le résultat vous déçoit, notez-le. Planifiez ensuite de nouveaux travaux avec de nouvelles données, pas avec les mêmes données de test.
Conséquence dans le travail en IA : des données de test qui ont aidé à choisir deviennent un deuxième jeu de validation. Leur score est optimiste, et personne n’a plus d’estimation honnête. Grace planifierait le travail de l’équipe expérimentée avec ce nombre. Le modèle décevrait en production, sans même s’être dégradé.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Classez ces éléments en paramètres et hyperparamètres : le coefficient de priority, max_depth d'un arbre, le taux d'apprentissage du boosting, la question à la première division d'un arbre, C de la régression logistique. Expliquez comment vous avez décidé.
Afficher la réponse
Paramètres : le coefficient de priority et la question à la première division d’un arbre. Hyperparamètres : max_depth, le taux d’apprentissage et C. Le critère est le moment où la valeur est fixée. fit() apprend un paramètre à partir des billets d’entraînement. Vous choisissez un hyperparamètre avant le début de fit(), et l’entraînement ne le change pas. Vous réglez donc les hyperparamètres par validation croisée, et l’entraînement trouve les paramètres.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Faites une prédiction : Mei ajoute deux configurations de boosting à la grille. L'une arrive en premier avec une AP en validation croisée de 0,3430 et une erreur-type de 0,0081. Que décide maintenant la règle d'une erreur-type, et pourquoi?
Afficher la réponse
La limite est 0,3430 − 0,0081 = 0,3349. La régression logistique avec C = 1 obtient 0,3393, au-dessus de la limite. Elle reste donc à égalité avec la meilleure. La règle choisit toujours la configuration la plus simple parmi celles à égalité : la régression logistique avec C = 1. Une nouvelle première place qui gagne par moins d’une erreur-type ne change pas la décision.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Tomás veut choisir la profondeur d'un arbre selon son score sur les billets d'entraînement, parce que « ça utilise le plus de données ». Qu'est-ce qui ne va pas, et qu'utilisez-vous plutôt?
Afficher la réponse
Sur les billets dont il a appris, un arbre plus profond obtient toujours un score au moins aussi bon. Le score d’entraînement choisit donc toujours l’arbre le plus profond. L’arbre sans limite obtient 1,000 sur les données d’entraînement et 0,137 en validation croisée : il a mémorisé les billets d’entraînement. Le score d’entraînement mesure la mémoire, pas la capacité à traiter de nouveaux billets. Utilisez plutôt la validation croisée sur les données d’entraînement. Le score de chaque pli provient de billets que l’arbre entraîné n’a pas vus. Vérifiez ensuite la profondeur choisie une seule fois sur les données de validation.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
fit()apprend les paramètres (28 coefficients et une ordonnée à l’origine). Vous choisissez les hyperparamètres avant son exécution (C, profondeur, taux d’apprentissage). Les valeurs par défaut sont une hypothèse qui fonctionne sur de nombreux jeux de données, pas forcément sur le vôtre.- Évaluez chaque configuration avec la même validation croisée sur les données d’entraînement et la même métrique. Jamais avec le score d’entraînement, jamais avec les données de test. Vérifiez ensuite le choix une seule fois sur le mois de validation.
- Une recherche par grille essaie toutes les combinaisons. Une recherche aléatoire essaie un nombre fixe de combinaisons aléatoires. Elle trouve souvent de bonnes valeurs pour le réglage qui compte avec moins d’entraînements.
- La règle d’une erreur-type : toute configuration à moins d’une erreur-type de la meilleure est à égalité avec elle; choisissez la plus simple. Le boosting menait de 0,0031 (erreur-type de 0,008). La décision est donc la régression logistique avec C = 1.
- Choisir sur un jeu de données gonfle son score : 0,411 rapporté contre 0,389 sur de nouveaux billets dans la simulation.
Ensuite, vous transformerez les scores en indicateurs pour l’équipe expérimentée et vérifierez si un score représente une probabilité. Puis vous ouvrirez les données de test, une seule fois.
Leçon suivante : Seuils et calibration