如何使用 tf.estimator 导入保存的 Tensorflow 模型训练并预测输入数据

Posted

技术标签:

【中文标题】如何使用 tf.estimator 导入保存的 Tensorflow 模型训练并预测输入数据【英文标题】:How to import an saved Tensorflow model train using tf.estimator and predict on input data 【发布时间】:2018-02-16 08:10:09 【问题描述】:

我已使用 tf.estimator .method export_savedmodel 保存模型,如下所示:

export_dir="exportModel/"

feature_spec = tf.feature_column.make_parse_example_spec(feature_columns)

input_receiver_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec)

classifier.export_savedmodel(export_dir, input_receiver_fn, as_text=False, checkpoint_path="Model/model.ckpt-400") 

如何导入此保存的模型并用于预测?

【问题讨论】:

您能否对您想要执行预测的环境多发表评论?您是否只想编写一个在同一进程中加载​​模型并执行预测的 Python 应用程序?您想运行自己的生产级服务来为您的模型提供服务吗?您想在云中使用托管服务吗? 现在,我正在尝试编写一个 python 脚本来加载模型并执行预测。 Example of export_savedmodel function 【参考方案1】:

我试图搜索一个好的基础示例,但似乎该主题的文档和示例有点分散。因此,让我们从一个基本示例开始:tf.estimator quickstart。

该特定示例实际上并未导出模型,所以让我们这样做(不需要用例 1):

def serving_input_receiver_fn():
  """Build the serving inputs."""
  # The outer dimension (None) allows us to batch up inputs for
  # efficiency. However, it also means that if we want a prediction
  # for a single instance, we'll need to wrap it in an outer list.
  inputs = "x": tf.placeholder(shape=[None, 4], dtype=tf.float32)
  return tf.estimator.export.ServingInputReceiver(inputs, inputs)

export_dir = classifier.export_savedmodel(
    export_dir_base="/path/to/model",
    serving_input_receiver_fn=serving_input_receiver_fn)

此代码上有大星号:TensorFlow 1.3 中似乎存在一个错误,不允许您在“罐装”估算器(例如 DNNClassifier)上执行上述导出。有关解决方法,请参阅“附录:解决方法”部分。

下面的代码引用export_dir(导出步骤的返回值)来强调它不是“/path/to/model”,而是该目录的子目录,其名称是一个时间戳

用例 1:在与训练相同的过程中执行预测

这是一种 sci-kit 学习类型的体验,并且已经通过示例进行了说明。为了完整起见,您只需在经过训练的模型上调用 predict

classifier.train(input_fn=train_input_fn, steps=2000)
# [...snip...]
predictions = list(classifier.predict(input_fn=predict_input_fn))
predicted_classes = [p["classes"] for p in predictions]

用例 2:将 SavedModel 加载到 Python/Java/C++ 中并执行预测

Python 客户端

如果您想在 Python 中进行预测,可能最容易使用的是SavedModelPredictor。在将使用SavedModel 的 Python 程序中,我们需要这样的代码:

from tensorflow.contrib import predictor

predict_fn = predictor.from_saved_model(export_dir)
predictions = predict_fn(
    "x": [[6.4, 3.2, 4.5, 1.5],
           [5.8, 3.1, 5.0, 1.7]])
print(predictions['scores'])

Java 客户端

package dummy;

import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.List;

import org.tensorflow.SavedModelBundle;
import org.tensorflow.Session;
import org.tensorflow.Tensor;

public class Client 

  public static void main(String[] args) 
    Session session = SavedModelBundle.load(args[0], "serve").session();

    Tensor x =
        Tensor.create(
            new long[] 2, 4,
            FloatBuffer.wrap(
                new float[] 
                  6.4f, 3.2f, 4.5f, 1.5f,
                  5.8f, 3.1f, 5.0f, 1.7f
                ));

    // Doesn't look like Java has a good way to convert the
    // input/output name ("x", "scores") to their underlying tensor,
    // so we hard code them ("Placeholder:0", ...).
    // You can inspect them on the command-line with saved_model_cli:
    //
    // $ saved_model_cli show --dir $EXPORT_DIR --tag_set serve --signature_def serving_default
    final String xName = "Placeholder:0";
    final String scoresName = "dnn/head/predictions/probabilities:0";

    List<Tensor> outputs = session.runner()
        .feed(xName, x)
        .fetch(scoresName)
        .run();

    // Outer dimension is batch size; inner dimension is number of classes
    float[][] scores = new float[2][3];
    outputs.get(0).copyTo(scores);
    System.out.println(Arrays.deepToString(scores));
  

C++ 客户端

您可能希望将tensorflow::LoadSavedModelSession 一起使用。

