10. Modell-Persistenz#

Zusammenfassung der Methoden zur Modell-Persistenz#

Persistenzmethode

Vorteile

Risiken / Nachteile

ONNX

  • Modelle ohne Python-Umgebung bereitstellen

  • Bereitstellungs- und Trainingsumgebungen unabhängig voneinander

  • Sicherste Option

  • Nicht alle scikit-learn-Modelle werden unterstützt

  • Benutzerdefinierte Schätzer erfordern mehr Aufwand zur Unterstützung

  • Originales Python-Objekt geht verloren und kann nicht rekonstruiert werden

skops.io

  • Sicherer als pickle-basierte Formate

  • Inhalte können teilweise validiert werden, ohne sie zu laden

  • Nicht so schnell wie pickle-basierte Formate

  • Unterstützt weniger Typen als pickle-basierte Formate

  • Erfordert dieselbe Umgebung wie die Trainingsumgebung

pickle

  • Nativ in Python

  • Kann die meisten Python-Objekte serialisieren

  • Effiziente Speichernutzung mit protocol=5

  • Das Laden kann beliebigen Code ausführen

  • Erfordert dieselbe Umgebung wie die Trainingsumgebung

joblib

  • Effiziente Speichernutzung

  • Unterstützt Memory Mapping

  • Einfache Abkürzungen für Komprimierung und Dekomprimierung

  • Pickle-basiertes Format

  • Das Laden kann beliebigen Code ausführen

  • Erfordert dieselbe Umgebung wie die Trainingsumgebung

cloudpickle

  • Kann nicht gepackten, benutzerdefinierten Python-Code serialisieren

  • Vergleichbare Ladeeffizienz wie pickle mit protocol=5

  • Pickle-basiertes Format

  • Das Laden kann beliebigen Code ausführen

  • Keine Garantien für Vorwärtskompatibilität

  • Erfordert dieselbe Umgebung wie die Trainingsumgebung

Nach dem Trainieren eines scikit-learn-Modells ist es wünschenswert, das Modell für die zukünftige Verwendung persistieren zu können, ohne neu trainieren zu müssen. Basierend auf Ihrem Anwendungsfall gibt es verschiedene Möglichkeiten, ein scikit-learn-Modell zu persistieren, und hier helfen wir Ihnen bei der Entscheidung, welche am besten zu Ihnen passt. Um eine Entscheidung zu treffen, müssen Sie die folgenden Fragen beantworten

  1. Benötigen Sie das Python-Objekt nach der Persistenz, oder möchten Sie es nur persistieren, um das Modell bereitzustellen und Vorhersagen daraus zu erhalten?

Wenn Sie das Modell nur bereitstellen müssen und keine weitere Untersuchung des Python-Objekts selbst erforderlich ist, dann ist ONNX möglicherweise die beste Wahl für Sie. Beachten Sie, dass nicht alle Modelle von ONNX unterstützt werden.

Falls ONNX für Ihren Anwendungsfall nicht geeignet ist, lautet die nächste Frage:

  1. Vertrauen Sie der Quelle des Modells absolut, oder gibt es Sicherheitsbedenken bezüglich der Herkunft des persistierten Modells?

Wenn Sie Sicherheitsbedenken haben, sollten Sie skops.io in Betracht ziehen, das Ihnen das Python-Objekt zurückgibt, aber im Gegensatz zu pickle-basierten Persistenzlösungen erlaubt das Laden des persistierten Modells nicht automatisch die Ausführung beliebigen Codes. Beachten Sie, dass dies eine manuelle Untersuchung der persistierten Datei erfordert, was skops.io Ihnen ermöglicht.

Die anderen Lösungen gehen davon aus, dass Sie der Quelle der zu ladenden Datei absolut vertrauen, da sie alle anfällig für die Ausführung von beliebigem Code beim Laden der persistenten Datei sind, da sie alle das Pickle-Protokoll im Hintergrund verwenden.

  1. Ist Ihnen die Leistung beim Laden des Modells wichtig und die gemeinsame Nutzung zwischen Prozessen, bei der ein speicherabgebildetes Objekt auf der Festplatte von Vorteil ist?

