Cómo importar un tren modelo Tensorflow guardado usando tf.estimator y predecir datos de entrada
tensorflow-serving (4)
He guardado el modelo usando tf.estimator .method export_savedmodel de la siguiente manera:
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")
¿Cómo puedo importar este modelo guardado y usarlo para las predicciones?
Debe exportar el modelo guardado usando tf.contrib.export_savedmodel y debe definir la función del receptor de entrada para pasar la entrada. Más tarde, puede cargar el modelo guardado (generalmente salvado.modelo.pb) desde el disco y servirlo.
Intenté buscar un buen ejemplo base, pero parece que la documentación y las muestras están un poco dispersas para este tema. Comencemos con un ejemplo básico: el inicio quickstart tf.estimator.
Ese ejemplo en particular no exporta un modelo, así que hagamos eso (no es necesario para el caso de uso 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)
Asterisco enorme en este código : parece haber un error en TensorFlow 1.3 que no le permite realizar la exportación anterior en un estimador "enlatado" (como DNNClassifier). Para una solución alternativa, consulte la sección "Apéndice: Solución alternativa".
El siguiente código hace referencia a
export_dir
(valor de retorno del paso de exportación) para enfatizar que no es "/ ruta / a / modelo", sino un subdirectorio de ese directorio cuyo nombre es una marca de tiempo.
Caso de uso 1: realice la predicción en el mismo proceso que el entrenamiento
Este es un tipo de experiencia de aprendizaje de ciencia ficción y ya está ejemplificado por la muestra.
Para completar, simplemente llame a
predict
en el modelo entrenado:
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]
Caso de uso 2: Cargue un modelo guardado en Python / Java / C ++ y realice predicciones
Cliente Python
Quizás lo más fácil de usar si quieres hacer predicciones en Python es
SavedModelPredictor
.
En el programa Python que usará el
SavedModel
, necesitamos un código como este:
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''])
Cliente 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));
}
}
Cliente C ++
Es probable que desee utilizar
tensorflow::LoadSavedModel
con
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;
}
}
Caso de uso 3: Sirva un modelo con TensorFlow Serving
La exportación de modelos de manera que pueda servir un
modelo de Clasificación
requiere que la entrada sea un objeto
tf.Example
.
Así es como podemos exportar un modelo para la publicación de 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)
Se remite al lector a la documentación de TensorFlow Serving para obtener más instrucciones sobre cómo configurar TensorFlow Serving, por lo que solo proporcionaré el código del cliente aquí:
# 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)
Tenga en cuenta que la clave,
examples
, a la que se hace referencia en
predict_request.inputs
debe coincidir con la clave utilizada en
serving_input_receiver_fn
en el momento de la exportación (cf. el constructor de
ServingInputReceiver
en ese código).
Apéndice: Evitar las exportaciones de modelos enlatados en TF 1.3
Parece haber un error en TensorFlow 1.3 en el que los modelos enlatados no se exportan correctamente para el caso de uso 2 (el problema no existe para los estimadores "personalizados"). Aquí hay una solución alternativa que envuelve un DNNClassifier para hacer que las cosas funcionen, específicamente para el ejemplo de 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")
No creo que haya un error con los estimadores enlatados (o más bien, si alguna vez hubo uno, se ha solucionado). Pude exportar con éxito un modelo de estimador enlatado usando Python e importarlo en Java.
Aquí está mi código para exportar el modelo:
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);
Para importar el modelo en Java, utilicé el código de cliente Java proporcionado por rhaertel80 anterior y funciona. Espero que esto también responda la pregunta anterior de Ben Fowler.
Parece que el equipo de TensorFlow no está de acuerdo en que haya un error en la versión 1.3 al usar estimadores enlatados para exportar un modelo en el caso de uso # 2. Envié un informe de error aquí: https://github.com/tensorflow/tensorflow/issues/13477
La respuesta que recibí de TensorFlow es que la entrada solo debe ser un solo tensor de cadena. Parece que puede haber una manera de consolidar múltiples funciones en un solo tensor de cadena usando ejemplos TF. serializados, pero no he encontrado un método claro para hacerlo. Si alguien tiene un código que muestre cómo hacer esto, lo agradecería.