12. Februar 2021
10 Min.

Ausrollen und Betreiben von ML-Modellen in Produktion mit KFServing Teil 2

Dieser Blogpost ist der zweite Teil über das Ausrollen und Betreiben von ML-Modellen in Produktion mit KFServing. Der erste Teil hat das serverlose Ausrollen eines ML-Empfehlungsdienstes, Autoscaling, Traffic Management und Versionsverwaltung behandelt. Jetzt betrachten wir das fachliche und technische Monitoring, Feedback Loops und Preprocessing sowie Postprocessing des ML-Modells als Service.

Betrieb in Produktion

Für das erfolgreiche Betreiben eines ML-Modells in Produktion sind je nach Anwendungsfall unterschiedliche Gesichtspunkte zu beachten. Eine elementare Komponente für jeden ML Use Case ist das Monitoring. Das Monitoring lässt sich in die technische und fachliche Überwachung unterteilen. Bei der technischen Komponente geht es im Wesentlichen um das Performance Monitoring des gesamten Clusters, einzelner Nodes, Pods und der Services. Das fachliche Monitoring hingegen ist stark vom Anwendungsfall abhängig. So sind Bestandteile wie ein Model Performance Monitoring, Outlier Detection, Concept Drift Detection oder Adversarial Detection zu erwägen.

Fachliches Monitoring

Für den vorgestellten Recommender im ersten Teil, der auf der Basis einer User-Session das nächste Produkt empfehlen soll, werden wir die Komponenten Model Performance Monitoring, Outlier Detection und Concept Drift Detection näher beleuchten und ausrollen. Die Architektur dafür sieht folgendermaßen aus:

Architektur fachliches Monitoring

Architektur fachliches Monitoring

Diese Architektur beruht auf den Best-Practices von KFServing und Seldon. KFServing arbeitet eng mit Seldon zusammen, um unter anderem die Themen des fachlichen Monitorings umzusetzen. Seldon benutzt dafür Algorithmen von der Python-Bibliothek Alibi-Detect, die verschiedene Verfahren für unterschiedliche Datenstrukturen zur Outlier Detection, Adversarial Detection oder Concept Drift Detection zur Verfügung stellt. Eine Übersicht über alle Verfahren und unterstützte Datenstrukturen ist hier zu finden. Für diese sehr auf den Anwendungsfall angepassten Komponenten gibt es leider keine fertigen YAML-Dateien zum Ausrollen. Hier muss eine gewisse Eigenentwicklung betrieben werden, wobei grundlegende Kenntnisse über die Verfahren zu empfehlen sind. Der Code für die komplette Umsetzung der Architektur liegt in diesem zugehörigen Repository.

In der Architektur starten wir bei den Anfragen in Form einer User-Session, die an den ML-Recommender geschickt werden. Diese durchläuft zuerst ein Preprocessing, bei dem beispielsweise die Session-ID verarbeitet und Standardisierungen sowie die Erstellung von Text-Embeddings der Produktkategorien vollzogen werden. Danach können die für das Modell zugeschnittenen Daten in den eigentlichen Recommender gegeben werden. Dieser gibt eine Produktempfehlung an das Postprocessing. Das Postprocessing kann zum Beispiel die Empfehlung mit der Session-ID anreichern, um den Rückschluss auf die Session für das Monitoring zu ermöglichen. Dann wird die Produktempfehlung zurückgegeben an den Aufrufer, der voraussichtlich das Backendsystem des fiktiven Webshops wäre. Zudem ermöglicht KFServing mit dem Einfügen von 3 Zeilen in der Deployment-Datei eine Weiterleitung sowohl der Anfragen als auch der Antworten. Das Ziel der Weiterleitung können wir mit einer URL bestimmen:


In unserem Fall werden die Requests und Responses an einen Broker des Knative-Eventing Ökosystems gesendet. Knative besteht aus der Serving und der Eventing Komponente. Knative-Serving ist als fester Bestandteil des KFServing-Frameworks vorhanden und dient unter anderem für das Autoscaling der Pods. Knative-Eventing muss als Erweiterung zusätzlich installiert werden. Installationsanweisungen dafür sind hier beschrieben. Der Knative Broker leitet die Nachrichten an beliebig viele weitere Komponenten weiter. Dazu muss für jede Komponente ein sogenannter Trigger eingerichtet werden:


