Kapitel 4. Effektives Abhängigkeitsmanagement in der Praxis

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Im vorigen Kapitel haben wir die Grundsätze für ein effektives Abhängigkeitsmanagement - kannst du dich an die vier Grundsätze erinnern - und die dazugehörigen Werkzeuge vorgestellt. In diesem Kapitel wollen wir uns ein wenig austoben und sie in die Praxis umsetzen.

In diesem Kapitel lernst du:

  • Wie "Auschecken und gehen" in der Praxis aussieht

  • Wie man Docker, batect und Poetry nutzt, um konsistente, reproduzierbare und produktionsähnliche Laufzeitumgebungen in jedem Schritt des ML-Lifecycles zu schaffen

  • Wie du automatisch Sicherheitslücken in deinen Abhängigkeiten erkennst und Aktualisierungen von Abhängigkeiten automatisierst

Die Techniken in diesem Kapitel verwenden wir in unseren realen Projekten, um reproduzierbare, konsistente, isolierte und produktionsähnliche Laufzeitumgebungen für unseren ML-Code zu schaffen. Sie helfen uns, Abhängigkeiten effektiv und sicher zu verwalten und die Abhängigkeitshölle zu vermeiden.

Fangen wir an!

Im Kontext: ML Entwicklungsworkflow

Unter siehst du in diesem Abschnitt "Check out and go" in Aktion. In der Code-Übung werden wir die folgenden Schritte durchlaufen, um ein Modell zu trainieren und zu bedienen, das die Wahrscheinlichkeit eines Kreditausfalls vorhersagt:

  1. Führe ein Go-Skript aus, um die erforderlichen Abhängigkeiten auf unserem Host-Rechner zu installieren.

  2. Erstelle eine lokale Entwicklungsumgebung mit Docker.

  3. Konfiguriere unseren Code-Editor so, dass er die virtuelle Umgebung des Projekts versteht, damit wir einen hilfreichen Programmierassistenten haben.

  4. Führe allgemeine Aufgaben im ML-Entwicklungszyklus aus (z. B. Modelle trainieren, Tests durchführen, API starten).

  5. Trainiere das Modell und setze es in der Cloud ein.

Um dieses Kapitel optimal zu nutzen, forkst und klonst du das Repository und machst bei der praktischen Übung mit, in der wir einen Klassifikator trainieren und testen, um die Wahrscheinlichkeit von Kreditausfällen vorherzusagen. Wir empfehlen dir, das Repository zu forken, denn so kannst du Docker und batect bei der Arbeit in der GitHub Actions CI-Pipeline deines geforkten Repositorys beobachten, während du deine Änderungen festlegst und veröffentlichst.

Bevor wir uns dem Code zuwenden, wollen wir uns ein klares Bild davon machen, was wir in einem typischen ML-Workflow containerisieren.

Identifizieren, was zu containerisieren ist

Der erste und wichtigste Schritt bei der Dockerisierung eines Projekts besteht darin, genau zu bestimmen , was wir containerisieren. Dies kann einige ML-Praktiker verwirren und zu einer Vermischung und gemeinsamen Nutzung von Zuständen führen. Wenn wir z. B. ein Image zwischen den beiden unterschiedlichen Aufgaben der Entwicklung eines ML-Modells und der Bereitstellung eines ML-Modells teilen, können wir unnötige Entwicklungsabhängigkeiten (z. B. Jupyter, Pylint) in einem Produktionscontainer (z. B. eine Modell-Web-API) finden. Das verlängert die Bau- und Startzeiten von Containern unnötig und vergrößert außerdem die Angriffsfläche für unsere API.

In der Softwareentwicklung ist das häufigste, was wir containerisieren, eine Webanwendung oder eine Web-API - also ein langlebiger Prozess, der nach der Ausführung eines Befehls gestartet wird (z. B. python manage.py runserver). In ML können wir auch eine containerisierte Webanwendung verwenden, um Modellvorhersagen (Inferenzen) über eine API bereitzustellen. In der Regel werden wir jedoch mehr als nur eine Webanwendung ausführen. Hier sind zum Beispiel einige gängige ML-Aufgaben und -Prozesse, die wir bei der Erstellung von ML-Lösungen ausführen würden:

  • Ein Modell trainieren

  • Das Modell als Web-API bereitstellen

  • Starten eines Notebook-Servers

  • Durchführung von Einsätzen (von ML-Trainingsaufträgen, Modell-API usw.)

  • Starten eines Dashboards oder eines Dienstes zur Verfolgung von Experimenten (dies wird in diesem Kapitel nicht behandelt, da der Betrieb von Dashboards als Webserver gut dokumentiert ist und mit Tools wie Streamlit und Docker relativ einfach ist )

Im Beispiel dieses Kapitels haben wir vier verschiedene Gruppen von Abhängigkeiten für die Ausführung von vier verschiedenen Aufgaben ermittelt (siehe Tabelle 4-1).

Tabelle 4-1. Komponenten, die wir containerisieren
Bild Beispiele für Aufgaben, die wir ausführen können Beispiele für Abhängigkeiten auf Betriebssystemebene Beispiele für Abhängigkeiten auf Anwendungsebene
1. Entwicklung Bild
  • ML-Modell trainieren
  • Feature Engineering
  • Automatisierte Tests durchführen
  • API-Server lokal starten
  • Starte den Jupyter-Notebook-Server
  • Python 3.10
  • gcc
  • tensorflow-model-server

Abhängigkeiten von der Produktion:

  • Pandas

  • scikit-learn

Abhängigkeiten von der Entwicklung:

  • Jupyter

  • Pytest

  • Pylint

2. Produktion API Bild
  • API-Server in der Cloud starten
  • Python 3.10
  • gcc
  • tensorflow-model-server

Abhängigkeiten von der Produktion:

  • Pandas

  • scikit-learn

3. Einsatz der Bild-Modell-Trainingspipeline
  • Bereitstellung der Modell-Trainings-Pipeline in der Cloud
  • Modelltraining durchführen

Die konkrete Abhängigkeit hängt davon ab, welches Tool oder welche Plattform wir für das Training unseres Modells in der Cloud verwenden. Sie könnte zum Beispiel eine der folgenden sein:

  • aws-cdk (AWS)

  • gcloud (GCP)

  • azure-cli (Azure)

  • Metaflow

  • Kubeflow

  • Terraform

  • usw.

4. Bereitstellung des Bildmodells als Webservice
  • Bereitstellen des Container-Images bei einem Modell- oder Container-Hosting-Dienst

