Jag stötte på exakt samma problem, och människan var det ett kaninhål. Tänkte lägga upp min lösning här eftersom det kan rädda någon en arbetsdag:
TensorFlow trådspecifika datastrukturer
I TensorFlow finns det två viktiga datastrukturer som fungerar bakom kulisserna när du anropar model.predict
(eller keras.models.load_model
, eller keras.backend.clear_session
, eller i stort sett vilken annan funktion som helst som interagerar med TensorFlow-backend):
- En TensorFlow-graf, som representerar strukturen på din Keras-modell
- En TensorFlow-session, som är kopplingen mellan din nuvarande graf och TensorFlow-körtiden
Något som inte är explicit tydligt i dokumenten utan lite grävande är att både sessionen och grafen är egenskaper för den aktuella tråden . Se API-dokument här och här.
Använda TensorFlow-modeller i olika trådar
Det är naturligt att vilja ladda din modell en gång och sedan anropa .predict()
på den flera gånger senare:
from keras.models import load_model
MY_MODEL = load_model('path/to/model/file')
def some_worker_function(inputs):
return MY_MODEL.predict(inputs)
I en webbserver- eller workerpool-kontext som Celery betyder detta att du kommer att ladda modellen när du importerar modulen som innehåller load_model
rad, så kommer en annan tråd att köra some_worker_function
, kör förutsägelse på den globala variabeln som innehåller Keras-modellen. Men att försöka köra förutsägelse på en modell som laddats i en annan tråd ger "tensor är inte en del av denna graf"-fel. Tack vare de flera SO-inlägg som berörde detta ämne, såsom ValueError:Tensor Tensor(...) är inte en del av denna graf. När du använder global variabel keras-modell. För att få detta att fungera måste du hänga på TensorFlow-grafen som användes - som vi såg tidigare är grafen en egenskap hos den aktuella tråden. Den uppdaterade koden ser ut så här:
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)
Den något överraskande vändningen här är:koden ovan är tillräcklig om du använder Thread
s, men hänger sig på obestämd tid om du använder Process
es. Och som standard använder Celery processer för att hantera alla sina arbetarpooler. Så vid det här laget är saker stilla fungerar inte på selleri.
Varför fungerar detta bara på Thread
s?
I Python, Thread
s delar samma globala exekveringskontext som den överordnade processen. Från Python _thread docs:
Den här modulen tillhandahåller primitiver på låg nivå för att arbeta med flera trådar (även kallade lätta processer eller uppgifter) – flera trådar av kontroll som delar sitt globala datautrymme.
Eftersom trådar inte är egentliga separata processer, använder de samma pythontolk och är därför föremål för det ökända Global Interpeter Lock (GIL). Kanske ännu viktigare för den här utredningen, de delar globalt datautrymme med föräldern.
I motsats till detta, Process
de är verkliga nya processer som skapats av programmet. Det betyder:
- Ny Python-tolkinstans (och ingen GIL)
- Globalt adressutrymme är duplicerat
Notera skillnaden här. Medan Thread
s har tillgång till en delad enskild global sessionsvariabel (lagrad internt i tensorflow_backend
modul av Keras), Process
es har dubbletter av Session-variabeln.
Min bästa förståelse för det här problemet är att Session-variabeln ska representera en unik koppling mellan en klient (process) och TensorFlow-körtiden, men genom att dupliceras i forkingsprocessen är denna anslutningsinformation inte korrekt justerad. Detta gör att TensorFlow hänger sig när man försöker använda en session som skapats i en annan process. Om någon har mer insikt i hur detta fungerar under huven i TensorFlow, skulle jag gärna höra det!
Lösningen/lösningen
Jag gick med att justera Selleri så att den använder Thread
s istället för Process
es för pooling. Det finns några nackdelar med detta tillvägagångssätt (se GIL-kommentaren ovan), men det gör att vi bara kan ladda modellen en gång. Vi är inte riktigt CPU-bundna i alla fall eftersom TensorFlow-körtiden maximerar alla CPU-kärnor (den kan kringgå GIL eftersom den inte är skriven i Python). Du måste förse Celery med ett separat bibliotek för att göra trådbaserad poolning; dokumenten föreslår två alternativ:gevent
eller eventlet
. Du skickar sedan biblioteket du väljer till arbetaren via --pool
kommandoradsargument.
Alternativt verkar det (som du redan har fått reda på @pX0r) som andra Keras backends som Theano inte har detta problem. Det är vettigt, eftersom dessa problem är nära relaterade till TensorFlow-implementeringsdetaljer. Jag personligen har inte provat Theano ännu, så din körsträcka kan variera.
Jag vet att den här frågan postades för ett tag sedan, men problemet finns fortfarande kvar, så förhoppningsvis kommer det här att hjälpa någon!