Wie man ordentliche Pipelines für maschinelles Lernen schreibt

Hallo Habr.



Das Thema Pipelining und Parallelisierung des maschinellen Lernens beschäftigt uns seit langem. Insbesondere frage ich mich, ob ein Fachbuch mit Schwerpunkt Python dafür ausreicht oder ob ein Überblick und möglicherweise komplexe Literatur benötigt wird. Wir haben uns entschlossen, einen einführenden Artikel über Pipelines für maschinelles Lernen zu übersetzen, der sowohl architektonische als auch angewandte Überlegungen behandelt. Lassen Sie uns diskutieren, ob Suchvorgänge in dieser Richtung relevant sind.





Haben Sie jemals eine Pipeline für maschinelles Lernen geschrieben, deren Ausführung lange gedauert hat? Oder noch schlimmer: Haben Sie das Stadium erreicht, in dem Sie Zwischenteile der Pipeline auf der Festplatte speichern müssen, damit Sie die Phasen der Pipeline nacheinander anhand von Kontrollpunkten untersuchen können? Oder schlimmer noch: Haben Sie jemals versucht, solch ekelhaften Code für maschinelles Lernen zu überarbeiten, bevor Sie diesen Code in Produktion genommen haben - und festgestellt, dass es Monate gedauert hat? Ja, jeder, der lange an Pipelines für maschinelles Lernen gearbeitet hat, musste sich damit auseinandersetzen. Warum also nicht eine gute Pipeline bauen, die uns genügend Flexibilität und die Möglichkeit bietet, den Code für den späteren Versand an die Produktion einfach umzugestalten?



Definieren wir zunächst eine Pipeline für maschinelles Lernen und diskutieren die Idee, Haltepunkte zwischen Pipeline-Phasen zu verwenden. Dann werden wir sehen, wie Sie solche Haltepunkte implementieren können, damit Sie beim Umzug der Pipeline in die Produktion nicht in den Fuß geschossen werden. Wir werden auch das Daten-Streaming und die Kompromisse diskutieren, die mit der OOP-Kapselung (Object Oriented Programming) verbunden sind, die Sie bei der Angabe von Hyperparametern in Pipelines durchlaufen müssen.



WAS SIND FÖRDERER?



FördererIst eine Folge von Schritten in der Datentransformation. Es wird nach dem alten Pipe-and-Filter-Entwurfsmuster erstellt (denken Sie beispielsweise an die Unix-Bash-Befehle mit den Pipes "|" oder den Umleitungsoperatoren ">"). Pipelines sind jedoch Objekte im Code. Daher können Sie eine Klasse für jeden Filter (dh für jede Stufe in der Pipeline) sowie eine andere Klasse zum Kombinieren aller dieser Stufen zu einer fertigen Pipeline haben. Einige Pipelines können andere Pipelines in Reihe oder parallel kombinieren, haben viele Ein- oder Ausgänge usw. Es ist praktisch, sich Pipelines für maschinelles Lernen wie folgt vorzustellen:



  • Kanal und Filter . Die Stufen der Pipeline verarbeiten Prozessdaten und die Stufen verwalten ihren internen Zustand, der aus den Daten gelernt werden kann.
  • . ; , . , – .
  • (DAG). , . : , , , , (, fit_transform ), , ( RNN). , , .








Conveyor Methods Conveyors (oder Pipeline - Stufen) , müssen die haben folgenden zwei Verfahren:



  • " Fit " zum Trainieren von Daten und zum Erfassen eines Zustands (z. B. ist ein solcher Zustand das Gewicht eines neuronalen Netzwerks)
  • " Transformieren " (oder "Vorhersagen"), um die Daten tatsächlich zu verarbeiten und eine Vorhersage zu generieren.
  • Hinweis: Wenn für eine Pipeline-Stufe keine dieser Methoden erforderlich ist, kann die Stufe von NonFittableMixin oder NonTransformableMixin erben, wodurch standardmäßig eine Implementierung einer dieser Methoden bereitgestellt wird, sodass nichts unternommen wird.




Die folgenden Methoden können optional auch in Pipeline-Phasen definiert werden :



  • fit_transform” , , , .
  • " Setup ", das die "Setup" -Methode in jeder dieser Phasen der Pipeline aufruft. Wenn eine Pipeline-Stufe beispielsweise ein neuronales TensorFlow-, PyTorch- oder Keras-Netzwerk enthält, können diese Stufen ihre eigenen neuronalen Graphen erstellen und sich registrieren, um vor dem Anpassen mit der GPU in der Setup-Methode zu arbeiten. Es wird nicht empfohlen, vor dem Anpassen Diagramme direkt in den Bühnenkonstruktoren zu erstellen. Dafür gibt es mehrere Gründe. Beispielsweise können die Schritte vor dem Start im Rahmen des Algorithmus für das automatische maschinelle Lernen, der nach den besten Hyperparametern für Sie sucht, mehrmals mit verschiedenen Hyperparametern kopiert werden.
  • " Teardown ", diese Methode ist funktional das Gegenteil von "Setup": Sie reißt Ressourcen ab.




