de en

Enterprise TensorFlow - Wie man ein trainiertes Modell speichert

Teil 2 in der Serie über Java / TensorFlow Interoperabilität, in dem es darum geht ein Modell so zu speichern, dass es in anderen Umgebungen wiederverwendet werden kann.

Hinweis: Die folgenden Erläuterungen treffen auch auf das Persistieren von Modellen in anderen Umgebungen als Java zu, z.B. TensorFlow Serving.

Rückblick: Was steckt unter der Haube?

Bevor wir uns ansehen, wie man Daten in TensorFlow speichert und in Java neu lädt, wollen wir zunächst kurz erläutern, wie TensorFlow aufgebaut ist, damit wir wissen, was wirklich gespeichert und geladen werden muss. Wenn ihr euch schon gut mit TensorFlow Grundlagen auskennt, könnt ihr diesen Abschnitt überspringen.

Wenn wir an TensorFlow denken, ist das oft synonym mit dem Python Code, den wir schreiben, aber dieser Code ist eigentlich nur ein Werkzeug, um den Berechnungsgraph für die TensorFlow-Engine zu erstellen. Ein Graph ist ein Netzwerk von Knoten und Verbindungen zwischen diesen Knoten. In TensorFlow definiert dieser Graph den Algorithmus, den wir für Training und Inferenz verwenden, er definiert, wie Tensoren von einer Operation (Knoten) zu einer anderen fließen, um den gewünschten Effekt zu erzielen. Daher der Name TensorFlow.

Also wollen wir natürlich die Struktur des Graphen speichern, um unsere Vorhersagen ablaufen zu lassen (es sei denn, wir implementieren den Inferenz-Algorithmus mit anderen Mitteln als der TensorFlow-Engine).

Bestimmte Knoten in unserem Graph enthalten Zustandsvariablen, die gelernt wurden, z.B. Gewichte für neuronale Netze. Wir müssen diesen Zustand in unserem Graphen wiederherstellen, um die richtigen Ergebnisse zu erhalten, sonst war das ganze Training umsonst. Diese Daten müssen wir immer speichern, unabhängig davon, ob wir die TensorFlow-Engine für die Inferenz verwenden oder stattdessen ein anderes Framework oder eine selbstgebaute Lösung.

Abhängig vom verwendeten Lernverfahren gibt es gewisse Hyperparameter, die ebenfalls bei der Persistenz berücksichtigt werden müssen. Einige Hyperparameter, wie die Größe und die Schichten eines neuronalen Netzwerks, sind implizit Teil unseres Graphen, sodass wir uns nicht darum kümmern müssen. Andere werden in Variablen gespeichert, wie gelernte Parameter und werden daher wie Lernergebnisse gespeichert. Andere Parametrisierungen (z. B. Schwellenwerte für die Klassifizierung) müssen jedoch möglicherweise vor dem Ausführen des Graphen speziell eingestellt werden oder sind Teil von Vor- oder Nachverarbeitungsschritten. Diese müssen manuell übertragen werden. Dies ist in der Regel unkompliziert, da eine einfache Konstante in eurem Java-Code hier i.d.R. reicht. Nehmt einfach alle Hyperparameter und überprüft, ob sie alle in eurem Inferenz-Code berücksichtigt werden.

Manchmal enthält ein TensorFlow-Diagramm zusätzliche Assets wie Wörterbücher für NLP-Anwendungen. Der Einfachheit halber werden wir diese in der folgenden Diskussion ignorieren.

Wir müssen auch sicherstellen, dass wir wissen, welche Operationen in unserem Graph initial aufgerufen werden müssen, um eine Vorhersage zu erzeugen, wir müssen also wissen, was unsere Eingangs- und Ausgangsknoten sind.

Der gesamte Zustand, den wir persistieren wollen, besteht aus:

  • Unsere Graphenstruktur
  • Den erlernten Parametern unseres Modells (Variablen des Graphen)
  • U.U. Hyperparameter (für das Modell oder für die Vor- / Nachbearbeitung)
  • Manchmal zusätzliche Assets
  • Die Informationen über Eingabe- / Ausgabeknoten (analog zur Signatur einer Funktion)

Entitäten, aus denen unser gespeicherter Zustand besteht