Die spezifische Abhängigkeit hängt davon ab, welches Tool oder welche Plattform wir verwenden, um unseren Webservice in der Cloud bereitzustellen. Sie könnte zum Beispiel eine der folgenden sein:

  • aws-cdk (AWS)

  • gcloud (GCP)

  • azure-cli (Azure)

  • Terraform

  • usw.

Abbildung 4-1 veranschaulicht jede Aufgabe, die, wie du inzwischen weißt, nichts anderes ist als ein containerisierter Prozess und die jeweiligen Images, die sie verwenden. Diese Abbildung ist eine visuelle Darstellung von Tabelle 4-1.

Abbildung 4-1. Allgemeine ML-Entwicklungsaufgaben und die dazugehörigen Bilder

Die genaue Aufteilung und Differenzierung der Images hängt von den Bedürfnissen des Projekts ab. Wenn die Aufteilung zu grobkörnig ist - z. B. ein Image für alle Aufgaben und Container - kann das monolithische Image zu schwer werden. Erinnere dich an unsere frühere Diskussion über die Kosten, die durch unnötige Abhängigkeiten entstehen. Wenn das Slicing zu feinkörnig ist - z. B. ein Image für jede Aufgabe oder jeden Container -, können unnötige Kosten durch den Code, den wir pflegen müssen, und die Erstellungszeiten der Images für jede Aufgabe entstehen.

Eine hilfreiche Heuristik, um herauszufinden, wie die Bilder auf aufgeteilt sind, ist die "Gemeinsamkeit" und "Verschiedenheit" der Abhängigkeiten. In diesem Beispiel teilen sich die Entwicklungsaufgaben ein Image, weil sie dieselben Abhängigkeiten haben, wie z. B. Jupyter oder scikit-learn. Die Verteilungsaufgaben werden in ein anderes Image ausgegliedert, weil sie keine dieser Abhängigkeiten benötigen - stattdessen brauchen sie Abhängigkeiten wie gcloud, aws-cli, azure-cli oder Terraform.

Mit diesem mentalen Rahmen im Kopf sind wir bereit, uns in die praktische Übung zu stürzen!

Praktische Übung: Reproduzierbare Entwicklungsumgebungen mit Hilfe von Containern

Gehen wir Schritt für Schritt durch, wie wir Entwicklungsumgebungen in unserem ML-Entwicklungslebenszyklus erstellen und nutzen können:

1. Auschecken und los: Installiere die vorausgesetzten Betriebssystem-Abhängigkeiten.

Führe das Go-Skript für dein Betriebssystem aus.

2. Erstelle eine lokale Entwicklungsumgebung (d. h. ein Build-Image).

Stelle sicher, dass die Docker-Laufzeit gestartet ist (entweder über Docker Desktop oder colima), und führe den folgenden Befehl aus, um die Abhängigkeiten in deinem lokalen Dev-Image zu installieren:

./batect --output=all setup
3. Starte die lokale Entwicklungsumgebung (d.h. starte den Container).

Starte den Container:

./batect start-dev-container

Teste dann, ob alles funktioniert, indem du Smoke-Tests für das Modelltraining durchführst:

scripts/tests/smoke-test-model-training.sh

Zum Schluss beendest du den Container, indem du im Terminal exit eingibst oder die Tastenkombination Strg + D drückst.

4. Stelle das ML-Modell lokal als Web-API bereit.

Starte die API im Entwicklungsmodus:

./batect start-api-locally

Sende dann lokal Anfragen an die API, indem du den folgenden Befehl von einem anderen Terminal außerhalb des Docker-Containers ausführst (er verwendet curl, das wir nicht installiert haben):

scripts/request-local-api.sh
5. Konfiguriere deine IDE so, dass sie die virtuelle Python-Umgebung verwendet, die von den go-Skripten erstellt wurde.

Für die IDEs, die wir für diese Übung empfehlen, gibt es online Anleitungen:

6. Trainiere das Modell in der Cloud.

Dieser Schritt wird zusammen mit Schritt #7 in der CI/CD-Pipeline durchgeführt. Darauf gehen wir später in diesem Abschnitt ein.

7. Setze die Modell-Web-API ein.

Zusammen mit Schritt #6, der in der CI/CD-Pipeline durchgeführt wird.

Für die Ungeduldigen sind diese Schritte in der README des Repositorys auf einen Blick zusammengefasst. Es ist eine gute Angewohnheit, diese Schritte in einer knappen README zusammenzufassen, damit die Mitwirkenden ihre lokale Umgebung leicht einrichten und die üblichen ML-Entwicklungsaufgaben ausführen können. Wir empfehlen dir, diese Schritte jetzt in dem Projekt auszuführen, das du geklont hast, um ein Gefühl für den gesamten Ablauf zu bekommen. Im weiteren Verlauf dieses Abschnitts gehen wir die einzelnen Schritte im Detail durch, damit du die einzelnen Komponenten unserer Entwicklungsumgebung verstehst und sie für dein eigenes Projekt anpassen kannst.

1. Auschecken und loslegen: Vorausgesetzte Abhängigkeiten installieren

Der erste Schritt beim Einrichten unserer lokalen Entwicklungsumgebung besteht darin, das Go-Skript auszuführen, um die erforderlichen Abhängigkeiten auf Host-Ebene zu installieren. Um zu beginnen, klonst du dein geforktes Repository:

$ git clone https://github.com/YOUR_USERNAME/loan-default-prediction

Alternativ kannst du das ursprüngliche Repository klonen, aber du wirst nicht sehen können, dass deine Codeänderungen auf GitHub Actions laufen, wenn du deine Änderungen pushst:

$ git clone https://github.com/davified/loan-default-prediction

Leserinnen und Leser, die auf Mac- oder Linux-Rechnern arbeiten, können das Go-Skript jetzt ausführen. Das kann eine Weile dauern, wenn du einige der Betriebssystem-Abhängigkeiten zum ersten Mal installierst:

# Mac users
$ scripts/go/go-mac.sh

# Linux users
$ scripts/go/go-linux-ubuntu.sh

In diesem Stadium sollten Windows-Nutzer diese Schritte befolgen:

  1. Lade Python3 herunter und installiere es, falls es noch nicht installiert ist. Wähle während der Installation bei der Eingabeaufforderung Python zu PATH hinzufügen.

  2. Gehe im Windows-Explorer/Suche auf App-Ausführungsaliase verwalten und deaktiviere App Installer for Python. Dadurch wird das Problem behoben, dass die ausführbare Datei python nicht im PATH gefunden wird.

  3. Führe das folgende Go-Skript in der PowerShell oder in der Eingabeaufforderung aus:

    .\scripts\go\go-windows.bat

    Wenn du einen HTTPSConnectionPool read timed out Fehler siehst, führe diesen Befehl einfach noch ein paar Mal aus, bis poetry install erfolgreich ist.

