TensorFlow分类器.export_savedmodel(入门)

7

我知道有关于"Serving a Tensorflow Model"的页面:

https://www.tensorflow.org/serving/serving_basic

但是这些函数假定您正在使用tf.Session(),而DNNClassifier教程则不是这样...然后我查看了DNNClassifier的API文档,它有一个export_savedmodel函数(export函数已过时),并且看起来很简单,但是我遇到了一个"'NoneType' object is not iterable"的错误...这意味着我可能传入了一个空变量,但是我不确定需要改变什么...我基本上是从tensorflow.org上的get_started/tflearn页面复制和粘贴了代码,但是添加了一些内容。

  directoryName = "temp"

  def serving_input_fn():
    print("asdf")

  classifier.export_savedmodel(
    directoryName,
    serving_input_fn
  )

在调用classifier.fit函数后……我相信export_savedmodel的其他参数都是可选的…你有什么想法吗?
教程和代码: https://www.tensorflow.org/get_started/tflearn#construct_a_deep_neural_network_classifier export_savedmodel的API文档: https://www.tensorflow.org/api_docs/python/tf/contrib/learn/DNNClassifier#export_savedmodel
3个回答

17

TensorFlow应用程序有两种类型:

  • 假定您正在使用tf.Session()的函数是来自“低级”Tensorflow示例的函数,以及
  • DNNClassifier教程是一种“高级”Tensorflow应用程序。

我将解释如何导出“高级”Tensorflow模型(使用export_savedmodel)。

export_savedmodel函数需要参数serving_input_receiver_fn,它是一个没有参数的函数,定义了模型和预测器的输入。因此,您必须创建自己的serving_input_receiver_fn,其中模型输入类型与训练脚本中的模型输入匹配,预测器输入类型与测试脚本中的预测器输入匹配。

另一方面,如果创建自定义模型,则必须定义export_outputs,由tf.estimator.export.PredictOutput函数定义,其输入为字典,该字典定义了名称,该名称必须与测试脚本中的预测器输出名称匹配。

例如:

训练脚本

def serving_input_receiver_fn():
    serialized_tf_example = tf.placeholder(dtype=tf.string, shape=[None], name='input_tensors')
    receiver_tensors      = {"predictor_inputs": serialized_tf_example}
    feature_spec          = {"words": tf.FixedLenFeature([25],tf.int64)}
    features              = tf.parse_example(serialized_tf_example, feature_spec)
    return tf.estimator.export.ServingInputReceiver(features, receiver_tensors)

def estimator_spec_for_softmax_classification(logits, labels, mode):
    predicted_classes = tf.argmax(logits, 1)
    if (mode == tf.estimator.ModeKeys.PREDICT):
        export_outputs = {'predict_output': tf.estimator.export.PredictOutput({"pred_output_classes": predicted_classes, 'probabilities': tf.nn.softmax(logits)})}
        return tf.estimator.EstimatorSpec(mode=mode, predictions={'class': predicted_classes, 'prob': tf.nn.softmax(logits)}, export_outputs=export_outputs) # IMPORTANT!!!
    onehot_labels = tf.one_hot(labels, 31, 1, 0)
    loss          = tf.losses.softmax_cross_entropy(onehot_labels=onehot_labels, logits=logits)
    if (mode == tf.estimator.ModeKeys.TRAIN):
        optimizer = tf.train.AdamOptimizer(learning_rate=0.01)
        train_op  = optimizer.minimize(loss, global_step=tf.train.get_global_step())
        return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
    eval_metric_ops = {'accuracy': tf.metrics.accuracy(labels=labels, predictions=predicted_classes)}
    return tf.estimator.EstimatorSpec(mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)

def model_custom(features, labels, mode):
    bow_column           = tf.feature_column.categorical_column_with_identity("words", num_buckets=1000)
    bow_embedding_column = tf.feature_column.embedding_column(bow_column, dimension=50)   
    bow                  = tf.feature_column.input_layer(features, feature_columns=[bow_embedding_column])
    logits               = tf.layers.dense(bow, 31, activation=None)
    return estimator_spec_for_softmax_classification(logits=logits, labels=labels, mode=mode)