Die TensorFlow API zum Speichern und Laden hat sich über die Zeit ziemlich gewandelt und auf der TensorFlow-Site, verschiedenen Blogs und StackOverflow finden sich viele verschiedene Beispiele, wie man den oben aufgeführten Zustand persistieren kann. Wir werden uns auf das SavedModel konzentrieren, da dies der Ansatz ist, den wir brauchen, um unser Modell bequem mit der TensorFlow Java API zu laden. Andere Ansätze zum Speichern und Laden verwenden Teile der Entitäten, aus denen das SavedModel besteht, sodass ihr andere APIs und Tutorials besser verstehen könnt, wenn ihr euch damit vertraut macht. Wenn ihr das Thema googlet oder wenn ihr mit der offiziellen TensorFlow-Dokumentation startet, stößt ihr zunächst immer auf die Klasse Saver. Intern verwendet SavedModel einen Saver, um einen Teil seiner Aufgaben zu erledigen, d.h. die Klassen überlappen sich in ihrer Funktionalität. SavedModel scheint jedoch der Fokus für zukünftige Versionen von TensorFlow zu sein, also schlage ich vor, sich nur mit dem Saver vertraut zu machen, wenn es wirklich nötig ist. Die vom Saver erzeugten Daten können allein nicht in Java geladen werden!.

Was ist nun eigentlich ein SavedModel? Ein SavedModel ist die Basis der TensorFlow-Persistenzhierarchie. Es enthält alles, was gespeichert werden muss. Ein SavedModelselbst ist nicht sehr komplex, es ist mehr oder weniger nur ein Verzeichnis, das “Tags” (beliebige String-IDs) zu MetaGraphen und einer Anzahl von „Checkpoints” (So nennt TensorFlow den Zustand unserer gelernten Variablen) zuordnet.

Der MetaGraph ist die wichtigste Entität in unserem gespeicherten Zustand. Ein MetaGraph ist der Graph zusammen mit anderen Metainformationen, wie Versionsinformationen und Assets. Der MetaGraph enthält auch die Information darüber, welche Knoten für den Input zuständig sind an welchen der Output unseres Graphen zu finden ist. TensorFlow nennt dies die Signatur eines MetaGraphen (ähnlich den Methodensignaturen von Programmiersprachen). Ein MetaGraph enthält nicht den Inhalt der gelernten Variablen!

Warum also kann ein SavedModel mehrere MetaGraphen enthalten? Die Idee ist, dass man je nach Anwendungsfall nicht immer alle Teile eines Graphen braucht. Für die eigentliche Inferenz / die Vorhersage braucht man z.B. viele Teile des Graphens nicht, die für das Lernen zuständig sind. Mit einem SavedModel können wir mehrere Momentaufnahmen von Teilgraphen unseres Gesamtgraphen machen, jeweils eine für jeden Anwendungsfall.

Ein MetaGraph wäre ohne den Status seiner Variablen (“Checkpoints”) nutzlos, also müssen wir sicherstellen, dass diese ebenfalls gespeichert werden. Der Grund, warum Checkpoints nicht mit dem MetaGraphen gespeichert werden, ist, dass mehrere MetaGraphen den gleichen Variablenknoten enthalten können, wenn sich die einzelnen Untergraphen überlappen. Wenn der MetaGraph auch den Variablenstatus enthält und wir mehrere sich teilweise überlappende MetaGraphen speichern, würden wir den Variabelinhalt in unseren Dateien duplizieren. Abhängig von der Größe des Modells kann dies Hunderte von Megabyte (oder sogar mehrere Gigabyte) bedeuten! Deshalb wird der Status jeder Variable nur einmal außerhalb des MetaGraphen gespeichert, auch wenn sie von mehr als einem MetaGraph en referenziert wird.

Im Folgenden die wichtigste API-Elemente / Konzepte, zum erfolgreichen Speichern eines Modells:

  • SavedModel: die Basis unserer Hierarchie, enthält mehrere MetaGraphen mit jeweils einem individuellen Namen (tag).
  • MetaGraph: Ein (Teil-) Graph für eine Aufgabe, enthält den Graphen, Assets und Signaturdefinitionen für Ein- / Ausgabe
  • Checkpoint: Ein Snapshot für den Variablenstatus für alle MetaGraphen in einem SavedModel

Von TensorFlow verwendete Dateiformate

