【问题标题】:Tensorflow classifier.export_savedmodel (Beginner)Tensorflow 分类器.export_savedmodel(初学者)
【发布时间】:2018-01-20 07:27:38
【问题描述】:

我知道“服务于 TensorFlow 模型”页面

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

但这些函数假设您使用的是 DNNClassifier 教程没有使用的 tf.Session()...然后我查看了 DNNClassifier 的 api 文档,它有一个 export_savedmodel 函数(不推荐使用导出函数),看起来很简单,但我得到一个“'NoneType'对象不可迭代”错误......这意味着我正在传递一个空变量,但我不确定我需要改变什么......我基本上已经从 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

【问题讨论】:

    标签: tensorflow tensorflow-serving


    【解决方案1】:

    TensorFlow 应用有两种:

    • 假设您使用 tf.Session() 的函数是来自“低级”Tensorflow 示例的函数,并且
    • DNNClassifier 教程是一个“高级”Tensorflow 应用程序。

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

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

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

    例如:

    培训脚本

    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,如何加载保存的模型?
    • output_dict = predictor({"predictor_inputs":[model_input]}) 可以批量处理而不是单个示例吗?
    【解决方案2】:

    如果您尝试在 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 上测试的工作示例:

    正在保存:

    首先你需要像这样以dict格式定义特征长度:

    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 notebook demo 示例,带有数据和说明:

    【讨论】:

      【解决方案3】:

      有两个可能的问题和可能的答案。首先,您会遇到 DNNClassifier 的缺失会话,该会话使用更高级别的估算器 API(而不是您自己操作操作的更低级别的 API)。 tensorflow 的好处是所有高级和低级 API 或多或少都可以互操作,所以如果你想要一个会话并使用该会话做一些事情,它就像添加一样简单:

      sess = tf.get_default_session()
      

      您可以在本教程的其余部分开始挂钩。

      您的问题的第二种解释是,export_savedmodel 怎么样,实际上 export_savedmodel 和服务教程中的示例代码试图实现相同的目标。当你训练你的图时,你设置了一些基础设施来为图提供输入(通常是来自训练数据集的批次)但是当你切换到“服务”时,你经常会从其他地方读取你的输入,并且你需要一些单独的基础设施来替换用于训练的图的输入。最重要的是,您用 print 填充的 serving_input_fn() 本质上应该返回一个输入操作。这在documentation也有说:

      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)
      

      【讨论】:

      • 我想尝试使用 classifier.export_savedmodel 保存鸢尾花模型,所以我尝试了 csv_serving_input_fn() (我认为这可能是合适的,因为鸢尾花模型使用的是 csv 数据)但它说全局名称'parse_csv'没有定义......超级令人沮丧,因为这看起来应该像调用一个函数一样简单,该函数为您返回图形的json数据结构表示,或者至少是这样与其他库...有一个相应的函数,您只需传入 json 并准备好图形。
      • parse_csv 与引用的file 相同,显然您需要根据您的要求调整输入链。
      • 我不确定输入链将如何专门针对 iris 数据集进行调整...我从 tensorflow.org/get_started 页面开始,一直持续到我到达 iris 数据集并意识到我'实际上想使用它,但没有关于如何使用分类器导出和重新初始化神经网络的说明。export_savedmodel... 对我来说似乎很神奇,他们只是将那部分排除在外,尤其是我已经说过的关于如何其他库的过程就像两个函数一样简单,它们相互传递 JSON 对象......无论如何都会继续
      • @Jacob 你能解决这个问题吗?我处于类似的情况,真的需要让这个工作:)
      • 抱歉,我把它烧掉了,然后用一个 JavaScript 库重新开始,我把它剥离到裸露的神经元并从那里建立起来……超级简单的 JSON.parse 和 JASON.stringify 互操作性,它不是像 Tensorflow 这样的黑匣子……我不得不优化几乎所有关于激活函数如何与权重交互并反向传播的内容,但我认为我现在的位置比我一直试图跳舞的要好得多TensorFlow 让我经历了疯狂......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-31
      • 2016-03-04
      • 2013-05-19
      • 1970-01-01
      • 1970-01-01
      • 2016-03-08
      • 2020-10-13
      相关资源
      最近更新 更多