En développement de solutions logicielles ou plus spécifiquement en science des données, le temps est toujours une ressource limitée. La prochaine échéance ou le prochain jalon s’approche chaque jour sans exception et différentes stratégies sont employées dans l’optique de livrer rapidement et de respecter les délais. En science des données, le temps d’entraînement des modèles est un enjeu non négligeable qui s’évite difficilement. Voici comment l’optimisation bayésienne peut diminuer l’entraînement de modèles inutiles.
Prenons un pas de recul. Dans une solution logicielle impliquant de l’apprentissage automatique, habituellement un seul modèle sera déployé au final. Toutefois, trouver ce modèle implique d’essayer différentes approches, différents modèles, différentes configurations de modèles (hyperparamètres), etc. Il est à noter que le réglage d’un modèle requiert souvent plus de temps que son ajustement final lorsque sa forme et ses hyperparamètres sont définis. En effet, la meilleure configuration n’est pas connue au préalable. Trouver celle-ci nécessite l’entraînement et la validation de différents modèles afin d’obtenir une estimation de leur erreur de généralisation respective. Ce processus itératif permet de converger vers un modèle final qui n’offre toutefois aucune garantie d’optimalité. Chaque itération implique d’entraîner un modèle qui ne servira pas en production. Pour certains modèles, le temps d’entraînement se compte en heures, voire en jours. Dans ce contexte, les options sont nombreuses et le nombre d’essais est limité. Il s’agit d’un dilemme entre l’exploitation et l’exploration.
Idéalement, plusieurs modèles et configurations de modèles seraient testés dans une phase d’exploration, puis les meilleurs modèles seraient raffinés dans une phase d’exploitation, où le modèle le plus performant serait conservé. Trop peu d’exploration peut avoir comme conséquence d’ignorer des modèles potentiellement plus performants, tandis que trop peu d’exploitation peut avoir comme conséquence d’ignorer des améliorations incrémentales facilement disponibles. La question est de savoir quand l’exploration prévaut sur l’exploitation, et vice-versa.
Cette image est souvent utilisée pour démontrer qu’il ne faut jamais abandonner, toutefois elle représente bien le dilemme entre l’exploitation et l’exploration. La stratégie d’exploitation étant ici mise en valeur.
L’approche la plus simple à cette problématique est de manuellement configurer les hyperparamètres d’un modèle, de lancer l’entraînement, d’attendre les résultats de cette configuration, de déterminer le prochain essai à effectuer en fonction des résultats obtenus et de recommencer jusqu’à ce qu’il n’y ait plus de temps disponible. Cette approche est connue sous le nom de “babysitting”.
Elle offre plusieurs avantages en terme de formation, puisqu’elle permet de comprendre en profondeur les différents leviers de chaque famille de modèle. De plus, cette approche permet de décider à chaque itération si l’exploitation ou l’exploration est la meilleure stratégie. Toutefois, elle n’est pas viable dans un contexte d’affaires, puisqu’elle nécessite, comme le nom l’indique, une supervision constante. Dans ce contexte, beaucoup de temps est consacré à monitorer le processus d’entraînement, ce qui n’apporte pas vraiment de valeur ajoutée au projet.
De plus, comme le prochain test est lancé manuellement en fonction des résultats précédents, si les résultats sont obtenus durant une période où le scientifique des données n’est pas disponible, alors le temps entre le moment où le processus est achevé et le moment où le scientifique des données est disponible est alors gaspillé. Cela peut sembler minime, mais en considérant les journées de congé et les nuits, beaucoup d’heures de temps de calculs sont perdues.
Une approche plus sophistiquée à ce problème consiste à automatiser le lancement du processus d’entraînement pour une liste de combinaisons de configurations données, communément appelée “Grid Search”. Une autre approche similaire consiste à définir une plage de configurations possibles et à lancer le processus d’entraînement de façon aléatoire dans cette plage de configurations. Cette dernière approche est appelée “Random Search”.
Bien que ces deux méthodes permettent au scientifique de délaisser la supervision du processus d’entraînement et d’explorer plus exhaustivement, ces deux méthodes ont d’autres faiblesses. Le “Grid Search” et le “Random Search” peuvent passer beaucoup de temps sur des configurations ayant peu de potentiel. Le temps de calculs gagné en automatisant une partie du processus est perdu en effectuant des tests inutiles. De plus, un phénomène bien connu dans le monde de la science des données est la malédiction de la dimensionnalité. En effet, ce phénomène indique qu’en augmentant le nombre de paramètres à configurer, le nombre de possibilités explose. Par conséquent, il est impossible d’effectuer le nombre de tests nécessaires afin d’obtenir une bonne représentation de la direction d’amélioration.
Finalement, dans l’approche “babysitting”, le scientifique des données prend en considération les itérations précédentes dans le choix du prochain essai, ce qui n’est pas le cas pour ces deux dernières approches. Ces approches sont donc orientées vers l’exploration, mais délaissent l’aspect exploitation.
Idéalement, un scientifique des données pourrait fournir une plage de possibilités de configurations de modèles et un algorithme déterminerait la prochaine configuration ayant le plus de potentiel à tester en fonction des résultats des itérations précédentes.
C’est ce que l’optimisation bayésienne effectue à travers un processus gaussien. Deux raisons permettent à une configuration donnée d’augmenter son potentiel, soit être dans une région loin de toutes configurations testées précédemment ou être dans une région près d’une configuration performante. En combinant ces deux critères, l’optimisation bayésienne cherche à réduire l’incertitude en explorant les régions peu explorées tout en exploitant les régions près d’une configuration performante. C’est ce qu’on appelle une fonction d’acquisition. Plusieurs méthodes pour obtenir la fonction d’acquisition existent, toutefois celles-ci sont à haut niveau, une combinaison des deux critères énoncés plus haut. Le dilemme exploration-exploitation est alors intrinsèquement pris en considération dans cette approche. Il est à noter que cette approche n’est pas une solution à la malédiction de la dimensionnalité, puisqu’avec une grande plage de configurations, l’exploration de celle-ci reste un enjeu.
BayesianOptimization est une librairie en Python qui offre des algorithmes d’optimisation bayésienne. Elle est bien documentée. Je vous recommande d’y jeter un coup d’oeil.
BayesianOptimization est une librairie qui requiert peu de travail par rapport aux bénéfices qu’on peut en tirer. Tout ce dont on a besoin, c’est une fonction de type “black-box”, qui retourne une valeur numérique (float) et une plage d’hyperparamètres.
Dans l’exemple suivant, la fonction “black-box” à optimiser est une fonction qui calcule la moyenne de l’erreur de généralisation (negative logarithmic loss), estimée en validation croisée d’une forêt aléatoire. Une valeur élevée de la negative logarithmic loss représente une bonne généralisation. On cherche donc à maximiser.
La plage d’hyperparamètres est de 100 à 1000 pour l’hyperparamètre n_estimators et de 5 à 50 pour l’hyperparamètre min_sample_split.
Il ne reste qu’à initialiser l’optimisateur bayésien (BayesianOptimization), en passant en paramètres la fonction “black-box” et la plage d’hyperparamètres. Par la suite, on maximise en définissant le nombre d’itérations aléatoires au départ (init_points) et le nombre d’itérations d’optimisation bayésienne (n_iter).
Même si la librairie offre un haut niveau d’abstraction, elle offre aussi des fonctionnalités plus avancées, comme l’écriture et la lecture de log, ainsi qu’une variété de fonctions d’acquisitions.
Logs
Pour écrire les résultats obtenus dans un log, il suffit d’initialiser un JSONLogger et de souscrire les OPTIMIZATION_STEP dans le log. Ainsi, chaque test effectué lors de l’optimisation sera enregistré dans un log.
Pour lire les résultats obtenus des tests antérieurs, BayesianOptimization offre la fonction load_logs, qui permet de continuer la recherche d’hyperparamètres à partir d’anciennes recherches.
Fonctions d’acquisitions
Comme mentionné plus haut, plusieurs fonctions d’acquisitions existent. Les fonctions d’acquisition implantées dans la librairie sont Upper Confidence Bounds (‘ucb’), Expected Improvement (‘ei’) et Probability of Improvement (‘poi’). Voici une ressource recommandée par l’auteur de la librairie qui porte sur les différences entre ces fonctions.
source: BayesianOptimisation, via YouTube
Différentes approches sont proposées pour résourdre le dilemme d’exploration-exploitation. L’approche “babysitting” est formatrice, mais difficilement réalisable en contexte d’affaires. Les “Grid Search” et “Random Search” offrent des capacités intéressantes en termes d’exploration, mais négligent l’aspect exploitation. Finalement, l’optimisation bayésienne offre une approche intéressante qui permet en contexte d’affaires d’automatiser une partie de la recherche d’hyperparamètres, d’éviter de perdre du temps et ultimement de livrer dans les délais une solution performante.
Contact et code source
Ressources
[1] Fernando Nogueira. (2014–). Bayesian Optimization: Open source constrained global optimization tool for Python.
[2] Fernando Nogueira. (2013). Machine learning — Bayesian optimization and multi-armed bandits. Consulté le 18 février 2020 sur https://www.youtube.com/watch?v=vz3D36VXefI&index=10&list=PLE6Wd9FR--EdyJ5lbFl8UuGjecvVw66F6
[3]Snoek, J., Larochelle, H., & Adams, R. (2012). Practical bayesian optimization of machine learning algorithms. In Advances in neural information processing systems (pp. 2951–2959).