Überwachung von Data-Science-Workflows

Die Zeiten, in denen ein Data Science-Projekt nur aus lose gekoppelten Notebooks für die Datenverarbeitung und das Modelltraining bestand, die die Date Scientists gelegentlich auf ihren Laptops ausführten, sind längst vorbei. Mit zunehmender Reife haben sich die Projekte zu großen Softwareprojekten mit mehreren Beteiligten und Abhängigkeiten sowie unterschiedlichen Modulen mit zahlreichen Klassen und Funktionen entwickelt. Der Data-Science-Workflow, der in der Regel mit der Datenvorverarbeitung, dem Feature-Engineering, dem Modelltraining, dem Tuning, der Evaluierung und schließlich der Inferenz beginnt und als ML-Pipeline bezeichnet wird, wird modularisiert. Diese Modularisierung macht den Prozess skalierbarer und automatisierbar, ergo geeignet für die Ausführung in Container-Orchestrierungssystemen oder in der Cloud-Infrastruktur. Die Extraktion wertvoller modell- oder datenbezogener KPIs kann, wenn sie manuell erfolgt, eine arbeitsintensive Aufgabe sein, umso mehr, wenn sie mit zunehmenden und/oder automatisierten Wiederholungen verbunden ist. Diese Informationen sind wichtig für den Vergleich verschiedener Modelle und die Beobachtung von Trends wie «Distribution-Shift» in den Trainingsdaten. Sie können auch dazu verwendet werden, ungewöhnliche Werte zu erkennen, von unausgewogenen Klassen bis hin zur Löschung von Ausreißern – was auch immer als notwendig erachtet wird, um die Robustheit eines Modells zu gewährleisten. Bibliotheken wie MLFlow können verwendet werden, um all diese Arten von Metriken zu speichern. Außerdem hängt die Operationalisierung von ML-Pipelines stark von der Verfolgung laufbezogener Informationen zur effizienten Fehlerbehebung sowie zur Aufrechterhaltung oder Verbesserung des Ressourcenverbrauchs der Pipeline ab.

Dies gilt nicht nur für die Welt von Data Science. Die heutigen Microservice-basierten Architekturen tragen ebenfalls dazu bei, dass die Wartung und Verwaltung von bereitgestelltem Code eine einheitliche Überwachung erfordert. Dies kann die von einem DevOps-Team benötigten Betriebs- und Wartungsstunden aufgrund eines ganzheitlicheren Verständnisses der beteiligten Prozesse drastisch reduzieren und gleichzeitig fehlerbedingte Ausfallzeiten verringern.

Es ist wichtig zu verstehen, wie verschiedene Formen der Überwachung darauf abzielen, die oben genannten Implikationen anzugehen, und wie modell- und datenbezogene Metriken ebenfalls zu diesem Ziel beitragen können. Zwar hat sich MLFlow als Industriestandard für die Überwachung von ML-bezogenen Metriken etabliert, doch kann auch die Überwachung dieser Metriken zusammen mit allen betrieblichen Informationen interessant sein.

Protokolle vs. Metriken

Protokolle liefern eine ereignisbasierte Momentaufnahme – Metriken geben einen Überblick aus der Vogelperspektive

Ein Protokoll ist eine zu einem bestimmten Zeitpunkt geschriebene Aufzeichnung (z. B. stdout/stderr) eines Ereignisses, das diskontinuierlich und in nicht vordefinierten Intervallen auftritt. Je nach Anwendung enthalten die Protokolle Informationen wie Zeitstempel, Auslöser, Name, Beschreibung und/oder Ergebnis des Ereignisses. Bei den Ereignissen kann es sich um einfache Anfragen oder Benutzeranmeldungen handeln, die der Entwickler des zugrunde liegenden Codes als wichtig erachtet hat. Wenn Sie sich bei diesem Prozess an bewährte Verfahren halten, können Sie sich eine Menge Ärger und Zeit bei der Einrichtung nachgelagerter Überwachungswerkzeuge sparen. Die Verwendung dedizierter Protokollbibliotheken und das Schreiben aussagekräftiger Protokollnachrichten sind hier von Vorteil.

INFO[2021-01-06T17:44:13.368024402-08:00] starting *secrets.YamlSecrets               
INFO[2021-01-06T17:44:13.368679356-08:00] starting *config.YamlConfig                 
INFO[2021-01-06T17:44:13.369046236-08:00] starting *s3.DefaultService                 
INFO[2021-01-06T17:44:13.369518352-08:00] starting *lambda.DefaultService             
ERROR[2021-01-06T17:44:13.369694698-08:00] http server error   error="listen tcp 127.0.0.1:6060: bind: address already in use"

Abb. 1: Ereignisprotokolle in Textform

Obwohl die Datenmenge eines einzelnen Protokolls vernachlässigbar ist, können Protokollströme schnell exponentiell ansteigen. Dies führt dazu, dass die Speicherung jedes einzelnen Protokolls nicht gut skalierbar ist, insbesondere in Form von halbstrukturierten Textdaten. Für Debugging oder Auditing kann es jedoch unvermeidlich sein, Protokolle in unveränderter Form zu speichern. Archivspeicherlösungen oder Aufbewahrungsfristen können hier helfen.

In anderen Fällen können das Parsen und Extrahieren von Protokollen in andere Formate, wie z. B. Schlüssel-Wert-Paare, diesen Einschränkungen entgegenwirken. Auf diese Weise kann ein Großteil der Ereignisinformationen erhalten bleiben, während der Speicherbedarf deutlich geringer ist.

{
  time: "2021-01-06T17:44:13-08:00",
  mode: "reader",
  debug_http_error: "listen tcp 127.0.0.1:6061: bind: address already in use"
  servicePort: 8089,
  duration_ms: 180262
}

Abb. 2: Strukturierte Ereignisprotokolle

Eine andere Form der Verringerung dieses Fußabdrucks kann durch Stichprobenmethoden erfolgen, wobei Metriken die prominentesten Vertreter sind.

Eine Metrik ist ein numerisches Maß für ein bestimmtes Ziel (ein bestimmtes Ereignis), das gleichmäßig über Zeitintervalle verteilt ist. Mathematische Aggregationen wie Summen oder Durchschnittswerte sind gängige Transformationen, die solche Metriken datenmäßig relativ klein halten.

{
   time: "2022-01-06T17:44:13-08:00",
   Duration_ms: 60,
   sum_requests: 500,
   sum_hits_endpoint_1: 250,
   sum_hits_endpoint_2: 117,
   avg_duration: 113,
 }

Abb. 3: Metriken

Metriken eignen sich daher gut für die schrittweise Verringerung der Datenauflösung auf breitere Frequenzen wie tägliche, wöchentliche oder sogar längere Analysezeiträume. Außerdem lassen sich Metriken in der Regel besser über mehrere Anwendungen hinweg vereinheitlichen, da sie im Vergleich zu rohen Protokollnachrichten stark strukturierte Daten enthalten. Dies verringert zwar die zuvor genannten Probleme, geht aber auf Kosten der Granularität. Daher eignen sich Metriken perfekt für hochfrequente Ereignisse, bei denen die Informationen zu einem einzelnen Ereignis weniger wichtig sind. Die Überwachung von Rechenressourcen ist ein Beispiel dafür. Beide Ansätze haben ihre Berechtigung in jeder Überwachungskonfiguration, da die verschiedenen Anwendungsfälle zu den unterschiedlichen Zielen passen. Anhand eines konkreten Beispiels aus einem kleinen Unternehmen lassen sich die Hauptunterschiede verdeutlichen:

Der Gesamtsaldo eines Bankkontos kann im Laufe der Zeit aufgrund von Abhebungen und Einzahlungen (die zu jedem Zeitpunkt erfolgen können) schwanken. Wenn man nur daran interessiert ist, dass sich Geld auf dem Konto befindet, sollte es ausreichen, eine aggregierte Metrik regelmäßig zu verfolgen. Interessiert man sich jedoch für den Gesamtzufluss im Zusammenhang mit einem bestimmten Kunden, ist die Protokollierung jeder Transaktion unumgänglich.

Architektur und Tool-Stack

In den meisten modernen Cloud-Stacks, wie z. B. Azure Appservice, wird die Protokollierung der Infrastruktur und der Anfrageseite mit dem Dienst selbst ausgeliefert. Mit zunehmendem Volumen kann dies jedoch kostspielig werden. Die Definition der Anwendungsfälle, das Verständnis der Bereitstellungsumgebung und die Abstimmung mit der Protokollierungsarchitektur sind Teil der Aufgaben von DevOps-Teams.

Aus der Sicht eines Entwicklers gibt es eine Vielzahl von Open-Source-Tools, die wertvolle Überwachungslösungen liefern können, die nur einen gewissen Aufwand für die Orchestrierung erfordern. Schlankere Setups können nur aus einem Backend-Server wie einer Zeitreihendatenbank und einem Tool zur Visualisierung bestehen. Komplexere Systeme können mehrere Protokollierungssysteme mit mehreren dedizierten Protokollversendern, Alarmmanagern und anderen Zwischenkomponenten umfassen (siehe Abbildung). Einige dieser Werkzeuge können notwendig sein, um Protokolle überhaupt erst zugänglich zu machen oder um verschiedene Protokollströme zu vereinheitlichen. Das Verständnis der Funktionsweise und des Leistungsbereichs der einzelnen Komponenten ist daher von zentraler Bedeutung.

Abb. 4: Überwachungsablauf von Anwendungen, die in einem Kubernetes-Cluster bereitgestellt werden (verändert, aus https://logz.io/blog/fluentd-vs-fluent-bit/)

Datenbank & Entwurf

Protokolle, zumindest wenn sie nach den bewährten Verfahren mit einem Zeitstempel versehen sind, und Metriken sind in der Regel Zeitreihendaten, die in einer Zeitreihendatenbank gespeichert werden können. In Fällen, in denen Textprotokolle unverändert gespeichert werden, verwenden andere Architekturen dokumentenorientierte Speichertypen mit einer leistungsstarken Abfrage-Engine (wie ElasticSearch) als Ergänzung. Neben den speicherbezogenen Unterschieden wird die Backend-Infrastruktur in zwei verschiedene Paradigmen unterteilt: Push und Pull. Diese Paradigmen befassen sich mit der Frage, wer für die anfängliche Aufnahme der Daten verantwortlich ist (Client oder Backend).

Die Entscheidung für das eine oder andere Paradigma hängt vom Anwendungsfall oder der Art der Informationen ab, die persistiert werden sollen. Push-Dienste eignen sich beispielsweise gut für die Ereignisprotokollierung, bei der die Informationen zu einem einzelnen Ereignis wichtig sind. Allerdings sind sie dadurch auch anfälliger für Überlastung durch zu viele Anfragen, was die Robustheit beeinträchtigt. Andererseits sind Pull-Systeme perfekt geeignet, um periodische Informationen abzurufen, die mit der Zusammensetzung von Metriken übereinstimmen.

Dashboard & Warnmeldungen

Um die Daten besser zu verstehen und eventuelle Unregelmäßigkeiten zu erkennen, sind Dashboards sehr nützlich. Überwachungssysteme eignen sich vor allem für einfache, „weniger komplexe“ Abfragen, da die Leistung zählt. Der Zweck dieser Tools ist auf die zu bearbeitenden Probleme spezialisiert, und sie bieten einen begrenzteren Bestand als einige der bekannten Software wie PowerBI. Das macht sie jedoch nicht weniger leistungsfähig in ihrem Einsatzbereich. Tools wie Grafana, das sich hervorragend für den Umgang mit protokollbasierten Metrikdaten eignet, kann sich mit verschiedenen Datenbank-Backends verbinden und maßgeschneiderte Lösungen aus mehreren Quellen erstellen. Tools wie Kibana, die ihre Stärken bei textbasierten Protokoll-Analysen haben, bieten Anwendern ein umfangreiches Abfrage-Toolkit für die Ursachenanalyse und Diagnose. Es ist erwähnenswert, dass beide Tools ihren Anwendungsbereich erweitern, um beide Welten zu unterstützen.

Abb. 5 Grafana Beispiel Dashboard (https://grafana.com/grafana/)

Während sich die Überwachung hervorragend eignet, um Unregelmäßigkeiten zu erkennen (proaktiv) und fehlerhafte Systeme gezielt zu analysieren (reaktiv), können DevOps-Teams sofort handeln, wenn sie über Anwendungsausfälle informiert werden. Alert-Manager bieten die Möglichkeit, nach Ereignissen zu suchen und Alarme über alle möglichen Kommunikationskanäle auszulösen, z. B. über Messaging, Incident-Management-Programme oder per E-Mail.

Scraper, Aggregatoren & Verlader

Da nicht jeder Microservice einen Endpunkt zur Verfügung stellt, an dem Protokolle und protokollbasierte Metriken bewertet oder extrahiert werden können – denken Sie an die Unterschiede zwischen Push und Pull -, müssen Vermittler einspringen. Dienste wie Scraper extrahieren und formatieren Protokolle aus verschiedenen Quellen, Aggregatoren führen eine Art von kombinierten Aktionen (Generierung von Metriken) durch und Shipper können als Push-Dienst für Push-basierte Backends fungieren. Fluentd ist ein perfekter Kandidat, der all die genannten Fähigkeiten in sich vereint und dennoch einen kleinen Fußabdruck beibehält.

End-to-End-Überwachung

Es gibt kostenpflichtige Dienste, die versuchen, ein ganzheitliches System für jede Art von Anwendung, Architektur und unabhängig von Cloud-Anbietern bereitzustellen, was für DevOps-Teams ein entscheidender Vorteil sein kann. Aber auch schlankere Setups können kosteneffiziente und zuverlässige Arbeit leisten.

Wenn man die Notwendigkeit ausschließt, Volltextprotokolle zu sammeln, können viele Standardanwendungsfälle mit einer Zeitreihendatenbank als Backend realisiert werden. InfluxDB ist dafür gut geeignet und einfach aufzusetzen, mit ausgereifter Integrationsfähigkeit in Grafana. Grafana als Dashboard-Tool lässt sich gut mit dem Alter Manager Service von Prometheus kombinieren. Als Vermittler ist fluentd perfekt geeignet, um die textuellen Protokolle zu extrahieren und die notwendigen Transformationen durchzuführen. Da InfluxDB push-basiert ist, kümmert sich fluentd auch darum, dass die Daten in InfluxDB gelangen.

Aufbauend auf diesen Tools deckt die Beispielinfrastruktur alles ab, von der Data Science Pipeline bis zu den später eingesetzten Modell-APIs, mit Dashboards für jeden Anwendungsfall. Bevor ein neuer Trainingslauf für die Produktion freigegeben wird, bieten die eingangs erwähnten ML-Metriken einen guten Einstiegspunkt, um die Legitimität des Modells zu überprüfen. Einfache Nutzerstatistiken, wie die Gesamtzahl der Anfragen und die Anzahl der einzelnen Anfragen, geben einen guten Überblick über die Nutzung des Modells, sobald es eingesetzt wird. Durch die Verfolgung der Antwortzeiten, z. B. eines API-Aufrufs, lassen sich Engpässe leicht aufdecken.

Auf der Ressourcenebene werden die APIs zusammen mit jedem Pipelineschritt überwacht, um Unregelmäßigkeiten wie plötzliche Spitzen im Speicherverbrauch zu beobachten. Durch die Verfolgung der Ressourcen im Zeitverlauf kann auch festgestellt werden, ob die verwendeten VM-Typen über- oder unterausgelastet sind. Durch die Optimierung dieser Metriken lassen sich möglicherweise unnötige Kosten einsparen. Schließlich sollten vordefinierte Fehlerereignisse, wie eine nicht erreichbare API oder fehlgeschlagene Trainingsläufe, einen Alarm auslösen und eine E-Mail versenden.

Abb. 6: Eingesetzte Infrastruktur mit Protokoll-Streams und Monitoring-Stack. 

Die gesamte Architektur, bestehend aus der Überwachungsinfrastruktur, der Data Science Pipeline und den bereitgestellten APIs, kann in einem (verwalteten) Kubernetes-Cluster ausgeführt werden. Aus einer DevOps-Perspektive ist die Kenntnis von Kubernetes bereits die halbe Miete. Dieser Open-Source-Stack kann nach oben und unten skaliert werden und ist nicht an ein kostenpflichtiges Abonnementmodell gebunden, was große Flexibilität und Kosteneffizienz bietet. Außerdem ist das Onboarding neuer Protokoll-Streams, bereitgestellter Anwendungen oder mehrerer Pipelines mühelos möglich. Sogar einzelne Frameworks können ausgetauscht werden. Wenn zum Beispiel Grafana nicht mehr geeignet ist, verwenden Sie einfach ein anderes Visualisierungstool, das sich mit dem Backend integrieren lässt und den Anforderungen des Anwendungsfalls entspricht.

Fazit

Nicht erst seit Anwendungen modularisiert und in die Cloud verlagert wurden, sind Protokollierung und Monitoring zentrale Bestandteile moderner Infrastrukturen. Dennoch verschlimmern sie sicherlich die Probleme, die entstehen, wenn sie nicht richtig eingerichtet sind. Neben der zunehmenden Operationalisierung des ML-Workflows wächst auch die Notwendigkeit für Unternehmen, gut durchdachte Überwachungslösungen einzurichten, um Modelle, Daten und alles um sie herum im Blick zu behalten.

Es gibt zwar dedizierte Plattformen, die für diese Herausforderungen entwickelt wurden, aber die charmante Idee hinter der vorgestellten Infrastruktur ist, dass sie nur aus einem einzigen Einstiegspunkt für Data Science-, MLOps- und Devops-Teams besteht und in hohem Maße erweiterbar ist. Benedikt Müller Benedikt Müller

Schaffe Mehrwert für Deine Data Science Projekte

Data Science und datengetriebene Entscheidungen sind für viele Unternehmen zu einem zentralen Bestandteil ihres Tagesgeschäfts geworden, der in den kommenden Jahren nur noch an Wichtigkeit zunehmen wird. Bis Ende 2022 werden viele Unternehmen eine Cloud-Strategie eingeführt haben:

„70 % der Unternehmen werden bis 2022 über eine formale Cloud-Strategie verfügen, und diejenigen, die diese nicht einführen, werden es schwer haben.“
– Gartner-Forschung

Dadurch, dass sich Cloud-Technologien zu einem Grundbaustein in allen Arten von Unternehmen entwickeln, werden sie auch immer leichter verfügbar. Dies senkt die Einstiegshürde für die Entwicklung Cloud-nativer Anwendungen.

In diesem Blogeintrag werden wir uns damit beschäftigen, wie und warum wir Data Science Projekte am besten in der Cloud durchführen. Ich gebe einen Überblick über die erforderlichen Schritte, um ein Data Science Projekt in die Cloud zu verlagern, und gebe einige Best Practices aus meiner eigenen Erfahrung weiter, um häufige Fallstricke zu vermeiden.

Ich erörtere keine spezifischen Lösungsmuster für einzelne Cloud-Anbieter, stelle keine Vergleiche auf und gehe auch nicht im Detail auf Best Practices für Machine Learning und DevOps ein.

Data Science Projekte profitieren von der Nutzung öffentlicher Cloud-Dienste

Ein gängiger Ansatz für Data Science Projekte besteht darin, zunächst lokal Daten zu bearbeiten und Modelle auf Snapshot-basierten Daten zu trainieren und auszuwerten. Dies hilft in einem frühen Stadium Schritt zu halten, solange noch unklar ist, ob Machine Learning das identifizierte Problem überhaupt lösen kann. Nach der Erstellung einer ersten Modellversion, die den Anforderungen des Unternehmens entspricht, soll das Modell eingesetzt werden und somit Mehrwert schaffen.

Zum Einsatz eines Modells in Produktion gibt es normalerweise zwei Möglichkeiten: 1) Einsatz des Modells in einer on-premises Infrastruktur oder 2) Einsatz des Modells in einer Cloud-Umgebung bei einem Cloud-Anbieter Deiner Wahl. Die lokale Bereitstellung des Modells on-premises mag zunächst verlockend klingen, und es gibt Fälle, in denen dies eine umsetzbare Option ist. Allerdings können die Kosten für den Aufbau und die Wartung einer Data Science-spezifischen Infrastruktur recht hoch sein. Dies resultiert aus den unterschiedlichen Anforderungen, die von spezifischer Hardware über die Bewältigung von Spitzenbelastung während Trainingsphasen bis hin zu zusätzlichen, voneinander abhängigen Softwarekomponenten reichen.

Verschiedene Cloud-Konfigurationen bieten unterschiedliche Freiheitsgrade

