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;
}