web-dev-qa-db-fra.com

Existe-t-il un exemple sur la façon de générer des fichiers protobuf contenant des graphes TensorFlow formés

Je regarde l'exemple de Google sur la manière de déployer et d'utiliser un graphe Tensorflow pré-formé (modèle) sur Android. Cet exemple utilise un fichier .pb à l'adresse: 

https://storage.googleapis.com/download.tensorflow.org/models/inception5h.Zip

qui est un lien vers un fichier à téléchargement automatique.

L'exemple montre comment charger le fichier .pb dans une session Tensorflow et l'utiliser pour effectuer une classification, mais il ne semble pas mentionner comment générer un tel fichier .pb après la formation d'un graphique (par exemple, en Python).

Y at-il des exemples sur la façon de faire cela?

43
scai

EDIT: Le freeze_graph.py script, qui fait partie du référentiel TensorFlow, sert maintenant d’outil pour générer un tampon de protocole représentant un modèle formé "gelé" à partir d’un TensorFlow GraphDef existant et d’un point de contrôle enregistré. Il utilise les mêmes étapes que décrites ci-dessous, mais il est beaucoup plus facile à utiliser.


Actuellement, le processus n'est pas très bien documenté (et peut être affiné), mais les étapes approximatives sont les suivantes:

  1. Construisez et entraînez votre modèle en tant que tf.Graph appelé g_1.
  2. Récupérez les valeurs finales de chacune des variables et stockez-les sous forme de tableaux numpy (à l’aide de Session.run() ).
  3. Dans un nouveau tf.Graph appelé g_2, créez tf.constant() tensors pour chacune des variables, en utilisant la valeur du tableau numpy correspondant récupéré à l'étape 2.
  4. Utilisez tf.import_graph_def() pour copier les nœuds de g_1 dans g_2 et utilisez l’argument input_map pour remplacer chaque variable dans g_1 par les tenseurs tf.constant() correspondants créés à l’étape 3. Vous pouvez également utiliser input_map pour spécifier un nouveau tenseur d’entrée (par exemple, remplacer un pipeline d’entrée par un tf.placeholder() ). Utilisez l'argument return_elements pour spécifier le nom du tenseur de sortie prévu.

  5. Appelez g_2.as_graph_def() pour obtenir une représentation du graphique en mémoire tampon de protocole.

(NOTE: Le graphe généré comportera des nœuds supplémentaires dans le graphe pour la formation. Bien que cela ne fasse pas partie de l'API publique, vous voudrez peut-être utiliser la fonction interne graph_util.extract_sub_graph() pour supprimer ces nœuds du graphe. .)

33
mrry

En guise d'alternative à ma réponse précédente en utilisant freeze_graph(), qui n'est utile que si vous l'appelez comme script, il existe une fonction très agréable qui fera tout le travail lourd pour vous et qui convient à être appelée à partir de votre code de formation modèle standard.

convert_variables_to_constants() fait deux choses:

  • Il gèle les poids en remplaçant les variables par des constantes
  • Il supprime les nœuds qui ne sont pas liés à la prédiction anticipée

En supposant que sess est votre tf.Session() et "output" est le nom de votre nœud de prédiction, le code suivant sérialisera votre graphe minimal en protobuf textuel et binaire.


from tensorflow.python.framework.graph_util import convert_variables_to_constants

minimal_graph = convert_variables_to_constants(sess, sess.graph_def, ["output"])

tf.train.write_graph(minimal_graph, '.', 'minimal_graph.proto', as_text=False)
tf.train.write_graph(minimal_graph, '.', 'minimal_graph.txt', as_text=True)
16
mirosval

Je ne pouvais pas comprendre comment mettre en œuvre la méthode décrite par mrry. Mais voici comment je l'ai résolu. Je ne suis pas sûr que ce soit la meilleure façon de résoudre le problème, mais au moins cela le résout.

Comme write_graph peut également stocker les valeurs des constantes, j'ai ajouté le code suivant au python juste avant l'écriture du graphique avec la fonction write_graph:

for v in tf.trainable_variables():
    vc = tf.constant(v.eval())
    tf.assign(v, vc, name="assign_variables")

Cela crée des constantes qui stockent les valeurs des variables après leur apprentissage, puis créent des tenseurs "assign_variables" pour les affecter aux variables. Désormais, lorsque vous appelez write_graph, les valeurs des variables seront stockées dans le fichier sous forme de constantes.

Il ne reste plus qu’à appeler ces tenseurs "assign_variables" dans le code c pour vous assurer que vos variables sont affectées des valeurs de constantes stockées dans le fichier. Voici une façon de le faire:

      Status status = NewSession(SessionOptions(), &session);
      std::vector<tensorflow::Tensor> outputs;
      char name[100];
      for(int i = 0;status.ok(); i++) {
        if (i==0)
            sprintf(name, "assign_variables");
        else
            sprintf(name, "assign_variables_%d", i);

        status = session->Run({}, {name}, {}, &outputs);
      }
4
Mostafa Hassan

Voici une autre prise sur la réponse de @ Mostafa. Une façon un peu plus simple d’exécuter les opérations tf.assign consiste à les stocker dans un tf.group. Voici mon code Python:

  ops = []
  for v in tf.trainable_variables():
    vc = tf.constant(v.eval())
    ops.append(tf.assign(v, vc));
  tf.group(*ops, name="assign_trained_variables")

Et en C++:

  std::vector<tensorflow::Tensor> tmp;
  status = session.Run({}, {}, { "assign_trained_variables" }, &tmp);
  if (!status.ok()) {
    // Handle error
  }

De cette façon, vous n’avez qu’un seul opérateur nommé à exécuter du côté C++, vous n’avez donc pas à vous soucier de l’itération des nœuds.

4
Kris Giesing

J'ai trouvé une fonction freeze_graph() dans la base de code Tensorflow qui pourrait être utile pour cela. D'après ce que j'ai compris, il permute les variables avec les constantes avant de sérialiser GraphDef. Ainsi, lorsque vous chargez ce graphique à partir de C++, il ne contient plus aucune variable et vous pouvez l'utiliser directement pour les prédictions.

Il existe également un test et quelques descriptions dans le Guide .

Cela semble être l'option la plus propre ici.

1
mirosval

Je viens de trouver ce post et c'était très utile merci! Je vais aussi avec la méthode de @ Mostafa, bien que mon code C++ soit un peu différent:

    std::vector<string> names;
    int node_count = graph.node_size();
    cout << node_count << " nodes in graph" << endl;

    // iterate all nodes
    for(int i=0; i<node_count; i++) {
        auto n = graph.node(i);
        cout << i << ":" << n.name() << endl;

        // if name contains "var_hack", add to vector
        if(n.name().find("var_hack") != std::string::npos) {
            names.Push_back(n.name());
            cout << "......bang" << endl;
        }
    }
    session.Run({}, names, {}, &outputs);

NB J'utilise "var_hack" comme nom de variable en python

1
memo