Kapitel 4. Kontinuierliche Bereitstellung fürMachine Learning Modelle
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Ist es wirklich die traurige Wahrheit, dass sich die Naturphilosophie (das, was wir heute Wissenschaft nennen) so weit von ihren Ursprüngen entfernt hat, dass sie nur noch Papyrologen zurückgelassen hat - Menschen, die Papier aufnehmen, Papier weglegen und beim Lesen und Schreiben zwar eifrig sind, aber das Greifbare ernsthaft vermeiden? Halten sie den direkten Kontakt mit Daten für wertlos? Sind sie, wie ein Hinterwäldler im Roman Tobacco Road, stolz auf ihre Unwissenheit?
Dr. Joseph Bogen
Als Profisportler hatte ich oft mit Verletzungen zu tun. Verletzungen haben alle möglichen Schweregrade. Manchmal waren es nur Kleinigkeiten, wie eine leichte Kontraktur in meiner linken Kniesehne nach einem intensiven Hürdentraining. Ein anderes Mal waren es ernstere Probleme, wie unerträgliche Schmerzen im unteren Rücken. Leistungssportler können es sich nicht leisten, mitten in der Saison einen freien Tag einzulegen. Wenn der Plan lautet, sieben Tage pro Woche zu trainieren, ist es wichtig, diese sieben Tage durchzuhalten. Wenn du einen Tag auslässt, hat das schwerwiegende Folgen, die das Training bis zu diesem Zeitpunkt beeinträchtigen (oder ganz zunichte machen) können. Ein Training ist wie das Schieben einer Schubkarre bergauf, und wenn du ein Training auslässt, trittst du zur Seite und lässt die Schubkarre bergab fahren. Die Folge davon ist, dass du die Schubkarre wieder aufheben musst, um sie wieder hochzuschieben. Du darfst keine Trainingseinheiten verpassen.
Wenn du verletzt bist und nicht trainieren kannst, ist es genauso wichtig, so schnell wie möglich wieder in Form zu kommen , wie alternative Trainingsmöglichkeiten zu finden. Das heißt, wenn deine Achillessehne schmerzt und du nicht laufen kannst, schau, ob du ins Schwimmbad gehen und den Cardio-Plan weiterführen kannst. Wiederholungen am Berg sind morgen nicht möglich, weil du dir einen Zeh gebrochen hast? Dann schwing dich auf das Fahrrad, um die gleichen Hügel zu bewältigen. Verletzungen erfordern eine Kriegsstrategie; aufgeben und aufhören ist keine Option, aber wenn du dich zurückziehen musst, dann gilt es, sich so wenig wiemöglich zurückzuziehen. Wenn wir keine Kanonen abfeuern können, sollten wir die Kavallerie mitnehmen. Es gibt immer eine Option, und Kreativität ist genauso wichtig wie der Versuch, sich vollständig zu erholen.
Auch die Genesung erfordert eine Strategie, aber mehr noch als eine Strategie erfordert sie eine ständige Bewertung. Da du mit einer Verletzung so viel wie möglich trainierst, ist es wichtig, zu beurteilen, ob die Verletzung schlimmer wird. Wenn du zum Ausgleich aufs Rad steigst, weil du nicht laufen kannst, musst du genau darauf achten, ob das Radfahren die Verletzung verschlimmert. Die ständige Bewertung von Verletzungen ist ein ziemlich simpler Algorithmus:
-
Beurteile jeden Tag als Erstes, ob die Verletzung gleich, schlimmer oder besser ist als am Vortag.
-
Wenn es schlimmer ist, dann vermeide die vorherigen Trainingseinheiten oder verändere sie. Das könnte die Erholung beeinträchtigen.
-
Wenn es dasselbe ist, vergleiche die Verletzung mit der letzten Woche oder sogar dem letzten Monat. Stelle die Frage : "Fühle ich mich gleich, schlechter oder besser als letzte Woche?"
-
Wenn du dich besser fühlst, ist das ein deutliches Zeichen dafür, dass die aktuelle Strategie funktioniert und du solltest damit weitermachen, bis du dich vollständig erholt hast.
Bei einigen Verletzungen musste ich sie in kürzeren Abständen beurteilen (anstatt bis zum nächsten Morgen zu warten). Das Ergebnis der ständigen Bewertung war der Schlüssel zur Genesung. In manchen Fällen musste ich herausfinden, ob eine bestimmte Handlung mir wehtat. Einmal brach ich mir einen Zeh (ich knallte mit ihm gegen die Ecke eines Bücherregals) und überlegte sofort: Kann ich gehen? Habe ich Schmerzen, wenn ich laufe? Die Antwort auf all diese Fragen war ein klares Ja. Ich versuchte an diesem Nachmittag schwimmen zu gehen. In den nächsten Wochen überprüfte ich immer wieder, ob das Laufen ohne Schmerzen möglich war. Schmerz ist kein Feind. Er ist der Indikator, der dir hilft zu entscheiden, ob du weitermachst oder aufhörst und die aktuelle Strategie überdenkst.
Ständige Evaluierung, Änderungen und Anpassungen an das Feedback sowie die Anwendung neuer Strategien, um erfolgreich zu sein, sind genau das, worum es bei Continuous Integration (CI) und Continuous Delivery (CD) geht. Selbst heute, wo Informationen über robuste Bereitstellungsstrategien leicht verfügbar sind, triffst du oft auf Unternehmen, die keine Tests oder eine schlechte Teststrategie haben, um sicherzustellen, dass ein Produkt für eine neue Version bereit ist, oder sogar auf Versionen, die Wochen (und Monate!) brauchen. Ich erinnere mich daran, wie ich versucht habe, eine neue Version eines großen Open-Source-Projekts zu schneiden, und es kam vor, dass es fast eine Woche dauerte. Noch schlimmer war, dass der Leiter der Qualitätssicherung (QA) E-Mails an alle Teamleiter/innen schickte und sie fragte, ob sie sich bereit für eine neue Version fühlten oder weitere Änderungen wünschten.
E-Mails zu verschicken und auf verschiedene Antworten zu warten, ist kein einfacher Weg, um Software zu veröffentlichen. Es ist fehleranfällig und höchst inkonsistent. Die Feedbackschleife, die CI/CD-Plattformen und -Schritte dir und deinem Team gewähren, ist von unschätzbarem Wert. Wenn du ein Problem entdeckst, musst du es automatisieren und es für die nächste Version zum Problem machen. Die ständige Evaluierung ist, genau wie Verletzungen bei Hochleistungssportlern, ein Grundpfeiler von DevOps und absolut entscheidend für den erfolgreichen Einsatz von maschinellem Lernen.
Mir gefällt die Beschreibung von kontinuierlich als Fortbestand oder Wiederholung eines Prozesses. CI/CD werden in der Regel zusammen erwähnt, wenn es um das System geht, das Artefakte erstellt, prüft und bereitstellt. In diesem Kapitel beschreibe ich, wie ein stabiler Prozess aussieht und wie du verschiedene Strategien anwenden kannst, um eine Pipeline für die Überführung von Modellen in die Produktion zu implementieren (oder zu verbessern).
Verpackung für ML-Modelle
Es ist noch gar nicht so lange her, dass ich zum ersten Mal von ML-Paketierungsmodellen gehört habe. Wenn du noch nie etwas von der Paketierung von Modellen gehört hast, ist das kein Problem - das ist alles noch relativ neu, und mit Paketierung ist hier keine spezielle Art von Betriebssystem-Paket wie eine RPM- (Red Hat Package Manager) oder DEB-Datei (Debian Package) mit speziellen Direktiven für die Bündelung und Verteilung gemeint. Es geht darum, ein Modell in einen Container zu packen, um die Vorteile von containerisierten Prozessen zu nutzen, die das Teilen, Verteilen und die einfache Bereitstellung erleichtern. Ich habe die Containerisierung bereits ausführlich in "Container" beschrieben und erklärt, warum es sinnvoll ist, sie für die Operationalisierung von maschinellem Lernen zu nutzen, anstatt andere Strategien wie virtuelle Maschinen zu verwenden, aber es lohnt sich, noch einmal darauf hinzuweisen, dass die Möglichkeit, ein Modell schnell und unabhängig vom Betriebssystem in einem Container auszuprobieren, ein wahr gewordener Traum ist.
Es gibt drei wichtige Merkmale der Verpackung von ML-Modellen in Containern, auf die wir eingehen sollten:
-
Solange eine Container-Laufzeitumgebung installiert ist, ist es mühelos möglich, einen Container lokal zu betreiben.
-
Es gibt viele Möglichkeiten, einen Container in der Cloud einzusetzen, mit der Möglichkeit, ihn je nach Bedarf zu vergrößern oder zu verkleinern.
-
Andere können ihn schnell und einfach ausprobieren und mit dem Container interagieren.
Die Vorteile dieser Eigenschaften sind, dass die Wartung weniger kompliziert wird und die Fehlersuche bei einem nicht funktionierenden Modell lokal (oder sogar in einem Cloud-Angebot) so einfach sein kann wie ein paar Befehle in einem Terminal. Je komplizierter die Einsatzstrategie ist, desto schwieriger wird die Fehlersuche und Untersuchung möglicher Probleme.
In diesem Abschnitt werde ich ein ONNX-Modell verwenden und es in einem Container verpacken, der eine Flask-App bedient, die die Vorhersage durchführt. Ich werde das ONNX-Modell RoBERTa-SequenceClassification verwenden, das sehr gut dokumentiert ist. Nachdem du ein neues Git-Repository angelegt hast, musst du als Erstes herausfinden, welche Abhängigkeiten du brauchst. Nachdem du das Git-Repository angelegt hast, fügst du zunächst die folgende Datei requirements.txt hinzu:
simpletransformers==0.4.0 tensorboardX==1.9 transformers==2.1.0 flask==1.1.2 torch==1.7.1 onnxruntime==1.6.0
Als Nächstes erstellst du ein Dockerfile, das alles in den Container installiert:
FROM python:3.8 COPY ./requirements.txt /webapp/requirements.txt WORKDIR /webapp RUN pip install -r requirements.txt COPY webapp/* /webapp ENTRYPOINT [ "python" ] CMD [ "app.py" ]
Das Dockerfile kopiert die Anforderungsdatei, erstellt ein webapp-Verzeichnis und kopiert den Anwendungscode in eine einzige app.py-Datei. Erstelle die Datei webapp/app.py, um die Sentiment-Analyse durchzuführen. Beginne mit dem Hinzufügen der Importe und allem, was du brauchst, um eine ONNX-Laufzeitsitzung zu erstellen:
from
flask
import
Flask
,
request
,
jsonify
import
torch
import
numpy
as
np
from
transformers
import
RobertaTokenizer
import
onnxruntime
app
=
Flask
(
__name__
)
tokenizer
=
RobertaTokenizer
.
from_pretrained
(
"roberta-base"
)
session
=
onnxruntime
.
InferenceSession
(
"roberta-sequence-classification-9.onnx"
)
Dieser erste Teil der Datei erstellt die Flask-Anwendung, definiert den Tokenizer, der mit dem Modell verwendet werden soll, und initialisiert schließlich eine ONNX-Laufzeitsitzung, für die ein Pfad zum Modell übergeben werden muss. Es gibt eine ganze Reihe von Importen, die noch nicht verwendet werden. Du wirst sie als Nächstes verwenden, wenn du die Flask-Route hinzufügst, um das Live-Inferencing zu aktivieren:
@app.route
(
"/predict"
,
methods
=
[
"POST"
])
def
predict
():
input_ids
=
torch
.
tensor
(
tokenizer
.
encode
(
request
.
json
[
0
],
add_special_tokens
=
True
)
)
.
unsqueeze
(
0
)
if
input_ids
.
requires_grad
:
numpy_func
=
input_ids
.
detach
()
.
cpu
()
.
numpy
()
else
:
numpy_func
=
input_ids
.
cpu
()
.
numpy
()
inputs
=
{
session
.
get_inputs
()[
0
]
.
name
:
numpy_func
(
input_ids
)}
out
=
session
.
run
(
None
,
inputs
)
result
=
np
.
argmax
(
out
)
return
jsonify
({
"positive"
:
bool
(
result
)})
if
__name__
==
"__main__"
:
app
.
run
(
host
=
"0.0.0.0"
,
port
=
5000
,
debug
=
True
)
Die Funktion predict()
ist eine Flask-Route, die die URL /predict aktiviert, wenn die Anwendung läuft. Die Funktion lässt nur POST
HTTP-Methoden zu. Es gibt noch keine Beschreibung der Beispieleingänge und -ausgänge, weil ein wichtiger Teil der Anwendung fehlt: das ONNX-Modell existiert noch nicht. Lade das ONNX-Modell RoBERTa-SequenceClassification lokal herunter und lege es in das Stammverzeichnis des Projekts. So sollte die endgültige Projektstruktur aussehen:
. ├── Dockerfile ├── requirements.txt ├── roberta-sequence-classification-9.onnx └── webapp └── app.py 1 directory, 4 files
Eine letzte Sache, die vor der Erstellung des Containers fehlt, ist die Anweisung, das Modell in den Container zu kopieren. Die Datei app.py verlangt, dass das Modell roberta-sequence-classification-9.onnx im Verzeichnis /webapp existiert. Aktualisiere die Dockerdatei, um dies zu berücksichtigen:
COPY roberta-sequence-classification-9.onnx /webapp
Jetzt hat das Projekt alles, was du brauchst, um den Container zu bauen und die Anwendung zu starten. Bevor wir den Container bauen, sollten wir überprüfen, ob alles funktioniert. Erstelle eine neue virtuelle Umgebung, aktiviere sie und installiere alle Abhängigkeiten:
$ python3 -m venv venv $ source venv/bin/activate $ pip install -r requirements.txt
Das ONNX-Modell befindet sich im Stammverzeichnis des Projekts, aber die Anwendung will es im Verzeichnis/webapp haben. Verschiebe es also in dieses Verzeichnis, damit sich die Flask-App nicht beschwert (dieser zusätzliche Schritt ist nicht nötig, wenn der Container läuft):
$ mv roberta-sequence-classification-9.onnx webapp/
Führe nun die Anwendung lokal aus, indem du die Datei app.py mit Python aufrufst:
$ cd webapp $ python app.py * Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Use a production WSGI server instead. * Debug mode: on * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Als nächstes ist die Anwendung bereit, HTTP-Anfragen zu verarbeiten. Bisher habe ich noch nicht gezeigt, was die erwarteten Eingaben sind. Es werden JSON-formatierte Anfragen mit JSON-Antworten sein. Verwende das Programm curl, um eine Beispiel-Nutzlast zu senden, um die Stimmung zu erkennen:
$ curl -X POST -H "Content-Type: application/JSON" \ --data '["Containers are more or less interesting"]' \ http://0.0.0.0:5000/predict { "positive": false } $ curl -X POST -H "Content-Type: application/json" \ --data '["MLOps is critical for robustness"]' \ http://0.0.0.0:5000/predict { "positive": true }
Die JSON-Anfrage ist ein Array mit einer einzelnen Zeichenkette, und die Antwort ist ein JSON-Objekt mit einem "positiven" Schlüssel, der die Stimmung des Satzes angibt. Nachdem du nun sichergestellt hast, dass die Anwendung läuft und die Live-Vorhersage richtig funktioniert, ist es an der Zeit, den Container lokal zu erstellen, um zu überprüfen, ob dort alles funktioniert. Erstelle den Container und versehe ihn mit einem aussagekräftigen Tag:
$ docker build -t alfredodeza/roberta . [+] Building 185.3s (11/11) FINISHED => [internal] load metadata for docker.io/library/python:3.8 => CACHED [1/6] FROM docker.io/library/python:3.8 => [2/6] COPY ./requirements.txt /webapp/requirements.txt => [3/6] WORKDIR /webapp => [4/6] RUN pip install -r requirements.txt => [5/6] COPY webapp/* /webapp => [6/6] COPY roberta-sequence-classification-9.onnx /webapp => exporting to image => => naming to docker.io/alfredodeza/roberta
Führe nun den Container lokal aus, um mit ihm auf die gleiche Weise zu interagieren, wie wenn du die Anwendung direkt mit Python ausführst. Erinnere dich daran, die Ports des Containers auf den localhost zu mappen:
$ docker run -it -p 5000:5000 --rm alfredodeza/roberta * Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Use a production WSGI server instead. * Debug mode: on * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Sende eine HTTP-Anfrage auf die gleiche Weise wie zuvor. Du kannst wieder das Programm curl verwenden:
$ curl -X POST -H "Content-Type: application/json" \ --data '["espresso is too strong"]' \ http://0.0.0.0:5000/predict { "positive": false }
Wir haben viele Schritte durchlaufen, um ein Modell zu verpacken und es in einen Container zu bekommen. Einige dieser Schritte mögen überwältigend erscheinen, aber schwierige Prozesse sind eine perfekte Gelegenheit, um sie zu automatisieren und die Muster der kontinuierlichen Bereitstellung zu nutzen. Im nächsten Abschnitt werde ich all dies mithilfe von Continuous Delivery automatisieren und diesen Container in einer Container-Registry veröffentlichen, die jeder nutzen kann.
Infrastructure as Code für die kontinuierliche Bereitstellung vonML-Modellen
Kürzlich habe ich bei der Arbeit gesehen, dass einige Test-Container-Images in einem öffentlichen Repository vorhanden waren, die von der Testinfrastruktur häufig verwendet wurden. Container-Images in einer Container-Registry (wie Docker Hub) sind bereits ein großer Schritt in die richtige Richtung für wiederholbare Builds und zuverlässige Tests. Ich stieß auf ein Problem mit einer der Bibliotheken, die in einem Container verwendet wurden, der ein Update benötigte, also suchte ich nach den Dateien, die zur Erstellung dieser Testcontainer verwendet wurden. Sie waren nirgends zu finden. Irgendwann hat ein Techniker sie lokal erstellt und die Images in die Registry hochgeladen. Das stellte ein großes Problem dar, denn ich konnte keine einfache Änderung an dem Image vornehmen, da die Dateien, die für die Erstellung des Images benötigt wurden, verloren gegangen waren.
Erfahrene Containerentwickler können einen Weg finden, die meisten (wenn nicht sogar alle) Dateien zu bekommen, um den Container neu zu bauen, aber das ist nebensächlich. Ein Schritt nach vorne in dieser problematischen Situation ist die Entwicklung einer Automatisierung, die diese Container automatisch aus den bekannten Quelldateien, einschließlich der Dockerdatei, bauen kann. Den Container neu zu bauen oder das Problem zu lösen, den Container zu aktualisieren und erneut in die Registry hochzuladen, ist wie die Suche nach Kerzen und Taschenlampen bei einem Stromausfall, anstatt einen Generator zu haben, der automatisch anspringt, sobald der Strom weg ist. Sei sehr analytisch, wenn Situationen wie die gerade beschriebene eintreten. Anstatt mit dem Finger auf andere zu zeigen und ihnen die Schuld zu geben, solltest du sie als Chance nutzen, den Prozess durch Automatisierung zu verbessern.
Das gleiche Problem tritt beim maschinellen Lernen auf. Wir gewöhnen uns schnell daran, dass die Dinge manuell (und komplex!) sind, aber es gibt immer eine Möglichkeit, sie zu automatisieren. In diesem Abschnitt werden wir nicht noch einmal alle Schritte der Containerisierung durchgehen (das haben wir bereits in "Container" getan ), aber ich werde auf die Details eingehen, die nötig sind, um alles zu automatisieren. Nehmen wir an, wir befinden uns in einer ähnlichen Situation wie der, die ich gerade beschrieben habe und jemand hat einen Container mit einem Modell erstellt, das in Docker Hub lebt. Niemand weiß, wie das trainierte Modell in den Container gekommen ist; es gibt keine Dokumentation, und es müssen Updates durchgeführt werden. Fügen wir noch ein wenig Komplexität hinzu: Das Modell ist in keinem Repository zu finden, aber es lebt in Azure als registriertes Modell. Um dieses Problem zu lösen, sollten wir ein wenig Automatisierung betreiben.
Warnung
Es könnte verlockend sein, Modelle zu einem GitHub-Repository hinzuzufügen. Das ist zwar durchaus möglich, aber GitHub hat (zum Zeitpunkt dieses Artikels) ein hartes Dateilimit von 100 MB. Wenn das Modell, das du verpacken willst, fast so groß ist, kannst du es möglicherweise nicht in das Projektarchiv aufnehmen. Außerdem ist Git (das System zur Versionskontrolle) nicht für die Versionierung von Binärdateien gedacht und hat deshalb den Nebeneffekt, dass große Repositories entstehen.
Im aktuellen Problemszenario ist das Modell auf der Azure ML-Plattform verfügbar und bereits registriert. Ich hatte noch keines, also habe ich RoBERTa-SequenceClassification mit Azure ML Studio schnell registriert. Klicke auf den Abschnitt Modelle und dann auf "Modell registrieren", wie in Abbildung 4-1 gezeigt.
Fülle das in Abbildung 4-2 gezeigte Formular mit den erforderlichen Angaben aus. In meinem Fall habe ich das Modell lokal heruntergeladen und muss es über das Feld "Datei hochladen" hochladen.
Hinweis
Wenn du mehr über die Registrierung eines Modells in Azure erfahren möchtest, beschreibe ich in "Registrierung von Modellen", wie du das mit dem Python SDK machst .
Da sich das trainierte Modell nun in Azure befindet, können wir das gleiche Projekt aus "Packaging for ML Models" wiederverwenden . Alles, was für das (lokale) Live-Inferencing nötig ist, ist bereits erledigt. Erstelle also ein neues GitHub-Repository und füge die Projektinhalte bis auf das ONNX-Modell hinzu. Erinnere dich daran, dass es eine Größenbeschränkung für Dateien in GitHub gibt, daher ist es nicht möglich, das ONNX-Modell in das GitHub-Repository einzufügen. Erstelle eine .gitigore-Datei, um das Modell zu ignorieren und zu verhindern, dass es versehentlich hinzugefügt wird:
*onnx
Nachdem wir den Inhalt des Git-Repositorys ohne das ONNX-Modell gepusht haben, können wir mit der Automatisierung der Modellerstellung und -bereitstellung beginnen. Dazu verwenden wir GitHub Actions, mit denen wir einen kontinuierlichen Bereitstellungsworkflow in einer YAML-Datei erstellen können, der ausgelöst wird, wenn konfigurierbare Bedingungen erfüllt sind. Die Idee ist, dass die Plattform bei jeder Änderung im Hauptzweig des Repository das registrierte Modell aus Azure abruft, den Container erstellt und ihn schließlich an eine Container-Registry sendet. Beginne damit, ein Verzeichnis .github/workflows/ im Stammverzeichnis deines Projekts zu erstellen und füge dann eine main.yml hinzu, die wie folgt aussieht:
name
:
Build and package RoBERTa-sequencing to Dockerhub
on
:
# Triggers the workflow on push or pull request events for the main branch
push
:
branches
:
[
main
]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch
:
Die Konfiguration hat bisher nichts anderes getan, als die Aktion zu definieren. Du kannst eine beliebige Anzahl von Aufträgen definieren, und in diesem Fall definieren wir einen Bauauftrag, der alles zusammenfügt. Füge das Folgende an die Datei main.yml an, die du zuvor erstellt hast:
jobs
:
build
:
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v2
-
name
:
Authenticate with Azure
uses
:
azure/login@v1
with
:
creds
:
${{secrets.AZURE_CREDENTIALS}}
-
name
:
set auto-install of extensions
run
:
az config set extension.use_dynamic_install=yes_without_prompt
-
name
:
attach workspace
run
:
az ml folder attach -w "ml-ws" -g "practical-mlops"
-
name
:
retrieve the model
run
:
az ml model download -t "." --model-id "roberta-sequence:1"
-
name
:
build flask-app container
uses
:
docker/build-push-action@v2
with
:
context
:
./
file
:
./Dockerfile
push
:
false
tags
:
alfredodeza/flask-roberta:latest
Der Bauauftrag besteht aus vielen Schritten. In diesem Fall hat jeder Schritt eine eigene Aufgabe, was eine hervorragende Möglichkeit ist, Fehlerbereiche zu trennen. Wenn alles in einem einzigen Skript enthalten wäre, wäre es schwieriger, mögliche Probleme zu erkennen. Der erste Schritt besteht darin, das Repository auszuchecken, wenn die Aktion ausgelöst wird. Da das ONNX-Modell nicht lokal existiert, müssen wir es von Azure abrufen und uns mit der Azure-Aktion authentifizieren. Nach der Authentifizierung wird das Az-Tool verfügbar gemacht und du musst den Ordner für deinen Arbeitsbereich und deine Gruppe anhängen. Schließlich kann der Auftrag das Modell anhand seiner ID abrufen.
Hinweis
Einige Schritte in der YAML-Datei haben eine uses
Direktive, die angibt, welche externe Aktion (z.B. actions/checkout
) und in welcher Version. Versionen können Zweige oder veröffentlichte Tags eines Repositorys sein. Im Fall von checkout
ist es das Tag v2
.
Wenn alle diese Schritte abgeschlossen sind, sollte das RoBERTa-Sequence-Modell die Grundlage für das Projekt bilden und die nächsten Schritte ermöglichen, den Container richtig zu bauen.
Die Workflow-Datei verwendet AZURE_CREDENTIALS
. Diese werden mit einer speziellen Syntax verwendet, die es dem Workflow ermöglicht, die für das Repository konfigurierten Geheimnisse abzurufen. Bei diesen Anmeldeinformationen handelt es sich um die Service Principal Informationen. Falls du mit dem Dienstprinzipal nicht vertraut bist, wird dies im Abschnitt "Authentifizierung" behandelt . Du brauchst die Konfiguration des Service Principals, der Zugriff auf die Ressourcen im Arbeitsbereich und in der Gruppe hat, in der sich das Modell befindet. Füge das Geheimnis zu deinem GitHub-Repository hinzu, indem du auf Einstellungen, dann auf Geheimnisse und schließlich auf den Link "Neues Repository-Geheimnis" klickst. Abbildung 4-3 zeigt das Formular, das dir beim Hinzufügen eines neuen Geheimnisses angezeigt wird.
Übertrage deine Änderungen in dein Repository und gehe dann auf die Registerkarte Aktionen. Ein neuer Lauf wird sofort geplant und sollte in wenigen Sekunden beginnen. Nach ein paar Minuten sollte alles abgeschlossen sein. In meinem Fall zeigt Abbildung 4-4, dass es fast vier Minuten dauert.
Es gibt nun eine ganze Reihe beweglicher Teile, die zu einem erfolgreichen Auftragsdurchlauf beitragen. Wenn du eine neue Reihe von Schritten (oder Pipelines, wie ich im nächsten Abschnitt erläutern werde) entwirfst, ist es eine gute Idee, die Schritte aufzuzählen und gierige Schritte zu identifizieren. Diese gierigen Schritte sind Schritte, die versuchen, zu viel zu tun und viel Verantwortung zu tragen. Auf den ersten Blick ist es schwer, einen Schritt zu erkennen, der problematisch sein könnte. Zur Pflege eines CI/CD-Auftrags gehört es, die Verantwortlichkeiten der Schritte zu verfeinern und sie entsprechend anzupassen.
Sobald die Schritte identifiziert sind, kannst du sie in kleinere Schritte unterteilen, was dir hilft, die Verantwortung der einzelnen Teile schneller zu verstehen. Ein schnelleres Verständnis bedeutet eine einfachere Fehlersuche, und auch wenn es nicht sofort ersichtlich ist, wirst du davon profitieren, wenn du dir das zur Gewohnheit machst.
Das sind die Schritte, die wir für die Verpackung des RoBERTa-Sequence-Modells haben:
-
Schau dir den aktuellen Zweig des Repositorys an.
-
Authentifiziere dich bei Azure Cloud.
-
Konfiguriere die automatische Installation von Azure CLI-Erweiterungen.
-
Hänge den Ordner an, um mit dem Arbeitsbereich zu interagieren.
-
Lade das ONNX-Modell herunter.
-
Baue den Container für das aktuelle Repo.
Ein letzter Punkt fehlt allerdings noch, und zwar die Veröffentlichung des Containers nach dem Build. Verschiedene Container-Registrierungsstellen verlangen hier unterschiedliche Optionen, aber die meisten unterstützen GitHub-Aktionen, was sehr erfreulich ist. Bei Docker Hub ist es ganz einfach: Du musst nur ein Token erstellen und es zusammen mit deinem Docker Hub-Benutzernamen als GitHub-Projektgeheimnis speichern. Sobald das erledigt ist, kannst du die Workflow-Datei so anpassen, dass sie den Authentifizierungsschritt vor der Erstellung enthält:
-
name
:
Authenticate to Docker hub
uses
:
docker/login-action@v1
with
:
username
:
${{ secrets.DOCKER_HUB_USERNAME }}
password
:
${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
Aktualisiere schließlich den Build-Schritt, um push: true
zu verwenden.
Kürzlich hat GitHub auch ein Container-Registry-Angebot veröffentlicht, das sich ganz einfach in GitHub Actions integrieren lässt. Die gleichen Docker-Schritte können mit kleinen Änderungen und der Erstellung eines PAT (Personal Access Token) verwendet werden. Beginne damit, ein PAT zu erstellen, indem du zu den Einstellungen deines GitHub-Kontos gehst, auf "Entwicklereinstellungen" klickst und dann auf "Persönliche Zugriffstoken". Sobald die Seite geladen ist, klickst du auf "Neues Token generieren". Gib dem Token eine aussagekräftige Beschreibung im Abschnitt "Anmerkung" und stelle sicher, dass er Schreibrechte für Pakete hat, wie in Abbildung 4-5.
Sobald du fertig bist, wird eine neue Seite mit dem eigentlichen Token angezeigt. Dies ist das einzige Mal, dass du den Token im Klartext siehst, also kopiere ihn unbedingt. Als Nächstes gehst du zu dem Repository, in dem sich der Container-Code befindet, und erstellst ein neues Repository-Geheimnis, genauso wie du es mit den Zugangsdaten für den Azure-Dienst gemacht hast. Gib dem neuen Geheimnis den Namen GH_REGISTRY und füge den Inhalt des im vorherigen Schritt erstellten PAT ein. Jetzt kannst du die Docker-Schritte aktualisieren, um das Paket mit dem neuen Token und der Container-Registry von GitHub zu veröffentlichen:
-
name
:
Login to GitHub Container Registry
uses
:
docker/login-action@v1
with
:
registry
:
ghcr.io
username
:
${{ github.repository_owner }}
password
:
${{ secrets.GH_REGISTRY }}
-
name
:
build flask-app and push to registry
uses
:
docker/build-push-action@v2
with
:
context
:
./
tags
:
ghcr.io/alfredodeza/flask-roberta:latest
push
:
true
In meinem Fall ist alfredodeza mein GitHub-Konto, also kann ich es zusammen mit dem flask-roberta-Namen des Repositorys taggen. Diese müssen mit deinem Konto und Repository übereinstimmen. Nachdem du die Änderungen in den Hauptzweig gepusht hast (oder nach dem Zusammenführen, wenn du einen Pull Request gestellt hast), wird der Auftrag ausgelöst. Das Modell sollte von Azure heruntergeladen, in den Container gepackt und schließlich als GitHub-Paket im Container-Registry-Angebot veröffentlicht werden.
Jetzt, da der Container das ONNX-Modell vollautomatisch verpackt und verteilt, indem er das CI/CD-Angebot und die Container-Registry von GitHub nutzt, haben wir das problematische Szenario gelöst, von dem ich zu Beginn des Kapitels ausgegangen bin: Ein Modell muss in einen Container verpackt werden, aber die Container-Dateien sind nicht verfügbar. Auf diese Weise schaffst du Klarheit für andere und für den Prozess selbst. Er ist in kleine Schritte unterteilt und ermöglicht es, den Container zu aktualisieren. Zum Schluss veröffentlichen die Schritte den Container in einer ausgewählten Registry.
Neben dem Verpacken und Veröffentlichen eines Containers kannst du mit CI/CD-Umgebungen noch eine ganze Reihe anderer Dinge tun. CI/CD-Plattformen sind die Grundlage für Automatisierung und zuverlässige Ergebnisse. Im nächsten Abschnitt gehe ich auf andere Ideen ein, die unabhängig von der Plattform gut funktionieren. Wenn du die allgemeinen Muster anderer Plattformen kennst, kannst du die Vorteile dieser Funktionen nutzen, ohne dich um die Implementierungen zu kümmern.
Cloud-Pipelines verwenden
Als ich das erste Mal von Pipelines hörte, dachte ich, sie seien fortschrittlicher als das typische Skripting-Muster (eine prozedurale Reihe von Anweisungen, die einen Build darstellen). Aber Pipelines sind überhaupt keine fortschrittlichen Konzepte. Wenn du schon einmal mit Shell-Skripten in einer Continuous-Integration-Plattform gearbeitet hast, wird dir die Verwendung einer Pipeline ganz einfach erscheinen. Eine Pipeline ist nichts anderes als eine Reihe von Schritten (oder Anweisungen), mit denen ein bestimmtes Ziel erreicht werden kann, z. B. die Veröffentlichung eines Modells in einer Produktionsumgebung, wenn es ausgeführt wird. Eine Pipeline mit drei Schritten zum Trainieren eines Modells kann zum Beispiel so einfach aussehen wie in Abbildung 4-7.
Du könntest dieselbe Pipeline als Shell-Skript darstellen, das alle drei Dinge auf einmal erledigt. Eine Pipeline, in der die Aufgaben getrennt werden, hat mehrere Vorteile. Wenn jeder Schritt eine bestimmte Verantwortung (oder Aufgabe) hat, ist er leichter zu verstehen. Wenn eine einstufige Pipeline, die die Daten abruft, validiert und das Modell trainiert, fehlschlägt, ist nicht sofort klar, warum sie fehlschlägt. Du kannst aber in die Details gehen, dir die Logs ansehen und den tatsächlichen Fehler überprüfen. Wenn du die Pipeline in drei Schritte aufteilst und der Schritt zum Trainieren des Modells fehlschlägt, kannst du den Bereich des Fehlers eingrenzen und schneller zu einer möglichen Lösung kommen.
Tipp
Eine allgemeine Empfehlung, die du auf die vielen Aspekte der Operationalisierung von maschinellem Lernen anwenden kannst, ist die Überlegung, jeden Vorgang für eine zukünftige Fehlersituation zu vereinfachen. Lass dich nicht dazu verleiten, schnell zu handeln und eine Pipeline (wie in diesem Fall) in einem einzigen Schritt einzurichten und zum Laufen zu bringen, weil das einfacher ist. Nimm dir die Zeit, darüber nachzudenken, was es für dich (und andere) einfacher machen würde, eine ML-Infrastruktur aufzubauen. Wenn ein Fehler auftritt und du problematische Aspekte identifizierst, kannst du die Implementierung zurückgehen und sie verbessern. Du kannst die Konzepte von CI/CD auf Verbesserungen anwenden: Die kontinuierliche Bewertung und Verbesserung von Prozessen ist eine solide Strategie für robuste Umgebungen.
Cloud-Pipelines unterscheiden sich nicht von anderen kontinuierlichen Integrationsplattformen, außer dass sie von einem Cloud-Provider gehostet oder verwaltet werden.
Einige Definitionen von CI/CD-Pipelines, denen du begegnen kannst, versuchen, Elemente oder Teile einer Pipeline starr zu definieren. In Wirklichkeit denke ich, dass die Teile der Pipeline locker definiert sein sollten und nicht durch Definitionen eingeschränkt werden. RedHat hat eine schöne Erklärung für Pipelines, die fünf allgemeine Elemente beschreibt: Build, Test, Release, Deploy und Validate. Diese Elemente sind vor allem zum Mischen und Anpassen gedacht, nicht um sie strikt in die Pipeline aufzunehmen. Wenn das Modell, das du baust, nicht bereitgestellt werden muss, ist ein Bereitstellungsschritt zum Beispiel gar nicht nötig. Wenn dein Arbeitsablauf das Extrahieren und Vorverarbeiten von Daten erfordert, musst du dies als weiteren Schritt implementieren.
Da du nun weißt, dass eine Pipeline im Grunde dasselbe ist wie eine CI/CD-Plattform mit mehreren Schritten, sollte es ein Leichtes sein, Operationen des maschinellen Lernens auf eine umsetzbare Pipeline anzuwenden. Abbildung 4-8 zeigt eine recht vereinfachte Pipeline, die aber auch mehrere andere Schritte umfassen kann. Wie ich bereits erwähnt habe, können diese Elemente zu einer beliebigen Anzahl von Operationen und Schritten kombiniert werden.
AWS SageMaker liefert hervorragende Beispiele, die sofort einsatzbereit sind, um komplexe Pipelines zu erstellen, die alles enthalten, was du zum Ausführen mehrerer Schritte brauchst. SageMaker ist eine spezialisierte Plattform für maschinelles Lernen, die nicht nur Schritte in einer Pipeline anbietet, um ein Ziel wie die Veröffentlichung eines Modells zu erreichen. Da sie auf maschinelles Lernen spezialisiert ist, stehen dir Funktionen zur Verfügung, die besonders wichtig sind, um Modelle in die Produktion zu bringen. Diese Funktionen gibt es in anderen gängigen Plattformen wie GitHub Actions nicht, oder wenn doch, dann sind sie nicht so gut durchdacht, weil das Hauptziel von Plattformen wie GitHub Actions oder Jenkins nicht darin besteht, Machine-Learning-Modelle zu trainieren, sondern so allgemein wie möglich zu sein, um die meisten gängigen Anwendungsfälle zu berücksichtigen.
Ein weiteres entscheidendes Problem, das nur schwer zu lösen ist, besteht darin, dass spezielle Maschinen für das Training (z. B. GPU-intensive Aufgaben) in einem allgemeinen Pipeline-Angebot einfach nicht verfügbar oder schwer zu konfigurieren sind.
Öffne SageMaker Studio und gehe zum Abschnitt Komponenten und Register in der linken Seitenleiste und wähle Projekte. Es werden mehrere SageMaker-Projektvorlagen zur Auswahl angezeigt, wie in Abbildung 4-9 dargestellt.
Hinweis
Obwohl die Beispiele für den Einstieg gedacht sind und Jupyter Notebooks zur Verfügung gestellt werden, eignen sie sich hervorragend, um mehr über die einzelnen Schritte zu erfahren und zu lernen, wie du sie an deine eigenen Bedürfnisse anpassen kannst. Nachdem du eine Pipeline-Instanz in SageMaker erstellt, trainiert und schließlich das Modell registriert hast, kannst du die Parameter für die Pipeline durchgehen, wie in Abbildung 4-10.
Ein weiterer wichtiger Teil der Pipeline, der alle beteiligten Schritte zeigt, ist ebenfalls verfügbar, wie in Abbildung 4-11 dargestellt.
Wie du siehst, sind die Vorbereitung der Daten, das Training, die Auswertung und die Registrierung eines Modells Teil der Pipeline. Das Hauptziel besteht darin, das Modell zu registrieren, um es später nach der Paketierung für Live-Inferencing einzusetzen. Es müssen auch nicht alle Schritte in dieser speziellen Pipeline erfasst werden. Du kannst auch andere Pipelines erstellen, die immer dann laufen, wenn ein neu registriertes Modell verfügbar ist. Auf diese Weise ist diese Pipeline nicht an ein bestimmtes Modell gebunden, sondern du kannst sie für jedes andere Modell wiederverwenden, das erfolgreich trainiert und registriert wird. Die Wiederverwendbarkeit von Komponenten und Automatisierung ist eine weitere wichtige Komponente von DevOps, die sich auch bei MLOps bewährt.
Jetzt, da die Pipelines entmystifiziert sind, können wir bestimmte Verbesserungen sehen, die sie robuster machen, indem wir das Ausrollen von Modellen manuell steuern oder sogar das Inferencing von einem Modell zum anderen umschalten.
Kontrollierte Markteinführung von Modellen
Es gibt einige Konzepte aus der Webservice-Bereitstellung, die sich gut auf Strategien für die Bereitstellung von Modellen in Produktionsumgebungen übertragen lassen, z. B. die Erstellung mehrerer Instanzen einer Live-Inferencing-Anwendung, um die Skalierbarkeit zu gewährleisten, und der schrittweise Wechsel von einem älteren zu einem neueren Modell. Bevor wir auf die Details eingehen, die den Kontrollteil der Einführung von Modellen in die Produktion betreffen, lohnt es sich, die Strategien zu beschreiben, bei denen diese Konzepte zum Tragen kommen.
In diesem Abschnitt werde ich zwei dieser Strategien im Detail besprechen. Obwohl diese Strategien ähnlich sind, haben sie bestimmte Verhaltensweisen, die du dir bei der Verteilung zunutze machen kannst:
-
Blau-grüner Einsatz
-
Kanarischer Einsatz
Eine blau-grüne Bereitstellung ist eine Strategie, bei der eine neue Version in die Staging-Umgebung gebracht wird, die mit der Produktionsumgebung identisch ist. Manchmal ist diese Staging-Umgebung die gleiche wie die Produktionsumgebung, aber der Datenverkehr wird anders (oder separat) geleitet. Ohne ins Detail zu gehen, ist Kubernetes eine Plattform, die diese Art der Bereitstellung problemlos ermöglicht, da du die beiden Versionen im selben Kubernetes-Cluster haben kannst, aber den Datenverkehr an eine separate Adresse für die neuere ("blaue") Version leitest, während der Produktionsverkehr weiterhin in die ältere ("grüne") Version geht. Der Grund für diese Trennung ist, dass sie weitere Tests ermöglicht und sicherstellt, dass das neue Modell wie erwartet funktioniert. Sobald diese Überprüfung abgeschlossen ist und bestimmte Bedingungen erfüllt sind, änderst du die Konfiguration, um den Datenverkehr vom aktuellen Modell auf das neue Modell umzuschalten.
Es gibt einige Probleme mit blau-grünen Bereitstellungen, die vor allem damit zusammenhängen, wie kompliziert es sein kann, Produktionsumgebungen zu replizieren. Auch hier ist Kubernetes die perfekte Lösung, denn der Cluster kann dieselbe Anwendung mit verschiedenen Versionen problemlos bereitstellen.
Eine Canary Deployment-Strategie ist etwas aufwändiger und risikoreicher. Abhängig von deinem Vertrauen und der Möglichkeit, die Konfiguration schrittweise zu ändern, ist dies ein guter Weg, um Modelle in die Produktion zu schicken. In diesem Fall wird der Datenverkehr schrittweise zum neueren Modell geleitet , während das vorherige Modell noch Vorhersagen liefert. Die beiden Versionen sind also live und bearbeiten gleichzeitig Anfragen, allerdings in unterschiedlichen Verhältnissen. Der Grund für diesen prozentualen Rollout ist, dass du Metriken und andere Prüfungen aktivieren kannst, um Probleme in Echtzeit zu erfassen und bei ungünstigen Bedingungen sofort ein Rollback durchführen zu können.
Nehmen wir zum Beispiel an, dass ein neues Modell mit besserer Genauigkeit und ohne festgestellte Drift bereit ist, in Produktion zu gehen. Nachdem mehrere Instanzen dieser neuen Version verfügbar sind, um den Datenverkehr zu empfangen, nimmst du eine Konfigurationsänderung vor, um 10% des gesamten Datenverkehrs an die neue Version zu senden. Während der Datenverkehr weitergeleitet wird, bemerkst du eine beängstigende Anzahl von Fehlern bei den Antworten. Die HTTP 500 Fehler zeigen an, dass die Anwendung einen internen Fehler hat. Nach einigen Untersuchungen stellt sich heraus, dass eine der Python-Abhängigkeiten, die das Inferencing durchführen, versucht, ein Modul zu importieren, das verschoben wurde, was eine Ausnahme verursacht. Wenn die Anwendung hundert Anfragen pro Minute erhält, ist der Fehler nur bei zehn von ihnen aufgetreten. Nachdem du die Fehler bemerkt hast, änderst du schnell die Konfiguration, um den gesamten Datenverkehr an die ältere Version zu senden, die derzeit eingesetzt wird. Dieser Vorgang wird auch als Rollback bezeichnet.
Die meisten Cloud-Provider haben die Möglichkeit, Modelle für diese Strategien kontrolliert auszurollen. Obwohl dies kein voll funktionsfähiges Beispiel ist, kann das Azure Python SDK den Prozentsatz des Datenverkehrs für eine neuere Version bei der Bereitstellung festlegen:
from
azureml.core.webservice
import
AksEndpoint
endpoint
.
create_version
(
version_name
=
"2"
,
inference_config
=
inference_config
,
models
=
[
model
],
traffic_percentile
=
10
)
endpoint
.
wait_for_deployment
(
True
)
Das Schwierige daran ist, dass das Ziel eines Kanarienvogel-Einsatzes darin besteht, die Zahl der Zugriffe schrittweise zu erhöhen, bis die traffic_percentile
bei 100% liegt. Die Erhöhung muss gleichzeitig mit der Einhaltung von Auflagen bezüglich der Gesundheit der Anwendung und minimalen (oder null) Fehlerraten einhergehen.
Überwachung, Protokollierung und detaillierte Metriken von Produktionsmodellen (abgesehen von der Modellleistung) sind absolut entscheidend für eine robuste Bereitstellungsstrategie. Ich halte sie für entscheidend für die Bereitstellung, aber sie sind auch ein Grundpfeiler der robusten DevOps-Praktiken, die in Kapitel 6 behandelt werden. Neben dem Monitoring, der Protokollierung und den Metriken, denen ein eigenes Kapitel gewidmet ist, gibt es noch weitere interessante Dinge, die bei der kontinuierlichen Bereitstellung überprüft werden sollten. Im nächsten Abschnitt sehen wir uns einige davon an, die sinnvoll sind und das Vertrauen in die Bereitstellung eines Modells in die Produktion erhöhen.
Prüftechniken für den Einsatz von Modellen
Bis jetzt funktioniert der in diesem Kapitel erstellte Container hervorragend und erfüllt genau unsere Anforderungen: Aus einigen HTTP-Anfragen mit einer sorgfältig erstellten Nachricht in einem JSON-Body wird eine JSON-Antwort erstellt, die die Stimmung vorhersagt. Ein erfahrener Ingenieur für maschinelles Lernen hätte die Genauigkeit und die Erkennung von Abweichungen (die in Kapitel 6 ausführlich behandelt werden) bereits eingebaut, bevor er das Modell verpackt. Gehen wir davon aus, dass dies bereits der Fall ist, und konzentrieren wir uns auf andere hilfreiche Tests, die du durchführen kannst, bevor du ein Modell in die Produktion überführst.
Wenn du eine HTTP-Anfrage an den Container schickst, um eine Vorhersage zu erstellen, müssen mehrere Software-Schichten von Anfang bis Ende durchlaufen werden. Auf einer hohen Ebene sind diese kritisch:
-
Der Kunde sendet eine HTTP-Anfrage mit einem JSON-Body in Form eines Arrays mit einem einzigen String.
-
Es muss ein bestimmter HTTP-PORT(5000) und ein bestimmter Endpunkt(predict) existieren, an den weitergeleitet wird.
-
Die Python-Flask-Anwendung muss die JSON-Payloads empfangen und sie in natives Python laden.
-
Die ONNX-Laufzeitumgebung muss die Zeichenkette auswerten und eine Vorhersage erstellen.
-
Eine JSON-Antwort mit einer HTTP 200 Antwort muss den booleschen Wert der Vorhersage enthalten.
Jeder einzelne dieser übergeordneten Schritte kann (und sollte) getestet werden.
Automatisierte Kontrollen
Beim Zusammenstellen des Containers für dieses Kapitel bin ich auf einige Probleme mit dem Python-Modul onnxruntime gestoßen: In der Dokumentation wird keine genaue Versionsnummer angegeben, was dazu führte, dass die neueste Version installiert wurde, die andere Argumente als Eingabe benötigte. Die Genauigkeit des Modells war gut, und ich konnte keine signifikante Abweichung feststellen. Trotzdem habe ich das Modell eingesetzt, nur um festzustellen, dass es völlig kaputt ist, sobald die Anfragen verbraucht sind.
Mit der Zeit werden die Anwendungen besser und widerstandsfähiger. Ein anderer Ingenieur könnte eine Fehlerbehandlung hinzufügen, um mit einer Fehlermeldung zu reagieren, wenn ungültige Eingaben erkannt werden, und vielleicht mit einer HTTP-Antwort mit einem entsprechenden HTTP-Fehlercode zusammen mit einer netten Fehlermeldung, die der Kunde verstehen kann. Du musst diese Arten von Ergänzungen und Verhaltensweisen testen, bevor du ein Modell in die Produktion gibst.
Manchmal gibt es keine HTTP-Fehlerbedingung und auch keine Python-Tracebacks. Was würde passieren, wenn ich eine Änderung wie die folgende an der JSON-Antwort vornehmen würde?
{
"positive"
:
"false"
}
Kannst du den Unterschied erkennen, ohne auf die vorherigen Abschnitte zurückzublicken? Die Änderung würde unbemerkt bleiben. Die Strategie für den Einsatz von Kanarienvögeln würde zu 100 % funktionieren, ohne dass ein Fehler entdeckt wird. Der Ingenieur für maschinelles Lernen würde sich über eine hohe Genauigkeit und keine Abweichung freuen. Und doch hat diese Änderung die Effektivität des Modells völlig zerstört. Wenn du den Unterschied nicht bemerkt hast, ist das in Ordnung. Ich stoße ständig auf diese Art von Problemen, und es kann manchmal Stunden dauern, bis ich das Problem entdecke: Statt false
(ein boolescher Wert) wird "false"
(ein String) verwendet.
Keine dieser Prüfungen sollte jemals manuell erfolgen; die manuelle Überprüfung sollte auf ein Minimum beschränkt werden. Die Automatisierung sollte eine hohe Priorität haben, und die Vorschläge, die ich bisher gemacht habe, können alle in die Pipeline aufgenommen werden. Diese Prüfungen können für andere Modelle verallgemeinert und wiederverwendet werden, aber auf hohem Niveau können sie parallel laufen, wie inAbbildung 4-12 gezeigt.
Linting
Neben einigen der erwähnten Funktionsprüfungen, wie dem Senden von HTTP-Anfragen, gibt es auch andere Prüfungen, die näher am Code der Flask-App liegen und viel einfacher zu implementieren sind, wie die Verwendung eines Linters(ich empfehle Flake8 für Python). Am besten wäre es, all diese Prüfungen zu automatisieren, um zu verhindern, dass du Probleme bekommst, wenn die Produktionsversion veröffentlicht werden soll. Unabhängig von der Entwicklungsumgebung, in der du arbeitest, empfehle ich dringend, einen Linter für das Schreiben von Code zu aktivieren. Bei der Erstellung der Flask-Anwendung bin ich auf Fehler gestoßen, als ich den Code für HTTP-Anfragen angepasst habe. Hier ist ein kurzes Beispiel für die Ausgabe des Linters:
$ flake8 webapp/app.py webapp/app.py:9:13: F821 undefined name 'RobertaTokenizer'
Undefinierte Namen machen Anwendungen kaputt. In diesem Fall habe ich vergessen, die RobertaTokenizer
aus dem Modul transformers
zu importieren. Sobald ich das bemerkte, fügte ich den Import hinzu und korrigierte ihn. Das dauerte nicht länger als ein paar Sekunden.
Je früher du diese Probleme erkennen kannst, desto besser. Wenn man über die Sicherheit von Software spricht, hört man oft den Begriff "Software-Lieferkette", wobei die Kette alle Schritte von der Entwicklung bis zur Auslieferung des Codes in die Produktion umfasst. Und in dieser Kette von Ereignissen gibt es einen ständigen Drang, nach links zu gehen. Wenn du dir diese Schritte als eine große Kette vorstellst, ist das ganz linke Glied der Entwickler, der die Software erstellt und aktualisiert, und das Ende der Kette (das am weitesten rechts liegt) ist das freigegebene Produkt, mit dem der Endverbraucher arbeiten kann.
Je früher du die Fehlererkennung nach links verschieben kannst, desto besser. Denn das ist billiger und schneller, als zu warten, bis es in der Produktion ist, wenn ein Rollback gemacht werden muss.
Kontinuierliche Verbesserung
Vor ein paar Jahren war ich der Release-Manager einer großen Open-Source-Software. Die Freigabe der Software war so kompliziert, dass ich dafür zwischen zwei Tagen und einer ganzen Woche brauchte. Es war schwierig, Verbesserungen vorzunehmen, da ich auch für andere Systeme verantwortlich war. Einmal, als ich versuchte, eine Version zu veröffentlichen und dabei die vielen verschiedenen Schritte zur Veröffentlichung der Pakete zu befolgen, bat mich ein Hauptentwickler, eine letzte Änderung vorzunehmen. Anstatt sofort "Nein" zu sagen, fragte ich: "Ist diese Änderung schon getestet worden?"
Die Antwort war völlig unerwartet: "Mach dich nicht lächerlich, Alfredo, das ist eine einzeilige Änderung, und es ist ein Dokumentationskommentar in einer Funktion. Wir müssen diese Änderung unbedingt in das Release aufnehmen." Der Druck, die Änderung einzubringen, kam von ganz oben, und ich musste nachgeben. Ich fügte die Änderung in letzter Minute ein und kürzte die Veröffentlichung.
Gleich am nächsten Morgen meldeten sich die Nutzer (und vor allem die Kunden) und beschwerten sich, dass die neueste Version völlig kaputt war. Sie ließ sich zwar installieren, aber sie lief nicht mehr. Schuld daran war eine einzeilige Änderung, die, obwohl sie ein Kommentar innerhalb einer Funktion war, von anderem Code analysiert wurde. Der Kommentar enthielt eine unerwartete Syntax, die den Start der Anwendung verhinderte. Mit dieser Geschichte soll der Entwickler nicht gezüchtigt werden. Er wusste es nicht besser. Der ganze Prozess war ein lehrreicher Moment für alle Beteiligten, und es war nun klar, wie teuer diese einzeilige Änderung war.
Es folgten eine Reihe von störenden Ereignissen. Abgesehen davon, dass ich den Veröffentlichungsprozess neu starten musste, dauerte die Testphase für diese eine Änderung einen weiteren (zusätzlichen) Tag. Schließlich musste ich die freigegebenen Pakete zurückziehen und die Repositories neu erstellen, damit neue Nutzer die vorherige Version erhalten.
Das war mehr als kostspielig. Die Anzahl der betroffenen Personen und die großen Auswirkungen waren eine hervorragende Gelegenheit, um darauf hinzuweisen, dass dies nicht noch einmal zugelassen werden sollte - auch wenn es sich nur um eine Änderung in einer Zeile handelt. Je früher der Fehler entdeckt wird, desto geringer sind die Auswirkungen und desto billiger ist es, ihn zu beheben .
Fazit
Kontinuierliche Lieferung und die Praxis des ständigen Feedbacks sind entscheidend für einen robusten Arbeitsablauf. Wie dieses Kapitel beweist, ist die Automatisierung und kontinuierliche Verbesserung der Feedbackschleife von großem Wert. Paketierungscontainer sowie Pipelines und CI/CD-Plattformen im Allgemeinen sollen es einfacher machen, weitere Prüfungen undVerifizierungen hinzuzufügen, die das Vertrauen in die Auslieferung von Modellen in dieProduktion erhöhen sollen.
Modelle in die Produktion zu überführen ist das oberste Ziel, aber das solltest du mit einem sehr hohen Maß an Vertrauen und in einer Reihe von stabilen Schritten tun. Deine Aufgabe endet nicht, sobald die Prozesse eingerichtet sind. Du musst immer wieder Wege finden, um dich später zu bedanken, indem du dir die Frage stellst: Was kann ich heute hinzufügen, um mir das Leben leichter zu machen, wenn dieser Prozess fehlschlägt? Abschließend empfehle ich dir, diese Arbeitsabläufe so zu gestalten, dass es einfach ist, weitere Kontrollen und Überprüfungen hinzuzufügen. Wenn es zu schwierig ist, wird sich niemand damit befassen wollen, was den Zweck einer robusten Pipeline für die Übergabe von Modellen an dieProduktion zunichte macht.
Jetzt, wo du ein gutes Verständnis dafür hast, wie Modelle geliefert werden und wie die Automatisierung aussieht, werden wir uns im nächsten Kapitel mit AutoML und Kaizen beschäftigen.
Übungen
-
Erstelle deine eigene Flask-Anwendung in einem Container, veröffentliche sie in einem GitHub-Repository, dokumentiere sie sorgfältig und füge GitHub-Aktionen hinzu, um sicherzustellen, dass siekorrekt gebaut wird.
-
Nimm Änderungen am ONNX-Container vor, damit er zu Docker Hub anstatt zu GitHub Packages pusht.
-
Ändere eine SageMaker-Pipeline so, dass du eine Eingabeaufforderung erhältst, bevor du das Modell nach dem Training registrierst.
-
Erstelle mit dem Azure SDK ein Jupyter-Notebook, das den Prozentsatz des Datenverkehrs in einem Container erhöht.
Kritisches Denken - Diskussionsfragen
-
Nenne mindestens vier kritische Prüfungen, die du hinzufügen kannst, um zu überprüfen, ob ein verpacktes Modell in einem Container korrekt gebaut wurde.
-
Was sind die Unterschiede zwischen kanarischen und blau-grünen Einsätzen? Welche Variante bevorzugst du? Und warum?
-
Warum sind Cloud-Pipelines im Vergleich zu GitHub Actions sinnvoll? Nenne mindestens drei Unterschiede.
-
Was bedeutet es, einen Container zu verpacken? Warum ist es nützlich?
-
Was sind drei Merkmale von Paketmodellen für maschinelles Lernen?
Get Praktische MLOps 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.