Bei der Nutzung der Cloud wird zwischen «Infrastructure as a Service» (IaaS), «Container as a Service» (CaaS), «Platform as a Service» (PaaS) und «Software as a Service» (SaaS) unterschieden, wobei man in der Regel Flexibilität gegen Wartungsfreundlichkeit tauscht. Die folgende Abbildung veranschaulicht die Unterschiedlichen Abdeckungen auf den einzelnen Serviceebenen.

  • «On-Premises» musst Du dich um alles selbst kümmern: Bestellung und Einrichtung der erforderlichen Hardware, Einrichtung Deiner Datenpipeline und Entwicklung, Ausführung und Überwachung Deiner Anwendungen.
  • Bei «Infrastructure as a Service» kümmert sich der Anbieter um die Hardwarekomponenten und liefert eine virtuelle Maschine mit einer festen Version eines Betriebssystems (OS).
  • Bei «Containers as a Service» bietet der Anbieter eine Container-Plattform und eine Orchestrierungslösung an. Du kannst Container-Images aus einer öffentlichen Registry verwenden, diese anpassen oder eigene Container erstellen.
  • Bei «Platform as a Service»-Diensten musst Du in der Regel nur noch Deine Daten einbringen, um mit der Entwicklung Deiner Anwendung loszulegen. Falls es sich um eine serverlose Lösung handelt, sind auch keine Annahmen zur Servergröße nötig.
  • «Software as a Service»-Lösungen als höchstes Service-Level sind auf einen bestimmten Zweck zugeschnitten und beinhalten einen sehr geringen Aufwand für Einrichtung und Wartung. Dafür bieten sie aber nur eine stark begrenzte Flexibilität, denn neue Funktionen müssen in der Regel beim Anbieter angefordert werden.

Öffentliche Cloud-Dienste sind bereits auf die Bedürfnisse von Data Science Projekten zugeschnitten

Zu den Vorteilen der Public-Cloud gehören Skalierbarkeit, Entkopplung von Ressourcen und Pay-as-you-go-Modelle. Diese Vorteile sind bereits ein Plus für Data Science Anwendungen, z. B. für die Skalierung von Ressourcen für den Trainingsprozess. Darüber hinaus haben alle drei großen Cloud-Anbieter einen Teil ihres Servicekatalogs auf Data Science Anwendungen zugeschnitten, jeder von ihnen mit seinen eigenen Stärken und Schwächen.

Dazu gehören nicht nur spezielle Hardware wie GPUs, sondern auch integrierte Lösungen für ML-Operationen wie automatisierte Bereitstellungen, Modellregistrierungen und die Überwachung von Modellleistung und Datendrift. Viele neue Funktionen werden ständig entwickelt und zur Verfügung gestellt. Um mit diesen Innovationen und Funktionen on-premises Schritt zu halten, musst Du eine beträchtliche Anzahl von Ressourcen aufwenden, ohne dass sich dies direkt auf Dein Geschäft auswirkt.

Wenn Du an einer ausführlichen Diskussion über die Bedeutung der Cloud für den Erfolg von KI-Projekten interessiert bist, dann schau Dir doch dieses White Paper auf dem statworx Content Hub an.

Die Durchführung Deines Projekts in der Cloud erfolgt in nur 5 einfachen Schritten

Wenn Du mit der Nutzung der Cloud für Data Science Projekte beginnen möchtest, musst Du im Vorfeld einige wichtige Entscheidungen treffen und entsprechende Schritte unternehmen. Wir werden uns jeden dieser Schritte genauer ansehen.

1.    Auswahl der Cloud-Serviceebene

Bei der Wahl der Serviceebene sind die gängigsten Muster für Data-Science-Anwendungen CaaS oder PaaS. Der Grund dafür ist, dass «Infrastructure as a Service» hohe Kosten verursachen kann, die aus der Wartung virtueller Maschinen oder dem Aufbau von Skalierbarkeit über VMs hinweg resultieren. SaaS-Dienste hingegen sind bereits auf ein bestimmtes Geschäftsproblem zugeschnitten und sind einfach in Betrieb zu nehmen, anstatt ein eigenes Modell und eine eigene Anwendung zu entwickeln.

CaaS bietet den Hauptvorteil, dass Container auf jeder Containerplattform eines beliebigen Anbieters bereitgestellt werden können. Und wenn die Anwendung nicht nur aus dem Machine Learning Modell besteht, sondern zusätzliche Mikrodienste oder Front-End-Komponenten benötigt, können diese alle mit CaaS gehostet werden. Der Nachteil ist, dass, ähnlich wie bei einer On-Premises-Einführung, Container-Images für MLops-Tools wie Model Registry, Pipelines und Modell-Performance-Monitoring nicht standardmäßig verfügbar sind und mit der Anwendung erstellt und integriert werden müssen. Je größer die Anzahl der verwendeten Tools und Bibliotheken ist, desto höher ist die Wahrscheinlichkeit, dass künftige Versionen irgendwann Inkompatibilitäten aufweisen oder sogar überhaupt nicht mehr zusammenpassen.

PaaS-Dienste wie Azure Machine Learning, Google Vertex AI oder Amazon SageMaker hingegen haben all diese Funktionalitäten bereits integriert. Der Nachteil dieser Dienste ist, dass sie alle mit komplexen Kostenstrukturen einhergehen und spezifisch für den jeweiligen Cloud-Anbieter sind. Je nach Projektanforderungen können sich die PaaS-Dienste in einigen speziellen Fällen als zu restriktiv erweisen.

Beim Vergleich von CaaS und PaaS geht es meist um den Kompromiss zwischen Flexibilität und einem höheren Grad an Anbieterbindung. Eine stärkere Bindung an den Anbieter ist mit einem Aufpreis verbunden, der für die enthaltenen Funktionen, die größere Kompatibilität und die höhere Entwicklungsgeschwindigkeit zu entrichten ist. Eine höhere Flexibilität wiederum geht mit einem höheren Integrations- und Wartungsaufwand einher.

2.    Daten in der Cloud verfügbar machen

In der Regel besteht der erste Schritt zur Bereitstellung Deiner Daten darin, einen Schnappschuss der Daten in einen Cloud-Objektspeicher hochzuladen. Diese sind gut mit anderen Diensten integriert und können später mit geringem Aufwand durch eine geeignetere Datenspeicherlösung ersetzt werden. Sobald die Ergebnisse des Machine Learning Modells aus geschäftlicher Sicht geeignet sind, sollten Data Engineers einen Prozess einrichten, um Deine Daten automatisch auf dem neuesten Stand zu halten.

3.    Aufbau einer Pipeline für die Vorverarbeitung

Ein entscheidender Schritt bei jedem Data Science Projekt ist der Aufbau einer robusten Pipeline für die Datenvorverarbeitung. Dadurch wird sichergestellt, dass Deine Daten sauber und bereit für die Modellierung sind, was Dir auf lange Sicht Zeit und Mühe erspart. Ein bewährtes Verfahren ist die Einrichtung einer CICD-Pipeline (Continuous Integration and Continuous Delivery), um die Bereitstellung und das Testen Deiner Vorverarbeitung zu automatisieren und sie in Deinen DevOps-Zyklus einzubinden. Die Cloud hilft Dir, Deine Pipelines automatisch zu skalieren, um jede für das Training Deines Modells benötigte Datenmenge zu bewältigen.

4.    Training und Evaluierung des Modells

In dieser Phase wird die Preprocessing-Pipeline durch Hinzufügen von Modellierungskomponenten erweitert. Dazu gehört auch die Abstimmung von Hyperparametern, die wiederum von Cloud-Diensten durch die Skalierung von Ressourcen und die Speicherung der Ergebnisse der einzelnen Trainingsexperimente zum leichteren Vergleich unterstützt wird. Alle Cloud-Anbieter bieten einen automatisierten Dienst für Machine Learning an. Dieser kann entweder genutzt werden, um schnell die erste Version eines Modells zu erstellen und die Leistung mit den Daten über mehrere Modelltypen hinweg zu vergleichen. Auf diese Weise kannst Du schnell beurteilen, ob die Daten und die Vorverarbeitung ausreichen, um das Geschäftsproblem zu lösen. Außerdem kann das Ergebnis als Benchmark für Data Scientists verwendet werden. Das beste Modell sollte in einer Modellregistrierung gespeichert werden, damit es einsatzbereit und transparent ist.

Falls ein Modell bereits lokal oder on-premises trainiert wurde, ist es möglich, das Training zu überspringen und das Modell einfach in die Modellregistrierung zu laden.

5.   Bereitstellung des Modells für die Business Unit

Der letzte und wahrscheinlich wichtigste Schritt ist die Bereitstellung des Modells für Deine Business Unit, damit diese einen Nutzen daraus ziehen kann. Alle Cloud-Anbieter bieten Lösungen an, um das Modell mit geringem Aufwand skalierbar bereitzustellen. Schließlich werden alle Teile, die in den früheren Schritten von der automatischen Bereitstellung der neuesten Daten über die Anwendung der Vorverarbeitung und die Einspeisung der Daten in das bereitgestellte Modell erstellt wurden, zusammengeführt.

Jetzt haben wir die einzelnen Schritte für das Onboarding Deines Data Science Projekts durchlaufen. Mit diesen 5 Schritten bist Du auf dem besten Weg, Deinen Data-Science-Workflow in die Cloud zu verlagern. Um einige der üblichen Fallstricke zu vermeiden, möchte ich hier einige Erkenntnisse aus meinen persönlichen Erfahrungen weitergeben, die sich positiv auf den Erfolg Deines Projekts auswirken können.

Erleichtere Dir den Umstieg auf die Cloud mit diesen nützlichen Tipps

Beginne frühzeitig mit der Nutzung der Cloud.

Wenn Du früh damit beginnst, kann sich Dein Team mit den Funktionen der Plattform vertraut machen. Auf diese Weise kannst Du die Möglichkeiten der Plattform optimal nutzen und potenzielle Probleme und umfangreiche Umstrukturierungen vermeiden.

Stelle sicher, dass Deine Daten zugänglich sind.

Dies mag selbstverständlich erscheinen, aber es ist wichtig, dass Deine Daten beim Wechsel in die Cloud leicht zugänglich sind. Dies gilt insbesondere dann, wenn Du Deine Daten lokal generierst und anschliessend in die Cloud übertragen musst.

Erwäge den Einsatz von serverlosem Computing.

Serverless Computing ist eine großartige Option für Data Science Projekte, da es Dir ermöglicht, Deine Ressourcen nach Bedarf zu skalieren, ohne dass Du Server bereitstellen oder verwalten musst.

Vergiss nicht die Sicherheit.

Zwar bieten alle Cloud-Anbieter einige der modernsten IT-Sicherheitseinrichtungen an, doch einige davon sind bei der Konfiguration leicht zu übersehen und können Dein Projekt einem unnötigen Risiko aussetzen.

Überwache Deine Cloud-Kosten.

Bei der Optimierung von on-premises Lösungen geht es oft um die Spitzenauslastung von Ressourcen, da Hardware oder Lizenzen begrenzt sind. Mit Skalierbarkeit und Pay-as-you-go verschiebt sich dieses Paradigma stärker in Richtung Kostenoptimierung. Die Kostenoptimierung ist in der Regel nicht die erste Maßnahme, die man zu Beginn eines Projekts ergreift, aber wenn man die Kosten im Auge behält, können unangenehme Überraschungen vermeiden und die Cloud-Anwendung zu einem späteren Zeitpunkt noch kosteneffizienter gestalten werden.

Lass Deine Data Science Projekte mit der Cloud abheben

Wenn Du Dein nächstes Data Science Projekt in Angriff nimmst, ist die frühzeitige Nutzung der Cloud eine gute Option. Die Cloud ist skalierbar, flexibel und bietet eine Vielzahl von Diensten, mit denen Du das Beste aus Deinem Projekt herausholen kannst. Cloud-basierte Architekturen sind eine moderne Art der Anwendungsentwicklung, die in Zukunft noch mehr an Bedeutung gewinnen wird.

Wenn Du die vorgestellten Schritte befolgst, wirst Du auf diesem Weg unterstützt und kannst mit neusten Trends und Entwicklungen Schritt halten. Außerdem kannst Du mit meinen Tipps viele der üblichen Fallstricke vermeiden, die oft auf diesem Weg auftreten. Wenn Du also nach einer Möglichkeit suchst, das Beste aus Deinem Data Science Projekt herauszuholen, ist die Cloud definitiv eine Überlegung wert. Alexander Broska Alexander Broska Alexander Broska Alexander Broska

Be Safe!

Im Zeitalter der Open-Source-Softwareprojekte sind Angriffe auf verwundbare Software allgegenwärtig. Python ist die beliebteste Sprache für Data Science und Engineering und wird daher zunehmend zum Ziel von Angriffen durch bösartige Bibliotheken. Außerdem können öffentlich zugängliche Anwendungen durch Angriffe auf Schwachstellen im Quellcode ausgenutzt werden.

Aus diesem Grund ist es wichtig, dass Dein Code keine CVEs (Common Vulnerabilities and Exposures) enthält oder andere Bibliotheken verwendet, die bösartig sein könnten. Das gilt besonders, wenn es sich um öffentlich zugängliche Software handelt, z. B. eine Webanwendung. Bei statworx suchen wir nach Möglichkeiten, die Qualität unseres Codes durch den Einsatz automatischer Scan-Tools zu verbessern. Deshalb besprechen wir den Wert von zwei Code- und Paketscannern für Python.

Automatische Überprüfung

Es gibt zahlreiche Tools zum Scannen von Code und seinen Abhängigkeiten. Hier werde ich einen Überblick über die beliebtesten Tools geben, die speziell für Python entwickelt wurden. Solche Tools fallen in eine von zwei Kategorien:

  • Statische Anwendungssicherheitstests (SAST): suchen nach Schwachstellen im Code und verwundbaren Paketen
  • Dynamische Anwendungssicherheitstests (DAST): suchen nach Schwachstellen, die während der Laufzeit auftreten

Im Folgenden werde ich bandit und safety anhand einer kleinen, von mir entwickelten streamlit-Anwendung vergleichen. Beide Tools fallen in die Kategorie SAST, da sie die Anwendung nicht laufen lassen müssen, um ihre Prüfungen durchzuführen. Dynamische Anwendungstests sind komplizierter und werden vielleicht in einem späteren Beitrag behandelt.

Die Anwendung

Um den Zusammenhang zu verdeutlichen, hier eine kurze Beschreibung der Anwendung: Sie wurde entwickelt, um die Konvergenz (oder deren Fehlen) in den Stichprobenverteilungen von Zufallsvariablen zu visualisieren, die aus verschiedenen theoretischen Wahrscheinlichkeitsverteilungen gezogen wurden. Die Nutzer:innen können die Verteilung (z. B. Log-Normal) auswählen, die maximale Anzahl der Stichproben festlegen und verschiedene Stichprobenstatistiken (z. B. Mittelwert, Standardabweichung usw.) auswählen.

Bandit

Bandit ist ein quelloffener Python-Code-Scanner, der nach Schwachstellen im Deinem Code – und nur in Deinem Code – sucht. Er zerlegt den Code in seinen abstrakten Syntaxbaum und führt Plugins gegen diesen aus, um auf bekannte Schwachstellen zu prüfen. Neben anderen Tests prüft es einfachen SQL-Code, der eine Öffnung für SQL-Injektionen bieten könnte, im Code gespeicherte Passwörter und Hinweise auf häufige Angriffsmöglichkeiten wie die Verwendung der Bibliothek „Pickle“.

Bandit ist für die Verwendung mit CI/CD konzipiert und gibt einen Exit-Status von 1 aus, wenn es auf Probleme stößt, wodurch die Pipeline beendet wird. Es wird ein Bericht erstellt, der Informationen über die Anzahl der Probleme enthält, die nach Vertrauenswürdigkeit und Schweregrad in drei Stufen unterteilt sind: niedrig, mittel und hoch. In diesem Fall findet bandit keine offensichtlichen Sicherheitslücken in unserem Code.

Run started:2022-06-10 07:07:25.344619

Test results:
        No issues identified.