Der nächste Schritt ist, unabhängig von deinem Betriebssystem, die Installation von Docker Desktop, falls es noch nicht installiert ist. Während dies im Go-Skript für Mac und Linux in einer Zeile erledigt werden kann (siehe Beispiel Go-Skript für Mac), war es zu kompliziert, um es im Windows-Go-Skript zu automatisieren. Daher haben wir uns entschieden, diesen Schritt aus Gründen der Symmetrie manuell außerhalb des Go-Skripts durchzuführen. Befolge die Online-Installationsschritte von Docker.

Es ist wichtig, dass wir diese Skripte kurz und bündig halten und nicht zu viele Abhängigkeiten auf Host-Ebene installieren. Sonst wird es schwierig, diese Skripte im Laufe der Zeit für mehrere Betriebssysteme zu pflegen. Wir wollen so viele unserer Abhängigkeiten wie möglich in Docker behalten.

2. Unsere lokale Entwicklungsumgebung erstellen

Als Nächstes installieren wir auf alle Abhängigkeiten auf Betriebssystem- und Anwendungsebene, die für die lokale Entwicklung des ML-Modells benötigt werden. Das machen wir mit einem einzigen Befehl: ./batect setup. Wie bereits versprochen, erklären wir an dieser Stelle , wie batect funktioniert. Abbildung 4-2 erklärt die drei Schritte, die hinter den Kulissen ablaufen.

Abbildung 4-2. Was passiert, wenn du eine Batect-Aufgabe ausführst

Wie in Abbildung 4-2 zu sehen ist, führt batect bei der Ausführung von ./batect setup die Aufgabe setup aus, die wir in batect.yml definiert haben. Die Aufgabe setup ist einfach definiert als: run ./scripts/setup.sh im Container dev. Schauen wir uns an, wie dies in batect.yml definiert ist:

# Ensure Docker runtime is started (either via Docker Desktop or colima)

# install application-level dependencies
$ ./batect --output=all setup 
# batect.yml
containers:
 dev: 
   build_directory: .
   volumes:
     - local: .
       container: /code
     - type: cache
       name: python-dev-dependencies
       container: /opt/.venv
   build_target: dev

tasks:
 setup:  
   description: Install Python dependencies
   run:
     container: dev
     command: ./scripts/setup.sh

So führen wir eine Batect-Aufgabe aus (z. B. setup). Die Option--output=all zeigt uns die Protokolle der Aufgabe an, während sie ausgeführt wird. So erhalten wir ein visuelles Feedback, das besonders bei langwierigen Aufgaben wie der Installation von Abhängigkeiten und dem Modelltraining nützlich ist.

Dieser Container-Block definiert unser dev Image. Hier legen wir die Konfigurationen für die Docker-Bau- und -Laufzeit fest, z. B. die zu mountenden Volumes oder Ordner, den Pfad zur Dockerdatei- also build_directory- und die Bauziele für mehrstufige Dockerdateien wie unsere. Sobald batect dieses Dev-Image erstellt hat, wird es von allen nachfolgenden batect-Aufgaben wiederverwendet, die dieses Image angeben (z. B. smoke-test-model-training, api-test und start-api-locally). So müssen wir nicht auf langwierige Rebuilds warten.

Dieser Aufgabenblock definiert unsere setup Aufgabe, die aus zwei einfachen Teilen besteht: welche command ausgeführt werden soll und welche container bei der Ausführung des Befehls verwendet werden soll. Außerdem können wir zusätzliche Konfigurationsoptionen für die Docker-Laufzeit angeben, wie z. B. Volumes und Ports.

Schauen wir uns den zweiten Schritt etwas genauer an und sehen wir uns an, wie wir unser Dockerfile konfiguriert haben:

FROM python:3.10-slim-bookworm AS dev 

WORKDIR /code 

RUN apt-get update && apt-get -y install gcc 

RUN pip install poetry
ADD pyproject.toml /code/
RUN poetry config installer.max-workers 10
ARG VENV_PATH
ENV VENV_PATH=$VENV_PATH
ENV PATH="$VENV_PATH/bin:$PATH" 

CMD ["bash"] 

Wir geben das Basisbild an, das die Grundlage für unser eigenes Bild bilden soll. Das Image python:3.10-slim-bookworm ist 145 MB groß, im Gegensatz zu python:3.10, das 915 MB groß ist. Am Ende dieses Kapitels werden wir die Vorteile der Verwendung kleiner Bilder beschreiben.

Die Anweisung WORKDIR legt ein Standardarbeitsverzeichnis für alle nachfolgenden Anweisungen RUN, CMD, ENTRYPOINT, COPY und ADD in der Dockerdatei fest. Es ist auch das Standard-Startverzeichnis, wenn wir den Container starten. Du kannst es auf jedes beliebige Verzeichnis setzen, solange du konsistent bist. In diesem Beispiel haben wir /code als Arbeitsverzeichnis festgelegt, in dem wir unseren Code ablegen werden, wenn wir den Container im nächsten Schritt starten.

Wir installieren gcc (GNU Compiler Collection) für den Fall, dass die Betreuer einer bestimmten Python-Bibliothek es versäumen, ein Rad für eine bestimmte CPU-Anweisung zu veröffentlichen. Mit gcc können wir selbst dann, wenn ein Python-Paket Räder für einen CPU-Typ (z. B. Intel-Prozessor) hat, die Betreuer es aber versäumt haben, Räder für einen anderen Typ (z. B. M1-Prozessoren) zu veröffentlichen, sicherstellen, dass wir die Räder in diesem Schritt aus den Quellen bauen können.1

In diesem Block installieren und konfigurieren wir Poetry. Wir weisen Poetry an, die virtuelle Umgebung im Projektverzeichnis(/opt/.venv) zu installieren und den Pfad zur virtuellen Umgebung zur Umgebungsvariablen PATH hinzuzufügen, damit wir Python-Befehle in Containern ausführen können, ohne die virtuelle Umgebung aktivieren zu müssen (z. B. mit poetry shell oder poetry run ...).

Schließlich bietet die Anweisung CMD einen Standardbefehl, der ausgeführt wird, wenn wir einen Container starten. In diesem Beispiel startet unser Docker-Image eine Bash-Shell, in der wir unsere Entwicklungsaufgaben ausführen können, wenn es als Container läuft. Das ist nur eine Voreinstellung und wir können diesen Befehl überschreiben, wenn wir unsere Container später starten.

Das Tolle an Docker ist, dass es keine Magie gibt: Du gibst im Dockerfile Schritt für Schritt an, was du im Docker-Image haben willst, und docker build führt jede Anweisung aus und "backt" ein Image auf der Grundlage des von dir angegebenen "Rezepts" (Dockerfile).

