web-dev-qa-db-fra.com

Comment importer un train de modèles Tensorflow enregistré à l'aide de tf.estimator et prévoir les données d'entrée

J'ai sauvegardé le modèle en utilisant tf.estimator .method export_savedmodel comme suit:

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

Comment puis-je importer ce modèle enregistré et l'utiliser pour les prévisions?

11
nayan

J'ai essayé de chercher un bon exemple de base, mais il semble que la documentation et les exemples soient un peu éparpillés pour ce sujet. Commençons donc par un exemple de base: tf.estimator quickstart .

Cet exemple particulier n'exporte pas de modèle. Faisons-le donc (inutile d'utiliser le cas d'utilisation 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)

Énorme astérisque sur ce code: il semble y avoir un bogue dans TensorFlow 1.3 qui ne vous permet pas d'exporter ci-dessus sur un estimateur "intégré" (tel que DNNClassifier). Pour une solution de contournement, consultez la section «Annexe: Solution de contournement».

Le code ci-dessous fait référence à export_dir (valeur renvoyée à l'étape d'exportation) pour souligner qu'il s'agit de pas "/ chemin/vers/modèle", mais plutôt d'un sous-répertoire de ce répertoire dont le nom est un horodatage.

Cas d'utilisation 1: Effectuer une prédiction dans le même processus que la formation

Il s’agit d’un type d’expérience d’apprentissage sci-kit, comme le montre déjà l’exemple. Par souci d’exhaustivité, il vous suffit d’appeler predict sur le modèle formé:

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]

Cas d'utilisation 2: chargez un modèle enregistré dans Python/Java/C++ et effectuez des prédictions

Client Python

Peut-être que la chose la plus simple à utiliser si vous souhaitez effectuer une prédiction en Python est SavedModelPredictor . Dans le programme Python qui utilisera la SavedModel, nous avons besoin d’un code comme celui-ci:

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'])

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

Client C++

Vous voudrez probablement utiliser tensorflow::LoadSavedModel avec 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;
  }
}

Cas d'utilisation 3: Servir un modèle à l'aide de TensorFlow Serving

L'exportation de modèles de manière à servir un Modèle de classification nécessite que l'entrée soit un objet tf.Example. Voici comment exporter un modèle pour le service 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)

Le lecteur est invité à se reporter à la documentation de TensorFlow Serving pour plus d'instructions sur la configuration de TensorFlow Serving. Je ne fournirai donc que le code client ici:

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

Notez que la clé, examples, qui est référencée dans le predict_request.inputs doit correspondre à la clé utilisée dans le serving_input_receiver_fn au moment de l’exportation (voir le constructeur à ServingInputReceiver dans ce code).

Annexe: Contournement des exportations de modèles en conserve dans TF 1.3

Il semble y avoir un bogue dans TensorFlow 1.3 dans lequel les modèles prédéfinis n’exportent pas correctement pour le cas d’utilisation 2 (le problème n’existe pas pour les estimateurs "personnalisés"). Voici une solution de contournement qui englobe un DNNClassifier pour que tout fonctionne, en particulier pour l'exemple 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")
37
rhaertel80

Je ne pense pas qu'il y ait un bug avec les estimateurs en conserve (ou plutôt s'il en existait un, il a été corrigé). J'ai réussi à exporter un modèle d'estimation intégré avec Python et à l'importer en Java.

Voici mon code pour exporter le modèle:

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

Pour importer le modèle en Java, j'ai utilisé le code client Java fourni par rhaertel80 ci-dessus et cela fonctionne. J'espère que cela répond également à la question de Ben Fowler ci-dessus.

3
MarquesDeCampo

Il semble que l’équipe TensorFlow n’est pas d’accord pour affirmer qu’il existe un bogue dans la version 1.3 qui utilise des estimateurs fixes pour l’exportation d’un modèle dans le cas d’utilisation 2. J'ai soumis un rapport de bogue ici: https://github.com/tensorflow/tensorflow/issues/13477

La réponse que j'ai reçue de TensorFlow est que l'entrée ne doit être qu'un tenseur de chaîne. Il semble qu’il existe un moyen de consolider plusieurs entités dans un même tenseur de chaînes à l’aide de fichiers TF.exemples sérialisés, mais je n’ai pas trouvé de méthode claire pour le faire. Si quelqu'un a un code montrant comment faire cela, je vous en serais reconnaissant. 

0
Ben Fowler

Vous devez exporter le modèle enregistré à l'aide de tf.contrib.export_savedmodel et définir la fonction de récepteur d'entrée pour passer l'entrée à . Plus tard, vous pourrez charger le modèle enregistré (généralement saved.model.pb) à partir du disque et le servir.

TensorFlow: Comment prédire à partir d'un modèle enregistré?

0
sudharsan tk