Code scanned:
        Total lines of code: 0
        Total lines skipped (#nosec): 0

Run metrics:
        Total issues (by severity):
                Undefined: 0
                Low: 0
                Medium: 0
                High: 0
        Total issues (by confidence):
                Undefined: 0
                Low: 0
                Medium: 0
                High: 0
Files skipped (0):

Umso wichtiger ist es, Bandit für die Verwendung in Deinem Projekt sorgfältig zu konfigurieren. Manchmal kann es eine Fehlermeldung auslösen, obwohl Du bereits weißt, dass dies zur Laufzeit kein Problem darstellen würde. Wenn Du zum Beispiel eine Reihe von Unit-Tests hast, die pytest verwenden und als Teil Deiner CI/CD-Pipeline laufen, wird Bandit normalerweise eine Fehlermeldung auslösen, da dieser Code die assert-Anweisung verwendet, die nicht für Code empfohlen wird, der nicht ohne das -O-Flag läuft.

Um dieses Verhalten zu vermeiden, kannst Du:

1. Scans gegen alle Dateien durchführen, aber den Test über die Befehlszeilenschnittstelle ausschließen.
2. eine Konfigurationsdatei yaml erstellen, um den Test auszuschließen.

Hier ist ein Beispiel:

# bandit_cfg.yml
skips: ["B101"] # skips the assert check

Dann können wir bandit wie folgt ausführen: bandit -c bandit_yml.cfg /path/to/python/files und die unnötigen Warnungen werden nicht auftauchen.

Safety

Entwickelt vom Team von pyup.io, läuft dieser Paketscanner gegen eine kuratierte Datenbank, die aus manuell überprüften Einträgen besteht, die auf öffentlich verfügbaren CVEs und Changelogs basieren. Das Paket ist für Python >= 3.5 verfügbar und kann kostenlos installiert werden. Standardmäßig verwendet es <a href="https://github.com/pyupio/safety-db">Safety DB</a>, die frei zugänglich ist. Pyup.io bietet auch bezahlten Zugang zu einer häufiger aktualisierten Datenbank.

Die Ausführung von safety check --full-report -r requirements.txt im Wurzelverzeichnis des Pakets gibt uns die folgende Ausgabe (aus Gründen der Lesbarkeit gekürzt):

+==============================================================================+
|                                                                              |
|                               /$$$$$$            /$$                         |
|                              /$$__  $$          | $$                         |
|           /$$$$$$$  /$$$$$$ | $$  \__//$$$$$$  /$$$$$$   /$$   /$$           |
|          /$$_____/ |____  $$| $$$$   /$$__  $$|_  $$_/  | $$  | $$           |
|         |  $$$$$$   /$$$$$$$| $$_/  | $$$$$$$$  | $$    | $$  | $$           |
|          \____  $$ /$$__  $$| $$    | $$_____/  | $$ /$$| $$  | $$           |
|          /$$$$$$$/|  $$$$$$$| $$    |  $$$$$$$  |  $$$$/|  $$$$$$$           |
|         |_______/  \_______/|__/     \_______/   \___/   \____  $$           |
|                                                          /$$  | $$           |
|                                                         |  $$$$$$/           |
|  by pyup.io                                              \______/            |
|                                                                              |
+==============================================================================+
| REPORT                                                                       |
| checked 110 packages, using free DB (updated once a month)                   |
+============================+===========+==========================+==========+
| package                    | installed | affected                 | ID       |
+============================+===========+==========================+==========+
| urllib3                    | 1.26.4    | <1.26.5                  | 43975    |
+==============================================================================+
| Urllib3 1.26.5 includes a fix for CVE-2021-33503: An issue was discovered in |
| urllib3 before 1.26.5. When provided with a URL containing many @ characters |
| in the authority component, the authority regular expression exhibits        |
| catastrophic backtracking, causing a denial of service if a URL were passed  |
| as a parameter or redirected to via an HTTP redirect.                        |
| https://github.com/advisories/GHSA-q2q7-5pp4-w6pg                            |
+==============================================================================+

Der Bericht enthält die Anzahl der überprüften Pakete, die Art der als Referenz verwendeten Datenbank und Informationen über jede gefundene Schwachstelle. In diesem Beispiel ist eine ältere Version des Pakets urllib3 von einer Schwachstelle betroffen, die technisch gesehen von einem Angreifer für einen Denial-of-Service-Angriff genutzt werden könnte.

Integration in den Workflow

Sowohl bandit als auch safety sind als GitHub Actions verfügbar. Die stabile Version von safety bietet auch Integrationen für TravisCI und GitLab CI/CD.

Natürlich kannst Du beide Pakete immer manuell von PyPI auf Deinem Runner installieren, wenn keine fertige Integration wie eine GitHub Actions verfügbar ist. Da beide Programme von der Kommandozeile aus verwendet werden können, kannst Du sie auch lokal in einen Pre-Commit-Hook integrieren, wenn die Verwendung auf Deiner CI/CD-Plattform nicht in Frage kommt.

Die CI/CD-Pipeline für die obige Anwendung wurde mit GitHub Actions erstellt. Nach der Installation der erforderlichen Pakete der Anwendung wird zuerst bandit und dann safety ausgeführt, um alle Pakete zu scannen. Wenn alle Pakete aktualisiert sind, werden die Schwachstellen-Scans bestanden und das Docker-Image wird erstellt.

Package check Code Check

Fazit

Ich würde dringend empfehlen, sowohl bandit als auch safety in Deiner CI/CD-Pipeline zu verwenden, da sie Sicherheitsüberprüfungen für Deinen Code und Deine Abhängigkeiten bieten. Bei modernen Anwendungen ist die manuelle Überprüfung jedes einzelnen Pakets, von dem Deine Anwendung abhängt, einfach nicht machbar, ganz zu schweigen von all den Abhängigkeiten, die diese Pakete haben! Daher ist automatisiertes Scannen unumgänglich, wenn Du ein gewisses Maß an Bewusstsein darüber haben willst, wie unsicher Dein Code ist.

Während bandit Deinen Code auf bekannte Exploits untersucht, prüft es keine der in Deinem Projekt verwendeten Bibliotheken. Hierfür benötigen Sie safety, da es die bekannten Sicherheitslücken in den Bibliotheken, von denen die Anwendung abhängt, aufzeigt. Zwar sind beide Frameworks nicht völlig idiotensicher, aber es ist immer noch besser, über einige CVEs informiert zu werden als über gar keine. Auf diese Weise kannst Du entweder Deinen anfälligen Code korrigieren oder eine anfällige Paketabhängigkeit auf eine sicherere Version aktualisieren.

Wenn Du Deinen Code sicher und Deine Abhängigkeiten vertrauenswürdig hältst, kannst Du potenziell verheerende Angriffe auf Deine Anwendung abwehren. Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock Thomas Alcock

Bei all dem Hype um KI in den letzten Jahren darf man nicht außer Acht lassen, dass ein Großteil der Unternehmen bei der erfolgreichen Implementierung von KI-basierten Anwendungen noch hinterherhinken. Dies ist gerade in vielen Industrien, wie z.B. in produzierenden Gewerben, recht offensichtlich (McKinsey).

Eine von Accenture 2019 durchgeführte Studie zum Thema Implementierung von KI in Unternehmungen zeigt, dass über 80% aller Proof of Concepts (PoCs) es nicht in Produktion schaffen. Außerdem gaben nur 5% aller befragten Unternehmen an, eine unternehmensweite KI-Strategie implementiert zu haben.

Diese Erkenntnisse regen zum Nachdenken an: Was genau läuft schief und warum schafft künstliche Intelligenz anscheinend noch nicht die ganzheitliche Transition von erfolgreichen, akademischen Studien zu der realen Welt?

1. Was ist data-centric AI?

„Data-centric AI is the discipline of systematically engineering the data used to build an AI system.“
Zitat von Andrew Ng, data-centric AI Pionier

Der data-centric Ansatz fokussiert sich auf eine stärkere Daten-integrierenden KI (data-first) und weniger auf eine Konzentration auf Modelle (model-first), um die Schwierigkeiten von KI mit der „Realität“ zu bewältigen. Denn, die Trainingsdaten, die meist bei Unternehmen als Ausgangspunkt eines KI-Projekts stehen, haben relativ wenig gemeinsam mit den akribisch kurierten und weit verbreiteten Benchmark Datensets wie MNIST oder ImageNet.

Das Ziel dieses Artikels ist, data-centric im KI-Workflow und Projektkontext einzuordnen, Theorien sowie relevante Frameworks vorzustellen und aufzuzeigen, wie wir bei statworx eine data-first KI-Implementierung angehen.

2. Welche Gedankengänge stecken hinter data-centric?

Vereinfacht dargestellt bestehen KI-Systeme aus zwei entscheidenden Komponenten: Daten und Modell(-Code). Data-centric fokussiert sich mehr auf die Daten, model-centric auf das Modell – duh!

Bei einer stark model-centric lastigen KI werden Daten als ein extrinsischer, statischer Parameter behandelt. Der iterative Prozess eines Data Science Projekts startet praktisch erst nach dem Erhalt der Daten bei den Modell-spezifischen Schritten, wie Feature Engineering, aber vor allem exzessives Trainieren und Fine tunen verschiedener Modellarchitekturen. Dies macht meist das Gros der Zeit aus, das Data Scientists an einem Projekt aufwenden. Kleinere Daten-Aufbereitungsschritte werden meist nur einmalig, ad-hoc am Anfang eines Projekts angegangen.

Model Centric Grafik

Im Gegensatz dazu versucht data-centric (automatisierte) Datenprozesse als zentralen Teil jedes ML Projekts zu etablieren. Hierunter fallen alle Schritte die ausgehend von den Rohdaten nötig sind, um ein fertiges Trainingsset zu generieren. Durch diese Internalisierung soll eine methodische Überwachbarkeit für verbesserte Qualität sorgen.

Data Centric Grafik

Man kann dabei data-centric Überlegungen in drei übergeordnete Kategorien zusammenfassen. Diese beschreiben lose, welche Aufgabenbereiche bei einem data-centric Ansatz bedacht werden sollten. Im Folgenden wurde versucht, diese bekannte Buzzwords, die im Kontext von data-centric immer wieder auftauchen, thematisch einer Kategorie zuzuordnen.

2.1. Integration von SMEs in den Development Prozess als wichtiges Bindeglied zwischen Data- und Model-Knowledge.

Die Einbindung von Domain Knowledge ist ein integraler Bestandteil von data-centric. Dies hilft Projektteams besser zusammenwachsen zu lassen und so das Wissen der Expert:innen, auch Subject Matter Experts (SMEs) genannt, bestmöglich im KI-Prozess zu integrieren.

  • Data Profiling:
    Data Scientists sollten nicht als Alleinkämpfer:innen die Daten analysieren und nur ihre Befunde mit den SMEs teilen. Data Scientists können ihre statistischen und programmatischen Fähigkeiten gezielt einsetzen, um SMEs zu befähigen, die Daten eigenständig zu untersuchen und auszuwerten.
  • Human-in-the-loop Daten & Model Monitoring:
    Ähnlich wie beim Profiling soll hierbei durch das Bereitstellen eines Einstiegpunktes gewährleistet werden, das SMEs Zugang zu den relevanten Komponenten des KI-Systems erhalten. Von diesem zentralen Checkpoint können nicht nur Daten sondern auch Modell-relevante Metriken überwacht werden oder Beispiele visualisiert und gecheckt werden. Gleichzeit gewährt ein umfängliches Monitoring die Möglichkeit, nicht nur Fehler zu erkennen, sondern auch die Ursachen zu untersuchen und das möglichst ohne notwendige Programmierkenntnisse.

2.2. Datenqualitätsmanagement als agiler, automatisierter und iterativer Prozess über (Trainings-)Daten

Die Datenaufbereitung wird als Prozess verstanden, dessen kontinuierliche Verbesserung im Vordergrund eines Data Science Projekts stehen sollte. Das Modell, anders als bisher, sollte hingegen erstmal als (relativ) fixer Parameter behandelt werden.

  • Data Catalogue, Lineage & Validation:
    Die Dokumentation der Daten sollte ebenfalls keine extrinsische Aufgabe sein, die oft nur gegen Ende eines Projekts ad-hoc entsteht und bei jeder Änderung, z.B. eines Modellfeatures, wieder obsolet sein könnte. Änderungen sollen dynamisch reflektiert werden und so die Dokumentation automatisieren. Data Catalogue Frameworks bieten hier die Möglichkeit, Datensätze mit Meta-Informationen anzureichern.
    Data Lineage soll im Weiteren dabei unterstützen, bei diversen Inputdaten, verschiedenen Transformations- und Konsolidierungsschritten zwischen roh- und finalem Datenlayer den Überblick zu behalten. Je komplexer ein Datenmodell, desto eher kann ein Lineage Graph Auskunft über das Entstehen der finalen Spalten geben (Grafik unten), beispielsweise ob und wie Filterungen oder bestimmte join Logiken benutzt wurden. Die Validierung (neuer) Daten hilft schließlich eine konsistente Datengrundlage zu gewährleisten. Hier helfen die Kenntnisse aus dem Data Profiling um Validierungsregeln auszuarbeiten und im Prozess zu integrieren.

Lineage Graph

  • Data & Label Cleaning:
    Die Notwendigkeit der Datenaufbereitung ist selbsterklärend und als Best Practice ein fester Bestandteil in jedem KI-Projekt. Eine Label-Aufbereitung ist zwar nur bei Klassifikations-Algorithmen relevant, wird aber hier selten als wichtiger pre-processing Schritt mitbedacht. Aufbereitungen können aber mit Hilfe von Machine Learning automatisiert werden. Falsche Labels können es nämlich für Modelle erschweren, exakte patterns zu erlernen.
    Auch sollte man sich bewusst machen, dass solange sich die Trainingsdaten ändern, die Datenaufbereitung kein vollkommen abgeschlossener Prozess sein kann. Neue Daten bedeuten oft auch neue Cleaning-Schritte.
  • Data Drifts in Produktion:
    Eine weit verbreitete Schwachstelle von KI-Applikationen tritt meist dann auf, wenn sich Daten nicht so präsentieren, wie es beim Trainieren der Fall war, beispielweise im Zeitverlauf ändern (Data Drifts). Um die Güte von ML Modellen auch langfristig zu gewährleisten, müssen Daten in Produktion kontinuierlich überwacht werden. Hierdurch können Data Drifts frühzeitig ausfindig gemacht werden, um dann Modelle neu auszurichten, wenn z.B. bestimmte Inputvariablen von ihrer ursprünglichen Verteilung zu stark abweichen.
  • Data Versioning:
    GitHub ist seit Jahren der go to Standard für Code Versionierungen, um mehr Übersicht und Kontrolle zwischen Codeständen zu haben. Aber auch Daten können versioniert werden und so eine ganzheitliche Prozesskontrolle bieten. Ebenfalls können so Code- mit Datenständen verknüpft werden. Dies sorgt nicht nur für bessere Überwachbarkeit, sondern hilft auch dabei, automatisierte Prozesse anzustoßen.

Data/Code Versionierung Grafik

2.3. Generieren von Trainingsdatensätzen als programmatischer Task.

Gerade das Erzeugen von (gelabelten) Trainingsdaten ist einer der größten Roadblocker für viele KI-Projekte. Gerade bei komplexen Problemen, die große Datensätze benötigen, ist der initiale, manuelle Aufwand enorm.

  • Data Augmentation:
    Bei vielen datenintensiven Deep Learning Modellen wird diese Technik schon seit längerem eingesetzt, um mit bestehenden Daten, artifizielle Daten zu erzeugen. Bei Bilddaten ist dies recht anschaulich erklärbar. Hier werden beispielsweise durch Drehen eines Bildes verschiedene Perspektiven desselben Objektes erzeugt. Aber auch bei NLP und bei „tabularen“ Daten (Excel und Co.) gibt es Möglichkeiten neue Datenpunkte zu erzeugen.

Data Augmentation Grafik

  • Automated Data Labeling:
    Normalerweise ist Labeling ein sehr arbeitsintensiver Schritt, in dem Menschen Datenpunkte einer vordefinierten Kategorie zuordnen. Einerseits ist dadurch der initiale Aufwand (Kosten) sehr hoch, andererseits fehleranfällig und schwierig zu überwachen. Hier kann ML durch Konzepte wie semi- oder weak supervison Automatisierungshilfe leisten, was den manuellen Aufwand erheblich reduziert.
  • Data Selection:
    Arbeiten mit großen Datensätzen sind im lokalen Trainingskontext schwierig zu handhaben. Gerade dann, wenn diese nicht mehr in den Arbeitsspeicher des Laptops passen. Und selbst wenn, dann dauern Trainingsläufe meist sehr lange. Data Selection versucht die Größe durch ein aktives Subsampling (ob gelabelt oder ungelabelt) zu reduzieren. Aktiv werden hier die „besten“ Beispiele mit der höchsten Vielfalt und Repräsentativität ausgewählt, um die bestmögliche Charakterisierung des Inputs zu gewährleisten – und das automatisiert.

Selbstverständlich ist es nicht in jedem KI-Projekt sinnvoll, alle aufgeführten Frameworks zu bedenken. Es ist Aufgabe eines jedes Development Teams, die nötigen Tools und Schritte im data-centric Kontext zu analysieren und auf Relevanz und Übertragbarkeit zu prüfen. Hier spielen neben datenseitigen Überlegungen auch Business Faktoren eine Hauptrolle, da neue Tools meist auch mehr Projektkomplexität bedeuten.

3. Integration von data-centric bei statworx

Data-centric Überlegungen spielen bei unseren Projekten gerade in der Übergangsphase zwischen PoC und Produktivstellen des Modells vermehrt eine führende Rolle. Denn auch in einigen unserer Projekte ist es schon vorgekommen, dass man nach erfolgreichem PoC mit verschiedenen datenspezifischen Problemen zu kämpfen hatte; meist hervorgerufen durch unzureichende Dokumentation und Validierung der Inputdaten oder ungenügende Integration von SMEs im Datenprozess und Profiling.

Generell versuchen wir daher unseren Kunden die Wichtigkeit des Datenmanagements für die Langlebigkeit und Robustheit von KI-Produkten in Produktion aufzuzeigen und wie hilfreiche Komponenten innerhalb einer KI-Pipeline verknüpft sind.

Gerade unser Data Onboarding – ein Mix aus Profiling, Catalogue, Lineage und Validation, integriert in ein Orchestrations-Framework – ermöglicht uns mit den oben genannten Problemen besser umzugehen und so hochwertigere KI-Produkte bei unseren Kunden zu integrieren.

Zusätzlich hilft dieses Framework dem ganzen Unternehmen, bisher ungenutzte, undokumentierte Datenquellen für verschiedene Use Cases (nicht nur KI) verfügbar zu machen. Dabei ist die enge Zusammenarbeit mit den SMEs auf Kundenseite essenziell, um so effektive und robuste Datenqualitäts-Checks zu implementieren. Die resultierenden Datentöpfe und -prozesse sind somit gut verstanden, sodass Validierungs-Errors vom Kunden verstanden und behoben werden können, was so zu einem langlebigen Einsatz des Service beiträgt.

Data & AI Pipeline von statworx Grafik

In einer abgespeckten, kundenspezifischen Data Onboarding Integration haben wir mit Hilfe verschiedener Open und Closed Source Tools eine für den Kunden einfach skalierbare und leicht verständliche Plattform geschaffen.

So haben wir beispielsweise Validationchecks mit Great Expectations (GE), einem Open Source Framework, umgesetzt. Dieses Tool bietet neben Python-basierter Integration diverser Tests auch eine Reporting Oberfläche, die nach jedem Durchlauf einen einfach verständlichen Einstiegspunkt in die Resultate bietet.

Data Validation Grafik

Diese Architektur kann dann in verschiedenen Kontexten laufen, ob in der Cloud, mit einem Closed Source Software wie Azure Data Factory oder on premises mit Open Source Tools wie Airflow – und kann um weitere Tools jederzeit ergänzt werden.

4. Data-centric im Status Quo von KI

Sowohl model- als auch data-centric beschreiben Handlungsansätze, wie man an ein KI-Projekt herangehen kann.

Model-centric ist in den letzten Jahren recht erwachsen geworden und es haben sich dadurch einige Best Practices in verschiedenen Bereichen entwickelt, auf denen viele Frameworks aufbauen.

Dies hat auch damit zu tun, dass in der akademischen Welt der Fokus sehr stark auf Modellarchitekturen und deren Weiterentwicklung lag (und noch liegt) und diese stark mit führenden KI-Unternehmen korreliert. Gerade im Bereich Computer Vision und Natural Langue Processing konnten kommerzialisierte Meta-Modelle, trainiert auf gigantischen Datensets, die Tür zu erfolgreichen KI Use Cases öffnen. Diese riesigen Modelle können auf kleineren Datenmengen für Endanwendungen gefinetuned werden, bekannt unter Transfer Learning.

Diese Entwicklung hilft allerdings nur einem Teil der gescheiterten Projekte, da gerade im Kontext von industriellen Projekten fehlende Kompatibilität oder Starrheit der Use Cases die Anwendungen von Meta-Modellen erschwert. Die Nicht-Starrheit findet sich häufig in maschinenlastigen Produktionsindustrien, wo sich das Umfeld, in dem Daten produziert werden, stetig ändert und sogar der Austausch einer einzelnen Maschine große Auswirkungen auf ein produktives KI-Modell haben kann. Wenn diese Problematik nicht richtig im KI-Prozess bedacht wurde, entsteht hier ein schwer kalkulierbares Risiko, auch bekannt unter Technical Debt [Quelle: https://proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf].

Zu guter Letzt stellen die Distributionen bei einigen Use Cases ein inhärentes Problem für ML dar. Modelle haben grundsätzlich Schwierigkeiten mit edge cases, sehr seltene und ungewöhnliche Beobachtungspunkte (die long tails [Quelle: https://medium.com/codex/machine-learning-the-long-tail-paradox-1cc647d4ba4b] einer Verteilung). Beispielweise ist es nicht ungewöhnlich, dass bei Fault Detection das Verhältnis von fehlerhaften zu einwandfreien Bauteilen eins zu mehreren Tausend beträgt. Die Abstraktionsfähigkeit bei ungesehenen, abseits der Norm liegenden Fehlern ist hier meist schlecht.

5. Schluss – Paradigmenwechsel in Sicht?

Diese Probleme zu bewältigen, ist zwar Teil des Versprechens von data-centric, aber präsentiert sich im Moment noch eher unausgereift.

Das lässt sich auch an der Verfügbarkeit und Maturität von Open Source Frameworks darlegen. Zwar gibt es schon vereinzelte, produktionsfertige Anwendungen, aber keine, die die verschiedenen Teilbereiche von data-centric zu vereinheitlichen versucht. Dies führt unweigerlich zu längeren, aufwendigeren und komplexeren KI-Projekten, was für viele Unternehmen eine erhebliche Hürde darstellt. Außerdem sind kaum Datenmetriken vorhanden, die Unternehmen ein Feedback geben, was sie denn genau gerade „verbessern“. Und zweitens, viele der Tools (bsp. Data Catalogue) haben einen eher indirekten, verteilten Nutzen.

Einige Start-ups, die diese Probleme angehen wollen, sind in den letzten Jahren entstanden. Dadurch, dass diese aber (ausschließlich) paid tier Software vermarkten, ist es eher undurchsichtig, inwiefern diese Produkte wirklich die breite Masse an Problemen von verschiedenen Use Cases abdecken können.

Obwohl die Aufführungen oben zeigen, dass Unternehmen generell noch weit entfernt sind von einer ganzheitlichen Integration von data-centric, wurden robuste Daten Strategien in der letzten Zeit immer wichtiger (wie wir bei statworx an unseren Projekten sehen konnten).

Mit vermehrtem akademischem Research in Daten Produkte wird sich dieser Trend sicherlich noch verstärken. Nicht nur weil dadurch neue, robustere Frameworks entstehen, sondern auch weil durch Uni-Absolvent:innen den Unternehmen mehr Wissen in diesem Gebiet zufließt.

Bild-Quellen:

Model-centric arch: eigene
Data-centric arch: eigene
Data lineage: https://www.researchgate.net/figure/Data-lineage-visualization-example-in-DW-environment-using-Sankey-diagram_fig7_329364764
Historisierung Code/Data: https://ardigen.com/7155/
Data Augmentation: https://medium.com/secure-and-private-ai-writing-challenge/data-augmentation-increases-accuracy-of-your-model-but-how-aa1913468722
Data & AI pipeline: eigene
Validieren mit GE: https://greatexpectations.io/blog/ge-data-warehouse/

 

Benedikt Müller Benedikt Müller Benedikt Müller Benedikt Müller Benedikt Müller Benedikt Müller Benedikt Müller Benedikt Müller

Im Bereich Data Science – wie der Name schon sagt – ist das Thema Daten, vom Data Cleaning bis hin zum Feature Engineering, einer der Grundpfeiler. Daten zu haben und auszuwerten ist die eine Seite, doch wie kommt man eigentlich an Daten für neue Problemstellungen?

Wenn man Glück hat, werden die Daten, die man benötigt, bereits zur Verfügung gestellt. Sei es über den Download eines ganzen Datensatzes oder die Verwendung einer API. Häufig muss man allerdings auch Informationen von Webseiten selbst zusammentragen – das nennt man Web Scraping. Je nachdem wie oft man Daten scrapen will, ist es von Vorteil, diesen Schritt zu automatisieren.

In diesem Beitrag soll es genau um diese Automatisierung gehen. Ich werde mittels Web Scraping und GitHub Actions an einem Beispiel aufzeigen, wie man sich selbst Datensätze über einen längeren Zeitraum erstellen kann. Dabei soll der Fokus auf den Erfahrungen liegen, die ich in den letzten Monaten gesammelt habe.

Der verwendete Code sowie die bisher gesammelten Daten befinden sich in diesem GitHub Repo.

Suche nach Daten – Ausgangslage

Bei meiner Recherche für den Blogbeitrag über die Benzinpreise, bin ich auch über Daten zur Auslastung der Parkhäuser in Frankfurt am Main gestoßen. Die Beschaffung dieser Daten legte den Grundstein für diesen Beitrag. Nach einigen Überlegungen und zusätzlicher Recherche kamen mir noch weitere thematisch passende Datenquellen in den Sinn:

  • Auslastung der Straßen
  • Verspätungen der S- und U-Bahnen
  • Events in der Nähe
  • Wetterdaten

Schnell stellte sich jedoch heraus, dass ich nicht alle diese Daten bekommen konnte, da sie nicht frei verfügbar sind bzw. es nicht gestattet ist, diese zu speichern. Da ich vorhatte, die gesammelten Daten auf GitHub zu speichern und verfügbar zu machen, war dies ein entscheidender Punkt, welche Daten in Frage kamen. Aus diesen Gründen fielen die Bahndaten vollkommen raus. Für die Straßenauslastung habe ich lediglich Daten für Köln gefunden und ich wollte es vermeiden, die Google API zu nutzen, da das durchaus seine eigenen Herausforderungen mit sich bringt. Es blieben also Event- und Wetterdaten.

Für die Wetterdaten des Deutschen Wetterdienstes kann das rdwd Packet genutzt werden. Da diese Daten bereits historisiert vorliegen, sind sie für diesen Blogbeitrag nebensächlich. Um an die verbleibenden Event- und Parkdaten zu kommen, haben sich die GitHub Actions als sehr nützlich erwiesen – auch wenn sie nicht ganz trivial in der Anwendung sind. Besonders der Umstand, dass diese kostenfrei genutzt werden können, machen sie zu einem empfehlenswerten Tool für solche Projekte.

Scrapen der Daten

Da sich dieser Beitrag nicht mit Details zum Thema Webscraping befassen wird, verweise ich an dieser Stelle auf den Beitrag von meinem Kollegen David.

Die Parkdaten stehen hier im XML-Format bereit und werden alle fünf Minuten aktualisiert. Sobald man die Struktur des XML verstanden hat, müsst ihr nur noch auf den richtigen Index zugreifen und ihr habt die Daten, die ihr möchtet.

In der Funktion get_parking_data() habe ich alles zusammengefasst, was ich benötige. Es wird ein Datensatz zur Area und ein Datensatz zu den einzelnen Parkhäusern erstellt.

Beispiel Datenauszug area

parkingAreaOccupancy;parkingAreaStatusTime;parkingAreaTotalNumberOfVacantParkingSpaces;
totalParkingCapacityLongTermOverride;totalParkingCapacityShortTermOverride;id;TIME
0.08401977;2021-12-01T01:07:00Z;556;150;607;1[Anlagenring];2021-12-01T01:07:02.720Z
0.31417114;2021-12-01T01:07:00Z;513;0;748;4[Bahnhofsviertel];2021-12-01T01:07:02.720Z
0.351417;2021-12-01T01:07:00Z;801;0;1235;5[Dom / Römer];2021-12-01T01:07:02.720Z
0.21266666;2021-12-01T01:07:00Z;1181;70;1500;2[Zeil];2021-12-01T01:07:02.720Z

Beispiel Datenauszug facility

parkingFacilityOccupancy;parkingFacilityStatus;parkingFacilityStatusTime;
totalNumberOfOccupiedParkingSpaces;totalNumberOfVacantParkingSpaces;
totalParkingCapacityLongTermOverride;totalParkingCapacityOverride;
totalParkingCapacityShortTermOverride;id;TIME
0.02;open;2021-12-01T01:02:00Z;4;196;150;350;200;24276[Turmcenter];2021-12-01T01:07:02.720Z
0.11547912;open;2021-12-01T01:02:00Z;47;360;0;407;407;18944[Alte Oper];2021-12-01T01:07:02.720Z
0.0027472528;open;2021-12-01T01:02:00Z;1;363;0;364;364;24281[Hauptbahnhof Süd];2021-12-01T01:07:02.720Z
0.609375;open;2021-12-01T01:02:00Z;234;150;0;384;384;105479[Baseler Platz];2021-12-01T01:07:02.720Z

Für die Eventdaten scrape ich die Seite stadtleben.de. Da es sich um eine HTML handelt, die recht gut strukturiert ist, kann ich über den Tag „kalenderListe“ auf die tabellarische Eventübersicht zugreifen. Das Resultat wird durch die Funktion get_event_data() erstellt.

Beispiel Datenauszug events

eventtitle;views;place;address;eventday;eventdate;request
Magical Sing Along - Das lustigste Mitsing-Event;12576;Bürgerhaus;64546 Mörfelden-Walldorf, Westendstraße 60;Freitag;2022-03-04;2022-03-04T02:24:14.234833Z
Velvet-Bar-Night;1460;Velvet Club;60311 Frankfurt, Weißfrauenstraße 12-16;Freitag;2022-03-04;2022-03-04T02:24:14.234833Z
Basta A-cappella-Band;465;Zeltpalast am Deutsche Bank Park;60528 Frankfurt am Main, Mörfelder Landstraße 362;Freitag;2022-03-04;2022-03-04T02:24:14.234833Z
BeThrifty Vintage Kilo Sale | Frankfurt | 04. & 05. …;1302;Batschkapp;60388 Frankfurt am Main, Gwinnerstraße 5;Freitag;2022-03-04;2022-03-04T02:24:14.234833Z

Automation der Abläufe – GitHub Actions

Das Grundgerüst steht. Ich habe je eine Funktion, die mir die Park- und Eventdaten beim Ausführen in eine .csv Datei schreiben. Da ich die Parkdaten alle fünf Minuten und die Eventdaten zur Sicherheit drei Mal am Tag abfragen möchte, kommen nun GitHub Actions ins Spiel.

Mit dieser Funktion von GitHub können neben Aktionen, die beim Mergen oder Committen auslösen, auch Workflows zeitlich geplant und durchgeführt werden. Hierfür wird eine .yml Datei im Order /.github/workflows erstellt.

Die Hauptbestandteile meines Workflows sind:

  • Der schedule – Alle zehn Minuten sollen die Funktionen ausgeführt werden.
  • Das OS – Da ich lokal auf einem Mac entwickle, nutze ich hier das macOS-latest.
  • Umgebungsvariablen – Hier ist neben meinem GitHub Token auch der Pfad für das Paketmanagement renv enthalten
  • Die einzelnen steps im Workflow selbst

Der Workflow durchläuft die folgenden Schritte:

  • Setup R
  • Pakete laden mit renv
  • Script ausführen um Daten zu scrapen
  • Script ausführen um die README zu aktualisieren
  • Pushen der neuen Daten zurück ins git

Jeder dieser Schritte ist an sich sehr klein und übersichtlich, jedoch liegt der Teufel wie so oft im Detail.

Limitation und Herausforderungen

Im Laufe der letzten Monate habe ich meinen Workflow immer wieder angepasst und optimiert, um den aufkommenden Fehlern und Problemen Herr zu werden. Nachfolgend also der Überblick über meine kondensierten Erfahrungen mit GitHub Actions.

Schedule Probleme

Wer zeitkritische Aktionen durchführen möchte, sollte auf andere Services zugreifen. GitHub Actions garantieren einem nicht, dass die Jobs exakt getimed werden (oder teilweise überhaupt durchgeführt werden). In der Tabelle sind die Zeiten zwischen zwei erfolgreichen Abfragen angegeben.

Zeitspanne in Minuten <= 5 <= 10 <= 20 <= 60 > 60
Anzahl Abfragen 1720 2049 5509 3023 194

Man sieht, dass die geplanten fünf Minuten Intervalle nicht immer eingehalten wurden. Hier sollte ich in Zukunft einen größeren Spielraum einplanen.

Merge Konflikte

Zu Beginn hatte ich zwei Workflows, einen für die Parkdaten und einen für die Events. Wenn diese sich zeitlich überlappt haben, dann kam es zu Merge-Konflikten, da beide Prozesse die README mit einen Zeitstempel updaten. Im Verlauf bin ich umgestiegen auf einen Workflow samt Errorhandling.
Auch wenn ein Durchlauf länger gedauert hat und der nächste bereits gestartet wurde, kam es beim Pushen zu Merge-Konflikten in den .csv-Daten. Lange Durchläufe entstanden häufig durch das R Setup und das Laden der packages. Als Konsequenz habe ich das Schedule-Intervall von fünf auf zehn Minuten erweitert.

Formatanpassungen

Es gab ein paar Situationen, in denen sich die Pfade oder Struktur der gescrapten Daten geändert haben, so dass ich meine Funktionen anpassen musste. Hierbei war die Einstellung, eine E-Mail zu bekommen, falls ein Prozess gescheitert ist, sehr hilfreich.

Fehlende Testmöglichkeiten

Es gibt bisher keine andere Möglichkeit ein Workflow-Script zu testen, als es wirklich laufen zu lassen. So kann man nach einem Tippfehler am Abend zu einer Mailflut mit gefailten Runs am Morgen aufwachen. Das sollte einen dennoch nicht davon abhalten einen lokalen Testlauf durchzuführen.

Kein Datenupdate

Seit Ende Dezember wurden die Parkdaten nicht mehr aktualisiert bzw. bereitgestellt. Das zeigt, dass selbst wenn man einen automatischen Prozess hat, man ihn dennoch weiter überwachen sollte. Ich habe dies erst später festgestellt, wodurch meine Abfragen Ende Dezember immer ins Leere liefen.

Fazit

Trotz der Komplikationen aus dem letzten Kapitel, empfinde ich das Ganze dennoch als einen massiven Erfolg. Während der letzten Monate habe ich mich immer wieder mit dem Thema befasst und die oben beschriebenen Tricks und Kniffe erlernt, die mir auch in Zukunft helfen werden, andere Probleme zu lösen. Ich hoffe, dass auch ihr ein paar wertvolle Hinweise mitnehmen und somit aus meinen Fehlern lernen könnt.

Da ich nun ein gutes halbes Jahr an Daten gesammelt haben, kann ich mich mit der Auswertung befassen. Das wird dann aber erst Gegenstand eines weiteren Blogbeitrages. Jakob Gepp Jakob Gepp

Management Summary

Mit Kubernetes steht uns eine Technologie zur Verfügung, welche in vielerlei Hinsicht die Bereitstellung und Wartung von Anwendungen und Rechenlasten, insbesondere das Training und Hosten von Machine Learning Modellen, enorm vereinfacht. Gleichzeitig ermöglicht sie uns, die benötigten Hardware-Ressourcen dazu an den Bedarf anzupassen, und bietet damit eine skalierbare und kostentransparente Lösung.

Dieser Artikel behandelt zuerst den Weg vom Server hin zu dem Management und der Orchestrierung von Containern: isolierte Anwendungen oder Modelle, welche mit all ihren Anforderungen einmal verpackt werden und im Anschluss fast überall ausgeführt werden können. Unabhängig vom Server können diese mit Kubernetes beliebig repliziert werden und ermöglichen somit aufwandslos und schier nahtlos eine durchgehende Erreichbarkeit ihrer Dienste auch unter hoher Last. Ebenfalls kann ihre Anzahl bis auf einen Mindeststand reduziert werden, wenn die Nachfrage vorübergehend oder periodisch schwindet, um Rechenressourcen anderweitig zu nutzen oder unnötige Kosten zu vermeiden.

Aus den Möglichkeiten dieser Infrastruktur geht ein nützliches Architektur-Paradigma hervor, die Microservices. Ehemals zentralisierte Anwendungen werden so in ihre Funktionalitäten heruntergebrochen, welche ein hohes Maß an Wiederverwendbarkeit bieten. Diese können von unterschiedlichen Diensten angesprochen und verwendet werden und skalieren einzeln je nach internem Bedarf. Ein Beispiel hierfür sind große und komplexe Sprachmodelle im Natural Language Processing, welche den Kontext eines Textes unabhängig von dessen weiterer Verwendung erfassen können, und damit vielen downstream Zwecken zugrunde liegen. Andere Microservices (Modelle), wie zur Text-Klassifikation oder Zusammenfassung, können diese aufrufen und die Teilergebnisse weiterverarbeiten.

Nach einer kurzen Einführung der allgemeinen Begrifflichkeiten und Funktionsweise von Kubernetes, sowie mögliche Anwendungsfälle, richtet sich das Augenmerk auf die am weitesten verbreitete Form Kubernetes zu nutzen: mit Cloud Anbietern wie Google GCP, Amazon AWS oder Microsoft Azure. Diese erlauben sog. Kubernetes Clustern, dynamisch mehr oder weniger Ressourcen zu beanspruchen, wenn gleich die entstehenden Kosten auf pay-per-use Basis absehbar bleiben. Auch weitere gängige Dienste wie Datenspeicher, Versionierung und Networking können von den Anbietern einfach eingebunden werden. Letztlich gibt der Beitrag noch einen Ausblick über Tools und Weiterentwicklungen, welche entweder die Nutzung von Kubernetes noch effizienter machen oder das Verfahren hin zu Serverless Architekturen weiter abstrahieren und vereinfachen.

Einleitung

Über die letzten 20 Jahre sind Unmengen neuer Technologien in der Softwareentwicklung und -Bereitstellung zu Tage gekommen, welche nicht nur die Auswahl an Diensten, Programmiersprachen, Bibliotheken oder ähnliches vervielfacht und diversifiziert haben, sondern gar auch bei vielen Anwendungsfällen oder -Gebieten bis hin zu einem Paradigmenwechsel geführt haben.

Abb. 1: Google Trends Chart, die den zuvor genannten Paradigmenwechsel verdeutlicht.

Betrachtet man so auch die Art und Weise der Bereitstellung von Softwarelösungen, Modellen oder Rechen- und Arbeitslasten über die Jahre, lässt sich erkennen wie auch in diesem Bereich die Neuerungen u.a. zu mehr Flexibilität, Skalierbarkeit und Ressourceneffizienz geführt haben.

Zu Beginn wurden diese als lokale Prozesse direkt auf einem (von mehreren Anwendungen geteilten) Server betrieben, was einige Einschränkungen und Probleme aufwarf: zum einen ist man bei der Auswahl der technischen Werkzeuge an die Begebenheiten der Server und deren Betriebssystem gebunden, zum anderen sind alle Anwendungen, welche auf dem Server gehostet werden, durch dessen Speicher- und Prozessorkapazitäten begrenzt. Somit teilen sie sich nicht nur in Summe die Ressourcen, sondern auch eine eventuelle Prozess-übergreifende Fehleranfälligkeit.

Als erste Weiterentwicklung können Virtuelle Maschinen daraufhin eine weitere Abstraktionsebene bieten: durch das auf dem Server aufgesetzte Emulieren („Virtualisieren“) einer eigenständigen Maschine entsteht für die Entwicklung und das Deployment Modularität und damit größere Freiheit: zum Beispiel in der Wahl des Betriebssystems oder der verwendeten Programmiersprachen und -Bibliotheken. Aus Sicht des „echten“ Servers können die Ressourcen, welche der Anwendung zustehen sollen, besser beschränkt bzw. garantiert werden. Jedoch sind deren Anforderungen auch bedeutend höher, da die Virtuelle Maschine auch das virtuelle Betriebssystem unterhalten muss.

Letztendlich wurde dieses Prinzip durch die Verbreitung von Containern, vor allem Docker, wesentlich verschlankt und vereinfacht. Vereinfacht gesagt baut/konfiguriert man für eine Anwendung oder ein Machine Learning Modell einen eigenen virtuellen, abgegrenzten Server. So enthält jeder Container sein eigenes Dateisystem und gewisse Systembibliotheken, aber nicht das Betriebssystem. Damit wird er technisch zu einem Sandkasten, dessen andere Konfiguration, Code-Abhängigkeiten oder Fehler sich nicht auf den Host-Server auswirken, aber gleichzeitig als relativ „leichtgewichtige“ Prozesse direkt auf diesem laufen können.

_Vergleich virtuelle Maschine und Docker Container Architektur
Abb. 2: Vergleich zwischen Virtueller Maschine und Docker Container Systemarchitektur, Quelle: https://i1.wp.com/www.docker.com/blog/wp-content/uploads/Blog.-Are-containers-..VM-Image-1-1024×435.png?ssl=1

Es besteht also die Möglichkeit, alles für die gewünschte Anwendung zu kopieren, installieren, usw., und dies in einem verpackten Container überall in einem einheitlichen Format bereitzustellen. Dies ist nicht nur für das Produktionsumfeld extrem nützlich, sondern findet bei STATWORX auch gerne in der Entwicklung von komplizierteren Projekten oder der Proof-of-Concept Phase Gebrauch. Zwischenschritte oder -Ergebnisse, wie beispielsweise die Extraktion von Text aus Bildern, können als Container wie ein kleiner Webserver von denjenigen verwendet werden, die an der Weiterverarbeitung des Textes interessiert sind, etwa die Extraktion gewisser zentraler Informationen, oder die Bestimmung von dessen Stimmung oder Absicht.

Diese Unterteilung in sogenannte „Microservices“ mit Hilfe von Containern hilft ungemein bei der Wiederverwendbarkeit der einzelnen Module, bei der Planung und Entwicklung der Architektur komplexer Systeme; sie befreit gleichzeitig die einzelnen Arbeitsschritte von technischen Abhängigkeiten gegenübereinander und erleichtert die Wartungs- und Update-Prozeduren.

Nach diesem kleinen Überblick über die mächtigen und vielseitigen Möglichkeiten der Bereitstellung von Software wird sich der folgende Text damit beschäftigen, wie man diese Container (sprich Anwendungen oder Modelle) verlässlich und skalierbar für Kunden, andere Anwendungen, interne Dienste oder Berechnungen mit Kubernetes bereitstellen kann.

Kubernetes – 8 wesentliche Komponenten

Kubernetes wurde 2014 von Google als open-source Container-Management Software (auch Container-Orchestrierung genannt) vorgestellt. Intern benutzte man bereits seit Jahren eigens entwickelte Tools, um Arbeitslasten und Anwendungen zu verwalten, und sah in der Entwicklung von Kubernetes nicht nur das Zusammenkommen von best practises und lessons learned, sondern auch die Möglichkeit damit ein neues Geschäftsfeld im Cloud Computing zu erschließen.

Der Name Kubernetes (griechisch für Steuermann) wurde angeblich in Bezug auf ein symbolisches Containerschiff ausgewählt, für dessen optimalen Betrieb jener verantwortlich ist.

1.    Nodes

Spricht man von einer Kubernetes-Instanz, wird sie als (Kubernetes) Cluster bezeichnet: dieses besteht aus mehreren Servern, genannt Nodes. Eine davon, die sogenannte Master-Node, ist komplett für den administrativen Betrieb zuständig, und ist die Schnittstelle, welche vom Entwickler angesprochen wird. Alle weiteren, genannt Worker-Nodes, sind zu Beginn unbelegt und damit flexibel einsetzbar. Während Nodes tatsächlich physische Instanzen sind, meist in Rechenzentren, sind die nun folgenden Begrifflichkeiten Konzepte von Kubernetes.

2.    Pods

Soll eine Anwendung auf dem Cluster bereitgestellt werden, wird im einfachsten Fall der gewünschte Container angegeben, und daraufhin (automatisch) ein sogenannter Pod erstellt und einer Node zugewiesen. Der Pod ähnelt hier einfach einem laufenden Container. Sollen gleich mehrere Instanzen der gleichen Anwendung parallel laufen, etwa um bessere Verfügbarkeit zu bieten, kann die Anzahl der Replicas angegeben werden. Hierbei wird die spezifizierte Anzahl an Pods mit jeweils derselben Anwendung auf die Nodes verteilt. Sollte der Bedarf nach der Anwendung trotz Replicas die Kapazitäten übersteigen, können mit dem Horizontal Autoscaler automatisch noch mehr Pods erstellt werden. Besonders bei Deep Learning Modellen mit verhältnismäßig langer Inferenzzeit können hier Metriken wie CPU- oder GPU-Auslastung überwacht werden, und die Anzahl der Pods vergrößert oder verringert werden, um sowohl Kapazitäten als auch Kosten zu optimieren.

Illustration des Autoscaling und der Belegung der Nodes
Abb. 3: Illustration des Autoscaling und der Belegung der Nodes. Die Breite der Balken entspricht dem Ressourcenbedarf der Pods bzw. der Kapazität der Nodes.

Um nicht zu verwirren: Letztlich ist jeder laufende Container, also jede Arbeitslast, ein Pod. Im Falle der Bereitstellung einer Anwendung geschieht das technisch über ein Deployment, zeitlich begrenzte Rechenlasten sind hingegen Jobs. Persistente Speicher wie Datenbanken werden mit StatefulSets verwaltet. Die folgende Abbildung gibt einen Überblick über die Begriffe:

Deployment-Controller
Abb. 4: Was ist was in Kubernetes? Im Deployment wird angegeben was gewünscht ist; der Deployment-Controller kümmert sich um das Erstellen, den Erhalt und das Skalieren der Modell-Container, welche denn als einzelne Pods auf den Nodes laufen. Jobs und StatefulSets funktionieren analog mit ihrem eigenen Controller.

3.    Jobs

Mit Kubernetes Jobs können sowohl einmalige als auch wiederkehrende Jobs (sog. CronJobs) in Form eines Container-Deployment auf dem Cluster ausgeführt werden.

Im einfachsten Fall können diese wie ein Skript gesehen werden, welches für Wartungs- oder Aufbereitungsarbeiten von beispielsweise Datenbanken genutzt werden kann. Des Weiteren verwendet man diese auch zum Batch-Processing, wenn zum Beispiel Deep Learning Modelle auf größere Datenmengen angewandt werden sollen und es sich aber nicht lohnt das Modell durchgehend auf dem Cluster zu halten. Der Modell-Container wird hier eigens hochgefahren, erhält Zugriff auf das gewünschte Dataset, führt seine Inferenz darüber aus, speichert die Ergebnisse und fährt sich herunter. Auch für die Herkunft und anschließende Speicherung der Daten ist man hier flexibel, so können eigene oder Cloud Datenbanken, Bucket/Objekt-Speicher oder auch lokale Daten und Logging-Frameworks angebunden werden.

Für wiederkehrende CronJobs kann ein einfaches Zeitschema spezifiziert werden, sodass beispielsweise nachts bestimmte Kundendaten, -transaktionen oder ähnliches verarbeitet werden. Mit Natural Language Processing können so zum Beispiel nachts automatisch Pressespiegel erstellt werden, welche am folgenden Morgen ausgewertet bereitstehen: Nachrichten zu einem Unternehmen, dessen Branche, Wirtschaftsstandorte, Kunden, usw. können aggregiert oder bezogen werden, mit NLP ausgewertet, zusammengefasst, und mit Stimmungsbildern präsentiert oder nach Themen/Inhalten geordnet werden.

Auch arbeitsintensive ETL (Extract Transform Load) Prozesse können so außerhalb der Geschäftszeiten durchgeführt oder vorbereitet werden.

4.    Rolling Updates

Soll ein Deployment auf die neuste Version gebracht werden, oder muss ein Rollback auf eine ältere Version vollzogen werden, können in Kubernetes Rolling Updates angestoßen werden. Diese garantieren durchgehende Erreichbarkeit der Anwendungen und Modelle innerhalb einer Continuous Integration/Continuous Deployment Pipeline.

Ein solches Rollout kann reibungslos in einem oder wenigen Schritten angestoßen und überwacht werden. Durch eine Rollout-History besteht auch die Möglichkeit, nicht nur auf eine vorherige Containerversion zurückzuspringen, sondern auch die vorherigen Deployment-Parameter wiederhergestellt werden, sprich Mindest- und Höchstanzahl der Nodes, welche Ressourcengruppe (GPU Nodes, CPU Nodes mit wenig/viel RAM,…), Health-Checks usw.

Wird ein Rolling Update angestoßen, werden die jeweiligen bestehenden Pods so lange am Laufen und erreichbar gehalten, bis dieselbe Anzahl an neuen Pods hochgefahren und zugänglich sind. Hier gibt es sowohl Methoden, um zu garantieren, dass keine Requests verloren gehen, wie auch Parameter, die für den Wechsel eine Mindesterreichbarkeit oder einen maximalen Überschuss an Pods regeln.

Illustration eines Rolling Updates
Abb. 5: Illustration eines Rolling Updates.

Die Abbildung 5 veranschaulicht das Rolling Update.

1) Die bisher aktuelle Version einer Anwendung liegt mit 2 Replicas auf dem Kubernetes Cluster und ist gewohnt ansprechbar.

2) Ein Rolling Update auf Version V2 wird gestartet, dieselbe Anzahl an Pods wie für V1 werden erstellt.

3) Sobald die neuen Pods den Zustand „Running“ haben und ggf. Health-Checks absolviert wurden, damit also funktional sind, werden die Container der älteren Version heruntergefahren.

4) Die älteren Pods sind entfernt und die Ressourcen wieder freigegeben.