3. Unsere lokale Entwicklungsumgebung starten

Jetzt können wir unsere lokale Entwicklungsumgebung betreten, indem wir den Container starten:

# start container (with batect)

$ ./batect start-dev-container 

# start container (without batect). You don’t have to run this command. 
# We’ve included it so that you can see the simplified interface that 
# batect provides

$ docker run -it \ 
      --rm \ 
      -v $(pwd):/code \ 
      -p 80:80 \ 
      loan-default-prediction:dev  

Diese batect-Aufgabe führt unseren Dev-Container aus (d. h. eine containerisierte Bash-Shell, die unsere Entwicklungsumgebung bildet). Die Docker-Laufzeitparameter sind in der batect-Aufgabe gekapselt, wie sie in der Datei batect.yml definiert sind. So können wir die Aufgabe ausführen, ohne die umfangreichen Implementierungsdetails mitzuführen, die du in der docker run Version der gleichen Aufgabe siehst.

-it ist die Abkürzung für -i (--interactive) und -t (--tty, TeleTYpewriter) und ermöglicht es dir, über das Terminal mit dem laufenden Container zu interagieren (d.h. Befehle zu schreiben und/oder Ausgaben zu lesen).

--rm weist Docker an, den Container und das Dateisystem automatisch zu entfernen, wenn der Container beendet wird. Das ist eine gute Angewohnheit, um zu verhindern, dass sich das Dateisystem des Containers auf dem Host anhäuft.

-v $(pwd):/code weist den Container an, ein Verzeichnis (oder Volume) vom Host ($(pwd) gibt den Pfad des aktuellen Arbeitsverzeichnisses zurück) in ein Zielverzeichnis(/code) im Container einzuhängen. Dieses gemountete Volume wird synchron gehalten, so dass alle Änderungen, die du innerhalb oder außerhalb des Containers vornimmst, synchron gehalten werden.

-p X:Y weist Docker an, Port X im Docker-Container auf Port Y auf dem Host zu veröffentlichen. So kannst du Anfragen von außerhalb des Containers an einen Server senden, der innerhalb des Containers auf Port 80 läuft.

Das ist das Bild, das wir zum Starten des Containers verwenden wollen. Da wir in unserem Dockerfile den Standardbefehl zum Ausführen angegeben haben (CMD ["bash"]), ist der resultierende Container ein Bash-Prozess, mit dem wir unsere Entwicklungsbefehle ausführen werden.

In unserem Entwicklungscontainer können wir jetzt Aufgaben oder Befehle ausführen, die wir normalerweise bei der Entwicklung von ML-Modellen verwenden. Um diese Befehle einfach und verständlich zu halten, haben wir die Implementierungsdetails in kurzen Bash-Skripten festgehalten, die du lesen kannst, wenn du willst:

# run model training smoke tests
$ scripts/tests/smoke-test-model-training.sh

# run api tests
$ scripts/tests/api-test.sh

# train model
$ scripts/train-model.sh

Alternativ kannst du diese Befehle auch mit batect auf dem Host ausführen. Dank des Caching-Mechanismus von Docker ist die Ausführung dieser Aufgaben gleich schnell, unabhängig davon, ob du sie von einem Container aus ausführst oder jedes Mal einen neuen Container auf dem Host startest. Diese batect-Tasks machen es einfach, Tasks in unserer CI-Pipeline zu definieren und CI-Fehler lokal zu reproduzieren. So kannst du gängige ML-Entwicklungsaufgaben mit batect ausführen:

# run model training smoke tests
$ ./batect smoke-test-model-training

# run api tests
$ ./batect api-test

# train model
$ ./batect train-model

4. Das ML-Modell lokal als Web-API bereitstellen

Unter starten wir in diesem Schritt unsere Web-API lokal. Die API kapselt unser ML-Modell, delegiert Vorhersageanfragen an das Modell und gibt die Vorhersage des Modells für die jeweilige Anfrage zurück. Die Möglichkeit, die API für manuelle oder automatisierte Tests lokal zu starten, bewahrt uns davor, in das Antipattern "pushing to know if something works" zu verfallen. Dieses Verhaltensmuster ist eine schlechte Angewohnheit, die die Feedback-Zyklen verlängert (von Sekunden bis zu mehreren Minuten), während wir darauf warten, dass Tests und Deployments in der CI/CD-Pipeline laufen, um eine Änderung auch nur in einer einzigen Codezeile zu testen.

So kannst du unsere Web-API lokal starten und mit ihr interagieren:

# start API in development mode
$ ./batect start-api

# send requests to the API locally. Run this directly from the host 
# (i.e. outside the container) as it uses curl, which we haven't 
# installed in our Docker image
$ scripts/request-local-api.sh

5. Konfiguriere unseren Code-Editor

Ein wichtiger Schritt im Abhängigkeitsmanagement ist die Konfiguration unseres Code-Editors für die Verwendung der virtuellen Umgebung des Projekts, damit er uns beim Schreiben von Code effizienter unterstützen kann. Wenn der Code-Editor so konfiguriert ist, dass er eine bestimmte virtuelle Umgebung verwendet, wird er zu einem sehr leistungsfähigen Werkzeug und kann während der Eingabe sinnvolle Hinweise und Vorschläge liefern.

In Kapitel 7 beschreiben wir, wie du das in zwei einfachen Schritten erreichen kannst:

  1. Lege die virtuelle Umgebung in unserem Code-Editor fest. Sieh dir die Anweisungen für PyCharm und VS Code an oder wirf einen Blick auf die Schritte in Kapitel 7 - essollte nur ein paar Minuten dauern.

  2. Nutze die Befehle des Code-Editors und die entsprechenden Tastenkombinationen, um erstaunliche Dinge zu tun (z. B. Code-Vervollständigung, Parameter-Infos, Inline-Dokumentation, Refactoring und vieles mehr). In Kapitel 7 werden wir diese Tastenkombinationen im Detail besprechen .

Für Schritt 1 kannst du den Pfad zu der virtuellen Umgebung verwenden, die das go-Skript auf dem Host installiert hat. Das Go-Skript zeigt dies als letzten Schritt an. Du kannst den Pfad auch abrufen, indem du den folgenden Befehl im Projektverzeichnis außerhalb des Containers ausführst:

$ echo $(poetry env info -p)/bin/python