In diesem Trigger für die Outlier-Detection Komponente benötigen wir nur die Requests und bestimmen mit der URI das Ziel. Damit haben wir mithilfe von Knative-Eventing und ein paar zusätzlichen Zeilen in den Deployment-Dateien eine asynchrone, event-basierte Architektur für das Monitoring geschaffen. Alle Anfragen und Antworten werden automatisch zur Model Performance, Outlier Detection und Concept Drift Detection übermittelt. Da diese Komponenten unter anderem auch aus ML-Modellen bestehen, werden sie wie den Recommender als KFServing InferenceService ausrollen. Das bringt viele Vorteile durch die Features von KFServing wie beispielsweise dem Autoscaling mit sich. Des Weiteren müssen wir nicht von Grund auf einen Service mit Schnittstelle entwickeln, sondern können auf ein Grundgerüst zurückgreifen, was eine Standardisierung und Vereinfachung bedeutet. In den nächsten Abschnitten betrachten wir die einzelnen Komponenten genauer. Die Ergebnisse des fachlichen Monitorings (Metriken) werden in einer Zeitreihendatenbank wie beispielsweise Prometheus oder InfluxDB persistent gespeichert, um darauf Alarme und Dashboards einrichten zu können, wie wir später sehen werden.

Model Performance Monitoring

Die Qualität der Empfehlungen des Modells in Produktion zu überwachen ist ein absolutes Muss. Wir möchten jederzeit den Überblick darüber haben, ob das aktuelle Modell etwa sehr schlechte Empfehlungen ausgibt oder sich ein Bias im Modell manifestiert hat, wodurch gewisse Klassen pauschal über andere bevorzugt werden. Dazu bieten sich je nach Use Case unterschiedliche Metriken an. Bei Klassifikationsaufgaben wie unserer sind das beispielsweise die Accuracy, Precision, Recall und der F1-Score. Falls nicht zu viele verschiedene Klassen existieren, lohnt sich auch das Runterbrechen auf einzelne Klassen. Oft liegt bei echten Daten ein Übergewicht bzw. eine ungleiche Verteilung der Klassen vor, was im Training zum bereits erwähnten Bias führen kann. In einem realen Szenario würden die Empfehlungen zu einem späteren Zeitpunkt mit den tatsächlich angeschauten Artikeln abgeglichen werden. Ich vereinfache das aus Demonstrationszwecken und übermittle die richtige Empfehlung mit der Session, damit die Performance-Metriken direkt erhoben werden können.

Wie bereits angekündigt werden alle Komponenten des fachlichen Monitorings als InferenceService von KFServing ausgerollt. Den Python-Code dafür werden wir hier nicht im Detail anschauen, da das den Rahmen sprengen würde. Das Grundgerüst besteht aus einer benutzerdefinierten Klasse von „kfserving.KFModel“, in der man die Methoden „predict“, „preprocess“ und „postprocess“ überschreibt, sodass eigene Logik implementiert werden kann. Diese eigene Klasse wird an einen „kfserving.KFServer“ übergeben, der mit einem „start“-Befehl hochgefahren wird. Den kompletten Code mitsamt den benötigten Bibliotheken verpackt man als Docker-Image und lädt dieses in eine private oder öffentliche Container-Registry hoch. Jetzt kann das Model Performance Monitoring wie der Recommender per YAML-Datei ausgerollt werden:


In Zeile 10 geben wir den Pfad zu unserem Dockerimage an. In diesem Fall habe ich das Image in eine private Container-Registry von Gitlab hochgeladen. Um diese nutzen zu können, wird ein sogenanntes „imagePullSecret“ mit den Namen „gitlab“ zur Authentifizierung eingesetzt. Zum Schluss wird ein Trigger, wie oben beschrieben, mit der URI dieses Service ausgerollt und fertig ist das Model Performance Monitoring.

Outlier Detection

Eine Anomalie-Erkennung dient zur Erkennung von außergewöhnlichen Daten – in unserem Fall User-Sessions. Beispielsweise wäre eine Session auffällig, wenn über eine längere Serie zufällig Produkte von jeglichen Kategorien angeschaut werden. Das entspricht nicht dem typischen Verhalten eines Menschen, der sich meistens für ähnliche Produkte interessiert und nicht komplett zufällige Artikel anschaut. Über das Auftreten solcher Sessions oder auch anderen Anomalien möchten wir den Überblick behalten und diese dokumentieren.