Der DevOps- und Zeitaufwand ist hierbei marginal, intern ändern sich keine Hostnamen oder ähnliches, während der Dienst aus Sicht der Konsumierenden wie bisher in gewohnter Weise ansprechbar ist (gleiche IP, URL, …) und lediglich auf die neuste Version gebracht wurde.

5.    Platform/Infrastructure as a Service

Natürlich lässt sich ein Kubernetes Cluster auch lokal auf eigener Hardware on-premises einrichten sowie auf teilweise vorgefertigten Lösungen wie DGX Workbenches.

Einige unserer Kunden haben strikte Richtlinien oder Auflagen bezüglich (Data-) Compliance oder Informationssicherheit, und möchten nicht, dass möglicherweise sensible Daten das Unternehmen verlassen. Weiterhin kann so vermieden werden, dass der Datenverkehr über nicht-europäische Knotenpunkte fließt oder generell in ausländischen Rechenzentren landet.

Erfahrungsgemäß ist dies aber nur in einem sehr geringen Anteil der Fall. Durch Verschlüsselung, Rechtemanagement und SLAs der Betreiber erachten wir die Verwendung von Cloud-Diensten und -Rechenzentren als allgemein sicher und verwenden diese auch für größere Projekte. Diesbezüglich sind auch Deployment, Wartung, CI/CD Pipelines dank Methoden der Containerization (Docker) und Abstraktion (Kubernetes) größtenteils identisch und einfach zu verwenden.