Wenn ja, können Sie joblib in Betracht ziehen. Wenn dies für Sie keine große Rolle spielt, können Sie das integrierte Modul pickle verwenden.

  1. Haben Sie pickle oder joblib ausprobiert und festgestellt, dass das Modell nicht persistiert werden kann? Das kann zum Beispiel passieren, wenn Sie benutzdefinierte Funktionen in Ihrem Modell haben.

Wenn ja, können Sie cloudpickle verwenden, das bestimmte Objekte serialisieren kann, die von pickle oder joblib nicht serialisiert werden können.

10.1. Workflow-Übersicht#

In einem typischen Workflow ist der erste Schritt das Trainieren des Modells mit scikit-learn und scikit-learn-kompatiblen Bibliotheken. Beachten Sie, dass die Unterstützung für scikit-learn und Drittanbieter-Schätzer über die verschiedenen Persistenzmethoden variiert.

10.1.1. Modell trainieren und persistieren#

Die Erstellung eines geeigneten Modells hängt von Ihrem Anwendungsfall ab. Als Beispiel trainieren wir hier einen sklearn.ensemble.HistGradientBoostingClassifier auf dem Iris-Datensatz.

>>> from sklearn import ensemble
>>> from sklearn import datasets
>>> clf = ensemble.HistGradientBoostingClassifier()
>>> X, y = datasets.load_iris(return_X_y=True)
>>> clf.fit(X, y)
HistGradientBoostingClassifier()

Sobald das Modell trainiert ist, können Sie es mit der gewünschten Methode persistieren und dann das Modell in einer separaten Umgebung laden und Vorhersagen daraus erhalten, gegeben Eingabedaten. Hier gibt es zwei Hauptpfade, je nachdem, wie Sie das Modell persistieren und bereitstellen möchten:

  • ONNX: Sie benötigen eine ONNX-Laufzeitumgebung und eine Umgebung mit den entsprechenden installierten Abhängigkeiten, um das Modell zu laden und die Laufzeitumgebung zur Vorhersage zu verwenden. Diese Umgebung kann minimal sein und muss nicht einmal Python installiert haben, um das Modell zu laden und Vorhersagen zu berechnen. Beachten Sie auch, dass onnxruntime typischerweise weniger RAM benötigt als Python, um Vorhersagen aus kleinen Modellen zu berechnen.

  • skops.io, pickle, joblib, cloudpickle: Sie benötigen eine Python-Umgebung mit den entsprechenden installierten Abhängigkeiten, um das Modell zu laden und Vorhersagen daraus zu erhalten. Diese Umgebung sollte dieselben **Pakete** und **Versionen** haben wie die Umgebung, in der das Modell trainiert wurde. Beachten Sie, dass keine dieser Methoden das Laden eines mit einer anderen Version von scikit-learn trainierten Modells unterstützt, und möglicherweise auch unterschiedliche Versionen anderer Abhängigkeiten wie numpy und scipy. Ein weiterer Punkt wäre die Ausführung des persistenten Modells auf einer anderen Hardware. In den meisten Fällen sollten Sie Ihr persistentes Modell auf einer anderen Hardware laden können.

10.2. ONNX#

ONNX, oder das Open Neural Network Exchange-Format, ist am besten geeignet für Anwendungsfälle, bei denen das Modell persistiert und dann das persistierte Artefakt zur Vorhersage verwendet werden soll, ohne das Python-Objekt selbst laden zu müssen. Es ist auch nützlich in Fällen, in denen die Bereitstellungsumgebung schlank und minimal sein muss, da die ONNX-Laufzeitumgebung kein Python benötigt.

ONNX ist eine binäre Serialisierung des Modells. Es wurde entwickelt, um die Benutzerfreundlichkeit der interoperablen Darstellung von Datenmodellen zu verbessern. Es zielt darauf ab, die Konvertierung von Datenmodellen zwischen verschiedenen Machine-Learning-Frameworks zu erleichtern und ihre Portabilität auf verschiedenen Computerarchitekturen zu verbessern. Weitere Details finden Sie im ONNX-Tutorial. Um scikit-learn-Modelle in ONNX zu konvertieren, wurde sklearn-onnx entwickelt. Allerdings werden nicht alle scikit-learn-Modelle unterstützt, und es ist auf den Kern von scikit-learn beschränkt und unterstützt die meisten Drittanbieter-Schätzer nicht. Man kann einen benutzerdefinierten Konverter für Drittanbieter- oder benutzerdefinierte Schätzer schreiben, aber die Dokumentation dafür ist spärlich und es kann schwierig sein.

