Comment écrire des pipelines d'apprentissage automatique soignés

Bonjour, Habr.



Le sujet du pipelining et de la parallélisation de l'apprentissage automatique fait partie de nos travaux depuis longtemps. En particulier, je me demande si un livre spécialisé mettant l'accent sur Python est suffisant pour cela, ou si une littérature plus globale et éventuellement complexe est nécessaire. Nous avons décidé de traduire un article d'introduction sur les pipelines d'apprentissage automatique, couvrant à la fois des considérations architecturales et plus appliquées. Voyons si les recherches dans cette direction sont pertinentes.





Avez-vous déjà écrit un pipeline d'apprentissage automatique dont l'exécution a pris beaucoup de temps? Ou pire: avez-vous atteint le stade où vous devez enregistrer les parties intermédiaires du pipeline sur le disque afin de pouvoir étudier les étapes du pipeline une par une, en vous appuyant sur des points de contrôle? Ou pire, avez-vous déjà essayé de refactoriser un code d'apprentissage machine aussi dégoûtant avant de mettre ce code en production - et avez constaté que cela prenait des mois? Oui, tous ceux qui ont travaillé sur des pipelines d'apprentissage automatique pendant longtemps ont dû faire face à cela. Alors pourquoi ne pas créer un bon pipeline qui nous donne suffisamment de flexibilité et la possibilité de refactoriser facilement le code pour une expédition ultérieure en production?



Tout d'abord, définissons un pipeline d'apprentissage automatique et discutons de l'idée d'utiliser des points d'arrêt entre les étapes du pipeline. Voyons ensuite comment nous pouvons mettre en œuvre de tels points d'arrêt pour ne pas nous tirer une balle dans le pied lors du passage du pipeline en production. Nous discuterons également du streaming de données et des compromis associés à l'encapsulation de programmation orientée objet (POO) que vous devez effectuer dans les pipelines lors de la spécification d'hyperparamètres.



QUE SONT LES CONVOYEURS?



ConvoyeurEst une séquence d'étapes dans la transformation des données. Il est créé selon l'ancien modèle de conception pipe-and-filter (rappelez-vous, par exemple, les commandes unix bash avec des tubes "|" ou des opérateurs de redirection ">"). Cependant, les pipelines sont des objets dans le code. Par conséquent, vous pouvez avoir une classe pour chaque filtre (c'est-à-dire pour chaque étape du pipeline), ainsi qu'une autre classe pour combiner toutes ces étapes dans un pipeline fini. Certains pipelines peuvent combiner d'autres pipelines en série ou en parallèle, avoir de nombreuses entrées ou sorties, etc. Il est pratique de considérer les pipelines d'apprentissage automatique comme:



  • Canal et filtres . Les étapes du pipeline traitent les données et les étapes gèrent leur état interne, qui peut être appris à partir des données.
  • . ; , . , – .
  • (DAG). , . : , , , , (, fit_transform ), , ( RNN). , , .




Méthodes de



convoyeur Les convoyeurs (ou étages de pipeline) doivent avoir les deux méthodes suivantes:



  • " Fit " pour s'entraîner sur des données et acquérir un état (par exemple, un tel état correspond aux poids d'un réseau neuronal)
  • « Transformer » (ou «prédire») pour traiter réellement les données et générer une prédiction.
  • Remarque: Si une étape de pipeline ne nécessite pas l'une de ces méthodes, l'étape peut hériter de NonFittableMixin ou NonTransformableMixin, qui fournira une implémentation de l'une de ces méthodes par défaut afin qu'elle ne fasse rien.




Les méthodes suivantes peuvent également être définies en option dans les étapes du pipeline :



  • fit_transform” , , , .
  • « Setup » qui appellera la méthode «setup» à chacune de ces étapes du pipeline. Par exemple, si une étape de pipeline contient un réseau neuronal TensorFlow, PyTorch ou Keras, ces étapes pourraient créer leurs propres graphes neuronaux et s'inscrire pour travailler avec le GPU dans la méthode «setup» avant l'ajustement. Il n'est pas recommandé de créer des graphiques directement dans les constructeurs de scène avant l'ajustement; il y a plusieurs raisons à cela. Par exemple, avant de commencer, les étapes peuvent être copiées plusieurs fois avec différents hyperparamètres dans le cadre de l'algorithme Automatic Machine Learning, qui recherche les meilleurs hyperparamètres pour vous.
  • « Teardown », cette méthode est fonctionnellement l'opposé de «setup»: elle détruit les ressources.