Alle großen Cloud-Betreiber wie Google (GCP), Amazon (AWS) und Microsoft (Azure), aber auch kleinere Anbieter und bald sogar spannende neue deutsche Projekte, bieten sehr ähnliche Kubernetes Dienste an. Dadurch wird es noch einfacher, ein Projekt oder Modell bereitzustellen und vor allem zu skalieren, da durch auto-scaling das Cluster je nach Ressourcenbedarf erweitert oder verkleinert werden kann. Dies entbindet uns aus technischer Sicht größtenteils davon die Nachfrage eines Dienstes abschätzen zu müssen, während die Rentabilität und Kostenstruktur gleichbleiben. Weiterhin können die Dienste auch in unterschiedlichen (geographischen) Zonen gehostet und betrieben werden, um schnellste Erreichbarkeit und Redundanz zu garantieren.

6.    Node-Vielfalt

Die Cloud-Betreiber bieten eine große Anzahl unterschiedlicher Node-Typen an, um für alle Anwendungsfälle vom einfacheren Webservice bis hin zu High Performance Computing alle Ressourcenanforderungen zu befriedigen. Besonders im Anwendungsfeld Deep Learning lassen sich so die immer größer werdenden Modelle stets auf der benötigten neuesten Hardware trainieren und bereitstellen.

Während wir beispielsweise für kleinere NLP Zwecke Nodes mit einer durchschnittlichen CPU und geringem Arbeitsspeicher verwenden, lassen sich große Transformer-Modelle im gleichen Cluster auf GPU-Nodes deployen, was deren Verwendung effektiv erst ermöglicht und gleichzeitig die Inferenz (Anwendung des Modells) um Faktor 20 beschleunigen kann. Da neuerdings die Bedeutung dedizierter Hardware für Neuronale Netze stetig zunimmt, bietet Google auch Zugriff auf die eigens entwickelten, für Tensorflow optimierten TPUs an.

Die Organisation und Gruppierung all dieser unterschiedlichen Nodes erfolgt in Kubernetes in sog. Node Pools. Diese können im Deployment ausgewählt bzw. angegeben werden, sodass den Pods der Modelle die richtigen Ressourcen zugeteilt werden.

7.    Cluster Autoscaling

Das Ausmaß der Nutzung von Modellen oder Diensten, intern oder durch Kunden, ist oftmals nicht absehbar oder schwankt zeitlich stark. Mit einem Cluster Autoscaler können automatisch neue Nodes erstellt werden, oder nicht benötigte „leerstehende“ Nodes entfernt werden. Auch hier kann ein Minimum an Nodes angegeben werden, welche immer bereitstehen sollen sowie, wenn gewünscht, auch eine maximale Anzahl, die nicht überschritten werden kann, um ggf. die Kosten zu deckeln.

8.    Anbindung anderer Dienste

Prinzipiell können Cloud Dienste verschiedener Anbieter kombiniert werden, komfortabler und einfacher ist jedoch die Nutzung eines Anbieters (Beispiel Google GCP). Somit können Dienste wie Datenbuckets, Container-Registry, Lambda Funktionen Cloud-intern ohne große Authentifizierungsprozesse eingebunden und verwendet werden. Des Weiteren ist gerade in einer Microservice-Architektur die Netzwerkkommunikation unter den einzelnen Hosts (Anwendungen, Modelle) wichtig und innerhalb eines Anbieters erleichtert. Hier kann auch Zugangskontrolle/RBAC implementiert werden, sowie mehrere Cluster oder Projekte mit einem Virtuellen Netzwerk überbrückt werden, um die Zuständigkeits- und Kompetenzbereiche besser zu trennen.

Umfeld und zukünftige Entwicklungen

Die steigende Nutzung und Verbreitung von Kubernetes haben ein ganzes Umfeld an nützlichen Tools, wie auch Weiterentwicklungen und weitere Abstraktionen mit sich gebracht, welche dessen Verwendung weiter erleichtern.

Tools und Pipelines basierend auf Kubernetes

Mit Kubeflow lässt sich beispielsweise das Training von Machine Learning Modellen als TensorFlow Training Job anstoßen und fertige Modelle mit TensorFlow Serving bereitstellen.

Der ganze Prozess kann auch in eine Pipeline verpackt werden, welche dann mit Verweis auf Trainings-, Validation- und Testdaten in Speicherbuckets das Training verschiedener Modelle durchführt, überwacht, deren Metriken loggt und die Modell-Performance vergleicht. Der Workflow beinhaltet auch die Aufbereitung der Inputdaten, sodass nach erstmaligem Aufbau der Pipeline einfach Experimente zur Exploration von Modellarchitekturen und Hyperparameter-Tuning angestellt werden können

Serverless

Durch Serverless Deployment Verfahren wie Cloud Run oder Amazon Fargate wird ein weiterer Abstraktionsschritt weg von den technischen Anforderungen unternommen. Hiermit können Container binnen Sekunden deployed werden, und skalieren wie Pods auf einem Kubernetes Cluster, ohne dass man dieses überhaupt erstellen oder warten muss. Dieselbe Infrastruktur wurde also noch einmal in ihrer Benutzung vereinfacht. Nach dem Prinzip pay-per-use wird nur die Zeit berechnet, in welcher der Code wirklich aufgerufen und ausgeführt wird.

Fazit

Kubernetes ist heute zu einer zentralen Säule im Machine Learning Deployment geworden. Der Weg von der Daten- und Modellexploration zum Prototyp und schließlich in die Produktion ist durch Bibliotheken wie PyTorch, TensorFlow und Keras zum einen enorm verschlankt und vereinfacht worden. Gleichzeitig können diese Methoden bei Bedarf aber auch enorm detailliert verwendet werden, um maßgeschneiderte Komponenten zu entwickeln oder mittels Transfer Learning bestehende Modelle einzubinden und anzupassen. Container Technologien wie Docker erlauben im Anschluss, das Ergebnis mit all dessen Anforderungen und Abhängigkeiten zu bündeln und ohne weiteren Aufwand fast überall blitzschnell auszuführen. Im letzten Schritt ist deren Bereitstellung, Wartung und Skalierung mit Kubernetes ebenfalls ungemein vereinfacht und leistungsfähig geworden.

All dies erlaubt uns eigene Produkte sowie Lösungen für Kunden strukturiert zu entwickeln:

  • Die Komponenten und die Rahmeninfrastruktur haben eine hohe Wiederverwendbarkeit
  • Mit verhältnismäßig geringem Zeit- und Kostenaufwand kann ein erster Meilenstein oder Proof-of-Concept erreicht werden
  • Die weiterführende Entwicklungsarbeit folgt auf natürliche Weise weiter diesem Prozess
  • Fertige Deployments skalieren ohne zusätzlichen Aufwand, mit Kosten proportional zum Bedarf
  • Daraus folgt eine verlässliche Plattform mit planbarer Kostenstruktur

Wenn Sie sich im Anschluss an diesen Artikel weiter über einige zentrale Komponenten informieren möchten, haben wir hier noch einige interessante Beiträge über:

Quellen

 

Jonas Braun Jonas Braun

Management Summary

Machine Learning Projekte zu deployen und zu überwachen ist ein komplexes Vorhaben. Neben dem konsequenten Dokumentieren von Modellparametern und den dazugehörigen Evaluationsmetriken, besteht die Herausforderung vor allem darin, das gewünschte Modell in eine Produktivumgebung zu überführen. Sofern mehrere Personen an der Entwicklung beteiligt sind, ergeben sich zusätzlich Synchronisationsprobleme in Bezug auf die Entwicklungsumgebungen und Versionsstände der Modelle. Aus diesem Grund werden Tools zum effizienten Management von Modellergebnissen bis hin zu umfangreichen Trainings- und Inferenzpipelines benötigt.

In diesem Artikel werden die typischen Herausforderungen entlang des Machine Learning Workflows dargestellt und mit MLflow eine mögliche Lösungsplattform beschrieben. Zusätzlich stellen wir drei verschiedene Szenarien dar, mit deren Hilfe sich Machine Learning Workflows professionalisieren lassen:

  1. Einsteigervariante:
    Modellparameter und Performance-Metriken werden über eine R/Python API geloggt und in einer GUI übersichtlich dargestellt. Zusätzlich werden die trainierten Modelle als Artefakt abgespeichert und können über APIs bereitgestellt werden.
  2. Fortgeschrittenes Modellmanagement:
    Neben dem Tracking von Parametern und Metriken werden bestimmte Modelle geloggt und versioniert. Dies ermöglicht ein kontrolliertes Monitoring und vereinfacht das Deployment von ausgewählten Modellversionen.
  3. Kollaboratives Workflowmanagement:
    Das Abkapseln von Machine Learning Projekten als Pakete oder Git Repositories und der damit einhergehenden lokalen Reproduzierbarkeit von Entwicklungsumgebungen, ermöglichen eine reibungslose Entwicklung von Machine Learning Projekten mit mehreren Beteiligten.

Je nach Reifegrad Ihres Machine Learning Projektes können die drei Szenarien als Inspiration für einen potenziellen Machine Learning Workflow dienen. Zum besseren Verständnis haben wir jedes Szenario detailliert ausgearbeitet und geben Empfehlungen hinsichtlich der zu verwendeten APIs und Deployment-Umgebungen.

Herausforderungen entlang des Machine Learning Workflows

Das Training von Machine Learning Modellen wird immer einfacher. Mittlerweile ermöglichen eine Vielzahl von Open Source Tools eine effiziente Datenaufbereitung sowie ein immer einfacheres Modelltraining und Deployment.

Der Mehrwert für Unternehmen entsteht vor allem durch das systematische Zusammenspiel von Modelltraining, in Form von Modellidentifikation, Hyperparametertuning und Fitting auf den Trainingsdaten, und Deployment, also dem Bereitstellen des Modells zur Berechnung von Vorhersagen. Insbesondere in frühen Phasen der Entwicklung von Machine Learning Initiativen wird dieses Zusammenspiel häufig nicht als kontinuierlicher Prozess etabliert. Ein Modell kann jedoch nur dann langfristig Mehrwerte generieren, wenn ein stabiler Produktionsprozess vom Modelltraining, über dessen Validierung bis hin zum Test und Deployment implementiert wird. Sofern dieser Prozess korrekt implementiert wird können bei der operativen Inbetriebnahme des Modells komplexe Abhängigkeiten und langfristig kostspielige Wartungsarbeiten entstehen [2]. Die folgenden Risiken sind hierbei besonders hervorzuheben

1. Gewährleistung von Synchronität

Häufig werden im explorativen Kontext Datenaufbereitungs- und Modellierungs-Workflows lokal entwickelt. Unterschiedliche Konfigurationen der Entwicklungsumgebungen oder gar der Einsatz von verschiedenen Technologien erschweren eine Reproduktion von Ergebnissen, insbesondere zwischen Entwickler*innen bzw. Teams. Zusätzlich ergeben sich potenzielle Gefahren hinsichtlich der Kompatibilität des Workflows, sofern mehrere Skripte in einer logischen Reihenfolge exekutiert werden müssen. Ohne einer entsprechenden Versionskontroll-Logik kann der Synchronisationsaufwand im Nachhinein nur mit großem Aufwand gewährleistet werden.

2. Aufwand der Dokumentation

Um die Performance des Modells zu bewerten, werden häufig im Anschluss an das Training Modellmetriken berechnet. Diese hängen von verschiedenen Faktoren ab, wie z.B. der Parametrisierung des Modells oder den verwendeten Einflussfaktoren. Diese Metainformationen über das Modell werden häufig nicht zentral gespeichert. Zur systematischen Weiterentwicklung und Verbesserung eines Modells ist es jedoch zwingend erforderlich, eine Übersicht über die Parametrisierung und Performance aller vergangenen Trainingsläufe zu haben.

3. Heterogenität von Modellformaten

Neben der Verwaltung von Modellparametern und Ergebnissen besteht die Herausforderung das Modell anschließend in die Produktionsumgebung zu überführen. Sofern verschiedene Modelle aus mehreren Paketen zum Training verwendet werden kann das Deployment aufgrund unterschiedlicher Pakete und Versionen schnell umständlich und fehleranfällig werden.

4. Wiederherstellung alter Ergebnisse

In einem typischen Machine Learning Projekt ergibt sich häufig die Situation, dass ein Modell über einen langen Zeitraum entwickelt wird. Beispielsweise können neue Features verwendet oder auch gänzlich neue Architekturen evaluiert werden. Nicht zwangsläufig führen diese Experimente zu besseren Ergebnissen. Sofern Experimente nicht sauber versioniert werden, besteht die Gefahr alte Ergebnisse nicht mehr nachbilden zu können.

Um diese und weitere Herausforderungen im Umgang und Management von Machine Learning Workflows zu lösen, wurden in den vergangenen Jahren verschiedene Tools entwickelt, wie beispielsweise TensorFlow TFX, cortex, Marvin oder MLFlow. Insbesondere letzteres ist aktuell eine der am häufigsten verwendeten Lösungen.

MLflow ist ein Open Source Projekt mit dem Ziel, das Beste aus existierenden ML Plattformen zu vereinen, um die Integration zu bestehenden ML Bibliotheken, Algorithmen und Deployment Tools so unkompliziert wie möglich zu gestalten [3]. Im Folgenden werden die wesentlichen MLflow Module vorgestellt und Möglichkeiten erörtert, mit der Machine Learning Workflows über MLflow abgebildet werden können.

MLflow Services

MLflow besteht aus vier Komponenten: MLflow Tracking, MLflow Models, MLflow Projectsund MLflow Registry. Je nach Anforderung an das Experimental- und Deployment-Szenario können alle Services gemeinsam genutzt, oder auch einzelne Komponenten isoliert werden.

Mit MLflowTracking lassen sich alle Hyperparameter, Metriken (Modell-Performance) und Artefakte, wie bspw. Charts, loggen. MLflow Tracking bietet die Möglichkeit, für jeden Trainings- oder Scoring-Lauf eines Modells Voreinstellungen, Parameter und Ergebnisse für ein kollektives Monitoring zu sammeln. Die geloggten Ergebnisse lassen sich in einer GUI visualisieren oder alternativ über eine REST API ansprechen.

Das Modul MLflow Models fungiert als Schnittstelle zwischen Technologien und ermöglicht ein vereinfachtes Deployment. Ein Modell wird je nach Typ als Binary, z.B, als reine Python-Funktion oder als Keras-, oder H2O-Modell gespeichert. Man spricht hierbei von den sogenannten model flavors. Weiterhin stellt MLflow Models eine Unterstützung zur Modellbereitstellung auf verschiedenen Machine Learning Cloud Services bereit, z.B. für AzureML und Amazon Sagemaker.

MLflow Projects dienen dazu, einzelne ML-Projekte in einem Paket oder Git-Repository abzukapseln. Die Basiskonfigurationen des jeweiligen Environments werden über eine YAML-Datei festgelegt. Über diese kann z.B. gesteuert werden, wie genau das conda-Environment parametrisiert ist, das im Falle einer Ausführung von MLflow erstellt wird. Durch MLflow Projects können Experimente, die lokal entwickelt wurden, auf anderen Rechnern in der gleichen Umgebung ausgeführt werden. Dies ist bspw. bei der Entwicklung in kleineren Teams von Vorteil.

Ein zentralisiertes Modellmanagement bietet MLflow Registry. Ausgewählte MLflow Models können darin registriert und versioniert werden. Ein Staging-Workflow ermöglicht ein kontrolliertes Überführen von Modellen in die Produktivumgebung. Der gesamte Prozess lässt sich wiederum über eine GUI oder eine REST API steuern.

Beispiele für Machine Learning Pipelines mit MLflow

Im Folgenden werden mit Hilfe der o.g. MLflow Module drei verschiedene ML Workflow-Szenarien dargestellt. Diese steigern sich von Szenario zu Szenario hinsichtlich der Komplexität. In allen Szenarien wird ein Datensatz mittels eines Python Skripts in eine Entwicklungsumgebung geladen, verarbeitet und ein Machine Learning Modell trainiert. Der letzte Schritt stellt in allen Szenarien ein Deployment des ML Modells in eine beispielhafte Produktivumgebung dar.

1. Szenario – Die Einsteigervariante

Szenario 1 – Simple Metrics TrackingSzenario 1 – Simple Metrics Tracking

Szenario 1 bedient sich der Module MLflow Tracking und MLflow Models. Hierbei können mittels der Python API die Modellparameter und Metriken der einzelnen Runs auf dem MLflow Tracking Server Backend Store gespeichert und das entsprechende MLflow Model File als Artefakt auf dem MLflow Tracking Server Artifact Store abgelegt werden. Jeder Run wird hierbei einem Experiment zugeordnet. Beispielsweise könnte ein Experiment ‚fraud_classification‘ lauten und ein Run wäre ein bestimmtes ML Modell mit einer Hyperparameterkonfiguration und den entsprechenden Metriken. Jeder Run wird zur eindeutigen Zuordnung mit einer einzigartigen RunID abgespeichert.