Die folgenden Methoden werden standardmäßig bereitgestellt, um die Hyperparameter zu steuern:







Neuanpassung von Pipelines, Mini-Batching und Online-Lernen



Für Algorithmen, die Mini-Batching verwenden, z. B. das Lernen von tiefen neuronalen Netzen (DNN), oder Algorithmen, die online lernen, wie z. B. Verstärkungslernen (RL), für Pipelines oder deren ideale Phasen Die Verkettung mehrerer Anrufe ist so geeignet, dass sie genau nacheinander folgen und im Handumdrehen an die Größe von Mini-Batches angepasst werden. Diese Funktion wird in einigen Pipelines und in einigen Phasen der Pipelines unterstützt. In einigen Phasen kann die erreichte Anpassung jedoch zurückgesetzt werden, da die "fit" -Methode erneut aufgerufen wird. Es hängt alles davon ab, wie Sie Ihre Pipeline-Phase programmiert haben. Im Idealfall sollte eine Pipeline-Stufe erst nach dem Aufrufen der "Teardown" -Methode und anschließendem Aufrufen der "Teardown" -Methode geleert werden.setup”Bis zur nächsten Anpassung, und die Daten wurden nicht zwischen den Armaturen oder während der Konvertierung gespült.



VERWENDEN VON PRÜFPUNKTEN IN FÖRDERERN



Es wird empfohlen , Haltepunkte in Pipelines zu verwenden, bis Sie diesen Code für andere Zwecke verwenden und die Daten ändern müssen. Wenn Sie in Ihrem Code nicht die richtigen Abstraktionen verwenden, schießen Sie sich möglicherweise in den Fuß.



Vor- und Nachteile der Verwendung von Checkpoints in Pipelines:



  • Haltepunkte können den Workflow beschleunigen, wenn sich die Programmier- und Debugging-Schritte in der Mitte oder am Ende der Pipeline befinden. Dadurch entfällt die Notwendigkeit, die ersten Stufen der Pipeline jedes Mal neu zu berechnen.
  • ( , ), , . , , – . , , , , , .
  • Möglicherweise haben Sie nur begrenzte Rechenressourcen und die einzige praktikable Option für Sie besteht darin, Schritt für Schritt auf der verfügbaren Hardware auszuführen. Sie können einen Haltepunkt verwenden und danach einige weitere Schritte hinzufügen. Anschließend werden die Daten an der Stelle verwendet, an der Sie aufgehört haben, wenn Sie die gesamte Struktur erneut ausführen möchten.




Nachteile der Verwendung von Haltepunkten in Pipelines:



  • Es werden Festplatten verwendet. Wenn Sie es also falsch machen, kann Ihr Code langsamer werden. Um die Arbeit zu beschleunigen, können Sie mindestens RAM Disk verwenden oder den Cache-Ordner in Ihren RAM einbinden.
  • Dies kann viel Speicherplatz beanspruchen. Oder viel RAM-Speicherplatz bei Verwendung eines im RAM bereitgestellten Verzeichnisses.
  • Der auf der Festplatte gespeicherte Status ist schwieriger zu verwalten: Ihr Programm verfügt über die zusätzliche Komplexität, die erforderlich ist, damit der Code schneller ausgeführt wird. Beachten Sie, dass Ihre Funktionen und Ihr Code aus Sicht der funktionalen Programmierung nicht mehr sauber sind, da Sie die mit der Festplattennutzung verbundenen Nebenwirkungen verwalten müssen. Nebenwirkungen, die mit der Verwaltung des Status der Festplatte (Ihres Caches) verbunden sind, können der Nährboden für alle Arten von seltsamen Fehlern sein
...



Es ist bekannt, dass einige der schwierigsten Fehler bei der Programmierung auf Probleme mit der Cache-Ungültigmachung zurückzuführen sind.



In der Informatik gibt es nur zwei wirklich knifflige Dinge: die Ungültigmachung des Caches und die Benennung von Entitäten. - Phil Carlton




Tipps zur ordnungsgemäßen Verwaltung von Status und Cache in Pipelines.



Es ist bekannt, dass Programmier-Frameworks und Entwurfsmuster ein begrenzender Faktor sein können - aus dem einfachen Grund, dass sie bestimmte Regeln regeln. Hoffentlich wird dies getan, um Ihre Codeverwaltungsaufgaben so einfach wie möglich zu halten, damit Sie selbst Fehler vermeiden und Ihr Code nicht unordentlich wird. Hier sind meine fünf Cent zum Entwerfen im Kontext von Pipelines und State Management:



FÖRDERSTUFEN DÜRFEN DIE EINSTELLUNGEN DER PRÜFPUNKTE IN DEN VON AUSGESTELLTEN DATEN NICHT STEUERN




Um dies zu verwalten, muss eine spezielle Pipelining-Bibliothek verwendet werden, die all dies für Sie erledigen kann.



Warum?



Warum sollten Pipeline-Phasen nicht die Platzierung von Checkpoints in den von ihnen erzeugten Daten steuern? Aus den gleichen guten Gründen, aus denen Sie beim Arbeiten eine Bibliothek oder ein Framework verwenden und die entsprechende Funktionalität nicht selbst reproduzieren:



  • Sie verfügen über einen einfachen Kippschalter, mit dem Haltepunkte vor der Bereitstellung in der Produktion problemlos vollständig aktiviert oder deaktiviert werden können.
  • , , , : , , . , .
  • / (I/O) . , . : , . ?
  • , , – . , , .
  • , , , , , , . , . .
  • , , (, , ) . , ( , ) . , , , , , , . , . , .




Das ist cool. Mit der richtigen Abstraktion können Sie jetzt Pipelines für maschinelles Lernen programmieren, um den Schritt zur Optimierung von Hyperparametern erheblich zu beschleunigen. Dazu müssen Sie das Zwischenergebnis jedes Tests zwischenspeichern und die Pipeline-Stufen immer wieder überspringen, wenn die Hyperparameter der Zwischen-Pipeline-Stufen unverändert bleiben. Wenn Sie bereit sind, den Code für die Produktion freizugeben, können Sie das Caching sofort vollständig deaktivieren, anstatt den Code für einen ganzen Monat zu überarbeiten.



Schlagen Sie nicht gegen diese Wand.



STREAMING DATA IN MACHINE LEARNING CONVEYORS Die



Parallelverarbeitungstheorie besagt, dass Pipelines ein Daten-Streaming-Tool sind, mit dem Sie Pipeline-Stufen parallelisieren können. Wäschebeispielveranschaulicht sowohl dieses Problem als auch seine Lösung. Beispielsweise kann eine zweite Stufe in der Pipeline beginnen, Teilinformationen aus der ersten Stufe der Pipeline zu verarbeiten, während die erste Stufe weiterhin neue Daten berechnet. Darüber hinaus ist es für die zweite Stufe des Förderers nicht erforderlich, dass die erste Stufe ihre Stufe der Verarbeitung aller Daten vollständig abschließt. Nennen wir diese speziellen Pipelines Streaming (siehe hier und hier ).



Versteh mich nicht falsch, die Arbeit mit Scikit-Learn-Pipelines macht sehr viel Spaß. Sie sind jedoch nicht für das Streaming ausgelegt. Nicht nur Scikit-Learn, sondern die meisten vorhandenen Pipeline-Bibliotheken nutzen die Streaming-Funktionen nicht, wenn sie könnten. Im gesamten Python-Ökosystem gibt es Multithreading-Probleme. In den meisten Pipeline-Bibliotheken blockiert jede Stufe vollständig und erfordert die gleichzeitige Transformation aller Daten. Es sind nur wenige Streaming-Bibliotheken verfügbar.



Das Aktivieren von Streaming kann so einfach sein wie die Verwendung einer Klasse StreamingPipelineanstelle vonPipelinedie Stufen einzeln zu verbinden. Gleichzeitig werden die Größe des Mini-Batches und die Größe der Warteschlange angegeben (um einen übermäßigen RAM-Verbrauch zu vermeiden, wird dadurch eine stabilere Arbeit in der Produktion sichergestellt). Im Idealfall würde eine solche Struktur auch Multithread-Warteschlangen mit Semaphoren erfordern , wie im Problem von Anbieter und Verbraucher beschrieben : Organisation der Informationsübertragung von einer Stufe der Pipeline zu einer anderen.



In unserem Unternehmen macht Neuraxle bereits eines besser als Scikit-Learn: Es geht um sequentielle Pipelines, die mit der MiniBatchSequentialPipeline- Klasse verwendet werden können .... Bisher ist dieses Ding nicht multithreaded (aber das ist in den Plänen). Zumindest werden bereits während des Anpassungs- oder Transformationsprozesses Daten in Form von Mini-Batches an die Pipeline übertragen, bevor die Ergebnisse erfasst werden. Dies ermöglicht die Arbeit mit großen Pipelines wie beim Scikit-Learn , diesmal jedoch auch mithilfe von Mini-Batching zahlreiche andere Möglichkeiten, einschließlich: Hyperparameter-Räume, Installationsmethoden, automatisches maschinelles Lernen und so weiter.