Les méthodes suivantes sont fournies par défaut, fournissant un contrôle d'hyperparamètres:







Réajustement de pipeline, mini-batching et apprentissage en ligne



Pour les algorithmes qui utilisent le mini-batching, tels que la formation de réseaux de neurones profonds (DNN) ou pour les algorithmes qui apprennent en ligne, tels que l'apprentissage par renforcement (RL), pour les pipelines ou leurs étapes idéales il convient d'enchaîner plusieurs appels de manière à ce qu'ils se suivent exactement les uns après les autres, et à la volée ils sont ajustés à la taille des mini-lots. Cette fonctionnalité est prise en charge dans certains pipelines et à certaines étapes des pipelines, mais à un certain stade, l'ajustement obtenu peut être réinitialisé en raison du fait que la méthode «fit» sera à nouveau appelée. Tout dépend de la façon dont vous avez programmé votre étape de pipeline. Dans l'idéal, une étape de pipeline ne doit être purgée qu'après avoir appelé la méthode «teardown», puis appelé «setup»Jusqu'à l'ajustement suivant, et les données n'étaient pas vidées entre les raccords ou pendant la conversion.



UTILISATION DES POINTS DE CONTRÔLE DANS LES CONVOYEURS



Il est recommandé d'utiliser des points d'arrêt dans les pipelines jusqu'à ce que vous deviez utiliser ce code à d'autres fins et modifier les données. Si vous n'utilisez pas les abstractions correctes dans votre code, vous pourriez vous tirer une balle dans le pied.



Avantages et inconvénients de l'utilisation de points de contrôle dans les pipelines:



  • Les points d'arrêt peuvent accélérer le flux de travail si les étapes de programmation et de débogage se trouvent au milieu ou à la fin du pipeline. Cela élimine le besoin de recalculer à chaque fois les premières étapes du pipeline.
  • ( , ), , . , , – . , , , , , .
  • Vous avez peut-être des ressources informatiques limitées et la seule option viable pour vous est d'exécuter une étape à la fois sur le matériel disponible. Vous pouvez utiliser un point d'arrêt, puis ajouter quelques étapes supplémentaires après, puis les données seront utilisées là où vous vous êtes arrêté si vous souhaitez réexécuter la structure entière.




Inconvénients de l'utilisation de points d'arrêt dans les pipelines:



  • Cela utilise des disques, donc si vous le faites mal, votre code peut ralentir. Pour accélérer les choses, vous pouvez au moins utiliser le disque RAM ou monter le dossier de cache sur votre RAM.
  • Cela peut prendre beaucoup d'espace disque. Ou beaucoup d'espace RAM lors de l'utilisation d'un répertoire monté sur RAM.
  • L'état stocké sur le disque est plus difficile à gérer: votre programme possède la complexité supplémentaire nécessaire pour accélérer l'exécution du code. Notez que du point de vue de la programmation fonctionnelle, vos fonctions et votre code ne seront plus propres, car vous devez gérer les effets secondaires associés à l'utilisation du disque. Les effets secondaires associés à la gestion de l'état du disque (votre cache) peuvent être le terreau de toutes sortes de bugs étranges
...



Certains des bogues les plus difficiles en programmation sont connus pour provenir de problèmes d'invalidation de cache.



En informatique, il n'y a que deux choses vraiment délicates: l'invalidation du cache et la dénomination des entités. - Phil Carlton




Conseils sur la façon de gérer correctement l'état et le cache dans les pipelines.



On sait que les cadres de programmation et les modèles de conception peuvent être un facteur limitant - pour la simple raison qu'ils régissent certaines règles. Espérons que cela soit fait pour garder vos tâches de gestion de code aussi simples que possible, afin que vous évitiez vous-même les erreurs et que votre code ne finisse pas par être compliqué. Voici mes cinq cents sur la conception dans le contexte des pipelines et de la gestion de l'état:



