73 votes

Comment exporter Keras .h5 vers tensorflow .pb ?

J'ai affiné le modèle initial avec un nouveau jeu de données et l'ai sauvegardé en tant que modèle ".h5" dans Keras. Maintenant, mon objectif est d'exécuter mon modèle sur Android Tensorflow qui n'accepte que l'extension ".pb". La question est de savoir s'il existe une bibliothèque dans Keras ou Tensorflow pour faire cette conversion ? J'ai vu ce post jusqu'à présent : https://blog.keras.io/keras-as-a-simplified-interface-to-tensorflow-tutorial.html mais je n'arrive pas encore à comprendre.

98voto

jdehesa Points 22254

Keras n'inclut pas par lui-même un moyen d'exporter un graphique TensorFlow en tant que fichier de tampons de protocole, mais vous pouvez le faire en utilisant les utilitaires TensorFlow habituels. Ici est un article de blog expliquant comment le faire en utilisant l'utilitaire script. freeze_graph.py inclus dans TensorFlow, ce qui est la façon "typique" de faire.

Cependant, je trouve personnellement ennuyeux d'avoir à faire un point de contrôle et ensuite d'exécuter un script externe pour obtenir un modèle, et préfère plutôt le faire à partir de mon propre code Python, donc j'utilise une fonction comme celle-ci :

def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):
    """
    Freezes the state of a session into a pruned computation graph.

    Creates a new computation graph where variable nodes are replaced by
    constants taking their current value in the session. The new graph will be
    pruned so subgraphs that are not necessary to compute the requested
    outputs are removed.
    @param session The TensorFlow session to be frozen.
    @param keep_var_names A list of variable names that should not be frozen,
                          or None to freeze all the variables in the graph.
    @param output_names Names of the relevant graph outputs.
    @param clear_devices Remove the device directives from the graph for better portability.
    @return The frozen graph definition.
    """
    graph = session.graph
    with graph.as_default():
        freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
        output_names = output_names or []
        output_names += [v.op.name for v in tf.global_variables()]
        input_graph_def = graph.as_graph_def()
        if clear_devices:
            for node in input_graph_def.node:
                node.device = ""
        frozen_graph = tf.graph_util.convert_variables_to_constants(
            session, input_graph_def, output_names, freeze_var_names)
        return frozen_graph

qui s'inspire de la mise en œuvre de freeze_graph.py . Les paramètres sont également similaires à ceux du script. session est l'objet de la session TensorFlow. keep_var_names n'est nécessaire que si vous voulez garder une variable non gelée (par exemple pour les modèles à état), donc généralement pas. output_names est une liste avec les noms des opérations qui produisent les sorties que vous voulez. clear_devices supprime simplement toute directive de périphérique pour rendre le graphe plus portable. Ainsi, pour une application Keras typique model avec une sortie, vous feriez quelque chose comme :

from keras import backend as K

# Create, compile and train model...

frozen_graph = freeze_session(K.get_session(),
                              output_names=[out.op.name for out in model.outputs])

Ensuite, vous pouvez écrire le graphique dans un fichier comme d'habitude avec tf.train.write_graph :

tf.train.write_graph(frozen_graph, "some_directory", "my_model.pb", as_text=False)

1 votes

Cela semble fonctionner avec moi. Cependant, le fichier .pb de sortie n'a pas les mêmes noms de nœuds d'entrée. Est-ce bien le cas ? Le nom du nœud d'entrée était input_1 mais après le gel, je ne connais pas le nouveau nom. Pouvez-vous me dire quel serait ce nouveau nom ?

2 votes

J'obtiens une erreur dans model.output.op.name. Pouvez-vous me dire comment résoudre ce problème ?

0 votes

@MagedSaeed Les noms des nœuds graphiques ne devraient pas changer, mais parfois, avec Keras, il est difficile de savoir quel nom exact a été donné à des objets particuliers (placeholders, prédictions, etc.). Visualisation du graphique dans TensorBoard peut vous aider.

31voto

Jeff Tang Points 499