def main():
    # ...
    # preprocess-> features_train_set and labels_train_set
    # ...
    classifier     = tf.estimator.Estimator(model_fn = model_custom)
    train_input_fn = tf.estimator.inputs.numpy_input_fn(x={"words": features_train_set}, y=labels_train_set, batch_size=batch_size_param, num_epochs=None, shuffle=True)
    classifier.train(input_fn=train_input_fn, steps=100)
    full_model_dir = classifier.export_savedmodel(export_dir_base="C:/models/directory_base", serving_input_receiver_fn=serving_input_receiver_fn)

测试脚本

def main():
    # ...
    # preprocess-> features_test_set
    # ...
    with tf.Session() as sess:
        tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], full_model_dir)
        predictor   = tf.contrib.predictor.from_saved_model(full_model_dir)
        model_input = tf.train.Example(features=tf.train.Features( feature={"words": tf.train.Feature(int64_list=tf.train.Int64List(value=features_test_set)) })) 
        model_input = model_input.SerializeToString()
        output_dict = predictor({"predictor_inputs":[model_input]})
        y_predicted = output_dict["pred_output_classes"][0]

(代码已在Python 3.6.3,Tensorflow 1.4.0中进行测试)


为什么你在测试脚本中没有使用 tf.estimator?如果我想在测试脚本中使用 tf.estimator,该如何加载保存的模型? - malioboro
output_dict = predictor({"predictor_inputs":[model_input]}) 可以处理批量数据而不是单个样例吗? - Nitin

3
如果您尝试在 TensorFlow 版本大于 1.6 中使用 `predictor`,可能会出现以下错误:
signature_def_key "serving_default". Available signatures are ['predict']. Original error:
No SignatureDef with key 'serving_default' found in MetaGraphDef.

这是一个在1.7.0上测试过的工作示例:

保存:

首先,您需要以字典格式定义功能长度,如下所示:

feature_spec = {'x': tf.FixedLenFeature([4],tf.float32)}

然后您需要构建一个函数,该函数具有与特征相同形状的占位符,并使用tf.estimator.export.ServingInputReceiver返回。
def serving_input_receiver_fn():
    serialized_tf_example = tf.placeholder(dtype=tf.string,
                                         shape=[None],
                                         name='input_tensors')
    receiver_tensors = {'inputs': serialized_tf_example}

    features = tf.parse_example(serialized_tf_example, feature_spec)
    return tf.estimator.export.ServingInputReceiver(features, receiver_tensors)

然后只需使用 export_savedmodel 进行保存:
classifier.export_savedmodel(dir_path, serving_input_receiver_fn)

完整的示例代码:
import os
from six.moves.urllib.request import urlopen

import numpy as np
import tensorflow as tf


dir_path = os.path.dirname('.')

IRIS_TRAINING = os.path.join(dir_path,  "iris_training.csv")
IRIS_TEST = os.path.join(dir_path,   "iris_test.csv") 

feature_spec = {'x': tf.FixedLenFeature([4],tf.float32)}

def serving_input_receiver_fn():
    serialized_tf_example = tf.placeholder(dtype=tf.string,
                                         shape=[None],
                                         name='input_tensors')
    receiver_tensors = {'inputs': serialized_tf_example}

    features = tf.parse_example(serialized_tf_example, feature_spec)
    return tf.estimator.export.ServingInputReceiver(features, receiver_tensors)




def main():
    training_set = tf.contrib.learn.datasets.base.load_csv_with_header(
        filename=IRIS_TRAINING,
        target_dtype=np.int,
        features_dtype=np.float32)
    test_set = tf.contrib.learn.datasets.base.load_csv_with_header(
        filename=IRIS_TEST,
        target_dtype=np.int,
        features_dtype=np.float32)

    feature_columns = [tf.feature_column.numeric_column("x", shape=[4])]


    classifier = tf.estimator.DNNClassifier(feature_columns=feature_columns,
                                          hidden_units=[10, 20, 10],
                                          n_classes=3,
                                          model_dir=dir_path)
  # Define the training inputs
    train_input_fn = tf.estimator.inputs.numpy_input_fn(
      x={"x": np.array(training_set.data)},
      y=np.array(training_set.target),
      num_epochs=None,
      shuffle=True)

  # Train model.
    classifier.train(input_fn=train_input_fn, steps=200)


    classifier.export_savedmodel(dir_path, serving_input_receiver_fn)


