Ich bin auf genau dasselbe Problem gestoßen, und Mann, war es ein Kaninchenbau. Wollte meine Lösung hier posten, da sie jemandem einen Tag Arbeit ersparen könnte:
TensorFlow Thread-spezifische Datenstrukturen
In TensorFlow gibt es zwei wichtige Datenstrukturen, die hinter den Kulissen arbeiten, wenn Sie model.predict
aufrufen (oder keras.models.load_model
, oder keras.backend.clear_session
, oder so ziemlich jede andere Funktion, die mit dem TensorFlow-Backend interagiert):
- Ein TensorFlow-Diagramm, das die Struktur Ihres Keras-Modells darstellt
- Eine TensorFlow-Sitzung, die die Verbindung zwischen Ihrem aktuellen Diagramm und der TensorFlow-Laufzeit darstellt
Etwas, das in der Dokumentation nicht explizit klar ist, ist, dass sowohl die Sitzung als auch der Graph Eigenschaften des aktuellen Threads sind . Siehe API-Dokumentation hier und hier.
Verwendung von TensorFlow-Modellen in verschiedenen Threads
Es ist natürlich, dass Sie Ihr Modell einmal laden und dann .predict()
aufrufen möchten darauf mehrfach später:
from keras.models import load_model
MY_MODEL = load_model('path/to/model/file')
def some_worker_function(inputs):
return MY_MODEL.predict(inputs)
In einem Webserver- oder Worker-Pool-Kontext wie Celery bedeutet dies, dass Sie das Modell laden, wenn Sie das Modul importieren, das load_model
enthält Zeile, dann wird ein anderer Thread some_worker_function
ausführen , wobei die Vorhersage für die globale Variable ausgeführt wird, die das Keras-Modell enthält. Wenn Sie jedoch versuchen, eine Vorhersage für ein Modell auszuführen, das in einem anderen Thread geladen ist, werden Fehler „Tensor ist kein Element dieses Diagramms“ erzeugt. Dank der mehreren SO-Posts, die dieses Thema berührten, wie ValueError:Tensor Tensor(...) is not an element of this graph. Bei Verwendung des globalen variablen Keras-Modells. Damit dies funktioniert, müssen Sie sich an das verwendete TensorFlow-Diagramm halten – wie wir bereits gesehen haben, ist das Diagramm eine Eigenschaft des aktuellen Threads. Der aktualisierte Code sieht folgendermaßen aus:
from keras.models import load_model
import tensorflow as tf
MY_MODEL = load_model('path/to/model/file')
MY_GRAPH = tf.get_default_graph()
def some_worker_function(inputs):
with MY_GRAPH.as_default():
return MY_MODEL.predict(inputs)
Die etwas überraschende Wendung hier ist:der obige Code ist ausreichend, wenn Sie Thread
verwenden s, hängt aber auf unbestimmte Zeit, wenn Sie Process
verwenden es. Und standardmäßig verwendet Celery Prozesse, um alle seine Worker-Pools zu verwalten. An diesem Punkt sind die Dinge also still funktioniert nicht bei Sellerie.
Warum funktioniert das nur bei Thread
s?
In Python Thread
s haben denselben globalen Ausführungskontext wie der übergeordnete Prozess. Aus den Python _thread-Dokumenten:
Dieses Modul bietet einfache Grundelemente für die Arbeit mit mehreren Threads (auch als leichtgewichtige Prozesse oder Aufgaben bezeichnet) – mehrere Steuerungsthreads, die ihren globalen Datenraum gemeinsam nutzen.
Da Threads eigentlich keine separaten Prozesse sind, verwenden sie denselben Python-Interpreter und unterliegen daher dem berüchtigten Global Interpeter Lock (GIL). Vielleicht noch wichtiger für diese Untersuchung ist, dass sie teilen globaler Datenraum mit dem Elternteil.
Im Gegensatz dazu Process
es sind tatsächlich neue Prozesse, die durch das Programm hervorgebracht werden. Das bedeutet:
- Neue Python-Interpreter-Instanz (und kein GIL)
- Der globale Adressraum ist dupliziert
Beachten Sie hier den Unterschied. Während Thread
s haben Zugriff auf eine gemeinsam genutzte einzelne globale Session-Variable (intern im tensorflow_backend
gespeichert). Modul von Keras), Process
Es gibt Duplikate der Session-Variablen.
Mein bestes Verständnis für dieses Problem ist, dass die Session-Variable eine eindeutige Verbindung zwischen einem Client (Prozess) und der TensorFlow-Laufzeit darstellen soll, aber durch die Duplizierung im Forking-Prozess werden diese Verbindungsinformationen nicht richtig angepasst. Dadurch bleibt TensorFlow hängen, wenn versucht wird, eine Sitzung zu verwenden, die in einem anderen Prozess erstellt wurde. Wenn jemand mehr Einblick in die Funktionsweise von TensorFlow hat, würde ich es gerne hören!
Die Lösung / Problemumgehung
Ich habe Celery so angepasst, dass es Thread
verwendet s anstelle von Process
es zum Poolen. Dieser Ansatz hat einige Nachteile (siehe GIL-Kommentar oben), aber dadurch können wir das Modell nur einmal laden. Wir sind sowieso nicht wirklich CPU-gebunden, da die TensorFlow-Laufzeit alle CPU-Kerne ausschöpft (sie kann die GIL umgehen, da sie nicht in Python geschrieben ist). Sie müssen Celery mit einer separaten Bibliothek versorgen, um Thread-basiertes Pooling durchzuführen; Die Dokumentation schlägt zwei Optionen vor:gevent
oder eventlet
. Anschließend übergeben Sie die ausgewählte Bibliothek über den --pool
an den Worker Befehlszeilenargument.
Alternativ scheint es (wie Sie bereits @pX0r herausgefunden haben), dass andere Keras-Backends wie Theano dieses Problem nicht haben. Das ist sinnvoll, da diese Probleme eng mit den Details der TensorFlow-Implementierung zusammenhängen. Ich persönlich habe Theano noch nicht ausprobiert, daher kann Ihre Laufleistung variieren.
Ich weiß, dass diese Frage vor einiger Zeit gepostet wurde, aber das Problem ist immer noch da draußen, also wird das hoffentlich jemandem helfen!