ONNX verwenden#

Um das Modell in das ONNX-Format zu konvertieren, müssen Sie dem Konverter auch Informationen über die Eingabe geben, worüber Sie mehr hier lesen können.

from skl2onnx import to_onnx
onx = to_onnx(clf, X[:1].astype(numpy.float32), target_opset=12)
with open("filename.onnx", "wb") as f:
    f.write(onx.SerializeToString())

Sie können das Modell in Python laden und die ONNX-Laufzeitumgebung verwenden, um Vorhersagen zu erhalten.

from onnxruntime import InferenceSession
with open("filename.onnx", "rb") as f:
    onx = f.read()
sess = InferenceSession(onx, providers=["CPUExecutionProvider"])
pred_ort = sess.run(None, {"X": X_test.astype(numpy.float32)})[0]

10.3. skops.io#

skops.io vermeidet die Verwendung von pickle und lädt nur Dateien, die Typen und Referenzen auf Funktionen haben, die entweder standardmäßig oder vom Benutzer als vertrauenswürdig eingestuft werden. Daher bietet es ein sichereres Format als pickle, joblib und cloudpickle.

skops verwenden#

Die API ist der von pickle sehr ähnlich, und Sie können Ihre Modelle wie in der Dokumentation beschrieben mit skops.io.dump und skops.io.dumps persistieren.

import skops.io as sio
obj = sio.dump(clf, "filename.skops")

Und Sie können sie mit skops.io.load und skops.io.loads wieder laden. Sie müssen jedoch die von Ihnen vertrauten Typen angeben. Sie können vorhandene unbekannte Typen in einem gedumpten Objekt / einer Datei mit skops.io.get_untrusted_types abrufen und nach Überprüfung des Inhalts an die Ladefunktion übergeben.

unknown_types = sio.get_untrusted_types(file="filename.skops")
# investigate the contents of unknown_types, and only load if you trust
# everything you see.
clf = sio.load("filename.skops", trusted=unknown_types)

Bitte melden Sie Probleme und Feature-Wünsche zu diesem Format auf der skops-Issue-Tracker.

10.4. pickle, joblib und cloudpickle#

Diese drei Module/Pakete verwenden im Hintergrund das pickle-Protokoll, kommen aber mit leichten Abweichungen.

  • pickle ist ein Modul aus der Python Standardbibliothek. Es kann jedes Python-Objekt serialisieren und deserialisieren, einschließlich benutzerdefinierter Python-Klassen und -Objekte.

  • joblib ist effizienter als pickle, wenn es mit großen Machine-Learning-Modellen oder großen NumPy-Arrays arbeitet.

  • cloudpickle kann bestimmte Objekte serialisieren, die von pickle oder joblib nicht serialisiert werden können, wie z. B. benutzdefinierte Funktionen und Lambda-Funktionen. Dies kann zum Beispiel passieren, wenn Sie einen FunctionTransformer verwenden und eine benutzerdefinierte Funktion zur Transformation der Daten verwenden.

Verwendung von pickle, joblib oder cloudpickle#

Abhängig von Ihrem Anwendungsfall können Sie eine dieser drei Methoden wählen, um Ihr scikit-learn-Modell zu persistieren und zu laden, und sie folgen alle derselben API.

# Here you can replace pickle with joblib or cloudpickle
from pickle import dump
with open("filename.pkl", "wb") as f:
    dump(clf, f, protocol=5)

Die Verwendung von protocol=5 wird empfohlen, um den Speicherverbrauch zu reduzieren und das Speichern und Laden großer NumPy-Arrays, die als angepasste Attribute im Modell gespeichert sind, zu beschleunigen. Sie können alternativ protocol=pickle.HIGHEST_PROTOCOL übergeben, was in Python 3.8 und neueren Versionen (zum Zeitpunkt der Erstellung) äquivalent zu protocol=5 ist.

Und später, wenn nötig, können Sie dasselbe Objekt aus der persistenten Datei laden.

# Here you can replace pickle with joblib or cloudpickle
from pickle import load
with open("filename.pkl", "rb") as f:
    clf = load(f)

10.5. Sicherheits- & Wartungsbeschränkungen#

