Data Science in der Praxis
Wie Softwareentwicklung und Data Science zusammenspielen
Wir erklären, wie Data Scientisten und Engineers Werkzeuge wie DVC nutzen können, um sämtliche Elemente eines Projekts zu versionieren: Angefangen beim Datensatz, über den Code bis zum Modell selbst. Das ist aber noch nicht alles!
In diesem Artikel erklären wir den gesamten Entwicklungsprozess, einschließlich Experimente und Reproduzierbarkeit, sowie Release und Deployment des Modells in die freie Wildbahn.
Durch die steigende Datenflut gewinnen maschinelle Lernverfahren nicht nur in der Forschung immer mehr an Beliebtheit, sondern werden auch zunehmend in der Wirtschaft und der Industrie eingesetzt. Kein Wunder, gestalten moderne Frameworks wie TensorFlow oder PyTorch den Einstieg in die Entwicklung von Machine-Learning-Modellen so einfach wie nie. Schnell lassen sich damit effiziente Algorithmen entwickeln, die im besten Fall ganze Regelwerke ablösen und selbstständig Entscheidungen treffen können. Die Entwicklung von ML-Modellen ist jedoch häufig noch vom experimentierfreudigen Data-Science-Ansatz geprägt: Hyperparameter werden sorglos manuell angepasst und getestet oder Features in den Daten werden unterschiedlich vorverarbeitet, bis das Modell eine vernünftige Qualität liefert. Diese Entwicklungsstrategie mag für kurzlebige Projekte funktionieren, verliert bei längeren Zeiträumen und in größeren Teams aber schnell an Übersicht. Auf der anderen Seite beachten etablierte Softwareentwicklungs-Strategien oftmals nicht den experimentellen Anteil in der Arbeit mit diesen Technologien. Das Ergebnis sind schwer nachvollziehbare Projektresultate und frustrierte Entwickler. Viele Projekte passen ihre Entwicklungsstrategien und -muster jedoch nicht auf die Besonderheiten dieser Technologien an.
Dieser Artikel zeigt, wie Data Scientisten und Engineers Werkzeuge wie DVC (Data Version Control) [1] nutzen können, um sämtliche Elemente eines Projekts zu versionieren: angefangen beim Datensatz, über den Programmcode bis zum trainierten Modell. Darüber hinaus wird der Weg von der reproduzierbaren Entwicklung bis zur Operationalisierung und dem Deployment des ML-Modells vorgestellt und damit der gesamte Entwicklungsprozess abgedeckt.
Von der Wissenschaft zur Ingenieurskunst
Mit datengetriebenen Produkten, die Data Science und Softwareentwicklung kombinieren, treffen zwei Welten aufeinander. In Softwareprojekten werden mit unterschiedlichsten Werkzeugen, Technologien und Frameworks lauffähige Applikationen entwickelt, die ihren Geschäftszweck möglichst lang und fehlerfrei erfüllen sollen. Data Scientists dagegen arbeiten im Experimentiermodus. In jeder Iteration werden Parameter optimiert, Daten augmentiert oder ein weiterer Vorverarbeitungsschritt eingeführt, um ein Modell weiter zu optimieren. Data Science hat nicht zum Ziel, fertige Softwareprodukte zu entwickeln, sondern auf Basis gegebener Daten Lösungsansätze zu erarbeiten und die Ergebnisse objektiv zu präsentieren. Bei der Entwicklung von Software hingegen haben sich über die Zeit, wie im Handwerk und dem Ingenieurwesen auch, Werkzeuge und Methoden etabliert, die die effiziente Entwicklung und einen erfolgreichen Produktionsbetrieb ermöglichen. So etwas war für Data-Science-Experimente, die oftmals nicht über die ML-Modelle hinaus gehen, nicht nötig. Dies ändert sich mit dem Einsatz von ML-Modellen in Softwareprojekten.
Daten, Modelle, Code
Neben dem Code für Features und Bugfixes oder Konfigurationen kommen mit dem Einsatz von ML-Modellen zu den Trainings- und Testdaten noch modellbezogene Artefakte hinzu. Dazu gehören der verwendete Algorithmus, die Experimente mit den jeweils genutzten Parametern sowie die Ergebnisse in Form von Modellgewichten und Metriken [2].
All diese Stellschrauben zu verwalten wird schnell unübersichtlich und schwer nachvollziehbar. Auch nur die kleinste Änderung führt zu einem neuen Experiment mit anderen Ergebnissen und damit auch zu einem neuen, potenziell deploybaren Artefakt. Wie in der Softwareentwicklung auch ist es im ersten Schritt wichtig, neben dem Code auch alle Änderungen an den Daten, Parametern und Algorithmen nachvollziehbar zu versionieren. Damit wird sichergestellt, dass Probleme mit dem Modell zu jeder Zeit reproduziert, behoben und auf Regressionen getestet werden können. Mit Trainingsdaten und Gewichten, die im Gegensatz zum Programmcode oftmals aus großen Binaries bestehen, kommen übliche Versionskontrollsysteme (VCS) wie Git [3] jedoch an ihre Grenzen. Erkennt ein VCS, dass sich eine Datei geändert hat, speichert es die gesamte Datei als Teil der nächsten Version erneut ab. Binaries mit teils mehreren 100MB in jeder Version führen daher leicht zu überfüllten Repositories.
Den Ernst der Lage erkennen
Große, sich häufig ändernde Binärdateien sind nicht die einzige Herausforderung bei der Verschmelzung von ML-Modellen und -Applikationen. Mit modernen Tools und Frameworks wie Jupyter, TensorFlow und Keras lassen sich schnell erste verwertbare Modelle trainieren, die vor lauter Euphorie gleich Anwendung in der Software finden sollen. Dass sich neben dem Programmcode auch die Trainingsdaten, der Algorithmus oder die Modellparameter regelmäßig ändern, wird häufig verdrängt. Nach dem ersten Feedback der Nutzer reicht ein kurzer Blick auf die Testdaten mit ls -1 in der Commandline, um zu zeigen, wie Bugfixes in das bestehende Modell eingearbeitet werden:
Listing 1: Daten "versionieren"
$ ls -1
data_2019_12_01.tgz
data_2019_12_01_v2.tgz
data_2019_12_01_v2_test.tgz
data_2019_12_01_v2_re_test_calibration.tgz
data_2019_12_01_v3.tgz
data_2019_12_02_v3_final.tgz
data_2019_12_02_v3_final_final.tgz
data_2019_12_03_v3_DIESMAL_RICHTIG.tgz
Ein Blick auf den Python-Code zeigt ein ähnliches Bild. Noch sind die Änderungen nicht groß und können manuell verwaltet werden. Aber bereits zu diesem Zeitpunkt ist es nur noch schwer nachvollziehbar, mit welcher Kombination von Parametern und Daten das bereitgestellte Modell trainiert wurde.
Listing 2: Experimente "verwalten"
train_data = ImageDataGenerator(rescale=1. / 255,
#horizontal_flip=True, # Nicht so wichtig
#horizontal_flip=True, # Hat nicht funktioniert
featurewise_center=True,
#featurewise_std_normalization=True, # Vielleicht später
rotation_range=20)
test_data = ImageDataGenerator(rescale=1. / 255,
#horizontal_flip=True, # Nicht so wichtig
#horizontal_flip=True, # Hat nicht funktioniert
featurewise_center=True,
#featurewise_std_normalization=True, # Vielleicht später
rotation_range=20)
Damit ergeben sich drei Probleme bei der Entwicklung und Integration von ML-Modellen, die es zu lösen gilt, von der Verwaltung und Reproduzierbarkeit von Experimenten ohne zu weit vom Workflow aus der Softwareentwicklung abzuweichen, über die Versionierung von Binärdaten bis hin zur Kollaboration und der Entwicklung neuer Modelle im Team.
DVC to the Rescue!
Data Version Control ist ein offenes Versionskontrollsystem für ML-Projekte. Das leichtgewichtige Tool integriert sich in den Workflow von Git und dessen Branch-Modell und stellt Data Scientisten und Entwicklern zusätzliche Funktionalitäten wie das Tracking großer Dateien oder automatisierte Pipelines zur Reproduzierbarkeit und für Deployments bereit.
Initialisieren und Experimente verwalten
DVC fühlt sich wie eine Erweiterung von Git an. Analog zu git init zur Initialisierung des Repositories wird dvc init für die Initialisierung von DVC ausgeführt und dessen Konfiguration im Unterordner .dvc im Git-Repository versioniert.
Listing 3: Repo initialisieren
$ dvc init
$ git status
new file: .dvc/.gitignore
new file: .dvc/config
$ git add .dvc
$ git commit -m "Initialize dvc"
Experimente werden mit DVC, wie Features bei der Software-Entwicklung, in Git-Zweigen verwaltet. Für Änderungen am Datensatz, dem Algorithmus oder den Parametern kann jeweils ein eigener Feature-Branch erstellt werden.
Listing 4: Experiment im Branch verwalten
$ git branch experiment1
$ git checkout experiment1
A .dvc/.gitignore
A .dvc/config
Switched to branch 'experiment1'
Wie bereits erwähnt, ist es mit DVC möglich, große Dateien zu versionieren. Dazu sollte zunächst, analog zu Git, ein externer Speicherort (Remote Storage) definiert werden. DVC bietet hier eine Fülle an Optionen, von lokalen Ordnern über Fernzugriffe per SSH bis hin zu Amazon S3 und anderen Cloudspeichern von Google oder Microsoft Azure. Der Einfachheit halber wird im Beispiel ein lokaler Ordner verwendet. Unser Beispielmodell soll Obst klassifizieren, daher nennen wir unseren Ordner fruits_cnn.
Listing 5: Remote konfigurieren
$ dvc remote add -d local_storage /data/fruits_cnn
Setting 'local_storage' as a default remote.
Der Befehl remote gleicht dabei dem von Git. Der erste Parameter ist der Name des Remotes, in diesem Fall local_storage und der zweite Parameter, /data/fruits_cnn ist die URL. -d legt den Remote als Standard für das Repository fest.
Daten und Pipelines
Um Experimente reproduzierbar zu machen, werden sogenannte Pipelines genutzt, die einzelne Verarbeitungsschritte miteinander verbinden. Die wohl einfachste Pipeline besteht aus:
- Daten laden
- Modelltraining
- Evaluation
Jeder Schritt wird über Dateien, als Abhängigkeiten (Dependencies) und Ausgaben (Outputs) miteinander verknüpft. Sobald die Pipeline definiert ist, kümmert sich DVC darum, nur die Schritte auszuführen, bei denen sich die Abhängigkeiten geändert haben.
Der erste Schritt ist das Laden der Daten. dvc run konfiguriert und führt die Pipeline aus. Die Informationen zum Verarbeitungsschritt werden in einer DVC-Konfigurationsdatei gespeichert, die über den Parameter -f load.dvc angegeben wird. Abhängigkeiten werden mit -d definiert und Ausgaben mit -o. Mit Aufruf in Listing 6 führt DVC den Schritt direkt aus und lädt die Trainingsdaten über das Skript load.sh.
Listing 6: Den ersten Verarbeitungsschritt ausführen
$ dvc run -f load.dvc \
-d load.sh \
-o data.tgz \
sh load.sh
Running command:
sh load.sh
Aber DVC macht noch mehr, als nur das Skript auszuführen. Ein kurzer Blick in die .gitignore zeigt den Eintrag /data.tgz. Die potenziell große Binärdatei wird automatisch ignoriert und in der Konfigurationsdatei load.dvc getrackt. Ein dvc push lädt die Datei in den konfigurierten externen Speicherort.
Listing 7: Binärdateien in die Remote-Location pushen
$ git add load.sh load.dvc
$ git commit -m "Add data loading"
$ dvc push
Mit dem Training des Modells wird analog verfahren. Damit DVC die Pipeline auflösen kann, ist die Datei data.tgz jetzt keine Ausgabe, sondern eine Abhängigkeit für den Trainingsschritt. Nach dem Training wird das Modell als .h5-Datei gespeichert und in DVC als Ausgabe definiert. Der Code ist in Listing 8 dargestellt.
Listing 8: Modell trainieren
$ dvc run -f train.dvc \
-d fruit_detector.py \
-d data.tgz \
-o model.h5 python \
fruit_detector.py
Running command:
python fruit_detector.py
$ git add fruit_detector.py train.dvc
$ git commit -m "Add training"
$ dvc push
Es fehlt nur noch der letzte Schritt unserer Pipeline: Die Evaluation. Im Evaluationsschritt wird das Ergebnis nicht als Ausgabe, sondern mithilfe des Parameters -M als Metrik definiert (s. Listing 9). Metriken können mit dvc metrics über alle Experimente hinweg ausgegeben und verglichen werden. Die Metriken lassen sich frei definieren. Das Skript evaluate_model.py speichert in unserem Fall die Genauigkeit und den Loss im JSON-Format ab.
Listing 9: Modell evaluieren
$ dvc run -f eval.dvc \
-d evaluate_model.py \
-d model.h5 \
-M metrics.json \
python evaluate_model.py
Running command:
python evaluate_model.py
$ git add evaluate_model.py metrics.json eval.dvc
$ git commit -m "Add evaluation"
$ dvc push
Reproduzierbarkeit mit DVC
Die Pipeline ist definiert und wurde Schritt für Schritt ausgeführt. Ein kurzer Aufruf des Befehls dvc pipeline show in Listing 10 visualisiert den Ausführungsgraphen und gibt einen Überblick über alle Verarbeitungsschritte. Die Pipeline hat DVC selbst anhand der Abhängigkeiten und Ausgaben erstellt. Dabei muss die Reihenfolge der Verarbeitungsschritte nicht zwingend linear sein. Eine Pipeline kann ein beliebiger, gerichteter, azyklischer Graph sein.
Listing 10: Visualisierung der Pipeline
$ dvc pipeline show --tree eval.dvc
eval.dvc
|__ train.dvc
|__ load.dvc
Mit dvc repro kann die Pipeline nun reproduziert werden, um die Schritte auszuführen, bei denen sich Abhängigkeiten geändert haben. Ändert sich zum Beispiel der Code zum Laden der Trainingsdaten, werden alle Schritte noch einmal ausgeführt (s. Listing 11). DVC bekommt diese Änderungen mit, indem es die Abhängigkeiten und Ausgaben mithilfe von md5-Hashsummen überwacht, die in den entsprechenden .dvc-Dateien
gespeichert werden.
Listing 11: Reproduzieren der Pipeline
$ dvc repro eval.dvc
Running command:
sh load.sh
Running command:
python fruit_detector.py
Running command:
python evaluate_model.py
Ein Überblick über die Experimente
Wie erwähnt nutzt der letzte Verarbeitungsschritt den Paramter -M zur Definition von Metriken und macht Experimente damit über alle Branches vergleichbar. Listing 12 zeigt wie dvc metrics show eine Übersicht über alle Experimente und deren Metriken gibt.
Listing 12: Metrik Übersicht
$ dvc metrics show --all-branches
experiment1:
metrics.json: {"loss": 0.0012, "accuracy": 0.9765}
experiment2:
metrics.json: {"loss": 0.0010, "accuracy": 0.9865}
working tree:
metrics.json: {"loss": 0.0010, "accuracy": 0.9865}
Machine-Learning-Modelle releasen und deployen
Die Trainingspipeline ist implementiert, versioniert und reproduzierbar. Damit die Grundsteine für ein automatisiertes Modell-Deployment gelegt sind, fehlt noch ein Release-Mechanismus. Da Git als VCS zum Einsatz kommt, reicht ein einfaches Git-Tag aus.
Listing 13: Release taggen
$ git checkout master
$ git merge experiment2
$ git tag -a release/0.1 -m "0.1 release"
Mit dvc get kann auf Dateien auf dem Remote zugegriffen werden. Mit dem Parameter --rev wird der gewünschte Tag angegeben. Listing 13 zeigt das Herunterladen des Modells in der Release-Version 0.1 vom externen Speicher.
Listing 14: Release herunterladen
$ GIT_REPO=...
$ dvc get --rev release/0.1 $GIT_REPO model.h5
Auch hier kann dvc metrics show verwendet werden. Der Parameter -T listet die Metriken aller Tags, und damit auch aller Releases, auf.
Listing 15: Metrik Übersicht der Releases
$ dvc metrics show -T
release/0.1:
metrics.json: {"loss": 0.0112, "accuracy": 0.9865}
working tree:
metrics.json: {"loss": 0.0112, "accuracy": 0.9865}
Mit dem Releasekonzept steht einem automatisierten Deployment nichts mehr im Wege. Häufig werden dazu Modell-Server wie TensorFlow Serving[4], OpenVINO[5] oder TensorRT[6] verwendet, die mit entsprechenden Model-Binaries umgehen können und die Inferenz per REST oder gRPC bereitstellen.
Fazit
Obwohl mehr und mehr IT-Projekte Machine-Learning-Modelle integrieren, ist es immer noch schwierig, den experimentellen Data-Science-Anteil mit der klassischen Softwareentwicklung in Einklang zu bringen. Damit werden Werkzeuge, die Data-Science auf operationaler Ebene unterstützen, immer wichtiger. DVC hilft, die unterschiedlichen Vorgehensweisen im Hinblick auf die Arbeit im Team, aber auch auf technischer Ebene, zu vereinheitlichen. Damit lassen sich schnell und ohne viel Aufwand hohe Automatisierungsgrade realisieren, die den Weg für regelmäßige Deployments von ML-Modellen ebnen.