Dies ist eine zweite - und doppelte - virtuelle Umgebung außerhalb des Containers, weil die Konfiguration eines containerisierten Python-Interpreters für PyCharm ein kostenpflichtiges Feature ist und für VS Code nicht gerade einfach ist. Ja, das ist eine Abweichung von Containern! In der Praxis würden wir für die PyCharm Professional-Lizenz bezahlen, weil sie einfach und relativ kostengünstig ist, und wir würden weiterhin eine einzige containerisierte virtuelle Umgebung für jedes Projekt verwenden. Wir wollten aber nicht, dass der Preis ein Hindernis für unsere Leser ist. Deshalb haben wir uns diesen Workaround ausgedacht, damit jeder mitmachen kann.

6. Modell in der Cloud trainieren

gibt es viele Möglichkeiten, ML-Modelle in der Cloud zu trainieren. Sie reichen von Open-Source- und selbst gehosteten ML-Plattformen wie Metaflow, Kubeflow und Ray bis hin zuverwalteten Diensten wie AWS SageMaker, Google Vertex AI und Azure Machine Learning und vielen anderen. Um dieses Beispiel einfach und verallgemeinerbar zu halten, haben wir uns für die einfachste Option entschieden: Wir trainieren das Modell auf einer CI-Compute-Instanz mit GitHub Actions. Das Trainieren unseres Modells auf der KI-Pipeline bietet zwar nicht die vielen Möglichkeiten und Rechenressourcen einer ML-Plattform, aber für die Zwecke dieser Übung ist es ausreichend.

Das Training unseres Modells mit einer KI-Pipeline ähnelt in einem Punkt dem Training mit diesen ML-Diensten: Wir trainieren ein Modell auf ephemeren Recheninstanzen in der Cloud. Daher können wir Docker verwenden, um die notwendigen Abhängigkeiten auf einer neuen Instanz zu installieren und zu konfigurieren. Du wirst dich wahrscheinlich für eine andere Technologie entscheiden, vor allem, wenn du in großem Maßstab trainieren willst. Die meisten, wenn nicht sogar alle dieser ML-Plattformen unterstützen das Modelltraining in Containern und verfügen über entsprechende Dokumentation.

In unserem Beispiel stellen wir den Trainingscode für unser Modell bereit, indem wir den Code einfach in das Repository pushen.2 Das folgende Codebeispiel erstellt eine CI/CD-Pipeline, die GitHub Actions nutzt, um einen Docker-Befehl zum Trainieren unseres Modells auszuführen, den du auf der Registerkarte GitHub Actions in deinem Forked Repository sehen kannst. Dadurch wird das Modelltraining auf einer CI/CD-Serverinstanz ausgeführt, ohne dass wir uns mit Shell-Skripten herumschlagen müssen, um Abhängigkeiten auf Betriebssystemebene - wie Python 3.x, Python Dev Tools oder gcc - auf der neuen CI-Instanz zu installieren. Das ist der Punkt, an dem Docker wirklich glänzt: Docker abstrahiert die meisten "Bare Metal"-Probleme bei der Ausführung von Code auf einer entfernten Compute-Instanz und ermöglicht es uns, konsistente Laufzeitumgebungen einfach zu reproduzieren.

# .github/workflows/ci.yaml

name: CI/CD pipeline
on: [push]
jobs:
  # ...
  train-model:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Train model
        run: ./batect train-model  

# batect.yml
containers:
  dev:
    ...

tasks:
  train-model:
    description: Train ML model
    run:
      container: dev
      command: scripts/train-model.sh

Dies definiert einen Schritt in unserer CI-Pipeline, um die batect-Aufgabe auszuführen, ./batect train-model.

7. Bereitstellen der Modell-Web-API

Unter werden wir diesen Schritt ausführen: (i) unser Modell-API-Image in einer Container-Registry veröffentlichen und (ii) einen Befehl ausführen, um unseren Cloud-Provider anzuweisen, ein Image mit einem bestimmten Tag bereitzustellen. In diesem Stadium brauchen wir nur noch Abhängigkeiten, die mit der Infrastruktur zu tun haben - z. B. aws-cdk (AWS), gcloud (GCP), azure-cli (Azure), Terraform. Wir brauchen keine der Abhängigkeiten von unserem Entwicklungscontainer. Deshalb ist es am besten, wenn wir ein separates Image angeben, um ein Image als Webservice bereitstellen zu können.

Damit dieses Code-Beispiel einfach und unabhängig vom verwendeten Cloud-Provider verallgemeinerbar ist, haben wir uns entschieden, diesen Schritt mit Pseudocode zu illustrieren:

# .github/workflows/ci.yaml

name: CI/CD pipeline
on: [push]
jobs:  

  # ... other jobs (e.g. run tests)

  publish-image:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Publish image to docker registry
        run: docker push loan-default-prediction-api:${{github.run_number}}

  deploy-api:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Deploy model
        run: ./batect deploy-api
    needs: [publish-image]

# batect.yml
containers:
  deploy-api-container:
    image: google/cloud-sdk:latest 


tasks:
  deploy-api:
    description: Deploy API image
    run:
      container: deploy-api-container
      command: gcloud run deploy my-model-api --image IMAGE_URL 

Pseudocode für: (i) unser Image von unserer CI/CD-Pipeline zu einer Docker-Registry zu pushen und (ii) dieses Image als API bereitzustellen. Normalerweise müssten wir das Image neu taggen, um die spezifische Docker-Image-Registry einzubeziehen, aber wir haben dieses Detail weggelassen, um das Beispiel einfach zu halten.

Für den Deployment-Schritt brauchen wir keine der Abhängigkeiten aus unserem Modelltraining und dem Serving, aber wir brauchen eine Abhängigkeit (z. B. gcloud, aws-cli, azure-cli, Terraform), die uns hilft, unser Image bei einem Container-Hosting-Dienst zu deployen. Ist dir aufgefallen, dass wir kein weiteres Dockerfile angeben müssen? Das liegt daran, dass batect uns erlaubt, Aufgaben mit vorgefertigten Images zu definieren, indem wir die Option image verwenden. Dank Containern und batect können wir diese Aufgabe auf die gleiche Weise auf CI oder auf unserem lokalen Rechner ausführen, indem wir einfach ./batect deploy-api aufrufen.

Pseudocode für die Bereitstellung eines Docker-Images für eine Container-Hosting-Technologie. Ersetze ihn durch den entsprechenden Befehl für den Cloud-Provider, den du verwendest (z. B. AWS, Azure, GCP, Terraform).

Im vorangegangenen Absatz haben wir auf mehrere neue Konzepte wie die Container-Registry und Cloud-Container-Hosting-Dienste hingewiesen. Wenn das zu viel ist, keine Sorge - wir werden diese Bausteine für den Weg eines ML-Modells zur Produktion in Kapitel 9 beschreiben.

