Redis
 sql >> Database >  >> NoSQL >> Redis

Keras prevede di non tornare all'interno del compito di sedano

Mi sono imbattuto nello stesso identico problema, e l'uomo era una tana del coniglio. Volevo pubblicare la mia soluzione qui poiché potrebbe far risparmiare a qualcuno una giornata di lavoro:

Strutture dati specifiche del thread TensorFlow

In TensorFlow, ci sono due strutture dati chiave che funzionano dietro le quinte quando chiami model.predict (o keras.models.load_model o keras.backend.clear_session , o praticamente qualsiasi altra funzione che interagisce con il back-end TensorFlow):

  • Un grafico TensorFlow, che rappresenta la struttura del tuo modello Keras
  • Una sessione TensorFlow, che è la connessione tra il tuo grafico corrente e il runtime TensorFlow

Qualcosa che non è esplicitamente chiaro nei documenti senza un po' di scavo è che sia la sessione che il grafico sono proprietà del thread corrente . Consulta i documenti API qui e qui.

Utilizzo di modelli TensorFlow in thread diversi

È naturale voler caricare il tuo modello una volta e poi chiamare .predict() su di esso più volte in seguito:

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 un server web o in un contesto di pool di lavoro come Celery, ciò significa che caricherai il modello quando importerai il modulo contenente load_model riga, quindi un thread diverso eseguirà some_worker_function , eseguendo predict sulla variabile globale contenente il modello Keras. Tuttavia, il tentativo di eseguire predict su un modello caricato in un thread diverso produce errori "il tensore non è un elemento di questo grafico". Grazie ai numerosi post SO che hanno toccato questo argomento, come ValueError:Tensor Tensor(...) non è un elemento di questo grafico. Quando si utilizza il modello keras variabile globale. Affinché funzioni, è necessario aggrapparsi al grafico TensorFlow che è stato utilizzato:come abbiamo visto in precedenza, il grafico è una proprietà del thread corrente. Il codice aggiornato si presenta così:

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)

La svolta in qualche modo sorprendente qui è:il codice sopra è sufficiente se stai usando Thread s, ma si blocca a tempo indeterminato se stai usando Process es. E per impostazione predefinita, Celery utilizza i processi per gestire tutti i suoi pool di lavoro. Quindi, a questo punto, le cose sono ferme non funziona su Celery.

Perché funziona solo su Thread s?

In Python, Thread s condividono lo stesso contesto di esecuzione globale del processo padre. Dai documenti Python _thread:

Questo modulo fornisce primitive di basso livello per lavorare con più thread (chiamati anche processi o attività leggere) — più thread di controllo che condividono il loro spazio dati globale.

Poiché i thread non sono veri e propri processi separati, utilizzano lo stesso interprete Python e quindi sono soggetti al famigerato Global Interpeter Lock (GIL). Forse ancora più importante per questa indagine, essi condividono spazio dati globale con il genitore.

In contrasto con questo, Process sono effettivi nuovi processi generati dal programma. Ciò significa:

  • Nuova istanza dell'interprete Python (e nessun GIL)
  • Lo spazio degli indirizzi globale è duplicato

Nota la differenza qui. Mentre Thread s hanno accesso a una singola variabile Session globale condivisa (memorizzata internamente in tensorflow_backend modulo di Keras), Process es hanno duplicati della variabile Session.

La mia migliore comprensione di questo problema è che la variabile Session dovrebbe rappresentare una connessione univoca tra un client (processo) e il runtime TensorFlow, ma essendo duplicata nel processo di fork, queste informazioni di connessione non vengono regolate correttamente. Ciò causa il blocco di TensorFlow quando si tenta di utilizzare una sessione creata in un processo diverso. Se qualcuno ha più informazioni su come funziona sotto il cofano in TensorFlow, mi piacerebbe sentirlo!

La soluzione/soluzione alternativa

Sono andato con la regolazione del sedano in modo che utilizzi Thread s invece di Process es per il raggruppamento. Ci sono alcuni svantaggi in questo approccio (vedi il commento GIL sopra), ma questo ci permette di caricare il modello solo una volta. Comunque non siamo davvero vincolati alla CPU poiché il runtime TensorFlow massimizza tutti i core della CPU (può eludere il GIL poiché non è scritto in Python). Devi fornire a Celery una libreria separata per eseguire il pooling basato su thread; i documenti suggeriscono due opzioni:gevent o eventlet . Quindi trasferisci la libreria che scegli nel lavoratore tramite il --pool argomento della riga di comando.

In alternativa, sembra (come hai già scoperto @pX0r) che altri backend Keras come Theano non abbiano questo problema. Ciò ha senso, dal momento che questi problemi sono strettamente correlati ai dettagli di implementazione di TensorFlow. Personalmente non ho ancora provato Theano, quindi il tuo chilometraggio potrebbe variare.

So che questa domanda è stata postata qualche tempo fa, ma il problema è ancora disponibile, quindi spero che questo possa aiutare qualcuno!