web-dev-qa-db-ja.com

tf.estimatorを使用して保存されたTensorflowモデルトレインをインポートし、入力データを予測する方法

次のように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") 

この保存されたモデルをインポートして予測に使用するにはどうすればよいですか?

18
nayan

良いベースの例を探してみましたが、ドキュメントとサンプルはこのトピックに少し散在しているようです。基本的な例から始めましょう: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(エクスポートステップからの戻り値)を参照して、not "/ 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 です。Python SavedModel、次のようなコードが必要です。

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を使用してモデルを提供する

分類モデル の提供に適した方法でモデルをエクスポートするには、入力が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")
45
rhaertel80

Estimatorの缶詰にはバグがあるとは思わない(むしろ、バグがある場合は修正されている)。 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の質問にも答えることを願っています。

3
MarquesDeCampo

TensorFlowチームは、ユースケース#2でモデルをエクスポートするために缶詰推定器を使用するバージョン1.3にバグがあることに同意していないようです。ここにバグレポートを提出しました: https://github.com/tensorflow/tensorflow/issues/13477

TensorFlowから受け取った応答は、入力は単一の文字列テンソルのみでなければならないというものです。シリアル化されたTF.examplesを使用して、複数の機能を単一の文字列テンソルに統合する方法があるように見えますが、これを行う明確な方法は見つかりませんでした。誰かがこれを行う方法を示すコードを持っているなら、私は感謝するでしょう。

1
Ben Fowler

Tf.contrib.export_savedmodelを使用して保存したモデルをエクスポートする必要があり、入力を渡す入力レシーバー関数を定義する必要があります。後で、保存したモデル(通常はsaved.model.pb)をディスクからロードして提供できます。

TensorFlow:SavedModelから予測する方法

0
sudharsan tk