Gut gemacht! In diesem Stadium hast du gelernt, wie man zuverlässig konsistente Umgebungen für die Entwicklung und den Einsatz von ML-Modellen schafft. Die Prinzipien, Praktiken und Muster in diesem Code-Repository sind das, was wir in realen Projekten verwenden, um ein neues ML-Projekt-Repository mit bewährten Praktiken einzurichten.

Als Nächstes schauen wir uns zwei weitere wichtige Praktiken an, die dir helfen können, Abhängigkeiten in deinen Projekten sicher zu verwalten.

Sicheres Abhängigkeitsmanagement

2017 hacktenAngreifer dasKreditüberwachungsunternehmen Equifax, indem sie eine Schwachstelle in einer veralteten Abhängigkeit (Apache Struts) ausnutzten, um in ihr System einzudringen. Dadurch wurden die persönlichen Daten von 143 Millionen Amerikanern preisgegeben und kosteten das Unternehmen 380 Millionen US-Dollar. Zu dem Zeitpunkt, als Equifax gehackt wurde, hatten die Entwickler von Apache Struts die Schwachstelle bereits gefunden, veröffentlicht und in einer neueren Version von Apache Struts behoben. Equifax benutzte jedoch immer noch eine ältere Version mit der Sicherheitslücke und hatte somit eine tickende Zeitbombe in seiner Infrastruktur.

Wusstest du, dass es Python-Abhängigkeiten gibt, die das Ausspähen deiner Cloud-Anmeldedaten oder die Ausführung von beliebigem Code ermöglichen? Weißt du, ob deine aktuellen Projekte für diese oder andere Schwachstellen anfällig sind? Nun, wenn wir unsere Abhängigkeiten nicht auf Schwachstellen überprüfen, werden wir es nicht wissen.

Abhängigkeiten auf dem neuesten Stand und frei von Sicherheitslücken zu halten, kann unheimlich mühsam sein, wenn wir dies manuell tun. Die gute Nachricht ist, dass die Technologie zur Erkennung und Behebung von Schwachstellen in unseren Abhängigkeiten in den letzten Jahren erheblich weiterentwickelt wurde und wir sie ohne großen Aufwand in unsere Projekte implementieren können.

In diesem Abschnitt beschreiben wir zwei Praktiken, die uns helfen können, diese Sicherheitsrisiken zu mindern:

  • Unnötige Abhängigkeiten beseitigen

  • Automatisierte Prüfungen und Aktualisierungen für Abhängigkeiten

Zusammen mit dem Grundlagenwissen aus dem vorangegangenen Abschnitt helfen dir diese Praktiken, produktionsreife und sichere ML-Pipelines und -Anwendungen zu erstellen.

Mit diesem Wissen im Hinterkopf wollen wir uns die erste Praxis ansehen: das Entfernen unnötiger Abhängigkeiten.

Unnötige Abhängigkeiten entfernen

Unnötige Abhängigkeiten - in Form von unnötig großen Basisbildern und ungenutzten Abhängigkeiten auf Anwendungsebene - können mehrere Probleme verursachen. In erster Linie vergrößern sie die Angriffsfläche deines Projekts und machen es anfälliger für böswillige Angreifer.

Zweitens verlängern sie die Zeit, die du zum Erstellen, Veröffentlichen und Abrufen deiner Images benötigst. Dadurch verlängert sich nicht nur der Feedback-Zyklus deiner CI/CD-Pipeline, sondern es kann auch deine Fähigkeit beeinträchtigen, schnell auf unerwartete Spitzen im Produktionsverkehr zu reagieren, wenn du ein hohes Verkehrsaufkommen bewältigst.

Schließlich können Abhängigkeiten, die zwar installiert, aber nie verwendet werden, das Projekt unübersichtlich und schwer zu pflegen machen. Selbst wenn die Abhängigkeiten nicht genutzt werden, können ihre transitiven Abhängigkeiten - d.h. Enkelkind-Abhängigkeiten - einen Einfluss (wie Versionseinschränkungen und Installationsfehler aufgrund von Versionsinkompatibilität) auf andere Abhängigkeiten und transitive Abhängigkeiten, die eigentlich benötigt werden, ausüben.

Als Faustregel gilt: Wir sollten:

  • Beginne mit Basisbildern, die so klein wie möglich sind - z.B. könnten wir das Bild python:3.10-slim-bookworm (145 MB) im Gegensatz zu python:3.10 (1 GB, fast siebenmal größer!) verwenden.

  • Entferne Abhängigkeiten, die nicht verwendet werden, aus pyproject.toml

  • Entwicklungsabhängigkeiten aus dem Produktionsimage ausschließen

Zum dritten Punkt: Hier ist ein Beispiel dafür, wie du Docker Multistage Builds nutzen kannst, um Entwicklungsabhängigkeiten aus deinem Produktionsimage auszuschließen. Das Codebeispiel unten hilft uns, die Größe des Docker-Images von 1,3 GB (Dev-Image) auf 545 MB (Production-API-Image) zu reduzieren:3

FROM python:3.10-slim-bookworm AS dev 

WORKDIR /code
RUN apt-get update && apt-get -y install gcc

RUN pip install poetry
ADD pyproject.toml /code/
RUN poetry config installer.max-workers 10

ARG VENV_PATH
ENV VENV_PATH=$VENV_PATH
ENV PATH="$VENV_PATH/bin:$PATH"

CMD ["bash"]

FROM dev AS builder 

COPY poetry.lock /code
RUN poetry export --without dev --format requirements.txt \
    --output requirements.txt

FROM python:3.10-slim-bookworm AS prod 

WORKDIR /code
COPY src /code/src
COPY scripts /code/scripts
COPY artifacts /code/artifacts
COPY --from=builder /code/requirements.txt /code
RUN pip install --no-cache-dir -r /code/requirements.txt
CMD ["./scripts/start-api-prod.sh"]

In der ersten Phase (dev) wird ein Dev-Image erstellt, das batect bei der Ausführung verwendet ./batect setup. Nachdem batect alle Entwicklungsabhängigkeiten installiert hat, wird der Container 1,3 GB groß. Der Code für diesen Schritt ist derselbe wie in den vorangegangenen Dockerfile-Codebeispielen.

Die zweite Stufe (builder) ist eine Zwischenstufe, in der wir mit poetry export eine requirements.txt-Datei erstellen. Diese Datei wird uns in der nächsten und letzten Phase helfen, das Produktionsbild so klein wie möglich zu halten, was wir im nächsten Punkt erklären werden.

