Narazil jsem na úplně stejný problém a člověk to byla králičí nora. Chtěl jsem sem zveřejnit své řešení, protože by to mohlo někomu ušetřit den práce:
Datové struktury specifické pro vlákno TensorFlow
V TensorFlow existují dvě klíčové datové struktury, které fungují za scénou, když voláte model.predict
(nebo keras.models.load_model
nebo keras.backend.clear_session
, nebo v podstatě jakákoli jiná funkce interagující s backendem TensorFlow):
- Graf TensorFlow, který představuje strukturu vašeho modelu Keras
- Relace TensorFlow, což je spojení mezi vaším aktuálním grafem a běhovým prostředím TensorFlow
Něco, co není v dokumentech bez hloubání jasně jasné, je to, že relace i graf jsou vlastnostmi aktuálního vlákna . Viz dokumenty API zde a zde.
Používání modelů TensorFlow v různých vláknech
Je přirozené, že chcete svůj model načíst jednou a poté zavolat .predict()
na to několikrát později:
from keras.models import load_model
MY_MODEL = load_model('path/to/model/file')
def some_worker_function(inputs):
return MY_MODEL.predict(inputs)
V kontextu webového serveru nebo pracovního fondu, jako je Celery, to znamená, že model načtete při importu modulu obsahujícího load_model
řádek, pak jiné vlákno spustí some_worker_function
, běží predikce na globální proměnné obsahující model Keras. Pokus o spuštění predikce na modelu načteném v jiném vláknu však vytváří chyby „tensor není prvkem tohoto grafu“. Díky několika příspěvkům SO, které se tohoto tématu dotýkaly, jako je ValueError:Tensor Tensor(...) není prvkem tohoto grafu. Při použití modelu globální proměnné keras. Aby to fungovalo, musíte se držet grafu TensorFlow, který byl použit – jak jsme viděli dříve, graf je vlastností aktuálního vlákna. Aktualizovaný kód vypadá takto:
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)
Zde je poněkud překvapivý obrat:výše uvedený kód je dostatečný, pokud používáte Thread
s, ale zablokuje se na dobu neurčitou, pokud používáte Process
es. A ve výchozím nastavení Celery používá procesy ke správě všech svých pracovních skupin. Takže v tuto chvíli jsou věci stále nepracuje na celeru.
Proč to funguje pouze u Thread
s?
V Pythonu Thread
s sdílejí stejný globální kontext provádění jako nadřazený proces. Z dokumentů Python _thread:
Tento modul poskytuje nízkoúrovňová primitiva pro práci s více vlákny (nazývanými také odlehčené procesy nebo úlohy) – více vláken řízení sdílejících svůj globální datový prostor.
Protože vlákna nejsou skutečnými samostatnými procesy, používají stejný pythonový interpret, a proto podléhají nechvalně známému Global Interpeter Lock (GIL). Pro toto vyšetřování je možná důležitější, že sdílejí globální datový prostor s nadřazeným.
Na rozdíl od toho Process
es jsou skutečné nové procesy vytvořené programem. To znamená:
- Nová instance interpretu Pythonu (a žádný GIL)
- Globální adresní prostor je duplicitní
Všimněte si rozdílu zde. Zatímco Thread
mají přístup ke sdílené jediné globální proměnné relace (uložené interně v tensorflow_backend
modul Keras), Process
es mají duplikáty proměnné Session.
Tento problém nejlépe chápu tak, že proměnná Session má představovat jedinečné spojení mezi klientem (procesem) a runtime TensorFlow, ale tím, že je duplikována v procesu rozvětvení, nejsou tyto informace o připojení správně upraveny. To způsobí, že TensorFlow přestane reagovat, když se pokusíte použít relaci vytvořenou v jiném procesu. Pokud má někdo více informací o tom, jak to funguje pod kapotou v TensorFlow, rád bych to slyšel!
Řešení / náhradní řešení
Šel jsem s úpravou celeru tak, aby používal Thread
s namísto Process
es pro sdružování. Tento přístup má určité nevýhody (viz komentář GIL výše), ale to nám umožňuje načíst model pouze jednou. Stejně nejsme ve skutečnosti vázáni na CPU, protože běhové prostředí TensorFlow maximalizuje všechna jádra CPU (může obejít GIL, protože není napsáno v Pythonu). Chcete-li provádět sdružování na základě vláken, musíte Celery dodat samostatnou knihovnu; dokumenty navrhují dvě možnosti:gevent
nebo eventlet
. Vybranou knihovnu pak předáte pracovníkovi prostřednictvím --pool
argument příkazového řádku.
Případně se zdá (jak jste již zjistili @pX0r), že ostatní backendy Keras, jako je Theano, tento problém nemají. To dává smysl, protože tyto problémy úzce souvisejí s detaily implementace TensorFlow. Osobně jsem Theano ještě nezkoušel, takže váš počet najetých kilometrů se může lišit.
Vím, že tato otázka byla položena před chvílí, ale problém stále existuje, takže doufám, že to někomu pomůže!