La méthode freeze_session fonctionne bien. Mais par rapport à la sauvegarde dans un fichier de point de contrôle, l'utilisation de l'outil freeze_graph fourni avec TensorFlow me semble plus simple, car il est plus facile à maintenir. Tout ce que vous avez à faire, ce sont les deux étapes suivantes :

Tout d'abord, ajoutez après votre code Keras model.fit(...) et former votre modèle :

from keras import backend as K
import tensorflow as tf
print(model.output.op.name)
saver = tf.train.Saver()
saver.save(K.get_session(), '/tmp/keras_model.ckpt')

Ensuite, accédez à votre répertoire racine TensorFlow, exécutez :

python tensorflow/python/tools/freeze_graph.py \
--input_meta_graph=/tmp/keras_model.ckpt.meta \
--input_checkpoint=/tmp/keras_model.ckpt \
--output_graph=/tmp/keras_frozen.pb \
--output_node_names="<output_node_name_printed_in_step_1>" \
--input_binary=true

3 votes

J'ai dû définir K.set_learning_phase(0) avant de sauvegarder le point de contrôle. Sinon, j'ai été confronté à l'erreur Keras error “You must feed a value for placeholder tensor 'keras_learning_phase' with dtype bool” en l'exécutant sur Android. Je mets 0 car je n'ai besoin du modèle que pour l'inférence.

5 votes

K.set_learning_phase doit être appelé avant de charger le modèle.

27voto

Amir Saniyan Points 2406

Mise à jour pour Tensorflow 2

Sauvegarde de tout dans une seule archive dans le TensorFlow SavedModel format (contient saved_model.pb ) :

model = ...  # Get model (Sequential, Functional Model, or Model subclass)
model.save('path/to/location')

ou dans l'ancienne version de Keras H5 format :

model = ...  # Get model (Sequential, Functional Model, or Model subclass)
model.save('model.h5')

Le format recommandé est le suivant SavedModel .

Chargement du modèle en retour :

from tensorflow import keras
model = keras.models.load_model('path/to/location')
model = keras.models.load_model('model.h5')

A SavedModel contient un programme TensorFlow complet, y compris les paramètres formés (c.-à-d, tf.Variables ) et le calcul. Il ne nécessite pas l'exécution du code de construction du modèle d'origine, ce qui le rend utile pour le partage ou le déploiement avec d'autres applications. TFLite , TensorFlow.js , TensorFlow Serving ou TensorFlow Hub .


Exemple pour Tensorflow 2

L'exemple simple suivant (exemple XOR) montre comment exporter des modèles Keras (dans les deux formats h5 et pb ), et l'utilisation du modèle en Python et C++ :


train.py :

import numpy as np
import tensorflow as tf

print(tf.__version__)  # 2.4.1

x_train = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], 'float32')
y_train = np.array([[0], [1], [1], [0]], 'float32')

inputs = tf.keras.Input(shape=(2,), name='input')
x = tf.keras.layers.Dense(64, activation='relu')(inputs)
x = tf.keras.layers.Dense(64, activation='relu')(x)
x = tf.keras.layers.Dense(64, activation='relu')(x)
x = tf.keras.layers.Dense(64, activation="relu")(x)
outputs = tf.keras.layers.Dense(1, activation='sigmoid', name='output')(x)

model = tf.keras.Model(inputs=inputs, outputs=outputs, name='xor')

model.summary()

model.compile(loss='mean_squared_error', optimizer='adam', metrics=['binary_accuracy'])

model.fit(x_train, y_train, epochs=100)

model.save('./xor/')  # SavedModel format

model.save('./xor.h5')  # Keras H5 format

Après avoir exécuté le script ci-dessus :

.
 train.py
 xor
    assets
    saved_model.pb
    variables
        variables.data-00000-of-00001
        variables.index
 xor.h5

predict.py :

import numpy as np
import tensorflow as tf

print(tf.__version__)  # 2.4.1

model = tf.keras.models.load_model('./xor/')  # SavedModel format
# model = tf.keras.models.load_model('./xor.h5')  # Keras H5 format

# 0 xor 0 = [[0.11921611]] ~= 0
print('0 xor 0 = ', model.predict(np.array([[0, 0]])))

# 0 xor 1 = [[0.96736085]] ~= 1
print('0 xor 1 = ', model.predict(np.array([[0, 1]])))