if __name__ == "__main__":
    main()

恢复

现在让我们恢复模型:

import tensorflow as tf 
import os

dir_path = os.path.dirname('.') #current directory
exported_path= os.path.join(dir_path,  "1536315752")

def main():
    with tf.Session() as sess:

        tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], exported_path)

        model_input= tf.train.Example(features=tf.train.Features(feature={
                'x': tf.train.Feature(float_list=tf.train.FloatList(value=[6.4, 3.2, 4.5, 1.5]))        
                })) 

        predictor= tf.contrib.predictor.from_saved_model(exported_path)

        input_tensor=tf.get_default_graph().get_tensor_by_name("input_tensors:0")

        model_input=model_input.SerializeToString()

        output_dict= predictor({"inputs":[model_input]})

        print(" prediction is " , output_dict['scores'])


if __name__ == "__main__":
    main()

这是一个带有数据和解释的Ipython笔记本演示例子,链接如下:Ipython notebook demo

2
有两个可能的问题和答案。首先,您可能会遇到一个缺失的会话(session)问题,这是针对使用更高级别的估算器API的DNNClassifier而言的(与更低级别的API相比,在那里您自己操作ops)。TensorFlow的好处在于所有高级和低级别的API都可以互操作,因此,如果您想要一个会话并对其进行操作,只需添加以下内容即可:
sess = tf.get_default_session()

您可以开始跟随教程的剩余部分进行操作。

您提出的第二个问题是,export_savedmodel怎么样?实际上,export_savedmodel和服务教程中的示例代码试图实现相同的目标。当您训练图表时,您设置了一些基础设施来提供输入到图表中(通常是从训练数据集中获取批次),但是当您切换到“服务”时,您通常会从其他地方读取输入,并且需要一些单独的基础设施来替换用于训练的图表的输入。底线是,您填充了一个打印语句的serving_input_fn()应该在本质上返回一个输入op。这也在文档中说过:

serving_input_fn:一个不带参数并返回InputFnOps的函数。

因此,它应该做类似于添加输入链的操作,而不是print("asdf"),这应该类似于builder.add_meta_graph_and_variables也添加的内容。

`serving_input_fn()`的示例可以在cloudml样本中找到(https://github.com/GoogleCloudPlatform/cloudml-samples/blob/master/census/customestimator/trainer/model.py#L240])。例如以下代码从JSON中提供输入:
def json_serving_input_fn():
  """Build the serving inputs."""
  inputs = {}
  for feat in INPUT_COLUMNS:
    inputs[feat.name] = tf.placeholder(shape=[None], dtype=feat.dtype)
  return tf.estimator.export.ServingInputReceiver(inputs, inputs)

1
我想尝试使用classifier.export_savedmodel保存鸢尾花模型,因此我尝试了csv_serving_input_fn()(我认为这可能是适合鸢尾花模型使用csv数据的),但它说全局名称'parse_csv'未定义...这非常令人沮丧,因为这似乎应该像调用一个函数那样简单,该函数会给你返回图形的json数据结构表示,或者至少在其他库中是这样的...有一个相应的函数,你只需要传入json并准备好运行图形。 - Jacob
parse_csv在同一file中被引用,显然您需要根据自己的要求调整输入链。 - amo-ej1
我不确定输入链如何针对鸢尾花数据集进行适应... 我从 https://www.tensorflow.org/get_started/ 页面开始,一直进行到鸢尾花数据集,然后意识到我实际上想使用它,但没有关于如何使用 classifier.export_savedmodel 导出和重新初始化神经网络的说明... 对我来说,他们竟然会把这部分留下来,尤其是考虑到其他库已经将这个过程简化为相互传递 JSON 对象的两个函数... 不管怎样,我还是会继续。 - Jacob
@Jacob,你解决了吗?我也遇到了类似的情况,真的需要让它工作起来 :) - Mark Sonn
抱歉,我将其全部清空并使用一个JavaScript库重新开始,从最基本的神经元开始构建...超级简单的JSON.parse和JSON.stringify互操作性,而且它不像Tensorflow那样是一个黑匣子...我不得不优化几乎所有有关激活函数如何与权重和反向传播接口的内容,但我认为现在我比试图跳舞应对Tensorflow带给我的疯狂要好得多... - Jacob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接