如何将Keras .h5导出到tensorflow .pb?

时间:2017-08-02 16:16:08

标签: python tensorflow keras

我使用新数据集进行了微调初始模型,并将其保存为Keras中的“.h5”模型。现在我的目标是在android Tensorflow上运行我的模型,它只接受“.pb”扩展名。问题是Keras或tensorflow中是否有任何库进行此转换?到目前为止我看过这篇文章:https://blog.keras.io/keras-as-a-simplified-interface-to-tensorflow-tutorial.html但还不知道。

12 个答案:

答案 0 :(得分:51)

Keras本身不包含将TensorFlow图导出为协议缓冲区文件的任何方法,但您可以使用常规TensorFlow实用程序来执行此操作。 Here是一篇博文,解释了如何使用TensorFlow中包含的实用程序脚本freeze_graph.py来完成它,这是“典型”的方式。

但是,我个人觉得必须制作检查点,然后运行外部脚本来获取模型,而不是喜欢从我自己的Python代码中执行它,所以我使用这样的函数:

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

受到freeze_graph.py实施的启发。参数也类似于脚本。 session是TensorFlow会话对象。仅当您想要保留某些未冻结的变量(例如,对于有状态模型)时才需要keep_var_names,因此通常不需要。 output_names是一个列表,其中包含生成所需输出的操作的名称。 clear_devices只删除任何设备指令以使图表更具可移植性。因此,对于具有一个输出的典型Keras model,您可以执行以下操作:

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])

然后您可以像往常一样使用tf.train.write_graph

将图表写入文件
tf.train.write_graph(frozen_graph, "some_directory", "my_model.pb", as_text=False)

答案 1 :(得分:20)

freeze_session方法正常。但是与保存到检查点文件相比,使用TensorFlow附带的freeze_graph工具对我来说似乎更简单,因为它更容易维护。您需要做的就是以下两个步骤:

首先,在您的Keras代码model.fit(...)之后添加并训练您的模型:

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')

然后cd到您的TensorFlow根目录,运行:

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

答案 2 :(得分:7)

目前,以上所有较早的答案已过时。自Tensorflow 2.1起

from tensorflow.keras.models import Model, load_model
model = load_model(MODEL_FULLPATH)
model.save(MODEL_FULLPATH_MINUS_EXTENSION)

将创建一个内部带有“ saved_model.pb”的文件夹

答案 3 :(得分:5)

以下简单示例(XOR示例)展示了如何导出Keras模型(以h5格式和pb格式)以及如何在Python和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 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


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

答案 4 :(得分:3)

要转换为张量流时,有一个非常重要的观点。如果您使用辍学,批处理规范化或诸如此类的其他任何层(这些层不可训练但可计算值),则您应该更改keras后端的学习阶段。这是一个discussion

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

答案 5 :(得分:2)

如果只希望模型用于推理,则应首先冻结图形,然后将其写为.pb文件。该代码段如下所示(code borrowed from here):

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)

您可以使用 keras_to_tensorflow 工具执行上述操作:https://github.com/amir-abdi/keras_to_tensorflow

keras_to_tensorflow 工具完成了上述操作,并提供了一些额外的功能,可提供更多样化的解决方案。只需使用正确的输入参数(例如input_modeloutput_model标志)进行调用即可。

如果要在张量流中重新训练模型,请使用带有output_meta_ckpt标志的上述工具来导出检查点和元图。

答案 6 :(得分:1)

请使用tf.saved_model.simple_save和一些示例代码:

with tf.keras.backend.get_session() as sess:
    tf.saved_model.simple_save(
        sess,
        export_path,
        inputs={'input': keras_model.input},
        outputs={'output': keras_model.output})

===更新====

您可以使用as_a_saved_model(示例代码)

saved_model_path = tf.contrib.saved_model.save_keras_model(model, "./saved_models")

答案 7 :(得分:1)

此解决方案对我有用。 感谢https://medium.com/tensorflow/training-and-serving-ml-models-with-tf-keras-fd975cc0fa27

import tensorflow as tf

# The export path contains the name and the version of the model
tf.keras.backend.set_learning_phase(0) # Ignore dropout at inference
model = tf.keras.models.load_model('./model.h5')
export_path = './PlanetModel/1'

# Fetch the Keras session and save the model
# The signature definition is defined by the input and output tensors
# And stored with the default serving key
with tf.keras.backend.get_session() as sess:
    tf.saved_model.simple_save(
        sess,
        export_path,
        inputs={'input_image': model.input},
        outputs={t.name:t for t in model.outputs})

答案 8 :(得分:1)

tf 2.2.0

导入tensorflow.keras而不是仅导入keras,因为它将以keras.engine.sequential.Sequential对象的形式加载您的模型,该对象无法直接转换为tensorflow .pb格式

#import keras
import tensorflow.keras as keras
model = keras.models.load_model(load_path)
model.save(save_path)

答案 9 :(得分:0)

使用estimator.export_savedmodel,我们可以轻松地将h5模型转换为保存的模型。 在https://www.tensorflow.org/api_docs/python/tf/estimator/Estimator

处查看文档
def prepare_image(image_str_tensor):
    image_contents = tf.read_file(image_str_tensor)
    image = tf.image.decode_jpeg(image_contents, channels=3)
    image = tf.image.resize_images(image, [224, 224])
    image = tf.cast(image, tf.float32)
    return preprocess_input(image)

def serving_input_receiver_fn():
    input_ph = tf.placeholder(tf.string, shape=[None])
    images_tensor = tf.map_fn(
          prepare_image, input_ph, back_prop=False, dtype=tf.float32)
    images_tensor = tf.image.convert_image_dtype(images_tensor, 
                      dtype=tf.float32)

    return tf.estimator.export.ServingInputReceiver({"input": images_tensor}, 
             {'image_url': input_ph})

estimator = tf.keras.estimator.model_to_estimator(
    keras_model_path=h5_model_path
)

estimator.export_savedmodel(saved_model_path, serving_input_receiver_fn=serving_input_receiver_fn)

答案 10 :(得分:0)

  

Tensorflow tf.saved_model API最适合生成PB模型

如果您有h5模型,请通过keras load_model加载它

from tensorflow import keras
model = keras.models.load_model("model.h5")

通过saved_model api保存tensorflow模型,它将以pb格式保存模型。该模型将具有通过Google Ai平台提供服务所需的元数据。因此,您可以将目录上载到Ai Platform来为模型提供服务。

import tensorflow as tf
tf.saved_model.save(model, './directory-to-save-file/')

答案 11 :(得分:0)

使用tensorflow 2.x: 如果您只想将图形定义保存在pbtxt中,请使用以下代码。

import tensorflow as tf
keras_model = ...
tf.io.write_graph(
  keras_model.output.graph,
  'model_dir',
  'model.pbtxt',
  as_text=True,
)