Unsere parallele Daten-Streaming-Lösung in Python



  • Die Anpassungs- und / oder Transformationsmethode kann mehrmals hintereinander aufgerufen werden, um die Anpassung mit neuen Mini-Chargen zu verbessern.
  • , -. , , .
  • , . , setup. , , . , TensorFlow, , , , C++, Python, GPU. joblib . .
  • , . , – , , , , .
  • . , , ; , . , , , , ( Joiner). , . , , , .




Darüber hinaus möchten wir sicherstellen, dass jedes Objekt in Python von Threads gemeinsam genutzt werden kann, damit es serialisierbar und neu ladbar ist. In diesem Fall kann der Code dynamisch zur Verarbeitung auf jedem Worker gesendet werden (es kann sich um einen anderen Computer oder Prozess handeln), auch wenn sich der erforderliche Code selbst nicht auf diesem Worker befindet. Dies erfolgt mithilfe einer Kette von Serialisierern, die für jede Klasse spezifisch sind und die Pipeline-Phase verkörpern. Standardmäßig verfügt jeder dieser Schritte über einen Serializer, mit dem Sie regulären Python-Code verarbeiten können. Verwenden Sie für komplexeren Code die GPU und importieren Sie Code in anderen Sprachen. Modelle werden einfach mit ihren Sparern serialisiertund dann in den Arbeiter nachgeladen. Wenn der Worker lokal ist, können Objekte auf eine Festplatte im RAM oder in ein im RAM bereitgestelltes Verzeichnis serialisiert werden.



KOMPROMISSE FÜR DIE INKAPSULATION Die



meisten Bibliotheken für das maschinelle Lernen in Pipelines haben noch eine weitere ärgerliche Sache. Es geht darum, wie Hyperparameter behandelt werden. Nehmen wir zum Beispiel Scikit-Learn. Hyperparameterräume (auch als statistische Verteilungen von Hyperparameterwerten bezeichnet ) müssen häufig außerhalb der Pipeline angegeben werden, wobei Unterstriche als Trennzeichen zwischen den Stufen der Pipeline (n) dienen. Während Zufallssuche und RastersucheDamit Sie Hyperparameter-Gitter oder Hyperparameter-Wahrscheinlichkeitsräume untersuchen können, wie sie in Scipy-Verteilungen definiert sind, bietet scikit-learn selbst nicht für jeden Klassifikator und Transformator Standard-Hyperparameter-Räume. Die Verantwortung für die Ausführung dieser Funktionen kann jedem der Objekte in der Pipeline zugewiesen werden. Somit ist das Objekt autark und enthält seine eigenen Hyperparameter. Dies verstößt nicht gegen das Prinzip der Einzelverantwortung, das Prinzip des Öffnens / Schließens und die Prinzipien der objektorientierten SOLID-Programmierung.



KOMPATIBILITÄT UND INTEGRATION Beim Codieren von



Pipelines für maschinelles Lernen ist zu beachten, dass sie mit vielen anderen Tools kompatibel sein müssen, insbesondere mit Scikit-Learn., TensorFlow , Keras , PyTorch und viele andere Bibliotheken für maschinelles und tiefes Lernen.

Zum Beispiel haben wir eine Methode geschrieben .tosklearn(), mit der wir Pipeline-Stufen oder eine gesamte Pipeline in BaseEstimatorein Basisobjekt der Scikit-Learn-Bibliothek verwandeln können. Wie bei anderen Bibliotheken für maschinelles Lernen besteht die Aufgabe darin, eine neue Klasse zu schreiben, die von unserer erbt, BaseStepund die Vorgänge zum Anpassen und Konvertieren sowie möglicherweise zum Setzen und Abreißen in einem bestimmten Code zu überschreiben. Sie müssen auch einen Sparer definieren, der Ihr Modell speichert und lädt. Hier ist die Dokumentation für die Klasse BaseStepund Beispiele dafür.



FAZIT



Zusammenfassend stellen wir fest, dass der Code von Pipelines für maschinelles Lernen, die für die Produktion bereit sind, viele Qualitätskriterien erfüllen muss, die durchaus erreichbar sind, wenn Sie die richtigen Entwurfsmuster einhalten und den Code gut strukturieren. Beachte das Folgende:



  • Im Code für maschinelles Lernen ist es sinnvoll, Pipelines zu verwenden und jede Stufe der Pipeline als Instanz einer Klasse zu definieren.
  • Die gesamte Struktur kann dann mit Haltepunkten optimiert werden, um die besten Hyperparameter zu finden und den Code wiederholt für dieselben Daten auszuführen (möglicherweise jedoch mit unterschiedlichen Hyperparametern oder geändertem Quellcode).
  • , RAM. , .
  • , – BaseStep, , .



All Articles