Die meisten Entitäten, die in TensorFlow gespeichert und geladen werden können, sind als Protobuf-Datensätze definiert. Protobuf ist ein sprachunabhängiges Serialisierungsformat, ähnlich wie JSON oder XML. Der große Unterschied ist, dass Protobuf in seinem „nativen Zustand” ein dicht gepacktes Binärformat und kein Textformat ist. Während der Entwicklung kann Protobuf in einer Textformatvariante verwendet werden, die JSON sehr ähnlich sieht und einfacher zu debuggen ist, da sie mit jedem Texteditor überprüft werden kann. Protobuf wird von Google für die meisten verteilten Systeme zur Kommunikation verwendet.

Ähnlich wie eine DTD oder ein Schema in XML werden Protobuf-Entitäten in externen Textdateien mit der Erweiterung “.proto” definiert (z.B. die Definition eines Graphen). Die “.proto” -Dateien werden von Precompilern verwendet, um Klassen und (De-) Serializer für verschiedene Sprachen zu erstellen. Für die meisten Sprachen gibt es gut gepflegte protobuf-Precompiler, daher ist das Laden und Speichern von Protobuf-Daten generell ziemlich einfach.

Serialisierte Protobuf-Datensätze, die in Dateien geschrieben werden, verwenden normalerweise die Dateierweiterung “.pb”, wenn sie im Binärformat vorliegen, oder “.pbtxt”, wenn sie im Textformat vorliegen. Um die Dinge interessant zu halten, verwendet TensorFlow manchmal diese Erweiterungen und manchmal nicht.

“Echte” Klassen der TensorFlow Python-API sind von automatisch generierte Protobuf-Containern anhand des „Def” zu unterscheiden. Ein tf.Graph ist also der eigentliche Graph, mit dem gearbeitet wird, ein tf.GraphDef ist ein serialisierbarer Protobuf-Datensatz, der die Informationen zum Wiederherstellen eines tf.Graph enthält. Nicht jedes „Def” hat eine entsprechende Python-Klasse, ein MetaGraphDef z.B. hat keine entsprechende MetaGraph -Klasse, sondern wird einfach als Sammlung von Daten, die einen Graphen und zusätzliche Informationen zum Ausführen des Modells enthalten, repräsentiert.

Ein gespeichertes SavedModel besteht nicht aus einer einzelnen Datei, sondern aus einem Ordner, der verschiedene Dateien und Unterordner mit dem gespeicherten Zustand enthält. Im Folgenden eine Liste der Dateien, die in einem SavedModel-Ordner vorzufinden sind (da sich diese API noch stetig ändert, lohnt es sich, von Zeit zu Zeit die offizielle Dokumentation zu konsultieren):

  • assets/enthält zusätzliche Graph-Assets
  • assets.extra/Hier werden benutzerdefinierte Dateien abgelegt, die nicht zum Standardformat gehören
  • variables/enthält den Variablenstatus (Checkpoints)
  • saved_model.pb: binäre Protobuf-Datei mit dem SavedModel, den MetaGraphen, dem Graph, den Signaturen usw. usf.

Der Ordner variables/ enthält zwei Dateitypen:

  • *.data/: Inhalt von Variablen. Die Dateinamen sind nummeriert und es kann mehr als eine Datei pro Checkpoint geben. Der Grund dafür ist, dass für sehr große neuronale Netze die Variabelninhalte ziemlich groß werden können - wenn nötig, teilt TensorFlow sie beim Speichern auf mehrere Dateien auf. Dies ist ein Binärformat, wahrscheinlich auch ein Protobuf. Es wird von einem Knoten im TensorFlow-Graphen geschrieben, sodass man tiefer graben muss, um mehr Informationen über das tatsächliche Format zu erhalten.
  • *.index: Wird zum Zuordnen von Variablennamen zu den Variabelinhalten in den *.data/-Dateien verwendet. Dies ist notwendig, da möglicherweise nicht klar ist, in welcher der aufgeteilten Dateien sich unsere Variabelinhalte befinden. Wie die *.data/-Dateien ist dies ein unbekanntes Binärformat.