pickle (und joblib und cloudpickle als Erweiterung davon) hat viele dokumentierte Sicherheitslücken per Design und sollte nur verwendet werden, wenn das Artefakt, d.h. die Pickle-Datei, von einer vertrauenswürdigen und verifizierten Quelle stammt. Sie sollten niemals eine Pickle-Datei aus einer nicht vertrauenswürdigen Quelle laden, genauso wenig wie Sie Code aus einer nicht vertrauenswürdigen Quelle ausführen sollten.

Beachten Sie auch, dass beliebige Berechnungen im ONNX-Format dargestellt werden können und es daher empfohlen wird, Modelle mit ONNX in einer sandboxed Umgebung bereitzustellen, um vor Rechen- und Speicherangriffen zu schützen.

Beachten Sie auch, dass es keine unterstützten Möglichkeiten gibt, ein Modell zu laden, das mit einer anderen Version von scikit-learn trainiert wurde. Während bei der Verwendung von skops.io, joblib, pickle oder cloudpickle, Modelle, die mit einer Version von scikit-learn gespeichert wurden, möglicherweise in anderen Versionen geladen werden können, ist dies jedoch vollständig ununterstützt und nicht ratsam. Es sollte auch bedacht werden, dass Operationen, die auf solchen Daten ausgeführt werden, zu unterschiedlichen und unerwarteten Ergebnissen führen oder sogar Ihren Python-Prozess zum Absturz bringen könnten.

Um ein ähnliches Modell mit zukünftigen scikit-learn-Versionen neu zu erstellen, sollten zusätzliche Metadaten zusammen mit dem gepickelten Modell gespeichert werden:

  • Die Trainingsdaten, z.B. ein Verweis auf einen unveränderlichen Snapshot.

  • Der Python-Quellcode, der zur Generierung des Modells verwendet wurde.

  • Die Versionen von scikit-learn und seinen Abhängigkeiten.

  • Die Kreuzvalidierungsbewertung, die auf den Trainingsdaten erzielt wurde.

Dies sollte es ermöglichen, zu überprüfen, ob die Kreuzvalidierungsbewertung im gleichen Bereich liegt wie zuvor.

Abgesehen von wenigen Ausnahmen sollten persistierte Modelle über Betriebssysteme und Hardwarearchitekturen hinweg portabel sein, vorausgesetzt, dass dieselben Versionen von Abhängigkeiten und Python verwendet werden. Wenn Sie auf einen Schätzer stoßen, der nicht portabel ist, öffnen Sie bitte ein Issue auf GitHub. Persistierte Modelle werden oft in Produktionsumgebungen mithilfe von Containern wie Docker eingesetzt, um die Umgebung und Abhängigkeiten einzufrieren.

Wenn Sie mehr über diese Themen erfahren möchten, lesen Sie bitte diese Vorträge:

10.5.1. Replikation der Trainingsumgebung in der Produktion#

Wenn die Versionen der verwendeten Abhängigkeiten von der Trainings- zur Produktionsumgebung abweichen können, kann dies zu unerwartetem Verhalten und Fehlern bei der Verwendung des trainierten Modells führen. Um solche Situationen zu vermeiden, wird empfohlen, dieselben Abhängigkeiten und Versionen sowohl in der Trainings- als auch in der Produktionsumgebung zu verwenden. Diese transitiven Abhängigkeiten können mit Hilfe von Paketmanagement-Tools wie pip, mamba, conda, poetry, conda-lock, pixi usw. fixiert werden.

Es ist nicht immer möglich, ein mit älteren Versionen der scikit-learn-Bibliothek und ihren Abhängigkeiten trainiertes Modell in einer aktualisierten Softwareumgebung zu laden. Stattdessen müssen Sie das Modell möglicherweise mit den neuen Versionen aller Bibliotheken neu trainieren. Beim Trainieren eines Modells ist es daher wichtig, das Trainingsrezept (z. B. ein Python-Skript) und Informationen zum Trainingsdatensatz sowie Metadaten zu allen Abhängigkeiten aufzuzeichnen, um dieselbe Trainingsumgebung für die aktualisierte Software automatisch rekonstruieren zu können.

InconsistentVersionWarning#

Wenn ein Schätzer mit einer scikit-learn-Version geladen wird, die inkonsistent mit der Version ist, mit der der Schätzer gepickelt wurde, wird eine InconsistentVersionWarning ausgelöst. Diese Warnung kann abgefangen werden, um die ursprüngliche Version zu erhalten, mit der der Schätzer gepickelt wurde.

