Vous vous souvenez de cette phrase : “En règle générale, il faudrait d’abord travailler sur la qualité de l’évaluation des modèles, puis sur la qualité des features, puis sur le choix de l’algorithme, et enfin sur l’optimisation des hyperparamètres de l’algorithme” ?
Eh bien, nous avons parlé de feature engineering et de qualité d’évaluation des modèles en détail. Nous allons enfin parler de la dernière étape : l’optimisation des hyperparamètres et de l’algorithme.
Ce chapitre sera légèrement différent des autres dans la mesure où il ressemblera plus à une collection de conseils et de techniques plutôt qu’une découverte de concepts et de raisonnements. Ceci est normal, car le sujet que nous traitons ici est très pratico-pratique. Il y a bien sûr une littérature théorique derrière, mais elle est particulièrement peu utile dans un contexte d’entreprise !
Connaissez les bases de la recherche d'hyper paramètres
Dans le chapitre Améliorez le modèle du cours d'initiation :
vous avez découvert la notion d’hyperparamètres pour un DecisionTreeClassifier ;
vous avez vu qu’on pouvait augmenter la performance du DecisionTreeClassifier en testant plusieurs valeurs de l’hyperparamètre max_depth et en choisissant la meilleure.
En effet, jouer sur les hyperparamètres permet de “guider” l’apprentissage de votre modèle de ML vers des performances potentiellement meilleures. Les hyperparamètres représentent en quelque sorte le point de départ de votre modèle. Ce point de départ peut le favoriser, comme le pousser à l’overfit ou l’underfit.
De plus, chaque type d’algorithme (SVM, XGBoost, réseau de neurones etc.) a ses propres hyperparamètres qui définissent l’architecture de l’algorithme pendant son apprentissage.
Mais du coup comment on fait pour trouver les meilleurs hyperparamètres d’un modèle ?
Déjà, il n’existe pas de meilleurs hyperparamètres dans l’absolu. Vos hyperparamètres vont toujours être conditionnés par la donnée que vous analysez. Autrement dit : 100 jeux de données différents auront très probablement 100 choix d’hyperparamètres optimaux différents.
La conséquence de cela est qu’il n’existe pas de règles ou de logique générale pour trouver les meilleurs hyperparamètres, car ils sont trop spécifiques à la donnée en question. La recherche d’hyperparamètres est donc un exercice à 100% empirique.
Nous en venons à la première méthode (la plus connue) pour trouver les meilleurs hyperparamètres : Tester énormément de combinaisons possibles, et garder celle qui vous donne la meilleure métrique de performance de votre choix (MAE, R², F1 score etc.). C’est ce qu’on appelle la Grid Search.
Pour comprendre pourquoi on parle de grille (grid), reprenons l’algorithme DecisionTreeRegressor comme exemple. Dans le cours d’initiation, seul l’hyperparamètre max_depth a été modifié. En revanche, on peut voir dans la documentation de Scikit-Learn que nous avons plusieurs autres hyperparamètres : min_sample_split, criterion, max_leaf_nodes etc.
Nous pouvons très bien faire varier 2, 3, 4 hyperparamètres en même temps (voire plus) pour entraîner le modèle et observer sa performance. Si on reste sur 2 hyperparamètres, On peut alors imaginer une matrice ou une “grille” des combinaisons possibles :
Nous allons alors tester autant de modèles que de cellules de cette grille, afin de trouver la combinaison possible d’hyperparamètres, selon une métrique de performance de notre choix. Nous appelons cela une recherche de type “brute force” car nous n’utilisons aucune règle ou aucune forme d’intelligence pour filtrer des combinaisons et réduire le nombre de tests.
De surcroît, c’est à nous de définir l’hyperparamètres à tester ainsi que les valeurs exactes que nous souhaitons tester. Ceci nous ramène aux trois limitations fondamentales de la Grid Search :
Le temps de calcul fastidieux. Si nous souhaitons tester 5 valeurs exactes de 5 hyperparamètres. Cela signifie que l’on va entraîner 5*5*5*5*5 = 5^5 = 3125 modèles différents. Si votre modèle met 10 secondes à s'entraîner sur un jeu de Train et à réaliser l’inférence sur un jeu de test, alors il faudra entre 8 et 9h pour tester toutes les combinaisons avec un Train-Test split simple. Imaginez alors avec un GridSearchCV qui ajoute une validation croisée !
Sélectionner les valeurs à tester par nous-mêmes constitue une approche “au doigt mouillé” ! Par exemple, si l’on souhaite tester les valeurs de max_depth 2, 3, 5, 10 et 20 pour tester plusieurs ordres de grandeurs, nous prenons le risque de passer à côté de l’optimum qui serait de 17.
Ça a l’air fastidieux quand même… On ne peut vraiment pas faire mieux que du “brute force” ?
Si ! Nous allons présenter deux méthodes, la première étant très proche de la Grid Search.
Il s’agit du Randomized Search. Le fonctionnement est assez simple à comprendre :
Définissez manuellement votre grille d’hyperparamètres et de valeurs à tester, comme vous le feriez avec une Grid Search classique ;
Sélectionnez le nombre maximum de combinaisons que vous souhaitez tester, par exemple 500 ;
Le RandomizedSearch va échantillonner aléatoirement une sous-grille à partir de la grille exhaustive, de telle sorte que le nombre de combinaisons totales à évaluer soit celui que vous avez sélectionné (ici en l’occurrence 500).
Mais pourquoi sélectionner aléatoirement quelques combinaisons d’une grille de Grid Search nous aiderait à trouver un meilleur modèle ? La Grid Search va tout tester, y compris ce qu’on a échantillonné !
C’est tout à fait vrai ! Par contre, l’objectif du Randomized Search n’est pas de trouver un meilleur modèle que le Grid Search, mais plutôt d’en trouver un presque aussi bon, mais en BEAUCOUP moins de temps.
D’ailleurs, ce n’est pas moi qui le dis. Les créateurs du Randomized Search ont écrit un papier scientifique très connu où ils démontrent, preuves à l’appui, qu’à temps égal de test de combinaisons, la Randomized Search dépasse souvent la Grid Search en qualité d’hyperparamètres trouvés.
Que ce soit pour la Grid Search et la RandomizedSearch, Scikit-learn est votre ami comme souvent. Vous pouvez implémenter ces méthodes avec la méthode GridSearchCV par exemple.
Ensuite, nous avons une autre méthode plus “intelligente”. Il s’agit du Bayesian Search.
Contrairement aux deux méthodes précédentes qui testent “à l’aveugle” chaque combinaison sans essayer de deviner un “pattern” de combinaisons intéressantes, le Bayesian Search va réorienter dynamiquement sa recherche d’hyperparamètres en fonction de la qualité des combinaisons précédemment testées.
En revanche, cette méthode n’est pas sans défauts :
Elle ne “vaut le coup” que pour des grilles suffisamment grandes, surtout quand vous testez plusieurs hyperparamètres.
Sur le plan mathématique, cette méthode est plus complexe à comprendre. Comme lecture d’approfondissement, nous vous renvoyons à cet article qui explique dans le détail comment cette méthode va “raisonner” pour trouver de meilleurs hyperparamètres.
Elle n’est pas disponible de manière native sur sklearn. Vous devez passer d’autres librairies pour l’implémenter, la plus connue étant Hyperopt-sklearn.
Réalisez une séparation Train-Validation-Test
Maintenant que nous connaissons les différentes méthodes de recherche d’hyperparamètres, comment pouvons-nous les utiliser en pratique ?
En réalisant une séparation train-test, voire une validation croisée, non ?
Intuitivement, c’est ce qu’on peut être tenté de faire effectivement, mais ce serait une prise de risque !
Quelle que soit la méthode de recherche d’hyperparamètres que vous utilisez, nous vous conseillons vivement de ne pas vous contenter d’un Train-Test split ou d’une validation croisée classique, comme vous avez pu le voir dans les chapitres précédents.
Pour éclairer ce point, regardons ce qui se passe quand on se limite à une séparation Train Test. Vous avez deux options dans ce cas-là :
Soit vous allez tester plusieurs hyperparamètres, et retenir la combinaison qui donne les meilleures performances sur le jeu de Train ;
Soit vous allez tester plusieurs hyperparamètres, et retenir la combinaison qui donne les meilleures performances sur le jeu de Test.
Le problème du premier cas est qu’il peut pousser votre modèle à de l’overfit comme vous vous servez de la même donnée pour choisir le type de modèle ET ses hyperparamètres. Autrement dit : vous misez “trop” de choix techniques sur un seul jeu de données.
Le deuxième cas est encore plus problématique, car il est source de Data Leakage ! Comme expliqué dans la partie précédente du cours, la donnée Test est censée approximer la donnée future que nous n’avons pas encore. Par conséquent, nous n’avons le droit de l’utiliser que pour de l’inférence ! Or ici, nous nous en servons pour construire notre modèle.
La solution intermédiaire est alors assez simple : nous allons scinder notre jeu de données en 3 parties cette fois-ci :
Le jeu d’apprentissage, qui ne servira que pour le choix du type de modèle (arbres, SVM, linéaire etc.) ;
Le jeu de validation, qui ne servira que pour le choix des hyperparamètres. Il permet d’éviter de mettre tous les œufs dans le même panier et de prendre toutes les décisions du modèle sur un seul jeu de données ;
Le jeu de test, dont le seul objectif est l’inférence d’un modèle dont l’architecture a été figée.
Dans Scikit-learn, cette séparation n’existe pas de manière native. Il faut procéder en deux temps :
D’abord, séparer le jeu en Train et Test avec la fonction train_test_split de sklearn.model_selection ;
Ensuite, vous réalisez de nouveau un train_test_split ou une validation croisée de votre choix sur le X_train. En effet, celui-ci sera scindé en un “nouveau” X_train et un jeu de test qui sera en réalité le jeu de validation (puisque le vrai jeu de test a été créé dans l’étape précédente et qu’on n’y touche plus).
Vous verrez souvent des repères du type : 70% du jeu de données en Train, 20% en validation et 10% en Test.
Ce n’est pas un mauvais point de départ, mais ce n’est pas un bon non plus. Plutôt que de faire des découpages avec des proportions arbitraires, essayez toujours de trouver une réponse guidée par le contexte métier ! Vous pouvez par exemple vous poser les questions suivantes :
Est-ce que j’ai assez de données pour accorder 10% des observations au jeu de validation ? Si ce n’est pas le cas, il faut viser une proportion beaucoup plus faible ;
Est-ce que j’ai un jeu de données très hétérogène au niveau des features ? Si c’est le cas, alors réduire la taille du jeu de train est une prise de risque. En effet, un jeu de données très hétérogène signifie qu’il y a beaucoup de cas de figure que votre modèle doit voir en apprentissage ! Il se peut alors qu’une recherche d’hyperparamètres soit contre productive, car la donnée que vous comptez sacrifier pour construire votre jeu de validation aurait pu servir comme matière première supplémentaire au jeu d'entraînement ;
Est-ce qu’il y a des observations dans mon jeu de données qui sont obsolètes ? Pensons au projet fil rouge : est-ce que des transactions datant d’avant la crise des subprimes sont encore pertinentes aujourd’hui ? Probablement pas ! Ce serait une erreur de les utiliser pour optimiser nos hyperparamètres, car le contexte derrière les données est hors-sujet.
Autrement dit, il faut toujours privilégier la qualité de la donnée, même pour la recherche d’hyperparamètres. Si ce n’est pas possible de faire une recherche d’hyperparamètres sans sacrifier de la qualité de données, alors cela ne vaut pas le coup.
Tant qu’on y est, parlons un peu plus des situations où cela vaut le coup d’entamer une recherche d’hyperparamètres !
Sachez quand utiliser une recherche d’hyperparamètres
En effet, ce n’est pas quelque chose qui est fait régulièrement dans les projets Data, pour plusieurs raisons. Nous allons nous focaliser sur trois raisons principales :
Premièrement, le feature engineering ou la restructuration du jeu d'entraînement et de test restent les méthodes les plus efficaces quand on a besoin de significativement augmenter la performance du modèle. C’est ce qui est souvent privilégié par les Data Scientists aguerris. Comme cité tout au long du cours, la recherche d'hyperparamètres ne permet que de “grapiller”.
Deuxièmement, la majorité des équipes Data contemporaines hébergeant leurs données et leur calcul dans le Cloud. Les professionnels du Machine Learning effectuent généralement tous leurs calculs dans des machines virtuelles du Cloud Provider de leur employeur (GCP, Azure, AWS etc.). La plupart de ces services facturent l’employeur à l’utilisation de la donnée et de la puissance de calcul. Le temps de calcul est de l’argent, littéralement !
Alors, imaginez le surcoût que cela créerait de faire une GirdSearchCV en testant 5000 combinaisons possibles ! Allons d’ailleurs jusqu’au bout, nous pouvons estimer ce coût :
À l’heure d’aujourd’hui, une machine virtuelle chez GCP de type n1_standard-8 * (32Go de RAM, 8 processeurs) coûte 0.5$/heure d’utilisation ;
Si votre validation croisée dure 10 minutes (ce qui est très réaliste quand on utilise un modèle non-linéaire avec un jeu de données d’une centaine de milliers de lignes) alors votre GridSearchCV durera 50 000 minutes soit 8333 heures soit 4166$ !
Troisièmement, les équipes Data et leurs clients ne cherchent quasiment jamais une performance “optimale”, ils cherchent avant tout une performance “suffisante”. Ce point est en effet le prolongement de la discussion des deux chapitres précédents où l’on a exploré ensemble la question “Qu’est-ce qu’un bon modèle au final ?”
L’exception à la règle serait un cas d’usage où il est particulièrement critique d’avoir le modèle le plus précis possible, car une erreur aurait des conséquences considérables. Pensez par exemple à un algorithme de classification dont l’objectif est de détecter un risque de déraillement d’un TGV pour cause de défauts dans les voies, ou alors un algorithme qui détermine si un cancer est opérable ou non.
Mais alors cela ne vaut jamais le coup d’utiliser une Grid Search en dehors de ce dernier cas de figure ?
Vous pouvez utiliser une petite Grid Search (une cinquantaine ou une centaine de combinaisons) pour estimer la sensibilité de votre modèle au changement d’hyperparamètres ! Si la performance de votre modèle ne bouge quasiment pas en changeant drastiquement les valeurs d’hyperparamètres, alors il serait a priori inutile de se préoccuper de cette piste d’optimisation. Dans le cas inverse, vous pouvez réfléchir à aller plus loin si vous avez déjà épuisé les pistes de feature engineering et de structuration de votre jeu de Train et de Test, tout en ayant des performances insuffisantes !
En résumé
L'optimisation des hyperparamètres consiste à ajuster les paramètres d'apprentissage du modèle pour améliorer ses performances. Les hyperparamètres doivent être adaptés à chaque jeu de données, et leur recherche est un exercice empirique.
La Grid Search est une méthode exhaustive de test des combinaisons d'hyperparamètres, mais elle peut être très coûteuse en temps de calcul. Une alternative est le Randomized Search, qui échantillonne aléatoirement des combinaisons et permet de trouver des résultats presque aussi bons avec beaucoup moins de calcul.
La Bayesian Search est plus intelligente, car elle adapte dynamiquement sa recherche en fonction des résultats précédents, mais elle est plus complexe à implémenter et utile surtout pour des grilles de paramètres importantes.
Pour éviter les biais et l'overfitting, il est recommandé de scinder son jeu de données en trois parties : Train, Validation, et Test. Cela permet de choisir à la fois le modèle et les hyperparamètres de manière rigoureuse, sans contaminer les données de test.
La recherche d'hyperparamètres est souvent secondaire dans les projets d'entreprise : l’ingénierie des features et une bonne séparation des jeux de données apportent souvent des améliorations plus significatives que l'optimisation des hyperparamètres.
Maintenant que vous avez optimisé vos modèles, il est temps de mettre vos connaissances à l'épreuve avec un quiz. Prêt à relever le défi ?