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 SavedModel
selbst ist nicht sehr komplex, es ist mehr oder weniger nur ein Verzeichnis, das “Tags” (beliebige String-IDs) zu MetaGraph
en 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 MetaGraph
en (ähnlich den Methodensignaturen von Programmiersprachen). Ein MetaGraph
enthält nicht den Inhalt der gelernten Variablen!
Warum also kann ein SavedModel
mehrere MetaGraph
en 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 MetaGraph
en gespeichert werden, ist, dass mehrere MetaGraph
en 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 MetaGraph
en 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 MetaGraph
en 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 mehrereMetaGraph
en 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
MetaGraph
en in einemSavedModel
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-Assetsassets.extra/
Hier werden benutzerdefinierte Dateien abgelegt, die nicht zum Standardformat gehörenvariables/
enthält den Variablenstatus (Checkpoints)saved_model.pb
: binäre Protobuf-Datei mit demSavedModel
, denMetaGraph
en, demGraph
, 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 (Estimator
, MonitoredTrainingSession
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 einemMetaGraphDef
. Dies ist Teil dessen, was sonst in einemsaved_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 keinSavedModel
und keinMetaGraph
, sondern nur einGraph
.
Teil einer TensorFlow Installation ist ein Kommandozeilen-Programm zum Untersuchen von SavedModel
s. Dies ist ein sehr nützliches Tool, es ist empfehlenswert, sich damit auseinanderzusetzen, wenn man regelmäßig mit SavedModel
s 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:
- Wir verpacken unseren Modellcode in einen
tf.estimator.Estimator
(oder benutzen einen der vorgefertigtenEstimator
austf.estimator
) - Der
Estimator
kümmert sich automagisch um das Speichern des Lernfortschritts des Trainings mittelsMetaGraph
en und Checkpoints (nichtSavedModel
s) - 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 SavedModel
kü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 inSavedModel
s (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 Estimator
s 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
mitEstimator.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 Estimator
s, 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 Estimator
s 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 Estimator
s wird ebenfalls in einem separaten Post behandelt, da das Exportieren eines SavedModel
für vorkonfigurierte und benutzerdefinierte Estimator
s genau das gleiche ist.
Speichern mittels Estimator
Das Speichern eines SavedModel
s 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 namensfoo
, bar
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.