LES ÉTAPES DU CONVOYEUR NE DOIVENT PAS CONTRÔLER LES PARAMÈTRES DE POINT DE TEST DANS LES DONNÉES ÉMISES PAR




Pour gérer cela, une bibliothèque de pipelining spéciale doit être utilisée pour faire tout cela pour vous.



Pourquoi?



Pourquoi les étapes du pipeline ne devraient-elles pas contrôler le placement des points de contrôle dans les données qu'elles produisent? Pour les mêmes bonnes raisons que vous utilisez une bibliothèque ou un framework lorsque vous travaillez et que vous ne reproduisez pas vous-même la fonctionnalité correspondante:



  • Vous disposerez d'un simple interrupteur à bascule qui facilitera l'activation ou la désactivation complète des points d'arrêt avant le déploiement en production.
  • , , , : , , . , .
  • / (I/O) . , . : , . ?
  • , , – . , , .
  • , , , , , , . , . .
  • , , (, , ) . , ( , ) . , , , , , , . , . , .




C'est super. Avec la bonne abstraction, vous pouvez désormais programmer des pipelines d'apprentissage automatique pour accélérer considérablement la phase de réglage des hyperparamètres; pour ce faire, vous devez mettre en cache le résultat intermédiaire de chaque test, en sautant les étapes du pipeline encore et encore, lorsque les hyperparamètres des étapes intermédiaires du pipeline restent inchangés. De plus, lorsque vous êtes prêt à publier le code en production, vous pouvez immédiatement désactiver complètement la mise en cache, plutôt que de refactoriser le code pour cela pendant un mois entier.



Ne touchez pas ce mur.



STREAMING DE DONNÉES DANS LES CONVOYEURS D'APPRENTISSAGE MACHINE



La théorie du traitement parallèle stipule que les pipelines sont un outil de diffusion de données qui vous permet de paralléliser les étapes du pipeline. Exemple de blanchisserieillustre bien ce problème et sa solution. Par exemple, une deuxième étape du pipeline peut commencer à traiter des informations partielles à partir de la première étape du pipeline, tandis que la première étape continue de calculer de nouvelles données. De plus, pour que la deuxième étape du convoyeur fonctionne, il n'est pas nécessaire que la première étape achève complètement son étape de traitement de toutes les données. Appelons ces pipelines spéciaux en streaming (voir ici et ici ).



Ne vous méprenez pas, travailler avec les pipelines scikit-learn est très agréable. Mais ils ne sont pas classés pour le streaming. Non seulement scikit-learn, mais la plupart des bibliothèques existantes en pipeline ne tirent pas parti des capacités de streaming quand elles le pouvaient. Il existe des problèmes de multithreading dans tout l'écosystème Python. Dans la plupart des bibliothèques en pipeline, chaque étape est complètement bloquante et nécessite la transformation de toutes les données à la fois. Il n'y a que quelques bibliothèques de streaming disponibles.



L'activation du streaming peut être aussi simple que d'utiliser une classe StreamingPipelineau lieu dePipelinepour relier les étapes une par une. Dans le même temps, la taille du mini-lot et la taille de la file d'attente sont indiquées (afin d'éviter une consommation excessive de RAM, cela garantit un travail plus stable en production). Idéalement, une telle structure nécessiterait également des files d'attente multithread avec des sémaphores, comme décrit dans le problème du fournisseur et du consommateur : pour organiser le transfert d'informations d'un étage du pipeline à un autre.



Dans notre entreprise, Neuraxle fait déjà quelque chose de mieux que scikit-learn: il s'agit de pipelines séquentiels qui peuvent être utilisés avec la classe MiniBatchSequentialPipeline.... Jusqu'à présent, cette chose n'est pas multi-thread (mais c'est dans les plans). Au minimum, il transmet déjà des données au pipeline sous forme de mini-lots pendant le processus d'ajustement ou de transformation, avant de collecter les résultats, ce qui permet de travailler avec de gros pipelines comme dans scikit-learn , mais cette fois en utilisant le mini-batching, ainsi que de nombreuses autres possibilités, notamment: espaces hyperparamètres, méthodes d'installation, apprentissage automatique, etc.