In der dritten Phase (prod) installieren wir nur das, was wir für die Produktions-API brauchen. Wir beginnen neu (FROM python:3.10-slim-bookworm) und kopieren nur den Code und die Artefakte, die wir für den Start der API benötigen. Wir installieren die Produktionsabhängigkeiten mit pip und der von Poetry generierten Datei requirements.txt, damit wir Poetry - eine Entwicklungsabhängigkeit - nicht in einem Produktionsimage installieren müssen.

Um das Produktionsimage zu erstellen, können wir den folgenden Befehl ausführen. Wir geben die Zielstufe (prod) an, wenn wir das Image erstellen:

$ docker build --target prod -t loan-default-prediction:prod .

Damit haben wir jetzt die Entwicklungsabhängigkeiten von unserem Produktions-API-Image ausgeschlossen, was unser Deployment-Artefakt sicherer macht und das Pushing und Pulling dieses Images beschleunigt.

Überprüfungen auf Sicherheitslücken automatisieren

Die zweite und wichtigste Maßnahme zur Absicherung unserer Anwendung besteht darin, die Prüfung auf Sicherheitslücken in unseren Abhängigkeiten zu automatisieren. Dazu gibt es drei Komponenten:

  • Automatisierte Prüfung auf Sicherheitslücken auf Betriebssystemebene durch Scannen von Docker-Images

  • Automatisierte Prüfung auf Sicherheitslücken auf Anwendungsebene durch Überprüfung von Abhängigkeiten

  • Automatisierte Aktualisierung von Abhängigkeiten auf Betriebssystem- und Anwendungsebene

Wenn du GitHub nutzt, kannst du all das mit Dependabot machen, einem in GitHub integrierten Dienst zum Scannen von Schwachstellen. Wenn du GitHub nicht nutzt, kannst du die gleiche Funktion auch mit anderen Open-Source-Tools für die Software Composition Analysis (SCA) umsetzen. Du kannst zum Beispiel Trivy verwenden, um Docker-Images und Python-Abhängigkeiten zu scannen, Snyk oder Safety, um nach verwundbaren Python-Abhängigkeiten zu suchen, und Renovate, um Aktualisierungen von Abhängigkeiten zu automatisieren.

SCA-Tools verwenden in der Regel einen ähnlichen Ansatz: Sie überprüfen deine Abhängigkeiten auf bekannte Schwachstellen oder Common Vulnerabilities and Exposures (CVE), indem sie auf eine globale Schwachstellendatenbank wie die National Vulnerability Database(nvd.nist.gov) zurückgreifen. Dependabot oder Renovate erstellen auch PRs in deinem Projekt, wenn sie feststellen, dass eine neuere Version einer bestimmten Abhängigkeit verfügbar ist.

Hinweis

Auch wenn die Überprüfung von Abhängigkeiten auf Sicherheitslücken und die automatische Aktualisierung von Abhängigkeiten uns dabei helfen, unser Risiko für verwundbare Abhängigkeiten deutlich zu reduzieren, kann es Szenarien geben, in denen Abhängigkeiten in öffentlichen Datenbanken für Sicherheitslücken markiert wurden, aber noch keine Korrekturen veröffentlicht wurden. Wenn eine neue Sicherheitslücke gefunden wird, dauert es natürlich eine gewisse Zeit, bis die Entwickler eine Lösung für die Schwachstelle veröffentlichen. Bis ein Fix gefunden wird, werden diese Schwachstellen als "Zero-Day-Schwachstellen" bezeichnet, weil seit der Veröffentlichung des Fixes null Tage vergangen sind.

Um dieses Risiko in den Griff zu bekommen, musst du Sicherheitsspezialisten in deinem Unternehmen zu Rate ziehen, um den Schweregrad der Schwachstellen in deinem Kontext zu bewerten, sie entsprechend zu priorisieren und Maßnahmen zur Risikominderung zu ermitteln.

Schauen wir uns an, wie wir dies in drei Schritten mit Dependabot in unserem GitHub-Repository einrichten können. Dependabot kann Pull Requests für zwei Arten von Updates auslösen: (i) Dependabot-Sicherheitsupdates sind automatisierte Pull Requests, die dir helfen, Abhängigkeiten mit bekannten Schwachstellen zu aktualisieren, und (ii) Dependabot-Versionsupdates sind automatisierte Pull Requests, die deine Abhängigkeiten auf dem neuesten Stand halten, auch wenn sie keine Schwachstellen haben.

Für diese Übung verwenden wir Dependabot Versionsupdates, da die Pull Requests sofort erstellt werden, solange es eine alte Abhängigkeit gibt, auch wenn es keine bekannten Sicherheitslücken gibt. So kannst du leichter mitmachen und siehst nach jedem Schritt das gewünschte Ergebnis.

Der erste Schritt besteht darin, Dependabot für dein Repository oder deine Organisation zu aktivieren. Dazu befolgst du die Schritte in der offiziellen GitHub-Dokumentation zur Aktivierung von Dependabot-Versionsupdates.

Zweitens: Wenn du die Schritte in der offiziellen Dokumentation zur Aktivierung der Dependabot-Versionsaktualisierung ausgeführt hast, wirst du aufgefordert, eine dependabot.yml-Datei im.github-Verzeichnis einzuchecken:

# .github/dependabot.yml

version: 2
updates:
 - package-ecosystem: "pip" 
   directory: "/"
   schedule:
     interval: "daily"

Wir geben das Paket-Ökosystem und das Verzeichnis an, das die Paketdatei enthält. In der offiziellen Dokumentation steht, dass wir pip angeben sollten, auch wenn wir Poetry verwenden. Wir geben auch an, ob Dependabot täglich, wöchentlich oder monatlich nach Updates suchen soll.

Hinweis

Es ist zwar einfach und verlockend, auch hier einen zweiten Update-Block für "docker" hinzuzufügen, aber in der Praxis kann das eine Herausforderung sein, da das Aktualisieren von Python-Versionen (z.B. von Python 3.10 auf 3.12) eine Kaskade von Versionsänderungen in Abhängigkeiten und transitiven Abhängigkeiten verursachen kann.

Trotzdem empfehlen wir, die Python-Version deines ML-Systems auf dem neuesten Stand zu halten, wenn du sicherstellen kannst, dass deine Anwendung und dein Abhängigkeitsstapel mit neueren Python-Versionen kompatibel sind. Eine solche Änderung sollte mit den automatisierten Tests und dem containerisierten Setup, die wir in diesem Buch vorstellen, leicht zu implementieren und zu testen sein.