Während des Trainings speichern verschiedene APIs (EstimatorMonitoredTrainingSession und andere) Teile eines SavedModel. In diesem Fall gibt es keine verschachtelten Ordner und einige zusätzliche Dateien, einige andere Dateien fehlen:

  • checkpoint: Eine einfache Textdatei mit dem Namen des letzten Checkpoints. Wird von TensorFlow benutzt, um herauszufinden, welcher Checkpoint zum Fortsetzen des Trainings geladen werden soll.
  • *.meta: Dies ist eine Protobuf-Datei mit einem MetaGraphDef. Dies ist Teil dessen, was sonst in einem saved_model.pb gespeichert ist.
  • graph.pbtxt: Je nachdem, wie das Modell gespeichert wird, wird ein vollständiger Protobuf-Export des Graphen in diese Datei geschrieben. Dies ist kein SavedModel und kein MetaGraph, sondern nur ein Graph.

Teil einer TensorFlow Installation ist ein Kommandozeilen-Programm zum Untersuchen von SavedModels. Dies ist ein sehr nützliches Tool, es ist empfehlenswert, sich damit auseinanderzusetzen, wenn man regelmäßig mit SavedModels arbeitet.

Speichern von Daten während des Trainings und für den Live Einsatz

Es gibt verschiedene Möglichkeiten, die obigen Dateien durch TensorFlow erstellen zu lassen. Einige davon scheinen deprecated zu sein. In diesem Abschnitt werde ich mich auf den Ansatz konzentrieren, der meiner Meinung nach im Moment das von Google empfohlene Vorgehen ist. Er liefert die Ergebnisse, die wir benötigen, um die Java TensorFlow-Wrapper-Bibliothek zu verwenden.

Das gesamte Thema Persistenz in TensorFlow ist ein eindrucksvolles Beispiel für das „Big Ball of Mud” Antipattern. Es scheint, dass wieder und wieder Teile hier und da aufgepfropft wurden, weil früheren Methoden einige entscheidende Funktionen fehlten. Dies wurde jedoch auf verschiedene Arten (globale Funktionen, Objektorientierung) an verschiedenen Stellen im Code durchgeführt. Dies macht eine der einfachsten und wichtigsten Aufgaben zu einem heiß diskutierten Thema, wie die große Anzahl an Blogposts und StackOverflow-Fragen zeigt.

Wir werden das gewünschte Ziel auf folgende Weise erreichen:

  1. Wir verpacken unseren Modellcode in einen tf.estimator.Estimator(oder benutzen einen der vorgefertigten Estimator aus tf.estimator)
  2. Der Estimator kümmert sich automagisch um das Speichern des Lernfortschritts des Trainings mittels MetaGraph en und Checkpoints (nicht SavedModels)
  3. Wenn wir mit dem Trainingsergebnis zufrieden sind, exportieren wir ein SavedModel für den Live Einsatz in unserer Java Umgebung

Wie bereits erwähnt, ist das erste Ergebnis, das bei der Suche nach „Speichern in TensorFlow“ auftaucht, meist die Klasse tf.train.Saver. Diese Klasse wird von den meisten der verschiedenen Speichermethoden in TensorFlow für einige Teile des Zustands verwendet. Ich rate jedoch davon ab, es direkt zu verwenden, da alles in der TensorFlow-Dokumentation darauf hinweist, das  SavedModelkünftig den Standard darstellt. In unserem Fall können wir den Saver sowieso nicht direkt verwenden, da die TensorFlow Java API ein komplettes SavedModel zum Laden benötigt, nicht nur die vom Saver erzeugten Teile. (SavedModel verwendet den Saver intern selbst für Checkpoints)

Verwendung der tf.estimator.Estimator API

Was ist ein Estimator?

Der Estimator ist die neueste von TensorFlow vorgeschlagene API zur Organisation des Trainingscodes. Ich sage „vorgeschlagen”, weil ein Modell komplett ohne es geschrieben werden kann, und die meisten Beispiele, die zur Zeit noch zu finden sind (noch) keine Estimator verwenden. Der Estimator übernimmt die folgenden Aufgaben:

  • Daten über die input_fn lesen
  • Definition des Modells
  • Training
  • Validierung
  • Test
  • Zwischenspeichern des Lernfortschritts
  • Export des Ergebnis

Kurz gesagt, der Estimator ist eine vollständige Definition eines TensorFlow-Modells mit allem Schnickschnack.