Im Bereich der Outlier Detection gibt es sehr viele unterschiedliche Verfahren sowohl aus der Statistik als auch dem maschinellen Lernen. KFServing verweist auf die Alibi-Detect Bibliothek, die eine Sammlung unterschiedlicher Verfahren bereithält, sodass man nicht selbst den kompletten Algorithmus schreiben muss. Für unseren Anwendungsfall liegt jedoch eine sehr spezielle Datenstruktur vor. Jedes angeklickte Produkt ist eine Klasse. Damit ist die User-Session eine variierende Sequenz von Klassen, was die Anomalie-Erkennung zu einer Herausforderung macht. Für diese Aufgabe eignet sich ein sogenannter Autoencoder aus dem Bereich des Machine Learnings. Dieser ist sehr flexibel, was die Datenstruktur angeht. Die wesentliche Funktionsweise ist leicht zu erklären:

Architektur Autoencoder

Architektur Autoencoder

Die Sessions werden als Input in den Autoencoder gegeben. Der Autoencoder besteht aus einem Encoder und Decoder, die eine Embedding und LSTM-Schichten enthalten. Der Encoder kodiert die Sessions in einen Vektor mit festgelegter Größe. Diesen Vektor nennt man den Latent Space und er fungiert als Flaschenhals. Aus diesen Kodierungen muss nämlich der Decoder wieder die ursprüngliche Session rekonstruieren. Somit ist die Aufgabe des Encoders, Strukturen und Muster in den Daten besonders effizient zu kodieren, damit der Decoder daraus möglichst die gleiche Session wiederherstellen kann. Hinter dem Flaschenhals steht die Intention, dass Sessions, die nicht dem normalen Muster der Daten entsprechen, wesentlich schwieriger zu rekonstruieren sind. Anhand des sogenannten Rekonstruktionsfehlers wird gemessen, wie gut die rekonstruierte Session mit der tatsächlichen übereinstimmt. Falls der Unterschied nach dem Training sehr groß ist, leitet man daraus eine Anomalie ab.

Der gesamte Code zum Trainieren des Autoencoders ist in diesem Jupyter-Notebook zu finden. Wir gehen hier nicht weiter darauf ein, sondern fahren mit dem fertig trainierten Autoencoder fort. Diesen speichern wir in einem AWS S3-Bucket wie den Recommender. Als nächstes inspizieren wir die Ergebnisse des Autoencoders bei unseren Trainingsdaten. Dazu betrachten wir exemplarisch drei Sessions mit hohen Rekonstruktionsfehlern:

Beispiele von nachgewiesenen Anomalien

Beispiele von nachgewiesenen Anomalien

Die ersten zwei Sessions sind aus den Trainingsdaten und die dritte ist eine künstlich erzeugte. Die Spalte „Count“ gibt an, wie oft ein Artikel im gesamten Datensatz vorkommt. In allen drei Beispielen ist gut zu erkennen, dass sich die angeklickten Artikel innerhalb einer Session stark unterscheiden und auch nicht in dieselbe Produktkategorie fallen. Damit weichen sie von einer typischen Sequenz ab, wo ein Benutzer ähnliche Artikel betrachtet. Typischerweise würde man diese Anomalien aus den Trainingsdaten entfernen.

Zusammen mit dem fertigen Autoencoder benötigen wir noch ein Pre- und Postprocessing:

Outlier Detection Architektur

Outlier Detection Architektur

Die weitergeleiteten Anfragen vom Knative Broker gelangen zuerst in eine Preprocessing-Schicht. Diese speichert temporär die Session, damit diese später zur Berechnung des Rekonstruktionsfehlers benutzt werden kann. Danach folgt der Aufruf des Autoencoders. Als Rückgabe erhalten wir die rekonstruierte Session. Im Postprocessing wird die zwischengespeicherte tatsächliche Session mit der rekonstruierten verglichen und daraus der Rekonstruktionsfehler berechnet. Falls dieser Fehler einen gewissen Threshold übersteigt, wird die betreffende Session als Anomalie eingestuft und das Ergebnis in der InfluxDB festgehalten. Das Bestimmen des Thresholds kann auf mehrere Weisen erfolgen. Beispielsweise kann man die Rekonstruktionsfehler über den gesamten Datensatz visualisieren und darauf basierend den Schwellenwert festlegen.