#include <unordered_set>
#include <utility>
#include <vector>

#include "tensorflow/cc/saved_model/loader.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/public/session.h"

namespace tf = tensorflow;

int main(int argc, char** argv) 
  const string export_dir = argv[1];

  tf::SavedModelBundle bundle;
  tf::Status load_status = tf::LoadSavedModel(
      tf::SessionOptions(), tf::RunOptions(), export_dir, "serve", &bundle);
  if (!load_status.ok()) 
    std::cout << "Error loading model: " << load_status << std::endl;
    return -1;
  

  // We should get the signature out of MetaGraphDef, but that's a bit
  // involved. We'll take a shortcut like we did in the Java example.
  const string x_name = "Placeholder:0";
  const string scores_name = "dnn/head/predictions/probabilities:0";

  auto x = tf::Tensor(tf::DT_FLOAT, tf::TensorShape(2, 4));
  auto matrix = x.matrix<float>();
  matrix(0, 0) = 6.4;
  matrix(0, 1) = 3.2;
  matrix(0, 2) = 4.5;
  matrix(0, 3) = 1.5;
  matrix(0, 1) = 5.8;
  matrix(0, 2) = 3.1;
  matrix(0, 3) = 5.0;
  matrix(0, 4) = 1.7;

  std::vector<std::pair<string, tf::Tensor>> inputs = x_name, x;
  std::vector<tf::Tensor> outputs;

  tf::Status run_status =
      bundle.session->Run(inputs, scores_name, , &outputs);
  if (!run_status.ok()) 
    cout << "Error running session: " << run_status << std::endl;
    return -1;
  

  for (const auto& tensor : outputs) 
    std::cout << tensor.matrix<float>() << std::endl;
  

用例 3:使用 TensorFlow Serving 服务模型

以适合Classification model 的方式导出模型要求输入是tf.Example 对象。以下是我们为 TensorFlow 服务导出模型的方法:

def serving_input_receiver_fn():
  """Build the serving inputs."""
  # The outer dimension (None) allows us to batch up inputs for
  # efficiency. However, it also means that if we want a prediction
  # for a single instance, we'll need to wrap it in an outer list.
  example_bytestring = tf.placeholder(
      shape=[None],
      dtype=tf.string,
  )
  features = tf.parse_example(
      example_bytestring,
      tf.feature_column.make_parse_example_spec(feature_columns)
  )
  return tf.estimator.export.ServingInputReceiver(
      features, 'examples': example_bytestring)

export_dir = classifier.export_savedmodel(
    export_dir_base="/path/to/model",
    serving_input_receiver_fn=serving_input_receiver_fn)

更多关于如何设置TensorFlow Serving的说明请读者参考TensorFlow Serving的文档,所以这里我只提供客户端代码:

  # Omitting a bunch of connection/initialization code...
  # But at some point we end up with a stub whose lifecycle
  # is generally longer than that of a single request.
  stub = create_stub(...)

  # The actual values for prediction. We have two examples in this
  # case, each consisting of a single, multi-dimensional feature `x`.
  # This data here is the equivalent of the map passed to the 
  # `predict_fn` in use case #2.
  examples = [
    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])))),
    tf.train.Example(
      features=tf.train.Features(
        feature="x": tf.train.Feature(
          float_list=tf.train.FloatList(value=[5.8, 3.1, 5.0, 1.7])))),
  ]

  # Build the RPC request.
  predict_request = predict_pb2.PredictRequest()
  predict_request.model_spec.name = "default"
  predict_request.inputs["examples"].CopyFrom(
      tensor_util.make_tensor_proto(examples, tf.float32))

  # Perform the actual prediction.
  stub.Predict(request, PREDICT_DEADLINE_SECS)

请注意,predict_request.inputs 中引用的密钥 examples 需要在导出时与 serving_input_receiver_fn 中使用的密钥匹配(参见该代码中 ServingInputReceiver 的构造函数)。

附录:解决 TF 1.3 中罐装模型的导出问题

TensorFlow 1.3 中似乎存在一个错误,其中固定模型无法正确导出用例 2(“自定义”估算器不存在该问题)。这是一个包装 DNNClassifier 以使事情正常工作的解决方法,特别是对于 Iris 示例:

# Build 3 layer DNN with 10, 20, 10 units respectively.
class Wrapper(tf.estimator.Estimator):
  def __init__(self, **kwargs):
    dnn = tf.estimator.DNNClassifier(**kwargs)

    def model_fn(mode, features, labels):
      spec = dnn._call_model_fn(features, labels, mode)
      export_outputs = None
      if spec.export_outputs:
        export_outputs = 
           "serving_default": tf.estimator.export.PredictOutput(
                  "scores": spec.export_outputs["serving_default"].scores,
                   "classes": spec.export_outputs["serving_default"].classes)

      # Replace the 3rd argument (export_outputs)
      copy = list(spec)
      copy[4] = export_outputs
      return tf.estimator.EstimatorSpec(mode, *copy)

    super(Wrapper, self).__init__(model_fn, kwargs["model_dir"], dnn.config)