Der dritte Schritt besteht darin, unser GitHub-Repository so zu konfigurieren, dass PRs nur dann zusammengeführt werden, wenn die Tests auf CI erfolgreich sind. Das ist ein wichtiger Schritt, um zu testen, dass die Änderungen an den Abhängigkeiten die Qualität unserer Software nicht beeinträchtigen. Verschiedene CI-Technologien haben unterschiedliche Möglichkeiten, dies zu tun, und du kannst in der jeweiligen Dokumentation für deine Toolchain nachschlagen. In unserem Beispiel verwenden wir GitHub Actions und zum Zeitpunkt der Erstellung dieses Artikels ist die Reihenfolge der Aktionen wie folgt:

  1. Auto-Merge zulassen. Klicke unter dem Namen deines Repositorys auf Einstellungen. Auf der Seite Einstellungen wählst du unter Pull Requests die Option "Auto-Merge zulassen". (Eine aktuelle Anleitung dazu findest du auch in der GitHub-Dokumentation zur Aktivierung der automatischen Zusammenführung ).

  2. Wir werden einen GitHub Actions Auftrag definieren, um PRs, die von Dependabot erstellt wurden, automatisch zusammenzuführen. Siehe die GitHub-Dokumentation zum Hinzufügen der Auto-Merge-Konfiguration für PRs, die von Dependabot erstellt wurden, und das Codebeispiel unten, das auch im Demo-Repository im .github-Verzeichnis verfügbar ist:

    # .github/workflows/automerge-dependabot.yaml
    
    name: Dependabot auto-merge
    on: pull_request
    
    permissions:
      contents: write
      pull-requests: write
    
    jobs:
      dependabot:
        runs-on: ubuntu-latest
        if: github.actor == 'dependabot[bot]'
        steps:
          - name: Dependabot metadata
            id: metadata
            uses: dependabot/fetch-metadata@v1
            with:
              github-token: "${{ secrets.GITHUB_TOKEN }}"
          - name: Enable auto-merge for Dependabot PRs
            run: gh pr merge --auto --merge "$PR_URL"
            env:
              PR_URL: ${{github.event.pull_request.html_url}}
              GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
  3. Schließlich fügst du unter Einstellungen > Verzweigungen eine Regel zum Schutz der Verzweigung hinzu, indem du das Kästchen "Vor dem Zusammenführen müssen Statusprüfungen bestanden werden" aktivierst, den Namen deiner Verzweigung (z. B. main) angibst und nach dem Namen deines Test-CI-Jobs suchst. In diesem Beispiel ist unser Auftrag train-model, der nach run-tests ausgeführt wird. Siehe GitHub-Dokumentation zum Hinzufügen einer Zweigschutzregel.

Wenn diese Schritte erledigt sind, werden die Abhängigkeiten deines Projekts regelmäßig und automatisch aktualisiert, getestet und zusammengeführt. Hurra! Ein großer Schritt auf dem Weg zu sicherer Software.

Hinweis

Nachdem du diese Schritte durchgeführt hast, wirst du feststellen, dass du deine lokalen Commits nicht mehr auf den Hauptzweig pushen kannst, da wir den Zweigschutz aktiviert haben.

Diejenigen, die an eine trunk-basierte Entwicklung gewöhnt sind, können ihr Team zur Bypass-Liste hinzufügen (siehe die GitHub-Dokumentation zur Umgehung des Zweigschutzes). Dein Team kann weiterhin das schnelle Feedback von CI/CD und trunk-basierter Entwicklung genießen, während die Änderungen von Dependabot durch Pull Requests gehen.

Bitte beachte, dass die Umgehung des Zweigschutzes nur für Repositories möglich ist, die zu einer Organisation gehören.

Klopfe dir mehrmals auf die Schulter! Du hast gerade die Prinzipien und Praktiken angewandt, die wir in realen Projekten einsetzen, um Abhängigkeiten in ML-Projekten effektiv zu verwalten und reproduzierbare, produktionsreife und sichere ML-Pipelines und -Anwendungen zu erstellen.

Fazit

In diesem Kapitel haben wir Folgendes behandelt:

  • Wie "Auschecken und gehen" in der Praxis aussieht und sich anfühlt

  • Wie man Docker, batect und Poetry nutzt, um konsistente, reproduzierbare und produktionsähnliche Laufzeitumgebungen in jedem Schritt des ML-Lifecycles zu schaffen

  • Wie du Sicherheitslücken in deinen Abhängigkeiten erkennst und wie du Abhängigkeiten automatisch auf dem neuesten Stand hältst

Die einzigartigen Herausforderungen des ML-Ökosystems - z. B. große und vielfältige Abhängigkeiten, große Modelle - können uns auf die Probe stellen, wie weit wir mit der Containerisierung unserer Software gehen können. Unserer Erfahrung nach sind Container-Technologien nach wie vor nützlich, aber im Kontext von ML müssen sie durch fortschrittliche Techniken ergänzt werden - z. B. Docker-Cache-Volumes, Batect, automatische Sicherheitsupdates -, damit wir unsere Abhängigkeiten weiterhin effektiv, sicher und mit kurzen Feedback-Zyklen verwalten können.

In den Kapiteln 3 und 4 versuchen wir, diese Prinzipien und Praktiken klar und einfach zu implementieren, damit wir unsere Abhängigkeiten schnell und zuverlässig einrichten und uns auf die Lösung der Probleme konzentrieren können, die wir lösen wollen, anstatt Zeit in der Abhängigkeitshölle zu verschwenden. Richtiges Abhängigkeitsmanagement ist eine tief hängende Frucht, die ML-Teams schon heute ernten können und von der sie in Form von Zeit, Aufwand, Zuverlässigkeit und Sicherheit profitieren.

Im nächsten Kapitel werden wir eine weitere leistungsstarke, grundlegende Praxis effektiver ML-Teams erkunden: automatisierte Tests.

1 Wie bereits in "Kompliziertes Bild: Unterschiedliche CPU-Chips und Befehlssätze" erklärt der Artikel "Why New Macs Break Your Docker Build, and How to Fix It" (Warum neue Macs deinen Docker-Build kaputt machen und wie man es behebt), warum dies passiert und warum es besonders häufig bei neuen Macs mit M1-Chips auftritt.

2 "Deploy" mag wie ein großes, furchteinflößendes Wort klingen, aber es bedeutet einfach den Vorgang, Code oder eine Anwendung von einem Quell-Repository in eine Ziel-Laufzeitumgebung zu verschieben.

3 Wenn wir docker history <image> auf unserem Produktions-Image (545 MB) ausführen, zeigt sich, dass die Python-Abhängigkeiten 430 MB ausmachen. Ein Blick in das Verzeichnis site-packages zeigt, dass die drei größten Beiträge scikit-learn (116 MB), SciPy (83 MB) und pandas (61 MB) sind.

Get Effektive Teams für maschinelles Lernen now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.