from sklearn.exceptions import InconsistentVersionWarning
warnings.simplefilter("error", InconsistentVersionWarning)

try:
    with open("model_from_previous_version.pickle", "rb") as f:
        est = pickle.load(f)
except InconsistentVersionWarning as w:
    print(w.original_sklearn_version)

10.5.2. Bereitstellung des Modellartefakts#

Der letzte Schritt nach dem Trainieren eines scikit-learn-Modells ist die Bereitstellung des Modells. Sobald das trainierte Modell erfolgreich geladen wurde, kann es zur Verwaltung verschiedener Vorhersageanfragen bereitgestellt werden. Dies kann die Bereitstellung des Modells als Webdienst mithilfe von Containerisierung oder anderen Strategien zur Modellbereitstellung gemäß den Spezifikationen umfassen.

10.6. Zusammenfassung der wichtigsten Punkte#

Basierend auf den verschiedenen Ansätzen zur Modell-Persistenz können die wichtigsten Punkte für jeden Ansatz wie folgt zusammengefasst werden:

  • ONNX: Es bietet ein einheitliches Format zur Persistenz beliebiger Machine-Learning- oder Deep-Learning-Modelle (außer scikit-learn) und ist nützlich für die Modellinferenz (Vorhersagen). Es kann jedoch zu Kompatibilitätsproblemen mit verschiedenen Frameworks führen.

  • skops.io: Trainierte scikit-learn-Modelle können einfach mit skops.io geteilt und in die Produktion überführt werden. Es ist sicherer als alternative Ansätze, die auf pickle basieren, da es keinen beliebigen Code lädt, es sei denn, der Benutzer fordert dies ausdrücklich an. Solcher Code muss paketiert und in der Ziel-Python-Umgebung importierbar sein.

  • joblib: Effiziente Memory-Mapping-Techniken machen es schneller bei der Verwendung desselben persistenten Modells in mehreren Python-Prozessen, wenn mmap_mode="r" verwendet wird. Es bietet auch einfache Abkürzungen zum Komprimieren und Dekomprimieren des persistenten Objekts ohne zusätzlichen Code. Es kann jedoch die Ausführung bösartigen Codes auslösen, wenn ein Modell aus einer nicht vertrauenswürdigen Quelle geladen wird, genau wie jeder andere Pickle-basierte Persistenzmechanismus.

  • pickle: Es ist nativ in Python und die meisten Python-Objekte können mit pickle serialisiert und deserialisiert werden, einschließlich benutzerdefinierter Python-Klassen und -Funktionen, solange sie in einem Paket definiert sind, das in der Zielumgebung importiert werden kann. Während pickle verwendet werden kann, um scikit-learn-Modelle einfach zu speichern und zu laden, kann es beim Laden eines Modells aus einer nicht vertrauenswürdigen Quelle die Ausführung bösartigen Codes auslösen. pickle kann auch sehr speichereffizient sein, wenn das Modell mit protocol=5 persistiert wurde, unterstützt aber kein Memory Mapping.

  • cloudpickle: Es hat eine vergleichbare Ladeeffizienz wie pickle und joblib (ohne Memory Mapping), bietet aber zusätzliche Flexibilität zur Serialisierung von benutzerdefiniertem Python-Code wie Lambda-Ausdrücken und interaktiv definierten Funktionen und Klassen. Es kann ein letzter Ausweg sein, um Pipelines mit benutzerdefinierten Python-Komponenten zu persistieren, wie z. B. einen sklearn.preprocessing.FunctionTransformer, der eine Funktion umschließt, die im Trainingsskript selbst oder allgemeiner außerhalb eines importierbaren Python-Pakets definiert ist. Beachten Sie, dass cloudpickle keine Garantien für Vorwärtskompatibilität bietet und Sie möglicherweise dieselbe Version von cloudpickle benötigen, um das persistierte Modell zusammen mit derselben Version aller Bibliotheken, die zur Definition des Modells verwendet wurden, zu laden. Wie die anderen Pickle-basierten Persistenzmechanismen kann es die Ausführung bösartigen Codes beim Laden eines Modells aus einer nicht vertrauenswürdigen Quelle auslösen.