Der gesamte Code für das Pre- und Postprecessing mit den benötigten Paketen wird als Docker-Image verpackt und in eine Container-Registry hochgeladen. Die YAML-Datei zum Ausrollen der Outlier Detection sieht folgendermaßen aus:


Ab Zeile 15 wird der Autoencoder als TensorFlow-Modell ausgerollt, wie wir es bereits aus dem ersten Teil kennen. In der Zeile 7-13 befindet sich die Definition für den Transformer. Das wichtigste ist der Pfad in Zeile 9 zu der Container-Registry, wo das gepackte Image liegt. Wie beim Model Performance Monitoring muss für die private Container-Registry ein „imagePullSecret“ hinterlegt werden. Als letztes wird der Trigger ausgerollt, den ich als Beispiel bei der Erläuterung der Architektur des fachlichen Monitorings aufgezeigt habe.

Zusammengefasst beinhaltet das Ausrollen der Outlier Detection diese Schritte:

  1. Trainieren des Autoencoder zur Rekonstruktion der Sessions
  2. Hochladen des Autoencoders in ein Cloud Storage (z.B. AWS S3 Bucket)
  3. Festlegen eines Thresholds des Rekonstruktionsfehlers für Anomalien
  4. Verpacken des Python-Codes und der Pakete als Docker-Image
  5. Hochladen des Images in eine Container-Registry
  6. Outlier Detection Komponente über „kubectl“ ausrollen
  7. Trigger für den Knative Broker ausrollen

Concept Drift Detection

Nachdem die Outlier Drift Detection detailliert beschrieben wurde, fasse ich mich bei der Concept Drift Detection kürzer. Der Hintergrund der Concept Drift Detection liegt darin, dass sich die Daten über die Zeit stark verändern können. Statistisch gesehen wird dabei von der Verteilung der Daten gesprochen. Falls diese sich signifikant verändert, können die Vorhersagen des Recommender-Modells unbrauchbar werden. Wenn dieser Fall eintritt, sollte ein neuer Trainingsprozess mit aktuellen Daten angestoßen werden. Um die Verteilung der Daten zu überwachen, werden wir sogenannte Kolmogorov-Smirnov-Tests aus der Alibi-Detect-Bibliothek verwenden. Dieser statistische Test überprüft, ob die Stichproben zweier Zufallsvariablen die gleiche Verteilung aufweisen. Zur Benutzung werden lediglich die Datengrundlage und eine Embedding-Schicht aus unserem trainierten Autoencoder benötigt. Die genaue Vorgehensweise ist gut dokumentiert und mit wenigen Zeilen umzusetzen. Als Ergebnis können wir das Concept Drift Model mithilfe der „save_detector“-Methode serialisieren. Der gesamte Python-Code angepasst für unseren Anwendungsfall ist in diesem Jupyter-Notebook zu finden. Das serialisierte Model und unseren Python-Code für ein benutzerdefiniertes KFServing-Model packen wir in ein Docker-Image und laden dieses in eine Container-Registry hoch.

Ein wesentlicher Unterschied zu der Outlier Detection liegt in der Stapelverarbeitung des Concept Drifts. Während bei der Anomlie-Erkennung jede Session einzeln betrachtet wurde, sammelt die Concept Drift Detection mehrere Sessions, damit die Stichprobe aussagekräftig genug für den Vergleich der Verteilung mit den Trainingsdaten ist. Das temporäre Speichern der Sessions geschieht in der Preprocessing-Schicht. In einem echten Webshop-System werden die Sessions selbstverständlich in einer Datenbank persistent abgelegt. Einfachheitshalber werden die Sessions für diesen Blogpost im Arbeitsspeicher gehalten, was keine sichere Variante ist. Wenn genügend Sessions vorhanden sind, wird dieser Stapel dem Concept Drift Model übergeben und die Antwort, ob eine Verteilung der Daten vorliegt, in die InfluxDB geschrieben. Wie bei den vorherigen Komponenten muss noch ein Trigger mit der URI der Concept Drift Detection zwecks einer Weiterleitung vom Knative Broker ausgerollt werden.