# 1 xor 0 = [[0.97254556]] ~= 1
print('1 xor 0 = ', model.predict(np.array([[1, 0]])))

# 1 xor 1 = [[0.0206149]] ~= 0
print('1 xor 1 = ', model.predict(np.array([[1, 1]])))

Convertir le modèle en ONNX :

ONNX est une nouvelle norme pour l'échange de modèles d'apprentissage profond. Elle promet de rendre les modèles d'apprentissage profond portables, empêchant ainsi le verrouillage des fournisseurs.

ONNX est un format ouvert conçu pour représenter les modèles d'apprentissage automatique. ONNX définit un ensemble commun d'opérateurs - les éléments constitutifs des modèles d'apprentissage automatique et d'apprentissage profond - et un format de fichier commun pour permettre aux développeurs d'IA d'utiliser les modèles avec une variété de cadres, d'outils, de moteurs d'exécution et de compilateurs.

$ pip install onnxruntime
$ pip install tf2onnx
$ python -m tf2onnx.convert --saved-model ./xor/ --opset 9 --output xor.onnx

# INFO - Successfully converted TensorFlow model ./xor/ to ONNX
# INFO - Model inputs: ['input:0']
# INFO - Model outputs: ['output']
# INFO - ONNX model is saved at xor.onnx

En spécifiant --opset l'utilisateur peut remplacer la valeur par défaut pour générer un graphique avec l'opset souhaité. Par exemple --opset 13 créerait un graphique onnx qui utilise uniquement les ops disponibles dans l'opset 13. Comme les anciennes opsets ont dans la plupart des cas moins d'ops, certains modèles peuvent ne pas être convertis sur une ancienne opset.


opencv-predict.py :

import numpy as np
import cv2

print(cv2.__version__)  # 4.5.1

model = cv2.dnn.readNetFromONNX('./xor.onnx')

# 0 xor 0 = [[0.11921611]] ~= 0
model.setInput(np.array([[0, 0]]), name='input:0')
print('0 xor 0 = ', model.forward(outputName='output'))

# 0 xor 1 = [[0.96736085]] ~= 1
model.setInput(np.array([[0, 1]]), name='input:0')
print('0 xor 1 = ', model.forward(outputName='output'))

# 1 xor 0 = [[0.97254556]] ~= 1
model.setInput(np.array([[1, 0]]), name='input:0')
print('1 xor 0 = ', model.forward(outputName='output'))

# 1 xor 1 = [[0.02061491]] ~= 0
model.setInput(np.array([[1, 1]]), name='input:0')
print('1 xor 1 = ', model.forward(outputName='output'))

predict.cpp :

#include <cstdlib>
#include <iostream>
#include <opencv2/opencv.hpp>

int main(int argc, char **argv)
{
    std::cout << CV_VERSION << std::endl; // 4.2.0

    cv::dnn::Net net;

    net = cv::dnn::readNetFromONNX("./xor.onnx");

    // 0 xor 0 = [0.11921611] ~= 0
    float x0[] = { 0, 0 };
    net.setInput(cv::Mat(1, 2, CV_32F, x0), "input:0");
    std::cout << "0 xor 0 = " << net.forward("output") << std::endl;

    // 0 xor 1 = [0.96736085] ~= 1
    float x1[] = { 0, 1 };
    net.setInput(cv::Mat(1, 2, CV_32F, x1), "input:0");
    std::cout << "0 xor 1 = " << net.forward("output") << std::endl;

    // 1 xor 0 = [0.97254556] ~= 1
    float x2[] = { 1, 0 };
    net.setInput(cv::Mat(1, 2, CV_32F, x2), "input:0");
    std::cout << "1 xor 0 = " << net.forward("output") << std::endl;

    // 1 xor 1 = [0.020614909] ~= 0
    float x3[] = { 1, 1 };
    net.setInput(cv::Mat(1, 2, CV_32F, x3), "input:0");
    std::cout << "1 xor 1 = " << net.forward("output") << std::endl;

    return EXIT_SUCCESS;
}

Compiler et exécuter :

$ sudo apt install build-essential pkg-config libopencv-dev
$ g++ predict.cpp `pkg-config --cflags --libs opencv4` -o predict
$ ./predict