Notre solution Python de streaming parallèle



  • La méthode d'ajustement et / ou de transformation peut être appelée plusieurs fois de suite pour améliorer l'ajustement avec de nouveaux mini-lots.
  • , -. , , .
  • , . , setup. , , . , TensorFlow, , , , C++, Python, GPU. joblib . .
  • , . , – , , , , .
  • . , , ; , . , , , , ( Joiner). , . , , , .




De plus, nous voulons nous assurer que tout objet en Python peut être partagé entre les threads afin qu'il soit sérialisable et rechargeable. Dans ce cas, le code peut être envoyé dynamiquement pour traitement sur n'importe quel worker (il peut s'agir d'un autre ordinateur ou processus), même si le code nécessaire lui-même ne se trouve pas sur ce worker. Cela se fait à l'aide d'une chaîne de sérialiseurs spécifiques à chaque classe qui incarne l'étape du pipeline. Par défaut, chacune de ces étapes dispose d'un sérialiseur qui vous permet de traiter du code Python normal et, pour un code plus complexe, d'utiliser le GPU et d'importer du code dans d'autres langues. Les modèles sont simplement sérialisés à l'aide de leurs économiseurspuis rechargé dans le travailleur. Si le travailleur est local, les objets peuvent être sérialisés sur un disque situé dans la RAM ou dans un répertoire monté dans la RAM.



COMPROMIS POUR L'INCAPSULATION



Il y a une autre chose ennuyeuse inhérente à la plupart des bibliothèques pour l'apprentissage automatique en pipeline. Il s'agit de la manière dont les hyperparamètres sont gérés. Prenez scikit-learn par exemple. Les espaces d'hyperparamètres (également appelés distributions statistiques des valeurs d'hyperparamètres ) doivent souvent être spécifiés en dehors du pipeline, avec des traits de soulignement comme séparateurs entre les étapes du ou des pipelines. Alors que la recherche aléatoire et la recherche de grillevous permettent d'explorer des grilles d'hyperparamètres ou des espaces de probabilité d'hyperparamètres tels que définis dans les distributions scipy , scikit-learn lui-même ne fournit pas d'espaces d'hyperparamètres par défaut pour chaque classificateur et transformateur. La responsabilité de l'exécution de ces fonctions peut être attribuée à chacun des objets du pipeline. Ainsi, l'objet sera autosuffisant et contiendra ses propres hyperparamètres. Cela ne viole pas le principe de responsabilité unique, le principe d'ouverture / fermeture et les principes de programmation orientée objet SOLID.



COMPATIBILITÉ ET INTÉGRATION Lors du codage de



pipelines d'apprentissage automatique, il est utile de garder à l'esprit qu'ils doivent être compatibles avec de nombreux autres outils, en particulier scikit-learn., TensorFlow , Keras , PyTorch et de nombreuses autres bibliothèques de machine et d'apprentissage en profondeur.

Par exemple, nous avons écrit une méthode .tosklearn()qui nous permet de transformer des étapes de pipeline ou un pipeline entier en BaseEstimatorun objet de base de la bibliothèque scikit-learn. Comme pour les autres bibliothèques d'apprentissage automatique, la tâche se résume à écrire une nouvelle classe qui hérite de la nôtre BaseStepet à surcharger dans un code spécifique les opérations d'ajustement et de transformation, ainsi que, éventuellement, de configuration et de démolition. Vous devez également définir un économiseur qui enregistrera et chargera votre modèle. Voici la documentation de la classe BaseStepet des exemples pour celle-ci.



CONCLUSION



Pour résumer, nous notons que le code des pipelines d'apprentissage automatique, prêt à passer en production, doit répondre à de nombreux critères de qualité, qui sont tout à fait réalisables si vous adhérez aux bons modèles de conception et structurez bien le code. Notez ce qui suit:



  • Dans le code de machine learning, il est logique d'utiliser des pipelines et de définir chaque étape du pipeline comme une instance d'une classe.
  • La structure entière peut ensuite être optimisée avec des points d'arrêt pour aider à trouver les meilleurs hyperparamètres et exécuter à plusieurs reprises le code sur les mêmes données (mais éventuellement avec différents hyperparamètres ou code source modifié).
  • , RAM. , .
  • , – BaseStep, , .



All Articles