Artikel MLFlow Tool Bild 01

Im Screenshot wird die MLflow Tracking UI beispielhaft nach der Ausführung eines Modelltrainings dargestellt. Der Server wird im Beispiel lokal gehostet. Selbstverständlich besteht auch die Möglichkeit den Server Remote, beispielsweise in einem Docker Container, innerhalb einer VM zu hosten. Neben den Parametern und Modellmetriken werden zudem der Zeitpunkt des Modelltrainings sowie der User und der Name des zugrundeliegenden Skripts geloggt. Klickt man auf einen bestimmten Run werden zudem weitere Informationen dargestellt, wie beispielsweise die RunID und die Modelltrainingsdauer.

Artikel MLFlow Tool Bild 02

Sofern man neben den Metriken zusätzlich noch weitere Artefakte, wie bspw. das Modell, geloggt hat, wird das MLflow Model Artifact ebenfalls in der Run-Ansicht dargestellt. In dem Beispiel wurde ein Modell aus dem sklearn.svm Package verwendet. Das File MLmodel enthält Metadaten mit Informationen über die Art und Weise, wie das Modell geladen werden soll. Zusätzlich dazu wird ein conda.yaml erstellt, das alle Paketabhängigkeiten des Environments zum Trainingszeitpunkt enthält. Das Modell selbst befindet sich als serialisierte Version unter model.pklund enthält die auf den Trainingsdaten optimierten Modellparameter.

Artikel MLFlow Tool Bild 03

Das Deployment des trainierten Modells kann nun auf mehrere Weisen erfolgen. Möchte man beispielsweise das Modell mit der besten Accuracy Metrik deployen, kann der MLflow Tracking Server über die Python API mlflow.list_run_infos angesteuert werden, um so die RunID des gesuchten Modells zu identifizieren. Nun kann der Pfad zu dem gewünschten Artefakt zusammengesetzt werden und das Modell bspw. über das Python Paket pickle geladen werden. Dieser Workflow kann nun über ein Dockerfile getriggert werden, was ein flexibles Deployment in die Infrastruktur Ihrer Wahl ermöglicht. MLFlow bietet für das Deployment auf Microsoft Azure und AWS zusätzliche gesonderte APIs an. Sofern das Modell bspw. auf AzureML deployed werden soll, kann ein Azure ML Container Image mit der Python API mlflow.azureml.build_image erstellt werden, welches als Webservice nach Azure Container Instances oder Azure Kubernetes Service deployed werden kann. Neben dem MLflow Tracking Server besteht auch die Möglichkeit andere Ablagesysteme für das Artefakt zu verwenden, wie zum Beispiel Amazon S3, Azure Blob Storage, Google Cloud Storage, SFTP Server, NFS und HDFS.

2. Szenario – Fortgeschrittenes Modellmanagement

Szenario 2 – Advanced Model ManagementSzenario 2 – Advanced Model Management

Szenario 2 beinhaltet, neben den in Szenario 1 verwendeten Modulen, zusätzlich MLflow Model Registry als Modelmanagementkomponente. Hierbei besteht die Möglichkeit, aus bestimmten Runs die dort geloggten Modelle zu registrieren und zu verarbeiten. Diese Schritte können über die API oder GUI gesteuert werden. Eine Grundvoraussetzung, um die Model Registry zu nutzen, ist eine Bereitstellung des MLflow Tracking Server Backend Store als Database Backend Store. Um ein Modell über die GUI zu registrieren, wählt man einen bestimmten Run aus und scrollt in die Artefakt Übersicht.

Artikel MLFlow Tool Bild 04

Mit einem Klick auf Register Model öffnet sich ein neues Fenster, in dem ein Modell registriert werden kann. Sofern man eine neue Version eines bereits existierenden Modells registrieren möchte, wählt man das gesuchte Modell aus dem Dropdown Feld aus. Ansonsten kann jederzeit ein neues Modell angelegt werden. Nach dem Klick auf den Button Register erscheint in dem Reiter Models das zuvor registrierte Modell mit einer entsprechenden Versionierung.

Artikel MLFlow Tool Bild 05

Jedes Modell beinhaltet eine Übersichtsseite, bei der alle vergangenen Versionen dargestellt werden. Dies ist bspw. nützlich, um nachzuvollziehen, welche Modelle wann in Produktion waren.

Artikel MLFlow Tool Bild 06

Wählt man nun eine Modellversion aus, gelangt man auf eine Übersicht, bei der beispielsweise eine Modellbeschreibung angefügt werden kann. Ebenso gelangt man über den Link Source Run zu dem Run, aus dem das Modell registriert worden ist. Hier befindet sich auch das dazugehörige Artefakt, das später zum Deployment verwendet werden kann.

Artikel MLFlow Tool Bild 07

Zusätzlich können einzelne Modellversionen in dem Bereich Stage in festgelegte Phasen kategorisiert werden. Dieses Feature kann beispielsweise dazu genutzt werden, um festzulegen, welches Modell gerade in der Produktion verwendet wird oder dahin überführt werden soll. Für das Deployment kann, im Gegensatz zu Szenario 1, die Versionierung und der Staging-Status dazu verwendet werden, um das geeignete Modell identifizieren und zu deployen. Hierzu kann z.B. die Python API MlflowClient().search_model_versions verwendet werden, um das gewünschte Modell und die dazugehörige RunID zu filtern. Ähnlich wie in Szenario 1 kann dann das Deployment beispielsweise nach AWS Sagemaker oder AzureML über die jeweiligen Python APIs vollzogen werden.

3. Szenario – Kollaboratives Workflowmanagement

Szenario 3 – Full Workflow ManagementSzenario 3 – Full Workflow Management

Das Szenario 3 beinhaltet, neben denen in Szenario 2 verwendeten Modulen, zusätzlich noch das Modul MLflow Projects. Wie bereits erläutert, eignen sich MLflow Projects besonders gut für kollaborative Arbeiten. Jedes Git Repository oder jede lokale Umgebung kann hierbei als Projekt fungieren und mittels eines MLproject File gesteuert werden. Hierbei können Paketabhängigkeiten in einem conda.yaml festgehalten und beim Starten des Projekts auf das MLproject File zugegriffen werden. Anschließend wird die entsprechende conda Umgebung mit allen Abhängigkeiten vor dem Training und Logging des Modells erstellt. Dies verhindert den Bedarf eines manuellen Angleichens der Entwicklungsumgebungen aller beteiligten Entwickler*innen und garantiert zudem standardisierte und vergleichbare Ergebnisse aller Runs. Insbesondere letzteres ist erforderlich im Deployment Kontext, da allgemein nicht garantiert werden kann, dass unterschiedliche Package-Versionen dieselben Modellartefakte produzieren. Anstelle einer conda Umgebung kann auch eine Docker Umgebung mittels eines Dockerfiles definiert werden. Dies bietet den Vorteil, dass auch von Python unabhängige Paketabhängigkeiten festgelegt werden können. Ebenso ermöglichen MLflow Projects durch die Anwendung unterschiedlicher commit hashes oder branch names das Verwenden verschiedener Projektstände, sofern ein Git Repository verwendet wird.

Ein interessanter Use Case hierbei ist die modularisierte Entwicklung von Machine Learning Trainingspipelines [4]. Hierbei kann bspw. die Datenaufbereitung vom Modelltraining entkoppelt und parallel weiterentwickelt werden, während parallel ein anderes Team einen unterschiedlichen branch name verwendet, um das Modell zu trainieren. Hierbei muss lediglich beim Starten des Projektes im MLflow Projects File ein unterschiedlicher branch name als Parameter verwendet werden. Die finale Datenaufbereitung kann im Anschluss auf denselben branch name gepusht werden, der zum Modelltraining verwendet wird und wäre somit bereits vollständig in der Trainingspipeline implementiert. Das Deployment kann ebenfalls als Teilmodul innerhalb der Projektpipeline mittels eines Python Skripts über das ML Project File gesteuert werden und analog zu Szenario 1 oder 2 auf eine Plattform Ihrer Wahl erfolgen.

Fazit und Ausblick

MLflow bietet eine flexible Möglichkeit den Machine Learning Workflow robust gegen die typischen Herausforderungen im Alltag eines Data Scientists zu gestalten, wie beispielsweise Synchronisationsprobleme aufgrund unterschiedlicher Entwicklungsumgebungen oder fehlendes Modellmanagement. Je nach Reifegrad des bestehenden Machine Learning Workflows können verschiedene Services aus dem MLflow Portfolio verwendet werden, um eine höhere Professionalisierungsstufe zu erreichen.

Im Artikel wurden drei, in der Komplexität aufsteigende, Machine Learning Workflows exemplarisch dargestellt. Vom einfachen Logging der Ergebnisse in einer interaktiven UI, bis hin zu komplexeren, modularen Modellierungspipelines können MLflow Services unterstützen. Logischerweise ergeben sich auch außerhalb des MLflow Ökosystems Synergien mit anderen Tools, wie zum Beispiel Docker/Kubernetes zur Modellskalierung oder auch Jenkins zur Steuerung der CI/CD Pipeline. Sofern noch weiteres Interesse an MLOps Herausforderungen und Best Practices besteht verweise ich auf das von uns kostenfrei zur Verfügung gestellte Webinar zu MLOps von unserem CEO Sebastian Heinz.

Quellen

John Vicente John Vicente

Management Summary

In diesem Beitrag sollen fünf Technologien vorgestellt werden, die jeder Data Engineer für seine tägliche Arbeit kennen und beherrschen sollte. Aufgeführt werden Spark als Data Processing Tool im Big Data Umfeld, Kafka als Streaming Platform, Airflow und Serverless-Architektur zur Koordinierung bzw. Orchestrierung. Zuvor werden Stellenwert und Rolle von SQL (Structured Query Language) und relationalen Datenbanken besprochen.

Trotz des stetigen Wandels hat sich SQL eine Sonderposition herausgearbeitet und findet sich auch in neuen Entwicklungen als Schnittstelle wieder. Mitnichten ist es aber so, dass wie vor Jahrzehnten ein stabiles Wissen von dieser Query-Sprache ausreicht, um das Gros der Datenarbeiten bewältigen zu können. Dafür ist zum einen die Datenlandschaft, zum anderen die verarbeiteten Daten mittlerweile zu heterogen geworden. Darüber hinaus reicht es in vielen Fällen nicht mehr aus, Daten nach einem zeitlich fixierten Schema zu aktualisieren oder die zu prozessierenden Datenmengen sind schlicht nicht mehr von klassischen ETL (Extract-Transform-Load) Prozessen in Datenbanken zu schultern.

Data Streaming Plattformen wie Apache Kafka sind eine Antwort auf die Real-time-Problematik, dabei hat es die Möglichkeit mit den Anforderungen zu skalieren und für Ausfallsicherheit zu sorgen. Bezüglich der Möglichkeit des Prozessierens enorm großer Datenmengen ist Spark das Tool der Wahl. Ebenso wie Kafka kann es mit den Anforderungen skalieren. Ferner bietet es eine großzügige Auswahl an Implementierungssprachen an.

Auf die Heterogenität der eingesetzten Data Stores und Prozessierungsframeworks kann man mit zwei Ansätzen antworten: eine zentrale (globale) Orchestrierung, die die verschiedenen Komponenten bedienen kann, wie es Airflow auf beeindruckende Weise tut; eine dezentrale (lokale) Lösung, die nur auf spezifische Signale einzelner Komponenten reagiert, wie es der Serverless-Ansatz vorsieht. Alle gängigen Cloud-Anbieter können einen solchen Service vorweisen. Zusammenfassend findet man hier also eine Mischung an Tools als Antwort auf die aktuellen Fragen im Data Engineering.

Intro

Die Datenlandschaft war in den letzten Jahrzehnten geprägt von einer zunehmenden Dynamik. Galten bis zum Jahrtausendwechsel Data Warehouses und relationale Datenbank-Managementsysteme (RDBMS) noch als der Goldstandard für die Datenhaltung und -aufbereitung, so durchbrach vor allem die Verbreitung und die Dynamik des Internets diese Alleinstellung. Zum einen vervielfachte sich damit die Datenmenge, zum anderen galt nun das Interesse vermehrt auch semi-strukturierten und unstrukturierteren Daten. Man war also im Big Data Zeitalter angekommen. Der nächste Schub wurde durch die von mobilen Endgeräten und Sensoren generierten Datenflüsse (Stichwort: Internet of Things) verursacht. Es ging nun nicht mehr nur darum, den erneut enorm gestiegenen Datenaufwand zu bewältigen, sondern Ereignisse aus vielen Datenpunkten in Echtzeit zu erkennen und darauf reagieren zu können. Schlussendlich hat das Cloud Computing zusätzliches Potential für Datenanalysen und -verarbeitung geborgen, indem Infrastruktur nun in vielerlei Hinsicht günstig zur Verfügung steht und diverse Technologien mit geringer Einstiegsschwelle genutzt werden können. Aus der anfänglich monolithischen Welt der Datenbanken ist eine heterogene und dynamische Datenlandschaft geworden, die Data Engineers benötigt, um den Anforderungen gerecht zu werden.

Anhand von fünf Technologien soll mit diesem Artikel eine Orientierung zur Lösung aktueller Problemstellungen im Bereich „Data Engineering“ gegeben werden. In den Gebieten „Batch Processing“ und „Streaming“ werden die etablierten Technologien Apache Spark und Apache Kafka vorgestellt. Mit Apache Airflow und der Serverless-Technologie Lambda werden zwei unterschiedliche Konzepte präsentiert, um Abläufe zu steuern. Zuletzt und entgegen der Stoßrichtung der Einleitung findet sich auch ein Kapitel über SQL und relationale Datenbanken.

* Unter Data Engineering ist hier ein sehr enger Begriff verstanden, nämlich die Tätigkeit, eine kontinuierliche Datenversorgung und -aufbereitung herzustellen.

SQL und relationale Datenbanksysteme

SQL ist eine Abkürzung für structured query language und ist fester Bestandteil von relationalen Datenbanksystemen. Entgegen des allgemeinen Tones und trotz diverser (Weiter-)Entwicklungen von NoSQL[1]-Lösungen, spielen SQL-Systeme weiterhin eine tragende Rolle in der modernen Datenarchitektur. Das zeigt auch eine Umfrage aus dem Jahr 2019, die sich folgendermaßen zusammenfassen lässt: Der Einsatz von NoSQL-Systemen ist in den allermeisten Fällen keine Abkehr, sondern ein Zusatz zu bestehenden Systemen, die auf SQL setzen.

Auch in modernen Frameworks spiegelt sich die Popularität von SQL wider: So zeugen die Entwicklung in Spark und Kafka (siehe beide in den nachstehenden Absätzen) vom Stellenwert, der SQL eingeräumt wird. Schließlich gibt es Tools, die SQL-Queries für NoSQL-Systeme kompatibel machen, hier beispielhaft zu nennen Apache Drill.

Ein Grund für die Popularität von SQL, neben seinen Vorzügen als einfach und semantisch an die englische Sprache angelehnt, liegt in seiner weiten Verbreitung. Auch außerhalb des Datenbankmilieus finden sich Analysten und Beschäftigte im Reporting, die SQL beherrschen.

SELECT
      DEPARTMENT,
      MANAGER,
      COUNT(USER_ID)
FROM TBL_EMPLOYEE
WHERE IS_EXTERNAL = 1
GROUP BY DEPARTMENT, MANAGER

Klar lesbar führt diese Query eine Selektion, Filterung und Gruppierung von Daten aus.

Relationale Datenbanken sind eng mit SQL verbunden. Diese ausgereifte Technologie sticht vor allem durch Konsistenz und Dauerhaftigkeit hervor. Zudem muss das Tabellenschema vor dem ersten Schreiben definiert sein, was zwar zu Erwartbarkeit und Sicherheit führt, aber auch als aufwendig in der Verwaltung und starr angesehen werden kann. Der Umgang mit Daten, deren Struktur nicht explizit angegeben werden kann oder wechselhaft ist, kann sich also in relationalen Datenbanken als beschwerlich gestalten. Gleiches gilt für komplex-strukturierte Daten, da auch diese in Tabellen und Relationen eingepasst werden müssen, damit sie von der Datenbank effektiv behandelt werden können.

Vorzüge von relationalen Datenbanken:

  • Eine seit den 1970er Jahren entwickelte und damit ausgereifte Technologie, die von vielen Experten beherrscht wird
  • Eine starke Typisierung und der Definitionszwang a priori von Tabellenschemata garantieren Erwartbarkeit und Sicherheit
  • Mit SQL eine weitverbreitete, verständliche Query-Sprache
  • Die Implementierung des ACID-Schemas, das Konsistenz, Sicherheit und Dauerhaftigkeit von Datenständen garantiert
  • Redundanzarmer Speicherverbrauch durch Normalisierung und Referenzmöglichkeiten

Spark

Fast schon ein Klassiker und nun bereits zur Version 3 gereift, ist Spark der Standard, um sehr große Datenvolumen effizient zu prozessieren. Die Performance steht vor allem auf zwei Säulen: Zum einen werden Verarbeitungsschritte auf eine Schar an Worker-Nodes verteilt, was große Datenmengen parallelisiert bearbeitbar macht. Zum anderen ist es ein intelligentes System, um Zwischenergebnisse im Arbeitsspeicher zu halten, um Berechnungsstrecken abzukürzen und Zugriffszeiten zu verkürzen.

Die Bearbeitung durch das Spark-Cluster wird erst dann ausgelöst, wenn die Strecke End-to-End (von den Ausgangsdaten über Transformationen bis zum Endprodukt) definiert ist. Wie angedeutet, versucht Spark die Aufgabenlast möglichst parallelisiert auf den Worker-Nodes auszuführen. Gleich einem Query-Optimizer in einer relationalen Datenbank, sucht Spark einen möglichst performanten Weg, die Definition in einzelne Schritte zu zerlegen und diese wiederum als Tasks an die Worker zu verteilen.

Anhand eines einfachen Beispiels soll dieses Schema veranschaulicht werden. Dabei wird angenommen, dass Daten aus einer großen Menge Dateien prozessiert werden, die in einem Textformat vorliegen. Es werden diverse Transformation durchgeführt, um die wichtigen Informationen zu extrahieren. Aus einer zweiten Datenquelle, die ebenfalls in einem rohen Zustand im Textformat vorliegt, sollen weitere Informationen entnommen werden und an das Zwischenprodukt angereichert werden.

Die unten dargestellte Abbildung, die Spark intern generiert, kann folgendes beobachtet werden:

Die Ausgangsdaten, die als Textdateien vorliegen, werden zu Anbeginn der Stages 8 und 10 eingelesen und anschließend transformiert, was sich in den Aktionen „map“ und „distinct“ widerspiegelt. Zuletzt werden die Ergebnisse mit einer „join“ Aktion (wie man sie auch von SQL kennt) in Stage 10 vereint.

Ein weiterer Vorteil von Spark ist die Kompatibilität mit verschiedenen Datenformaten. So erlaubt Spark Lese- und Schreibvorgänge in unter anderem JSON, XML, CSV, Avro, kann aber auch ebenso gut mit Datenbankverbindungen umgehen – seien es nun klassische RDBMS oder Big-Data Datenbanken wie Hive.  

Zwar basiert die Engine auf Java, längst aber hat Python (in Form der PySpark Bibliothek) als primäre Implementierungssprache die mit Abstand weiteste Verbreitung[2]. Entwicklungen wie die von Koalas, welches die beliebte Datenanalyse-Bibliothek Pandas in Spark integriert, unterstreicht dies zusätzlich. Weitere unterstütze Sprachen sind R, Scala und SQL.

Sparks primäre Stärke ist die Abarbeitungen definierter Prozessierungs-Jobs, in allen Facetten und Farben. Für Use-Cases hingegen, die geringe Latenzen auf Datenanfragen erwarten, sollte man besser auf andere Tools setzen, wie etwa Presto, Impala oder Dremio. 

Vorzüge von Spark:

  • Verarbeitet große Datenmengen (TB-Bereich) sehr effizient
  • Skaliert durch Hinzufügen weiterer Worker Nodes zum Cluster
  • Unterstützt viele Datenformate und Anbindungen zu Datenbanken
  • Prozessierungs-Abfragen lassen sich in SQL, R, Scala, Java und Python schreiben

Spark gibt es in allen möglichen Ausgaben und Größen:

  • Jupyter Notebooks mit PySpark und lokalem Spark Context (zum Beispiel als Docker-Container)
  • On-premise Lösungen wie Cloudera oder Hortonworks
  • Notebook zentrierte Cloud-Lösung von Databricks
  • Cluster in der Cloud: EMR in AWS, HDInsights in Azure, Dataproc in GCP

Kafka

