Redis
 sql >> Base de données >  >> NoSQL >> Redis

Keras prédit de ne pas revenir à l'intérieur de la tâche de céleri

J'ai rencontré exactement le même problème, et l'homme était-ce un terrier de lapin. Je voulais publier ma solution ici car cela pourrait faire gagner une journée de travail à quelqu'un :

Structures de données spécifiques aux threads TensorFlow

Dans TensorFlow, deux structures de données clés fonctionnent en arrière-plan lorsque vous appelez model.predict (ou keras.models.load_model , ou keras.backend.clear_session , ou à peu près n'importe quelle autre fonction interagissant avec le backend TensorFlow) :

  • Un graphique TensorFlow, qui représente la structure de votre modèle Keras
  • Une session TensorFlow, qui est la connexion entre votre graphique actuel et l'environnement d'exécution TensorFlow

Quelque chose qui n'est pas explicitement clair dans la documentation sans quelques recherches est que la session et le graphique sont des propriétés du thread actuel . Consultez la documentation de l'API ici et ici.

Utilisation de modèles TensorFlow dans différents threads

Il est naturel de vouloir charger votre modèle une fois puis d'appeler .predict() dessus plusieurs fois plus tard :

from keras.models import load_model

MY_MODEL = load_model('path/to/model/file')

def some_worker_function(inputs):
    return MY_MODEL.predict(inputs)

Dans un contexte de serveur Web ou de pool de travail comme Celery, cela signifie que vous chargerez le modèle lorsque vous importerez le module contenant le load_model ligne, alors un thread différent exécutera some_worker_function , en exécutant la prédiction sur la variable globale contenant le modèle Keras. Cependant, essayer d'exécuter la prédiction sur un modèle chargé dans un thread différent produit des erreurs "le tenseur n'est pas un élément de ce graphique". Grâce aux plusieurs messages SO qui ont abordé ce sujet, tels que ValueError :Tensor Tensor(...) n'est pas un élément de ce graphique. Lors de l'utilisation du modèle keras à variable globale. Pour que cela fonctionne, vous devez vous accrocher au graphique TensorFlow qui a été utilisé - comme nous l'avons vu précédemment, le graphique est une propriété du thread actuel. Le code mis à jour ressemble à ceci :

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 tournure quelque peu surprenante ici est :le code ci-dessus est suffisant si vous utilisez Thread s, mais se bloque indéfiniment si vous utilisez Process es. Et par défaut, Celery utilise des processus pour gérer tous ses pools de travailleurs. Donc, à ce stade, les choses sont toujours ne fonctionne pas sur Céleri.

Pourquoi cela ne fonctionne-t-il que sur Thread ? s ?

En Python, Thread s partagent le même contexte d'exécution global que le processus parent. À partir de la documentation Python _thread :

Ce module fournit des primitives de bas niveau pour travailler avec plusieurs threads (également appelés processus ou tâches légers) - plusieurs threads de contrôle partageant leur espace de données global.

Parce que les threads ne sont pas de véritables processus séparés, ils utilisent le même interpréteur python et sont donc soumis au tristement célèbre Global Interpeter Lock (GIL). Peut-être plus important encore pour cette enquête, ils partagent espace de données global avec le parent.

Contrairement à cela, Process es sont réels nouveaux processus engendrés par le programme. Cela signifie :

  • Nouvelle instance d'interpréteur Python (et pas de GIL)
  • L'espace d'adressage global est dupliqué

Notez la différence ici. Tandis que Thread s ont accès à une seule variable de session globale partagée (stockée en interne dans le tensorflow_backend module de Keras), Process es ont des doublons de la variable Session.

Ma meilleure compréhension de ce problème est que la variable Session est censée représenter une connexion unique entre un client (processus) et l'environnement d'exécution TensorFlow, mais en étant dupliquée dans le processus de fork, ces informations de connexion ne sont pas correctement ajustées. Cela provoque le blocage de TensorFlow lorsque vous essayez d'utiliser une session créée dans un processus différent. Si quelqu'un a plus d'informations sur la façon dont cela fonctionne sous le capot dans TensorFlow, j'aimerais l'entendre !

La solution/le contournement

J'ai opté pour l'ajustement de Celery afin qu'il utilise Thread s au lieu de Process es pour la mise en commun. Il y a quelques inconvénients à cette approche (voir le commentaire GIL ci-dessus), mais cela nous permet de charger le modèle une seule fois. De toute façon, nous ne sommes pas vraiment liés au processeur puisque le runtime TensorFlow maximise tous les cœurs du processeur (il peut contourner le GIL car il n'est pas écrit en Python). Vous devez fournir à Celery une bibliothèque distincte pour effectuer une mise en commun basée sur les threads ; la documentation suggère deux options :gevent ou eventlet . Vous passez ensuite la bibliothèque que vous choisissez dans le travailleur via le --pool argument de ligne de commande.

Alternativement, il semble (comme vous l'avez déjà découvert @pX0r) que d'autres backends Keras tels que Theano n'ont pas ce problème. Cela a du sens, car ces problèmes sont étroitement liés aux détails de mise en œuvre de TensorFlow. Personnellement, je n'ai pas encore essayé Theano, donc votre kilométrage peut varier.

Je sais que cette question a été postée il y a un certain temps, mais le problème est toujours là, alors j'espère que cela aidera quelqu'un !