Dashboard

Den Abschluss des fachlichen Monitorings bildet ein Grafana-Dashboard, auf dem alle relevanten Kennzahlen und Metriken auf einen Blick zu sehen sind:

Dashboard fachliches Monitoring

Dashboard fachliches Monitoring

Mithilfe des Grafana Alertings können noch Alarme beispielsweise auf einen Concept Drift oder geringe Werte bei den Metriken der Model Performance eingestellt werden.

Technisches Monitoring

Ein Performance Monitoring für ein Kubernetes Cluster lässt sich durch viele verschiedene Tools erreichen. Beliebte Open-Source Kombinationen sind beispielsweise Prometheus und Grafana und/oder ein ELK-Stack. Da das Performance Monitoring einer serviceorientierten Architektur ein größeres Gebiet ist, betrachten wir das ganze aus einer abstrakten Sicht. Das grundlegende Ziel ist sicherzustellen, dass der ML-Service reibungslos ausgeführt wird. Dazu dienen unterschiedliche Metriken, die sich auf verschiedene Einheiten wie das Kubernetes Cluster, Nodes, Pods, Namespaces oder Services beziehen können. Beispiele für solche Metriken wären unter anderem: CPU und Memory Usage, Requests per Second, Response Time und Success Rate.

Der Einfachheit halber und aufgrund des bereits vorhandenen Knative-Stacks habe ich die Knative-Monitoring Komponente benutzt. Das Paket beinhaltet ElasticSearch, Kibana, Prometheus und Grafana und kann mit einem Befehl ausgerollt werden:


Zum Beispiel sieht ein vorgefertigtes Grafana-Dashboard für die CPU und Memory Verwendung für den Container des Recommenders folgendermaßen aus:

Dashboard CPU & Memory Usage

Dashboard CPU & Memory Usage

Ein Ausschnitt des Dashboards für die Requests:

Dashboard HTTP Requests

Dashboard HTTP Requests

Feedback-Schleifen

Bisher haben wir uns viele Gedanken gemacht, wie die Performance des ML-Modells am besten überwacht und sichergestellt werden kann. Eine weitere Methode ist den Benutzer direkt zu fragen, was die Person von den Vorschlägen hält oder ob ein anderer Vorschlag sinnvoller gewesen wäre. Diese Art nennt man explizites Feedback. Eine andere Variante ist das implizite Feedback. In unserem Anwendungsfall würde das beispielsweise bedeuten, ob auf das vorgeschlagene Produkt geklickt wurde, was bereits im Model Performance Monitoring überwacht wird. Anhand des expliziten und impliziten Feedbacks kann ein obsoletes ML-Modell ausgemacht werden. Dementsprechend kann Feedback ein elementares Mittel sein, um die Qualität der Vorhersagen beizubehalten oder sogar zu steigern.

Fazit

In diesem Blogpost wurde eine komplette Kubernetes Architektur zum Monitoring des ML-Recommenders in Produktion erstellt. Das Grundgerüst besteht aus Knative-Eventing, was eine eventbasierte, asynchrone und erweiterbare Bauweise liefert, die mit wenigen zusätzlichen Zeilen umsetzbar ist. Der Knative Broker kann an beliebig viele Komponenten Anfragen und Antworten weiterleiten. Für ein fachliches Monitoring wurden die Komponenten Model Performance Monitoring, Outlier Detection und Concept Drift Detection erstellt und als KFServing InferenceService ausgerollt. Die Outlier Detection hat die größte Eigenentwicklung gefordert, da dabei nicht auf vorhandene Verfahren von Alibi-Detect zurückgegriffen werden konnte. Hingegen waren die Model Performance und Concept Drift Detection ohne viel Aufwand zu bewerkstelligen. Die Metriken werden in eine InfluxDB geschrieben und mit einem Grafana-Dashboard visualisiert. Damit ist trotz der komplexen Anforderung, dass jede Komponente auf den jeweiligen Anwendungsfall und die zugrundeliegenden Daten abgestimmt werden müssen, mit einem verhältnismäßig geringen Aufwand eine vollständige, skalierbare State of the Art Architektur realisiert worden.

Artikel kommentieren