Kafka ist eine verteilte, fehler-tolerante und performante Streaming-Plattform. Wenn es darum geht, hohes Datenaufkommen in Echtzeit zu verarbeiten, kommt häufig Kafka ins Spiel. Die Plattform wird auch für Use Cases genutzt, in denen eine große Anzahl heterogener Systeme mit gegenseitigen Abhängigkeiten auftreten. Anders als bei gewöhnlichen Data Stores, ist ein Stream per Definition nicht erschöpft und Operationen darauf sind ebenso kontinuierlich.

Messages werden als Key-Value-Paare behandelt und in Topics gegliedert. Topics wiederum sind in Partitionen unterteilt, die redundant und damit gegen Ausfälle gesichert auf mehreren Nodes gehalten werden. Und schließlich gibt es noch die Teilnehmer an beiden Enden: Konsumenten (Consumers) und Produzenten (Producers), die aus den Topics lesen bzw. in jene schreiben.

In der Abbildung sieht man, welche Stelle Kafka im System einnimmt, nämlich als Datenhub: Von unten liefern die Quellsysteme hauptsächlich ins Kafka-System (Producer) und von oben werden die (mithin aufbereiteten) Streams konsumiert (Consumer).

Durch die Streaming API und die Entwicklung von ksql, das es ermöglicht, direkt SQL Queries auf Streams auszuführen, steht eine high-level Interaktionsmöglichkeit zur Verfügung, um mit Streams zu interagieren und aus bestehenden neue zu bauen. Dabei sind alle Transformationen vertreten, die man auch aus anderen Processing Frameworks (wie Spark) oder Datenbank-SQL-Dialekten kennt: Filtern, Gruppieren, Joinen, Mappen, etc. Darüber hinaus gibt es aber auch die Streams immanente Zeitachse, die mit Windowing Funktionen behandelt werden kann, also die Fragestellungen danach, wie oft Events pro Zeitabschnitt stattgefunden haben, z.B. Klicks auf Elementen von Web Pages.

Die Vorzüge von Kafka nochmal auf einem Blick:

  • Ermöglicht Analysen und Operationen in real time
  • Integriert heterogene Systeme zu einem zentralen Datenhub (Producer und Consumer API)
  • Skalierbar und ausfallsicher durch redundantes Halten von Partitionen
  • Stellt mit ksqldb eine Technologie bereit, um mit SQL Queries Streams zu transformieren

Ein gutes Starter-Kit findet man hier. In allen gängigen Cloud Plattformen gibt es auch Services (Amazon MSK, Azure HD Insight, …).

Airflow

Ein weiteres Themenfeld ist die Strukturierung des Ablaufs der einzelnen Datenaufbereitungs- und Datenverarbeitungsschritte. Besonders bei einer hohen Anzahl an beteiligten und einer hohen Heterogenität in den Komponenten ist es höchst ratsam ein Orchestrierungstool einzusetzen, um einen stabilen Ablauf zu garantieren. Für moderne Orchestrierungstools gibt es dabei einen breiten Anforderungskatalog, der in folgende Kategorien eingeteilt werden kann:

  • Kontrollierbarkeit: Die beteiligten Komponenten und Schritte sind wohldefiniert. Die Ausführung soll nachvollziehbar sein, sowohl im Laufen als auch im Nachgang.
  • Integrierbarkeit: Diverse und meist sehr heterogene Komponenten sollen ansprechbar und ihre Ausführung soll beobachtbar sein.
  • Reaktivität: Ergebnisse einzelner Schritte sollen ausgewertet werden können und in den Ablaufprozess einfließen. So soll zum Beispiel definiert sein, was im Fall des Scheiterns eines ganzen Schrittes passieren soll: eine Wiederholung, eine Abweichung vom normalen Prozessweg, ein Ignorieren?
  • Skalierbarkeit: Das Wachsen der Ablaufstruktur kann durch die Erweiterung der Ausführungsinfrastruktur möglichst einfach aufgefangen werden.

Apache Airflow erweist sich für diese Anforderungen als optimaler Kandidat. Komplexe Abläufe werden als directed acyclic graph (DAG) beschrieben, die Schritte sind als Knoten durch Bedingungen untereinander verknüpft. Diese DAGs bilden eine ausführbare Einheit, ihre Ausführung lässt sich automatisieren. Die Beschreibung wird rein in Python geschrieben. Das hat den Vorteil, dass die Entwicklung schnell geht und Operatoren gegebenenfalls leicht selbst geschrieben werden können. Zumeist ist das jedoch gar nicht nötig, da in Apache Airflow bereits mit eine Fülle an diversen und gängigen Operatoren implementiert ist. Darunter fallen: Triggern von Spark Jobs, Bereitstellung von Cloud Infrastruktur und das Ausführen von Datenbank-Queries.

Im Beispiel unten sieht man einen beispielhaften DAG-Ablauf, bei dem mehrere Stränge parallel ausgeführt werden und zuletzt zusammengeführt werden.

In der Abbildung sieht man, wie DAGs mehrere Richtungen nehmen können. Der letzte Schritt wertet die Ergebnisse von beiden Vorgängern aus und führt die Stränge wieder zusammen.

Beim Thema Skalierbarkeit liefert Airflow zwei Lösungswege mit: Der Celery Executor verteilt Tasks über einen Message Broker auf die dort registrierten Arbeiterprozesse. Kubernetes lässt sich als Ausführungsplattform über den Kubernetes Executor anschließen.

Zuletzt soll noch die Verwaltung von Zugängen zu Fremdsystemen (seien es nun Datenbanken oder Cloud Computing Instanzen) angesprochen werden. Dafür gibt es den sogenannten Secrets Manager, um übersichtlich, zentral und verschlüsselt die Verbindungsschlüssel zu den Systemen hinterlegen kann.

Die Vorzüge von Airflow auf einem Blick:

  • Komplexe Abläufe werden in DAGs beschrieben und angesteuert
  • Airflow bietet ein Füllhorn an Operatoren zur Kommunikation mit Fremdsystemen
  • Die Ausführung kann skalierbar konfiguriert werden mithilfe von Celery und Kubernetes Executors
  • Secrets von Datenbanken und anderen Systemen werden von Airflow zentral verwaltet und können im Code referenziert werden

Es gibt bereits fertige Docker Images, um Airflow auszuprobieren. Die Installation und Einrichtung ist aber auch recht einfach. Darüber hinaus bietet die Google Cloud auch den verwalteten Airflow Service Cloud Composer an, mit dem man sofort loslegen kann.

Serverless

Zuletzt soll hier noch ein komplett anderer Ansatz der Ablaufsteuerung angeführt werden, ein Ansatz der sich der Event-Driven Architektur zuordnen lässt. Statt einer zentralen Steuereinheit, die die Abläufe orchestriert, wird hierbei dezentral definiert, mit welchen Abläufen auf spezifische Events reagiert werden soll. Das kann zum Beispiel eine Veränderung im Data Lake sein, ein Infrastrukturevent wie die Bereitstellung einer Recheninstanz oder auch schlicht ein Zeit-Event. Auf Seiten der Funktionsaufrufe sind typische Beispiele das Anstoßen einer Data Ingestion, das Ausführen eines Tabellenupdates oder das Senden einer Benachrichtigung. Durch lokale Konfiguration der Reaktivität, wird hiermit also ein Ablaufkomplex zusammengebaut.

In der Abbildung sieht man das Schema der Serverless-Architektur, entnommen von OpenWhisk, einer Open-Source Serverless Plattform. Ein Feed stellt den Eventfluss zur Verfügung. Eine Regel verbindet das Auftreten eines Events (Trigger) mit der Aktion (Action).

Eine weitere Besonderheit ist das Serverless-Paradigma. Demnach entfällt der Aufwand für die Bereitstellung von Rechenressourcen zu sorgen. Stattdessen bestimmt die unterliegende Umgebung (also z.B. eine Cloud-Infrastruktur), wie Ressourcen allokiert werden und wo Code ausgeführt wird. Ein Vertreter einer solchen Applikation zur Verwaltung und Deployment von Ressourcen ist Kubernetes. Dem Entwickler wird also die Annehmlichkeit geboten, seinen Fokus ganz auf die Entwicklung zu legen. Zudem ist die Ausführung sprachagnostisch und alle gängigen modernen Programmiersprachen werden unterstützt. Damit können Ausführungen in verschiedenen Sprachen koexistieren und auch miteinander gekoppelt werden.  

Einige Beispiele für den Einsatz:

  • Eine neue Datei ist im ADL (Azure Data Lake) abgelegt worden und eine Data Ingestion soll angestoßen werden.
  • Ein Tabellen-Inhalt der No-SQL-Datenbank DynamoDB wurde verändert und dies soll ins Archiv geschrieben werden (das entspricht Triggers in Datenbanken).
  • Zeitereignisse: Jede Stunde sollen Daten zum Modelltraining bereitgestellt werden (vergleichbar mit cron jobs in der serverzentrierten Welt).

Die Vorteile nochmal auf einem Blick:

  • Keine Aufwände für Infrastrukturbereitstellung, stattdessen Konzentration auf Implementierung der Funktionalität.
  • Event-Driven statt zentraler Orchestrierung.
  • Funktionen können in verschiedenen Programmiersprachen geschrieben werden .
  • Funktionen verschiedener Sprache können koexistieren.

Die Anbieter findet man meistens in der Cloud: Lambda in AWS, Cloud Functions in GCP, Azure Functions in der Azure Cloud – ohne das Open-Source-Projekt OpenWhisk verschweigen zu wollen.

Zusammenfassung

Dem Wandel und den Brüchen zum Trotz bildet SQL weiterhin die zentrale Schnittstelle zu Daten, sowohl was Queries als auch die Prozessierung betrifft. Womöglich überdauert SQL sogar noch die Welt der relationalen Datenbanken, aus der sie ursprünglich kam.

Andererseits lässt sich ebenso feststellen, dass es längst nicht mehr genügt, SQL zu beherrschen, um modernen Problemstellungen gerecht zu werden, was in der vormaligen Welt der Data Warehouses galt. Verlässt man den bereitgestellten Standard an Funktionalitäten in Spark oder Kafka, den SQL mitliefert, um spezifische Funktionen zu implementieren (man spricht hier von User Defined Functions), so benötigt es zusätzliches Wissen in Programmiersprachen wie Python oder Java. Ebenso erfordert der Umgang mit Airflow oder der Serverless-Technologie, wie in den vorherigen Abschnitten gezeigt wurde, die Beherrschung der Programmiersprache Python – oder im Fall von Serverless der einer anderen modernen Sprache.

Wählt man fünf Technologien aus, fallen im Gegenzug andere herunter, die dennoch hier Erwähnung finden sollen:

  • MongoDB als Vertreter von Dokumentdatenbanken (NoSQL), die mit Flexibilität und Skalierbarkeit glänzen können.
  • Apache Cassandra als Vertreter von Key-Value-Datenbanken (NoSQL), das vor allem für hohe Skalierbarkeit steht.
  • Apache Flink als Data Streaming Framework in Ergänzung zu Apache Kafka.
  • Apache Beam als Abstraktionsschicht zur Datenpipeline-Definition, die dann in Apache Flink oder in Apache Spark ausgeführt werden können.

[1] NoSQL bedeutet in erster Linie die Abkehr vom SQL Paradigma und solche Datenbanken bilden keine homogene Klasse. Stattdessen kann man grob drei Unterklassen definieren: Graphendatenbanken, Dokumentendatenbanken und Key-Value-Datenbanken.

[2] laut Studie mit einem Anteil von 70% in Notebooks

Andre Münch Andre Münch

Einleitung

Künstliche Intelligenz (KI) ist für deutsche Firmen keineswegs eine Zukunftsvorstellung mehr. Laut einer Umfrage von Deloitte unter ca. 2.700 KI-Experten aus neun Ländern, geben über 90 Prozent der Befragten an, dass ihr Unternehmen Technologien aus einem der Bereiche Machine Learning (ML), Deep Learning, Natural Language Processing (NLP) und Computer Vision nutzt oder plant zu nutzen. Dieser hohe Anteil lässt sich nicht nur dadurch erklären, dass die Firmen das Potenzial von KI erkannt haben. Vielmehr stehen auch deutlich mehr standardisierte Lösungen für den Einsatz dieser Technologien zur Verfügung. Diese Entwicklung hat dazu geführt, dass die Einstiegshürde in den vergangenen Jahren immer weiter gesunken ist.

Das Problem mit Standardlösungen

So bieten zum Beispiel die drei großen Cloud Anbieter – Amazon Web Services (AWS), Google Cloud Plattform (GCP) und Microsoft Azure – für bestimmte Problemstellungen standardisierte Lösungen an (z.B. Objekterkennung auf Bildern, Übersetzung von Texten, und automatisiertes Machine Learning). Bislang lassen sich aber noch nicht alle Probleme mit Hilfe solcher standardisierten Anwendungen lösen. Dafür kann es unterschiedliche Gründe geben: Der häufigste Grund ist, dass die verfügbaren Standardlösungen nicht auf die gewünschte Fragestellung passen. So ist zum Beispiel im Bereich NLP die Klassifikation von ganzen Texten häufig als eine Standardlösung verfügbar. Soll hingegen eine Klassifikation nicht auf Text-, sondern Wort-Ebene stattfinden, werden dazu andere Modelle benötigt, die nicht immer als Teil von Standardlösungen verfügbar sind. Und selbst wenn diese zur Verfügung stehen, sind die möglichen Kategorien in der Regel vordefiniert und lassen sich nicht weiter anpassen. Ein Service also, der für die Klassifikation von Wörtern in die Kategorien Ort, Person und Zeit gebaut wurde, kann nicht ohne weiteres für die Klassifikation von Wörtern in den Kategorien Kunde, Produkt und Preis verwendet werden. Viele Unternehmen sind deshalb auch weiterhin darauf angewiesen, eigene ML Modelle zu entwickeln. Da die Entwicklung von Modellen oftmals auf lokalen Rechnern erfolgt, muss sichergestellt werden, dass diese Modelle nicht nur dem Entwickler zur Verfügung stehen. Nach der Entwicklung eines Modells besteht eine wichtige Herausforderung darin, das Modell unterschiedlichen Nutzern zur Verfügung zu stellen, da nur dann auch ein Mehrwert für das Unternehmen entsteht.

Mit MLOps Modelle nutzbar machen

Bei ML & KI Projekten im Unternehmen treten sowohl bei der Entwicklung als auch bei der Bereitstellung eigene Herausforderungen in Erscheinung. Während die Entwicklung oftmals an der mangelnden Verfügbarkeit geeigneter Daten scheitert, kann die Bereitstellung daran scheitern, dass ein Modell nicht mit der Produktionsumgebung kompatibel ist. So werden beispielsweise Machine Learning Modelle zum überwiegenden Teil mit Open Source Sprachen oder neuen ML-Frameworks (z.B. Dataiku oder H2O) entwickelt, während eine operative Produktionsumgebung oftmals mit proprietärer und langjährig erprobter Software arbeitet. Die enge Verzahnung dieser beiden Welten stellt oftmals beide Komponenten vor gewichtige Herausforderungen. Deshalb ist es wichtig, die Entwicklung von ML Modellen mit der Arbeit der IT Operations zu verknüpfen. Diesen Prozess bezeichnet man als MLOps, da hier Data Scientists mit der IT zusammenarbeiten, um Modelle produktiv nutzbar zu machen.

MLOps ist eine ML-Entwicklungskultur und -praxis, deren Ziel es ist, die Entwicklung von ML-Systemen (Dev) und den Betrieb von ML-Systemen (Ops) zu verbinden. In der Praxis bedeutet MLOps, auf Automatisierung und Überwachung zu setzen. Dieses Prinzip erstreckt sich auf alle Schritte der ML-Systemkonfiguration wie Integration, Testen, Freigabe, Bereitstellung und Infrastrukturverwaltung. Der Code eines Modells ist dabei einer von vielen weiteren Bestandteilen, wie in Abbildung 1 veranschaulicht wird. Die Abbildung zeigt neben dem ML Code auch andere Schritte des MLOps Prozesses auf und verdeutlicht, dass der ML Code selbst einen relativ kleinen Anteil des gesamten Prozesses ausmacht.


Abbildung 1: Wichtige Bestandteile des MLOps Prozesses

Weitere Aspekte von MLOps sind z.B. die kontinuierliche Bereitstellung und Qualitätsprüfung der Daten, oder das Testen des Modells und ggf. die Fehlerbehebung (sog. Debugging). Docker Container haben sich bei der Bereitstellung von eigens entwickelten ML Modellen als Kerntechnologie herauskristallisiert und werden deshalb in diesem Beitrag vorgestellt.

Warum Docker Container?

Die Herausforderung bei der Bereitstellung von ML Modellen ist, dass ein Modell in einer bestimmten Version einer Programmiersprache geschrieben ist. Diese Sprache ist im Regelfall nicht in der Produktionsumgebung verfügbar und muss deshalb erst installiert werden. Zudem verfügt das Modell über eigene Bibliotheken, Laufzeiten und andere technische Abhängigkeiten, die ebenfalls erst in der Produktionsumgebung installiert werden müssten. Docker löst dieses Problem über sogenannte Container, in denen sich Anwendungen samt aller Bestandteile isoliert verpacken und als eigene Services bereitstellen lassen. Diese Container enthalten alle Komponenten, die die Anwendung bzw. das ML Modell zum Ausführen benötigt inklusive Code, Bibliotheken, Laufzeiten und Systemtools. Über Container lassen sich deshalb auch eigene Modelle und Algorithmen in jeder Umgebung bereitstellen und zwar ohne Sorge, dass bspw. fehlende oder inkompatible Bibliotheken zu Fehlern führen.


Abbildung 2: Vergleich von Docker Containern und virtuellen Maschinen

Vor dem Siegeszug von Docker waren lange Zeit virtuelle Maschinen das Tool der Wahl, um Anwendungen und ML Modelle isoliert bereitzustellen. Allerdings hat sich herausgestellt, dass Docker über diverse Vorteile gegenüber virtuellen Maschinen verfügt. Dies sind insbesondere verbesserte Ressourcennutzung, Skalierbarkeit und die schnellere Bereitstellung neuer Software. Im Folgenden sollen die drei Punkte detaillierter beleuchtet werden.

Verbesserte Ressourcennutzung

Abbildung 2 vergleicht schematisch, wie Applikationen in Docker Containern bzw. auf virtuellen Maschinen laufen können. Virtuelle Maschinen verfügen über ihr eigenes Gastbetriebssystem, auf welchem unterschiedliche Applikationen laufen. Die Virtualisierung des Gastbetriebssystems auf der Hardwareebene nimmt viel Rechenleistung und Speicherplatz in Anspruch. Deshalb können auf einer virtuellen Maschine, bei gleichbleibender Effizienz, weniger Applikationen gleichzeitig laufen.

Docker Container teilen sich hingegen das Betriebssystem des Hosts und benötigen kein eigenes Betriebssystem. Applikationen in Docker Containern fahren deshalb schneller hoch und nehmen, durch das geteilte Betriebssystems des Hosts, weniger Rechenleistung und Speicherplatz in Anspruch. Diese geringere Ressourcenauslastung ermöglicht es auf einem Server mehrere Applikationen parallel laufen zu lassen, was die Nutzungsrate eines Servers verbessert.

Skalierbarkeit

Einen weiteren Vorteil bieten Container im Bereich der Skalierung: Soll innerhalb des Unternehmens ein ML Modell häufiger genutzt werden, muss die Anwendung die zusätzlichen Anfragen bedienen können. Glücklicherweise lassen sich ML Modelle mit Docker leicht skalieren indem zusätzliche Container mit der gleichen Anwendung hochgefahren werden. Insbesondere Kubernetes, eine Open Source Technologie zur Container-Orchestrierung und skalierbaren Bereitstellung von Web-Services, eignet sich aufgrund seiner Kompatibilität mit Docker für flexible Skalierung. Mit Kubernetes können Web-Services basierend auf der aktuellen Auslastung flexibel und automatisiert hoch- oder runterskaliert werden.

Bereitstellung neuer Software

Von Vorteil ist außerdem, dass Container sich nahtlos von lokalen Entwicklungsmaschinen auf Produktionsmaschinen schieben lassen. Deshalb sind sie einfach auszutauschen, wenn z.B. eine neue Version des Modells bereitgestellt werden soll. Die Isolierung des Codes und aller Abhängigkeiten in einem Container führt zudem zu einer stabileren Umgebung, in der das Modell betrieben werden kann. Dadurch treten Fehler aufgrund von bspw. falschen Versionen einzelner Bibliotheken weniger häufig auf und lassen sich gezielter beheben.

Das Modell wird innerhalb eines Containers dabei als Web-Service bereitgestellt, den andere Nutzer und Anwendungen über gewöhnliche Internetprotokolle (z.B. HTTP) ansprechen können. Auf diesem Weg kann das Modell als Web-Service von anderen Systemen und Nutzern angefragt werden, ohne dass diese selbst spezifische technischen Voraussetzungen erfüllen müssen. Es müssen also nicht erst Bibliotheken oder die Programmiersprache des Modells installiert werden, um das Modell nutzbar zu machen.