Réponse originale

L'exemple simple suivant (exemple XOR) montre comment exporter des modèles Keras (dans les deux formats h5 et pb ), et l'utilisation du modèle en Python et C++ :


train.py :

import numpy as np
import tensorflow as tf

def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):
    """
    Freezes the state of a session into a pruned computation graph.

    Creates a new computation graph where variable nodes are replaced by
    constants taking their current value in the session. The new graph will be
    pruned so subgraphs that are not necessary to compute the requested
    outputs are removed.
    @param session The TensorFlow session to be frozen.
    @param keep_var_names A list of variable names that should not be frozen,
                          or None to freeze all the variablesin the graph.
    @param output_names Names of the relevant graph outputs.
    @param clear_devices Remove the device directives from the graph for better portability.
    @return The frozen graph definition.
    """
    graph = session.graph
    with graph.as_default():
        freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
        output_names = output_names or []
        output_names += [v.op.name for v in tf.global_variables()]
        input_graph_def = graph.as_graph_def()
        if clear_devices:
            for node in input_graph_def.node:
                node.device = ''
        frozen_graph = tf.graph_util.convert_variables_to_constants(
            session, input_graph_def, output_names, freeze_var_names)
        return frozen_graph

X = np.array([[0,0], [0,1], [1,0], [1,1]], 'float32')
Y = np.array([[0], [1], [1], [0]], 'float32')

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(64, input_dim=2, activation='relu'))
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

model.compile(loss='mean_squared_error', optimizer='adam', metrics=['binary_accuracy'])

model.fit(X, Y, batch_size=1, nb_epoch=100, verbose=0)

# inputs:  ['dense_input']
print('inputs: ', [input.op.name for input in model.inputs])

# outputs:  ['dense_4/Sigmoid']
print('outputs: ', [output.op.name for output in model.outputs])

model.save('./xor.h5')

frozen_graph = freeze_session(tf.keras.backend.get_session(), output_names=[out.op.name for out in model.outputs])
tf.train.write_graph(frozen_graph, './', 'xor.pbtxt', as_text=True)
tf.train.write_graph(frozen_graph, './', 'xor.pb', as_text=False)

predict.py :

import numpy as np
import tensorflow as tf

model = tf.keras.models.load_model('./xor.h5')

# 0 ^ 0 =  [[0.01974997]]
print('0 ^ 0 = ', model.predict(np.array([[0, 0]])))

# 0 ^ 1 =  [[0.99141496]]
print('0 ^ 1 = ', model.predict(np.array([[0, 1]])))

# 1 ^ 0 =  [[0.9897714]]
print('1 ^ 0 = ', model.predict(np.array([[1, 0]])))

# 1 ^ 1 =  [[0.00406971]]
print('1 ^ 1 = ', model.predict(np.array([[1, 1]])))

opencv-predict.py :

import numpy as np
import cv2 as cv

model = cv.dnn.readNetFromTensorflow('./xor.pb')

# 0 ^ 0 =  [[0.01974997]]
model.setInput(np.array([[0, 0]]), name='dense_input')
print('0 ^ 0 = ', model.forward(outputName='dense_4/Sigmoid'))

# 0 ^ 1 =  [[0.99141496]]
model.setInput(np.array([[0, 1]]), name='dense_input')
print('0 ^ 1 = ', model.forward(outputName='dense_4/Sigmoid'))

# 1 ^ 0 =  [[0.9897714]]
model.setInput(np.array([[1, 0]]), name='dense_input')
print('1 ^ 0 = ', model.forward(outputName='dense_4/Sigmoid'))

# 1 ^ 1 =  [[0.00406971]]
model.setInput(np.array([[1, 1]]), name='dense_input')
print('1 ^ 1 = ', model.forward(outputName='dense_4/Sigmoid'))

predict.cpp :

#include <cstdlib>
#include <iostream>
#include <opencv2/opencv.hpp>