classifier = Wrapper(feature_columns=feature_columns,
                     hidden_units=[10, 20, 10],
                     n_classes=3,
                     model_dir="/tmp/iris_model")

【讨论】:

非常感谢您的详细解释。我可以使用 print(predictions['scores']) 和 print(predictions['classes']) 获得每个课程的分数。我们能不能得到预测的类。 @nayan DNNClassifier 的输出旨在支持非常大的输出空间,您可能希望在其中预测前 n 个类。这个想法是classes 键包含与scores 输出中的分数对应的类的名称。但是,我不相信您实际上可以做到top-n。所以你在classes 中得到的只是类列表,按顺序重复每个输出。要获得预测的类,您有两种选择:(1) 编写一个自定义估计器(可能包装 DNNClassifier 或类似的东西来完成这项艰苦的工作)(2) 让客户端采用 scores 的 argmax 谢谢。我能够使用 argmax 的分数获得前 1 个预测类。如果有与用例2中的python预测函数等效的c/c++ api,那么它可以集成到ios/android平台。 谢谢。我能够在 PC 上运行 java 和 c++ 客户端。当我尝试在android上集成java代码时出现以下错误java.lang.UnsupportedOperationException:Android不支持加载SavedModel。如果此功能对您很重要,请在 org.tensorflow.SavedModelBundle.load(Native Method) 处提交错误至 github.com/tensorflow/tensorflow/issues 我尝试使用 python 脚本 freeze_graph.py python tensorflow/python/tools/freeze_graph.py --input_graph model/graph.pbtxt --input_checkpoint model/model.ckpt-3000 冻结模型 - -output_node_names=dnn/head/predictions/probabilities 。得到以下错误消息 TypeError: names_to_saveables must be a dict mapping string names to Tensors/Variables.不是变量:Tensor("dnn/hiddenlayer_0/bias:0", shape=(5,), dtype=float32)。请帮忙。【参考方案2】:

TensorFlow 团队似乎不同意 1.3 版中存在错误,即在用例 #2 下使用固定估计器导出模型。我在这里提交了一个错误报告: https://github.com/tensorflow/tensorflow/issues/13477

我从 TensorFlow 收到的响应是输入只能是单个字符串张量。似乎有一种方法可以使用序列化的 TF.examples 将多个特征整合到一个字符串张量中,但我还没有找到一个明确的方法来做到这一点。如果有人有代码显示如何执行此操作,我将不胜感激。

【讨论】:

【参考方案3】:

我不认为罐装估算器存在错误(或者更确切地说,如果曾经有过,它已被修复)。我能够使用 Python 成功导出罐装估算器模型并将其导入 Java。

这是我导出模型的代码:

a = tf.feature_column.numeric_column("a");
b = tf.feature_column.numeric_column("b");
feature_columns = [a, b];

model = tf.estimator.DNNClassifier(feature_columns=feature_columns ...);

# To export
feature_spec = tf.feature_column.make_parse_example_spec(feature_columns);
export_input_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec);
servable_model_path = model.export_savedmodel(servable_model_dir, export_input_fn, as_text=True);

为了在Java中导入模型,我使用了上面rhaertel80提供的Java客户端代码,它可以工作。希望这也能回答 Ben Fowler 的上述问题。

【讨论】:

您能否将预测的 Java 端添加到此答案中,好吗?主要看你是如何为Java中的build_parsing_serving_input_receiver_fn准备输入的。【参考方案4】:

您需要使用 tf.contrib.export_savedmodel 导出保存的模型,并且您需要定义输入接收器函数以将输入传递给。 稍后您可以从磁盘加载保存的模型(通常为 saved.model.pb)并提供它。

TensorFlow: How to predict from a SavedModel?

【讨论】:

以上是关于如何使用 tf.estimator 导入保存的 Tensorflow 模型训练并预测输入数据的主要内容,如果未能解决你的问题,请参考以下文章

tf.Estimator - 将 TensorBoard 日志路由到与模型目录不同的目录

如何使用 tf.estimator.DNNClassifier (Scikit Flow?)

如何使用自定义 tf.Estimator 在张量板事件文件中仅创建一份图形副本?

修改 tf.estimator.Estimator 如何为 Tensorboard 创建摘要

如何在SummarySaverHook和Estimator中使用tensorflow.metrics.x?

Tensorflow在Python中导出和重用Estimator对象