Neben Docker existieren noch weitere Containertechnologien wie rkt und Mesos, wobei Docker mit seiner nutzerfreundlichen Bedienung und ausführlichen Dokumentation neuen Entwicklern einen leichten Einstieg ermöglicht. Durch die große Nutzerbasis existieren für viele Standardanwendungen Vorlagen, die mit geringem Aufwand in Containern hochgefahren werden können. Gleichzeitig dienen diese kostenlosen Vorlagen als Basis für Entwicklungen eigener Anwendungen.

Nicht zuletzt wegen dieser Vorteile wird Docker im MLOps Prozess mittlerweile als Best Practice angesehen. Der Prozess der Modellentwicklung gleicht zunehmend dem Softwareentwicklungsprozess, nicht zuletzt auch wegen Docker. Deutlich wird dies daran, dass containerbasierte Anwendungen durch Standardtools zur stetigen Integration und Bereitstellung (CI/CD) von Web-Services unterstützt werden.

Welche Rolle spielen Docker Container in der MLOps Pipeline?

Wie bereits erwähnt, handelt es sich bei MLOps um einen komplexen Prozess der kontinuierlichen Bereitstellung von ML Modellen. Zentrale Bestandteile eines solchen Systems sind in Abbildung 1 veranschaulicht. Der MLOps Prozess ähnelt stark dem DevOps Prozess, da auch die Entwicklung von Systemen des maschinellen Lernens eine Form der Softwareentwicklung ist. Standardkonzepte aus dem DevOps Bereich, wie stetige Integration von neuem Code und Bereitstellung neuer Software, finden sich deshalb im MLOps Prozess wieder. Dabei kommen neue, ML-spezifische Bestandteile wie das kontinuierliche Modelltraining und die Modell- und Datenvalidierung hinzu.

Grundsätzlich gilt es als Best Practice die Entwicklung von ML Modellen in einer MLOps Pipeline einzubetten. Die MLOps Pipeline umfasst alle Schritte von der Bereitstellung und Transformation von Daten über das Modelltraining bis hin zur kontinuierlichen Bereitstellung fertiger Modelle auf Produktionsservern. Der Code für jeden Schritt in der Pipeline wird hierbei in einem Docker Container verpackt und die Pipeline startet die Container in einer festgelegten Reihenfolge. Docker Container zeigen besonders hier ihre Stärke, da durch die Isolierung des Codes innerhalb einzelner Container kontinuierlich Codeänderungen an den entsprechenden Stellen der Pipeline eingearbeitet werden können, ohne dass die gesamte Pipeline ausgetauscht werden muss. Dadurch sind die Kosten für die Wartung der Pipeline relativ gering. Die großen Cloud Anbieter (GCP, AWS und Microsoft Azure) bieten zudem Services an, mit denen Docker Container automatisch gebaut, bereitgestellt und als Web-Services gehostet werden können. Um die Skalierung von Containern zu erleichtern und möglichst flexibel zu gestalten, bieten Cloud Anbieter ebenfalls vollständig verwaltete Kubernetes Produkte an. Für die Verwendung von ML Modellen im Unternehmen bedeutet diese Flexibilität Kostenersparnisse, da eine ML Anwendung einfach runterskaliert wird für den Fall, dass die Nutzungsrate sinkt. Gleichermaßen kann eine höhere Nachfrage durch Bereitstellung weiterer Container gewährleistet werden, ohne dass der Container mit dem Modell gestoppt werden muss. Nutzer der Anwendung erfahren so keine unnötige Downtime.

Fazit

Für die Entwicklung von Machine Learning Modellen und MLOps Pipelines stellen Docker Container eine Kerntechnologie dar. Die Vorteile bestehen in der Portabilität, Modularisierung und Isolierung des Codes von Modellen, geringem Wartungsaufwand bei Integration in Pipelines, schnellere Bereitstellung neuer Versionen des Modells und Skalierbarkeit über serverlose Cloudprodukte zur Bereitstellung von Containern. Bei STATWORX haben wir das Potenzial von Docker Containern erkannt und nutzen diese aktiv. Mit diesem Wissen unterstützten wir unsere Kunden bei der Verwirklichung ihrer Machine Learning und KI-Projekte. Sie wollen Docker in Ihrer MLOps Pipeline nutzen? Unsere Academy bietet Remote Trainings zu Data Science mit Docker, sowie kostenlose Webinare zu den Themen MLOps und Docker an.

Thomas Alcock Thomas Alcock

In meinem ersten Blogbeitrag dieser Reihe habe ich gezeigt, wie du deine R-Skripte in einem Docker-Container ausführen kannst. Für viele der Projekte, an denen wir hier bei STATWORX arbeiten, verwenden wir das RShiny-Framework, um unsere Produkte in interaktive Webapplikationen zu verwandeln.

Die Verwendung von Containern hat für die Bereitstellung von ShinyApps eine Vielzahl von Vorteilen. Zunächst sind es die üblichen Vorzüge wie einfache Cloud-Bereitstellung, Skalierbarkeit und praktisches Scheduling, aber es behebt auch einen der wesentlichen Nachteile von Shiny: Shiny erstellt nur eine einzige R-Sitzung pro App. Sollten also mehrere User auf dieselbe App zugreifen, dann arbeiten sie alle mit der selben R-Sitzung, was zu einer Vielzahl von Problemen führt. Mithilfe von Docker können wir dieses Problem umgehen und für jede:n User:in eine eigene Container-Instanz starten. Dadurch erhält jede:r User:in Zugriff auf eine eigene Instanz der App und somit eine eigene R-Sitzung. Ich gehe davon aus, dass du meinen vorigen Blogbeitrag über das Einbinden von R-Skripten in ein Docker-Image gelesen hast oder bereits über Grundkenntnisse der Docker-Terminologie verfügst.

Lassen wir also einfache R-Skripte hinter uns und führen wir jetzt ganze ShinyApps in Docker aus!

 

Das Setup

Einrichten eines Projekts

Für die Arbeit mit ShinyApps ist es ratsam, das Projekt-Setup von RStudio zu nutzen, insbesondere wenn man Docker verwendet. Projekte erleichtern nicht nur die Ordnung in RStudio, sondern ermöglichen es uns auch, mit dem Paket renv eine Paketbibliothek für unser spezifisches Projekt einzurichten. Dies ist besonders praktisch, um die benötigten Pakete für ein Programm in ein Docker-Image zu installieren.

Zu Demonstrationszwecken verwende ich eine Beispiel-App, die in einem früheren Blogbeitrag erstellt wurde und die du aus dem STATWORX GitHub Repository klonen kannst. Sie befindet sich im Unterordner „example-app“ und besteht aus den drei typischen Skripten, die von ShinyApps genutzt werden (global.R, ui.R und server.R) sowie aus Dateien, die zur Paketbibliothek renv gehören. Solltest du zur Übung ebenfalls die oben verlinkte Beispiel-App verwenden, musst du kein eigenes RStudio-Projekt einrichten. Stattdessen kannst du example-app.Rproj öffnen, das den von mir bereits eingerichteten Projektkontext startet. Falls du direkt mit einer eigenen App arbeiten möchtest und noch kein Projekt dafür erstellt hast, kannst du dein eigenes Projekt einrichten, indem du die von RStudio bereitgestellten Anweisungen befolgst.

Einrichten einer Paketbibliothek

Das RStudio-Projekt, das ich zur Verfügung gestellt habe, wird bereits mit einer Paketbibliothek geliefert, die in der Datei renv.lock gespeichert ist. Wenn du es vorziehst, mit deiner eigenen Anwendung zu arbeiten, kannst du deine eigene renv.lock Datei erstellen, indem du das renv Paket von deinem RStudio Projekt aus installierst und renv::init() ausführst. Dies initialisiert renv für dein Projekt und erstellt eine renv.lock Datei in deinem Projekt-Stammverzeichnis. Mehr Informationen über renv findest du unter RStudio’s Einführungsartikel.

Das Dockerfile

Das Dockerfile ist erneut das zentrale Element bei der Erstellung des Docker-Images. Während wir bisher nur ein einziges Skript in ein Image eingebaut haben, wollen wir diesen Prozess nun für eine ganze Anwendung wiederholen. Der Schritt von einem einzelnen Skript zu einem Ordner mit mehreren Skripten ist klein, aber es sind einige bedeutende Änderungen erforderlich, damit die App reibungslos läuft.

# Base image https://hub.docker.com/u/rocker/
FROM rocker/shiny:latest

# system libraries of general use
## install debian packages
RUN apt-get update -qq && apt-get -y --no-install-recommends install 
    libxml2-dev 
    libcairo2-dev 
    libsqlite3-dev 
    libmariadbd-dev 
    libpq-dev 
    libssh2-1-dev 
    unixodbc-dev 
    libcurl4-openssl-dev 
    libssl-dev

## update system libraries
RUN apt-get update && 
    apt-get upgrade -y && 
    apt-get clean

# copy necessary files
## app folder
COPY /example-app ./app
## renv.lock file
COPY /example-app/renv.lock ./renv.lock

# install renv & restore packages
RUN Rscript -e 'install.packages("renv")'
RUN Rscript -e 'renv::consent(provided = TRUE)'
RUN Rscript -e 'renv::restore()'

# expose port
EXPOSE 3838

# run app on container start
CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = 3838)"]

Das Base-Image

Die erste Änderung betrifft das Base-Image. Da wir hier eine ShinyApp verdockern, können wir uns eine Menge Arbeit ersparen, indem wir das Base Image „rocker/shiny“ verwenden. Dieses Image kümmert sich um die notwendigen Dependencies für die Ausführung einer ShinyApp und enthält mehrere R-Pakete, die bereits vorinstalliert sind.

Erforderliche Dateien

Es ist natürlich notwendig, alle relevanten Skripte und Dateien für deine ShinyApp in dein Docker-Image einzubauen. Das Dockerfile erledigt genau das, indem es den gesamten Ordner, der die Anwendung enthält, in das Image kopiert.

Um die benötigten R-Pakete in ihrer korrekten Version in das Docker-Image zu installieren, verwenden wir am besten renv. Deshalb kopieren wir zuerst die Datei renv.lock separat in das Image. Das renv-Paket muss ebenfalls separat installiert werden, indem wir die Fähigkeit des Dockerfiles nutzen, mithilfe von RUN Rscript -e R-Code auszuführen. Diese Paketinstallation erlaubt es uns renv direkt aufzurufen und die kopierte Paketbibliothek in renv.lock innerhalb des Images mit renv::restore() wiederherzustellen. Dadurch wird die gesamte Paketbibliothek im Docker-Image installiert, mit genau der gleichen Version und dem gleichen Quellcode aller Pakete wie in der lokalen Projektumgebung. Und all dies mit nur ein paar Zeilen Code in unserem Dockerfile.

Starten der App zur Laufzeit

Ganz am Ende des Dockerfiles weisen wir den Container an, den folgenden R-Befehl auszuführen:

shiny::runApp('/app', host = '0.0.0.0', port = 3838)

Das erste Argument legt den Dateipfad zu den benötigten Skripten fest, welche in diesem Fall unter ./app abgelegt sind. Für den exponierten Port habe ich 3838 gewählt. Dies entspricht der Standardeinstellung für RStudio Server, kann aber nach Belieben geändert werden.

Wenn der letzte Befehl ausgeführt wird, startet jeder Container, der auf diesem Image basiert, die betreffende Anwendung automatisch zur Laufzeit (und schließt sie natürlich wieder, wenn sie beendet wurde).

Der letzte Schliff

Mit dem fertigen Dockerfile fehlen nur noch wenige Schritte bis zum laufenden Docker-Container. Dazu muss zunächst noch das Image erstellt werden, um anschließend einen Container basierend auf diesem Image zu starten.

Erstellen des Images

Also öffnen wir das Terminal, navigieren zu dem Ordner, der unser neues Dockerfile enthält und starten den Erstellungsprozess des Images:

docker build -t my-shinyapp-image . 

Starten eines Containers

Nachdem dieser Prozess abgeschlossen ist, können wir nun unser neu erstelltes Image testen, indem wir einen Container starten:

docker run -d --rm -p 3838:3838 my-shinyapp-image

Und da ist sie, die ShinyApp läuft auf localhost:3838!

docker-shiny-app-example

Ausblick

Jetzt, wo die ShinyApp in einem Docker-Container läuft, ist sie bereit für den Einsatz. Die Containerisierung unserer App macht diesen Prozess bereits sehr viel einfacher. Es gibt jedoch noch weitere Tools, die wir einsetzen, um Sicherheit, Skalierbarkeit und nahtloses Deployment auf dem neuesten Stand der Technik zu gewährleisten. Im nächsten Beitrag erwartet dich deshalb eine Einführung in ShinyProxy, womit du noch tiefer in das Spektrum an Möglichkeiten von RShiny und Docker eintauchen kannst.

Oliver Guggenbühl Oliver Guggenbühl

In meinem ersten Blogbeitrag dieser Reihe habe ich gezeigt, wie du deine R-Skripte in einem Docker-Container ausführen kannst. Für viele der Projekte, an denen wir hier bei STATWORX arbeiten, verwenden wir das RShiny-Framework, um unsere Produkte in interaktive Webapplikationen zu verwandeln.

Die Verwendung von Containern hat für die Bereitstellung von ShinyApps eine Vielzahl von Vorteilen. Zunächst sind es die üblichen Vorzüge wie einfache Cloud-Bereitstellung, Skalierbarkeit und praktisches Scheduling, aber es behebt auch einen der wesentlichen Nachteile von Shiny: Shiny erstellt nur eine einzige R-Sitzung pro App. Sollten also mehrere User auf dieselbe App zugreifen, dann arbeiten sie alle mit der selben R-Sitzung, was zu einer Vielzahl von Problemen führt. Mithilfe von Docker können wir dieses Problem umgehen und für jede:n User:in eine eigene Container-Instanz starten. Dadurch erhält jede:r User:in Zugriff auf eine eigene Instanz der App und somit eine eigene R-Sitzung. Ich gehe davon aus, dass du meinen vorigen Blogbeitrag über das Einbinden von R-Skripten in ein Docker-Image gelesen hast oder bereits über Grundkenntnisse der Docker-Terminologie verfügst.

Lassen wir also einfache R-Skripte hinter uns und führen wir jetzt ganze ShinyApps in Docker aus!

 

Das Setup

Einrichten eines Projekts

Für die Arbeit mit ShinyApps ist es ratsam, das Projekt-Setup von RStudio zu nutzen, insbesondere wenn man Docker verwendet. Projekte erleichtern nicht nur die Ordnung in RStudio, sondern ermöglichen es uns auch, mit dem Paket renv eine Paketbibliothek für unser spezifisches Projekt einzurichten. Dies ist besonders praktisch, um die benötigten Pakete für ein Programm in ein Docker-Image zu installieren.

Zu Demonstrationszwecken verwende ich eine Beispiel-App, die in einem früheren Blogbeitrag erstellt wurde und die du aus dem STATWORX GitHub Repository klonen kannst. Sie befindet sich im Unterordner „example-app“ und besteht aus den drei typischen Skripten, die von ShinyApps genutzt werden (global.R, ui.R und server.R) sowie aus Dateien, die zur Paketbibliothek renv gehören. Solltest du zur Übung ebenfalls die oben verlinkte Beispiel-App verwenden, musst du kein eigenes RStudio-Projekt einrichten. Stattdessen kannst du example-app.Rproj öffnen, das den von mir bereits eingerichteten Projektkontext startet. Falls du direkt mit einer eigenen App arbeiten möchtest und noch kein Projekt dafür erstellt hast, kannst du dein eigenes Projekt einrichten, indem du die von RStudio bereitgestellten Anweisungen befolgst.

Einrichten einer Paketbibliothek

Das RStudio-Projekt, das ich zur Verfügung gestellt habe, wird bereits mit einer Paketbibliothek geliefert, die in der Datei renv.lock gespeichert ist. Wenn du es vorziehst, mit deiner eigenen Anwendung zu arbeiten, kannst du deine eigene renv.lock Datei erstellen, indem du das renv Paket von deinem RStudio Projekt aus installierst und renv::init() ausführst. Dies initialisiert renv für dein Projekt und erstellt eine renv.lock Datei in deinem Projekt-Stammverzeichnis. Mehr Informationen über renv findest du unter RStudio’s Einführungsartikel.

Das Dockerfile

Das Dockerfile ist erneut das zentrale Element bei der Erstellung des Docker-Images. Während wir bisher nur ein einziges Skript in ein Image eingebaut haben, wollen wir diesen Prozess nun für eine ganze Anwendung wiederholen. Der Schritt von einem einzelnen Skript zu einem Ordner mit mehreren Skripten ist klein, aber es sind einige bedeutende Änderungen erforderlich, damit die App reibungslos läuft.

# Base image https://hub.docker.com/u/rocker/
FROM rocker/shiny:latest

# system libraries of general use
## install debian packages
RUN apt-get update -qq && apt-get -y --no-install-recommends install 
    libxml2-dev 
    libcairo2-dev 
    libsqlite3-dev 
    libmariadbd-dev 
    libpq-dev 
    libssh2-1-dev 
    unixodbc-dev 
    libcurl4-openssl-dev 
    libssl-dev

## update system libraries
RUN apt-get update && 
    apt-get upgrade -y && 
    apt-get clean

# copy necessary files
## app folder
COPY /example-app ./app
## renv.lock file
COPY /example-app/renv.lock ./renv.lock

# install renv & restore packages
RUN Rscript -e 'install.packages("renv")'
RUN Rscript -e 'renv::consent(provided = TRUE)'
RUN Rscript -e 'renv::restore()'

# expose port
EXPOSE 3838

# run app on container start
CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = 3838)"]

Das Base-Image

Die erste Änderung betrifft das Base-Image. Da wir hier eine ShinyApp verdockern, können wir uns eine Menge Arbeit ersparen, indem wir das Base Image „rocker/shiny“ verwenden. Dieses Image kümmert sich um die notwendigen Dependencies für die Ausführung einer ShinyApp und enthält mehrere R-Pakete, die bereits vorinstalliert sind.

Erforderliche Dateien

Es ist natürlich notwendig, alle relevanten Skripte und Dateien für deine ShinyApp in dein Docker-Image einzubauen. Das Dockerfile erledigt genau das, indem es den gesamten Ordner, der die Anwendung enthält, in das Image kopiert.

Um die benötigten R-Pakete in ihrer korrekten Version in das Docker-Image zu installieren, verwenden wir am besten renv. Deshalb kopieren wir zuerst die Datei renv.lock separat in das Image. Das renv-Paket muss ebenfalls separat installiert werden, indem wir die Fähigkeit des Dockerfiles nutzen, mithilfe von RUN Rscript -e R-Code auszuführen. Diese Paketinstallation erlaubt es uns renv direkt aufzurufen und die kopierte Paketbibliothek in renv.lock innerhalb des Images mit renv::restore() wiederherzustellen. Dadurch wird die gesamte Paketbibliothek im Docker-Image installiert, mit genau der gleichen Version und dem gleichen Quellcode aller Pakete wie in der lokalen Projektumgebung. Und all dies mit nur ein paar Zeilen Code in unserem Dockerfile.

Starten der App zur Laufzeit

Ganz am Ende des Dockerfiles weisen wir den Container an, den folgenden R-Befehl auszuführen:

shiny::runApp('/app', host = '0.0.0.0', port = 3838)

Das erste Argument legt den Dateipfad zu den benötigten Skripten fest, welche in diesem Fall unter ./app abgelegt sind. Für den exponierten Port habe ich 3838 gewählt. Dies entspricht der Standardeinstellung für RStudio Server, kann aber nach Belieben geändert werden.

Wenn der letzte Befehl ausgeführt wird, startet jeder Container, der auf diesem Image basiert, die betreffende Anwendung automatisch zur Laufzeit (und schließt sie natürlich wieder, wenn sie beendet wurde).

Der letzte Schliff

Mit dem fertigen Dockerfile fehlen nur noch wenige Schritte bis zum laufenden Docker-Container. Dazu muss zunächst noch das Image erstellt werden, um anschließend einen Container basierend auf diesem Image zu starten.

Erstellen des Images

Also öffnen wir das Terminal, navigieren zu dem Ordner, der unser neues Dockerfile enthält und starten den Erstellungsprozess des Images:

docker build -t my-shinyapp-image . 

Starten eines Containers

Nachdem dieser Prozess abgeschlossen ist, können wir nun unser neu erstelltes Image testen, indem wir einen Container starten:

docker run -d --rm -p 3838:3838 my-shinyapp-image

Und da ist sie, die ShinyApp läuft auf localhost:3838!

docker-shiny-app-example

Ausblick

Jetzt, wo die ShinyApp in einem Docker-Container läuft, ist sie bereit für den Einsatz. Die Containerisierung unserer App macht diesen Prozess bereits sehr viel einfacher. Es gibt jedoch noch weitere Tools, die wir einsetzen, um Sicherheit, Skalierbarkeit und nahtloses Deployment auf dem neuesten Stand der Technik zu gewährleisten. Im nächsten Beitrag erwartet dich deshalb eine Einführung in ShinyProxy, womit du noch tiefer in das Spektrum an Möglichkeiten von RShiny und Docker eintauchen kannst.

Oliver Guggenbühl Oliver Guggenbühl