How to import an saved Tensorflow model train using tf.estimator and predict on input data
Asked Answered
E

4

24

I have save the model using tf.estimator .method export_savedmodel as follows:

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

How can I import this saved model and use for predictions?

Eudemon answered 7/9, 2017 at 14:24 Comment(3)
Can you comment a bit more on the environment in which you want to perform predictions? Do you just want to write a Python app that loads the model in the same process and performs prediction? Do you want to run your own production-grade service for serving your model? Do you want to use a managed service in the cloud?Thenna
Now, I am trying to write a python script to load the model and perform prediction.Eudemon
Example of export_savedmodel functionKaiser
T
56

I tried to search for a good base example, but it appears the documentation and samples are a bit scattered for this topic. So let's start with a base example: the tf.estimator quickstart.

That particular example doesn't actually export a model, so let's do that (not need for use case 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)

Huge asterisk on this code: there appears to be a bug in TensorFlow 1.3 that doesn't allow you to do the above export on a "canned" estimator (such as DNNClassifier). For a workaround, see the "Appendix: Workaround" section.

The code below references export_dir (return value from the export step) to emphasize that it is not "/path/to/model", but rather, a subdirectory of that directory whose name is a timestamp.

Use Case 1: Perform prediction in the same process as training

This is an sci-kit learn type of experience, and is already exemplified by the sample. For completeness' sake, you simply call predict on the trained model:

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]

Use Case 2: Load a SavedModel into Python/Java/C++ and perform predictions

Python Client

Perhaps the easiest thing to use if you want to do prediction in Python is SavedModelPredictor. In the Python program that will use the SavedModel, we need code like this:

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 Client

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++ Client

You'll likely want to use tensorflow::LoadSavedModel with Session.

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

Use Case 3: Serve a model using TensorFlow Serving

Exporting models in a manner amenable to serving a Classification model requires that the input be a tf.Example object. Here's how we might export a model for TensorFlow serving:

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)

The reader is referred to TensorFlow Serving's documentation for more instructions on how to setup TensorFlow Serving, so I'll only provide the client code here:

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

Note that the key, examples, that is referenced in the predict_request.inputs needs to match the key used in the serving_input_receiver_fn at export time (cf. the constructor to ServingInputReceiver in that code).

Appendix: Working around Exports from Canned Models in TF 1.3

There appears to be a bug in TensorFlow 1.3 in which canned models do not export properly for use case 2 (the problem does not exist for "custom" estimators). Here's is a workaround that wraps a DNNClassifier to make things work, specifically for the Iris example:

# 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")
Thenna answered 10/9, 2017 at 9:29 Comment(14)
Thank you very much for the details explanation. I am able to get the scores for each classes using print(predictions['scores']) and print(predictions['classes']) . can we able to get the predicted class.Eudemon
@Eudemon The output of DNNClassifier is designed to support a very large output space where you might want predict the top-n classes. The idea is that the classes key contains the names of the classes corresponding to the scores in the scores output. However, I don't believe you actually can do top-n, yet. So what you get in classes is just the list of classes, in order, repeated for every output. To get the predicted class, you have two options: (1) write a custom estimator (possibly wrapping DNNClassifier or similar to do the hard work) (2) have the client take the argmax of scoresThenna
Thanks. I am able to get the top 1 predict class using argmax of scores. If there is an equivalent c/c++ api for python predictor function as in Use Case 2, So that it can be integrated to iOS/android platform.Eudemon
Thanks. i am able to run the java and c++ client on PC. when i tried to integrate the java code on android got following error java.lang.UnsupportedOperationException: Loading a SavedModel is not supported in Android. File a bug at github.com/tensorflow/tensorflow/issues if this feature is important to you at org.tensorflow.SavedModelBundle.load(Native Method)Eudemon
I have tried to freeze the model with python script 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 . get following error message TypeError: names_to_saveables must be a dict mapping string names to Tensors/Variables. Not a variable: Tensor("dnn/hiddenlayer_0/bias:0", shape=(5,), dtype=float32). Please help.Eudemon
Have you tried using --input_saved_model_dir instead of --input_{graph_model,checkpoint}? This question probably deserves its own question/answer on stack overflow.Thenna
Thanks. --input_save_model_dir option is working and able to freeze the model. By mistake i was using old source code folder for tensor-flow and --input_save_model_dir option was not available there.Eudemon
@Thenna How can I convert a SavedModel to .pb format. The SavedModel was exported using estimator.export_savedmodel, I can load this predictor using tf.contrib.predictor.from_saved_model(saved_model_dir).Romanov
Do I still need such workaround in TF > 1.3, esp. TF 1.10? Has it been solved in newer version TF?Ashelman
I'm pretty sure it was addressed in TF 1.4, but it looks like the getting started link has changed and I can't easily find the model that this post was based on. Maybe you can try out your code and if there are problems start a new thread (feel free to bring said thread to my attention).Thenna
@Thenna when I export the serving input function using tf.estimator.export.ServingInputReceiver(features,feature_placeholders)and saving that input function using estimator.export_savedmodel(export_dir, csv_serving_input_fn_vtwo) I got the following error ValueError: too many values to unpack (expected 2)Transmutation
The example in my answer above uses (features, features). So you're doing something a bit different. Make sure the type of both features and feature_placeholders is a dict. If you are still having problems, considering creating a new question.Thenna
I tried your method and got "ValueError: Got signature_def_key "serving_default". Available signatures are ['predict']. Original error: No SignatureDef with key 'serving_default' found in MetaGraphDef." error.Landers
@AyodhyankitPaul That might warrant it's own question. It would be helpful to note what version of TF you are using.Thenna
S
3

I dont think there is a bug with canned Estimators (or rather if there was ever one, it has been fixed). I was able to successfully export a canned estimator model using Python and import it in Java.

Here is my code to export the model:

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

To import the model in Java, I used the Java client code provided by rhaertel80 above and it works. Hope this also answers Ben Fowler's question above.

Samuelsamuela answered 22/10, 2017 at 9:23 Comment(1)
Could you add the Java side of the prediction to this answer, please? Mainly to see how you are preparing the input for build_parsing_serving_input_receiver_fn in Java.Panier
P
1

It appears that the TensorFlow team does not agree that there is a bug in version 1.3 using canned estimators for exporting a model under use case #2. I submitted a bug report here: https://github.com/tensorflow/tensorflow/issues/13477

The response I received from TensorFlow is that the input must only be a single string tensor. It appears that there may be a way to consolidate multiple features into a single string tensor using serialized TF.examples, but I have not found a clear method to do this. If anyone has code showing how to do this, I would be appreciative.

Pernik answered 4/10, 2017 at 14:43 Comment(0)
P
0

You need to export the saved model using tf.contrib.export_savedmodel and you need to define input receiver function to pass input to. Later you can load the saved model ( generally saved.model.pb) from the disk and serve it.

TensorFlow: How to predict from a SavedModel?

Pirandello answered 5/12, 2017 at 2:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.