UPDATE - 15.01.2020 : Die derzeitige bewährte Methode für kleine Losgrößen sollte darin bestehen, Eingaben direkt in das Modell einzuspeisen - dh preds = model(x)
und wenn sich Ebenen beim Zug / bei der Inferenz unterschiedlich verhalten model(x, training=False)
. Nach dem letzten Commit ist dies jetzt dokumentiert .
Ich habe diese nicht bewertet, aber laut Git-Diskussion lohnt es sich auch, es zu versuchen predict_on_batch()
- insbesondere mit Verbesserungen in TF 2.1.
ULTIMATIVER SCHULD : self._experimental_run_tf_function = True
. Es ist experimentell . Aber es ist nicht wirklich schlecht.
Für alle TensorFlow-Entwickler: Bereinigen Sie Ihren Code . Es ist ein Chaos. Und es verstößt gegen wichtige Codierungspraktiken, z. B. wenn eine Funktion eine Sache tut ; _process_inputs
macht viel mehr als "Prozesseingaben", das gleiche gilt für _standardize_user_data
. "Ich werde nicht genug bezahlt" - aber Sie zahlen, wenn Sie mehr Zeit damit verbringen, Ihre eigenen Sachen zu verstehen, und wenn Benutzer Ihre Issues-Seite mit Fehlern füllen, die mit einem klareren Code leichter behoben werden können.
ZUSAMMENFASSUNG : Es ist nur ein bisschen langsamer mit compile()
.
compile()
setzt ein internes Flag, das eine andere Vorhersagefunktion zuweist predict
. Diese Funktion erstellt bei jedem Aufruf ein neues Diagramm und verlangsamt es im Vergleich zu nicht kompilierten. Der Unterschied ist jedoch nur dann ausgeprägt, wenn die Zugzeit viel kürzer als die Datenverarbeitungszeit ist . Wenn wir die Modellgröße auf mindestens mittelgroß erhöhen , werden beide gleich. Siehe Code unten.
Diese leichte Verlängerung der Datenverarbeitungszeit wird durch die verstärkte Grafikfähigkeit mehr als kompensiert. Da es effizienter ist, nur ein Modelldiagramm beizubehalten, wird das eine Vorkompilierungsprogramm verworfen. Nichtsdestotrotz : Wenn Ihr Modell im Verhältnis zu Daten klein ist, sind Sie ohne compile()
Modellschluss besser dran . Siehe meine andere Antwort für eine Problemumgehung.
WAS SOLLTE ICH TUN?
Vergleichen Sie die kompilierte und die nicht kompilierte Modellleistung wie im Code unten.
- Kompiliert ist schneller :
predict
Auf einem kompilierten Modell ausführen .
- Kompiliert ist langsamer :
predict
Auf einem nicht kompilierten Modell ausführen .
Ja, beides ist möglich und hängt von (1) der Datengröße ab. (2) Modellgröße; (3) Hardware. Der Code unten zeigt tatsächlich, dass das kompilierte Modell schneller ist, aber 10 Iterationen sind ein kleines Beispiel. Siehe "Problemumgehungen" in meiner anderen Antwort für die "Anleitung".
DETAILS :
Das Debuggen dauerte eine Weile, hat aber Spaß gemacht. Im Folgenden beschreibe ich die Haupttäter, die ich entdeckt habe, zitiere einige relevante Dokumentationen und zeige Profiler-Ergebnisse, die zum endgültigen Engpass geführt haben.
(der FLAG == self.experimental_run_tf_function
Kürze halber)
Model
Standardmäßig instanziiert mit FLAG=False
. compile()
setzt es auf True
.
predict()
beinhaltet den Erwerb der Vorhersagefunktion, func = self._select_training_loop(x)
- Ohne spezielle kwargs, die an
predict
und übergeben werden compile
, sind alle anderen Flags so, dass:
- (A)
FLAG==True
->func = training_v2.Loop()
- (B)
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
- Ausgehend von der Quellcode-Dokumentzeichenfolge ist (A) stark grafisch abhängig, verwendet eine stärkere Verteilungsstrategie und Ops neigen dazu, Diagrammelemente zu erstellen und zu zerstören, was die Leistung "beeinträchtigen" kann.
Wahr Schuldige : _process_inputs()
für Buchhaltung 81% der Laufzeit . Seine Hauptkomponente? _create_graph_function()
, 72% der Laufzeit . Diese Methode existiert nicht einmal für (B) . Verwendung eines mittelgroßen Modell jedoch _process_inputs
weist weniger als 1% der Laufzeit . Code unten und Profilerstellungsergebnisse folgen.
DATENVERARBEITER :
(A) : <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>
verwendet in _process_inputs()
. Relevanter Quellcode
(B) : numpy.ndarray
, zurückgegeben von convert_eager_tensors_to_numpy
. Relevanter Quellcode und hier
MODEL EXECUTION FUNCTION (zB vorhersagen)
(A) : Verteilungsfunktion und hier
(B) : Verteilungsfunktion (unterschiedlich) und hier
PROFILER : Ergebnisse für Code in meiner anderen Antwort "winziges Modell" und in dieser Antwort "mittleres Modell":
Winziges Modell : 1000 Iterationen,compile()
Winziges Modell : 1000 Iterationen, Nr compile()
Mittleres Modell : 10 Iterationen
DOKUMENTATION (indirekt) über die Auswirkungen von compile()
: Quelle
Im Gegensatz zu anderen TensorFlow-Operationen konvertieren wir keine numerischen Python-Eingaben in Tensoren. Außerdem ein neues Diagramm wird für jeden einzelnen Python Zahlenwert erzeugt , zum Beispiel rufenden g(2)
und g(3)
zwei neue Graphen erzeugen
function
Instanziiert ein separates Diagramm für jeden eindeutigen Satz von Eingabeformen und Datentypen . Das folgende Codefragment führt beispielsweise dazu, dass drei verschiedene Diagramme verfolgt werden, da jede Eingabe eine andere Form hat
Ein einzelnes tf.function-Objekt muss möglicherweise mehreren Berechnungsgraphen unter der Haube zugeordnet werden. Dies sollte nur als Leistung sichtbar sein (das Verfolgen von Diagrammen hat einen Rechenaufwand und Speicherkosten ungleich Null ), sollte jedoch die Richtigkeit des Programms nicht beeinträchtigen
Gegenbeispiel :
from tensorflow.keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from tensorflow.keras.layers import Flatten, Dropout
from tensorflow.keras.models import Model
import numpy as np
from time import time
def timeit(func, arg, iterations):
t0 = time()
for _ in range(iterations):
func(arg)
print("%.4f sec" % (time() - t0))
batch_size = 32
batch_shape = (batch_size, 400, 16)
ipt = Input(batch_shape=batch_shape)
x = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x = LSTM(512, activation='relu', return_sequences=True)(ipt)
x = Conv1D(128, 400, 1, padding='same')(x)
x = Flatten()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
X = np.random.randn(*batch_shape)
timeit(model.predict, X, 10)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 10)
Ausgänge :
34.8542 sec
34.7435 sec