Laut der Doku sind Estimator der empfohlene Weg, in Zukunft TensorFlow Code zu organisieren, und sie bieten eine Reihe von Vorteilen:

  • Durch die Einhaltung eines gemeinsamen Standards und Best Practices werden Programme besser verständlich und vergleichbarer.
  • Estimator kümmern sich für uns um das Laden und Speichern während des Trainings.
  • Estimator ermöglichen einen (vergleichsweise) einfachen Export inSavedModels (dies ist unser Hauptgrund für die Verwendung in diesen Beispielen).
  • Häufig reicht ein vorgefertigter Estimator und einen eigenen Estimator zu schreiben ist überflüssig.
  • Estimator können schnell gegeneinander ausgetauscht werden, so dass ein bequemer Vergleich von Modellen möglich ist.

Im Gegensatz zu vielen anderen Aspekten des “Gerüstbauteils” von TensorFlow scheinen die Estimators auch halbwegs gut designed zu sein (mit Ausnahme kleinerer Unschönheiten hier und da)

Einen Estimator verwenden

Die folgenden Schritte sind notwendig, um einen Estimator zu verwenden:

  • Definieren der Struktur der Traningsdaten über die tf.feature_column API.
  • Definieren einer input_fn, um Trainingsdaten einzulesen
  • Erzeugen einer tf.estimator.RunConfig Instanz, um den Trainingsprozess zu steuern (Zielordner, Logging, etc.).
  • Trainieren des Modells mit Estimator.train.
  • Validieren des Modells mit Estimator.validate.
  • Exportieren des Modells als SavedModel mit Estimator.export_savedmodel

Da der Schwerpunkt dieses Beitrags auf dem Speichern von Modellen für die Verwendung in einer Java-Umgebung liegt, werden wir uns nur auf den letzten Teil konzentrieren. Ein vollständiges Beispiel für die Verwendung von Estimators, einschließlich der Definition von Input, Preprocessing, Training und Export in ein SavedModel finden Sie hier. Details zu den anderen Schritten werden in späteren Posts behandelt, die sich mit den Feinheiten des Estimators befassen. Soll ein benutzerdefinierter Estimator verwendet werden, ist dieser vergleichsweise einfach selbst zu schreiben. Er funktioniert wie ein vorgefertigter Estimator, er benötigt lediglich eine eigene eigene model_fn, um den TensorFlow Graph für das individuelle Modell zu definieren. Das Erstellen von benutzerdefinierten Estimators wird ebenfalls in einem separaten Post behandelt, da das Exportieren eines SavedModel für vorkonfigurierte und benutzerdefinierte Estimators genau das gleiche ist.

Speichern mittels Estimator

Das Speichern eines SavedModels aus einem trainierten Estimator erfordert nur einen Methodenaufruf, wir müssen nur einige zusätzliche Argumente für diesen Aufruf bereitstellen. Der Schlüssel zu einem erfolgreichen Export ist die Angabe einer korrekten serving_input_fn. Diese ist ähnlich zur input_fn, die für Training und die Validierung benötigt wird, sie legt fest, wie Daten für das Modell beim Ausführen der Inferenz bereitgestellt werden. Wir müssen die Funktion jedoch nicht selbst programmieren, es braucht stattdessen nur eine Map mit Eingabeparametern und einen weiteren API-Aufruf, damit TensorFlow die Methode für uns erstellt.

Zum Glück ist die Erstellung dieser Map sehr einfach: Wir definieren einfach eine leere Map und fügen Platzhalter mit ihren jeweiligen Namen als Schlüssel hinzu. Wenn ein Modell also drei Float-Eingabe-Tensoren namensfoobar und baz benötigt, wird die serving_input_fn wie folgt definiert:

feature_spec = {}
feature_spec['foo'] = tf.placeholder(tf.float32, shape=[None], name='foo')
feature_spec['bar'] = tf.placeholder(tf.float32, shape=[None], name='bar')
feature_spec['baz'] = tf.placeholder(tf.float32, shape=[None], name='baz')

serving_input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn(feature_spec)

Die shape hängt natürlich von den Eingabedaten ab, in diesem Fall haben wir nur einzelne float-Werte pro Datum, so dass [None] ausreicht ([None] gibt an, dass wir eine unbekannte Anzahl von Werten haben - so können wir eine beliebige Anzahl von Beispielen in einem Batch ausführen). Für ein 256x256 Graustufenbild wäre die shape [256,256,None].

Nun, da wir die serving_input_fn haben, müssen wir nur noch entscheiden, wo wir speichern wollen und schon sind wir fertig:

regressor.export_savedmodel(
    # under this dir a subdir with the SavedModel is created
    export_dir_base = "saved_models",
    # this will define the part of the graph
    # that feeds data during inference
    serving_input_receiver_fn = serving_input_fn,
    # with this map you can populate a directory
    # with custom data - this can be used to add
    # additional data to the saved model, but you
    # have to load it manually
    assets_extra=None,
    # for debugging, you can export some protobuf
    # files as text
    as_text=False,
    # if you want a different checkpoint than the
    # last, you can pass a different checkpoint file
    # here, None always usess the last checkpoint
    checkpoint_path=None
)

Das war’s. Der Export hat ein Unterverzeichnis unter saved_models mit einem Millisekunden-Zeitstempel als Namen angelegt. Es ist das Verzeichnis, aus dem wir in unseren Java-Code laden müssen.

Alternativen zum Estimator  / SavedModel-Ansatz

Wie bereits erwähnt, gibt es viel zu viele Wege, um trainierte Modelle in/aus TensorFlow zu importieren und exportieren, wir haben uns auf den aktuellsten und zukunftssichersten konzentriert. Dennoch gibt es für bestimmte Sonderfälle zwei weitere wissenswerte Ansätze.

Manuelles Speichern von Variablen

Der SavedModel-Ansatz ist für Fälle gedacht, in denen wir unser Modell wieder mit TensorFlow laden wollen, sei es mit einem TensorFlow-Wrapper für eine andere Sprache als Python oder mit Standard-Python-TensorFlow-Code. Wie im vorherigen Post erwähnt, gibt es jedoch Fälle, in denen man den trainierten Zustand in einem anderen Rahmen oder in selbsterstelltem Code verwenden möchte. In diesem Fall interessiert nur der Variabelinhalt. Da das Checkpoint-Format ziemlich undurchsichtig ist, brauchen wir mehr Kontrolle über das Format, in dem wir unseren Variabelinhalt speichern.

Die Lösung dieses Problems besteht darin, die für uns interessanten Variabel-Knoten auszuwerten, das Ergebnis in einem numpy-Array zu speichern und die Daten dann manuell in ein Format unserer Wahl (z.B. CSV) zu schreiben.

Wenn man bedenkt, wie kompliziert der „richtige” Weg ist, Daten zu persistieren, ist das schmerzhaft einfach und unkompliziert zu verstehen.

Graph Freezing

Graph Freezing ist eine Technik, bei der die Variablen in einem Graphen durch Konstanten ersetzt werden, die den aktuellen Variablenzustand enthalten. Da der Wert von Konstanten mit dem Graphen und nicht mit den Variablenwerten gespeichert wird, können wir auf diese Weise ein komplettes Modell in eine einzige Datei exportieren, indem wir einfach den Graphen speichern und Variablen ignorieren. Da die API zum Laden von TensorFlow-Modellen in Java automatisch auch den Variabelinhalt lädt, haben wir durch diese Methode keinen Mehrwert. Der große Vorteil scheint zu sein, dass man am Ende nur eine Datei hat, was meiner Meinung nach den Aufwand und die zusätzliche Projektkomplexität nicht wert ist - man kann das gleiche einfach erreichen, indem man z.B. die exportierten Dateien zippt.

Dennoch ist es ein interessanter und eleganter Ansatz und könnte in bestimmten Zusammenhängen von Vorteil sein, daher ist er eine Erwähnung wert. Wie es gemacht wird, steht in diesem tollen Tutorial.

Zusammenfassung

  • Die TensorFlow Persistenz-API leider recht kompliziert und verwirrend.
  • Der SavedModel-Ansatz ist der Weg für die zukünftige Entwicklung und der Ansatz, den wir zum Laden unseres Modells aus Java benötigen.
  • Empfohlene Best Practice: Wrappen Sie Ihr Modell in einen Estimator (oder verwenden Sie einen vorgefertigten Estimator).
  • Der Estimator kümmert sich automatisch um das Speichern und Laden während des Trainings.
  • Wenn Sie mit Ihrem Modell zufrieden sind, exportieren Sie Ihr SavedModel aus Ihrem Estimator.
  • Verwenden Sie den Saver nicht mehr manuell, auch wenn dies in vielen Tutorials und Beispielen zu finden ist.