int main(int argc, char **argv)
{
    cv::dnn::Net net;

    net = cv::dnn::readNetFromTensorflow("./xor.pb");

    // 0 ^ 0 = [0.018541215]
    float x0[] = { 0, 0 };
    net.setInput(cv::Mat(1, 2, CV_32F, x0), "dense_input");
    std::cout << "0 ^ 0 = " << net.forward("dense_4/Sigmoid") << std::endl;

    // 0 ^ 1 = [0.98295897]
    float x1[] = { 0, 1 };
    net.setInput(cv::Mat(1, 2, CV_32F, x1), "dense_input");
    std::cout << "0 ^ 1 = " << net.forward("dense_4/Sigmoid") << std::endl;

    // 1 ^ 0 = [0.98810625]
    float x2[] = { 1, 0 };
    net.setInput(cv::Mat(1, 2, CV_32F, x2), "dense_input");
    std::cout << "1 ^ 0 = " << net.forward("dense_4/Sigmoid") << std::endl;

    // 1 ^ 1 = [0.010002014]
    float x3[] = { 1, 1 };
    net.setInput(cv::Mat(1, 2, CV_32F, x3), "dense_input");
    std::cout << "1 ^ 1 = " << net.forward("dense_4/Sigmoid") << std::endl;

    return EXIT_SUCCESS;
}

1 votes

Merci beaucoup pour cet exemple complet. Pourrais-je vous demander d'inclure une phrase supplémentaire ? Si les gens utilisent directement keras au lieu de tf.keras, ils doivent également utiliser "keras.backend.get_session()" au lieu de "tf.keras.backend.get_session()" dans l'appel de fonction, sinon vous obtiendrez une erreur concernant les variables non initialisées. Je n'avais pas réalisé que vous utilisiez le préfixe "tf.keras" auparavant et cette petite différence m'a coûté une heure...

0 votes

Cela m'a été très utile. Une alternative à opencv-predict.py qui n'utilise pas cv2 : import tensorflow as tf from tensorflow.python.platform import gfile f = gfile.FastGFile(r'.\xor\xor.pb', 'rb') graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) f.close() tfSession = tf.InteractiveSession() tfSession.graph.as_default() tf.import_graph_def(graph_def) out = tfSession.graph.get_tensor_by_name('import/dense_4/Sigmoid:0‌​') tfSession.run(out, {'import/dense_input:0': np.array([[0,0]])})

0 votes

@gebbissimo Je reçois cette erreur AttributeError: module 'tensorflow.keras.backend' has no attribute 'get_session' . Avez-vous une idée ? Je l'utilise comme tf.keras.backend.get_session()

7voto

Hazarapet Tunanyan Points 1458

Il y a un point très important lorsque vous voulez convertir à tensorflow. Si vous utilisez le dropout, la normalisation par lot ou toute autre couche de ce type (qui n'est pas entraînable mais dont les valeurs sont calculées), vous devez modifier la phase d'apprentissage du backend keras . Voici un discussion à ce sujet.

import keras.backend as K
k.set_learning_phase(0) # 0 testing, 1 training mode

2voto

AHA Points 864

Si vous souhaitez que le modèle ne serve qu'à l'inférence, vous devez d'abord geler le graphique, puis l'écrire sous la forme de .pb fichier. L'extrait de code ressemble à ceci ( code emprunté ici ) :

import tensorflow as tf
from tensorflow.python.framework import graph_util
from tensorflow.python.framework import graph_io
import keras
from keras import backend as K

sess = K.get_session()

constant_graph = graph_util.convert_variables_to_constants(
        sess,
        sess.graph.as_graph_def(),
        ["name_of_the_output_graph_node"])

graph_io.write_graph(constant_graph, "path/to/output/folder", 
                     "output_model_name", as_text=False)

Vous pouvez faire ce qui précède en utilisant le keras_to_tensorflow outil : https://github.com/amir-abdi/keras_to_tensorflow

Le site keras_to_tensorflow prend en charge les opérations ci-dessus, avec quelques fonctionnalités supplémentaires pour une solution plus diversifiée. Il suffit de l'appeler avec les arguments d'entrée corrects (par ex. input_model et output_model drapeaux).

Si vous souhaitez réentraîner le modèle dans tensorflow, utilisez l'outil ci-dessus avec l'option output_meta_ckpt pour exporter les points de contrôle et les méta-graphes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X