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?

Fruchtzwerg
  • 10,999
  • 12
  • 40
  • 49
nayan
  • 243
  • 1
  • 4
  • 6
  • 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? – rhaertel80 Sep 08 '17 at 15:23
  • Now, I am trying to write a python script to load the model and perform prediction. – nayan Sep 09 '17 at 05:46
  • [Example of export_savedmodel function](https://stackoverflow.com/a/48329456/4268517) – David Valenzuela Urrutia Jan 18 '18 at 21:15

4 Answers4

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")
rhaertel80
  • 8,254
  • 1
  • 31
  • 47
  • 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. – nayan Sep 10 '17 at 16:22
  • @nayan 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 `scores` – rhaertel80 Sep 11 '17 at 17:41
  • 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. – nayan Sep 12 '17 at 04:11
  • 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 https://github.com/tensorflow/tensorflow/issues if this feature is important to you at org.tensorflow.SavedModelBundle.load(Native Method) – nayan Sep 16 '17 at 06:34
  • 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. – nayan Sep 18 '17 at 02:47
  • 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. – rhaertel80 Sep 18 '17 at 13:53
  • 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. – nayan Sep 19 '17 at 03:44
  • @rhaertel80 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)`. – Effective_cellist Mar 05 '18 at 11:55
  • Do I still need such workaround in TF > 1.3, esp. TF 1.10? Has it been solved in newer version TF? – soloice Aug 16 '18 at 04:34
  • 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). – rhaertel80 Aug 16 '18 at 14:16
  • @rhaertel80 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)** – Madhi Aug 27 '18 at 11:05
  • 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. – rhaertel80 Aug 28 '18 at 15:40
  • 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. – Aaditya Ura Sep 07 '18 at 10:17
  • @AyodhyankitPaul That might warrant it's own question. It would be helpful to note what version of TF you are using. – rhaertel80 Sep 09 '18 at 08:59
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.

  • 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. – Stephen Newell Apr 17 '18 at 16:04
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.

Ben Fowler
  • 21
  • 1
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?

sudharsan tk
  • 494
  • 6
  • 14