¡Reserva! Google I / O regresa del 18 al 20 de mayo Regístrese ahora
Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Usando el formato SavedModel

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Un modelo guardado contiene un programa TensorFlow completo, que incluye parámetros entrenados (es decir, tf.Variable s) y cálculo. No requiere el código de construcción del modelo original para ejecutarse, lo que lo hace útil para compartir o implementar con TFLite , TensorFlow.js , TensorFlow Serving o TensorFlow Hub .

Puede guardar y cargar un modelo en formato SavedModel utilizando las siguientes API:

Creación de un modelo guardado a partir de Keras

Para una introducción rápida, esta sección exporta un modelo de Keras previamente entrenado y sirve solicitudes de clasificación de imágenes con él. El resto de la guía completará los detalles y discutirá otras formas de crear modelos guardados.

import os
import tempfile

from matplotlib import pyplot as plt
import numpy as np
import tensorflow as tf

tmpdir = tempfile.mkdtemp()
physical_devices = tf.config.list_physical_devices('GPU')
for device in physical_devices:
  tf.config.experimental.set_memory_growth(device, True)
file = tf.keras.utils.get_file(
    "grace_hopper.jpg",
    "https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg")
img = tf.keras.preprocessing.image.load_img(file, target_size=[224, 224])
plt.imshow(img)
plt.axis('off')
x = tf.keras.preprocessing.image.img_to_array(img)
x = tf.keras.applications.mobilenet.preprocess_input(
    x[tf.newaxis,...])
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg
65536/61306 [================================] - 0s 0us/step

png

Utilizará una imagen de Grace Hopper como ejemplo de ejecución y un modelo de clasificación de imágenes preentrenado de Keras, ya que es fácil de usar. Los modelos personalizados también funcionan y se tratan en detalle más adelante.

labels_path = tf.keras.utils.get_file(
    'ImageNetLabels.txt',
    'https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
imagenet_labels = np.array(open(labels_path).read().splitlines())
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt
16384/10484 [==============================================] - 0s 0us/step
pretrained_model = tf.keras.applications.MobileNet()
result_before_save = pretrained_model(x)

decoded = imagenet_labels[np.argsort(result_before_save)[0,::-1][:5]+1]

print("Result before saving:\n", decoded)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet/mobilenet_1_0_224_tf.h5
17227776/17225924 [==============================] - 0s 0us/step
Result before saving:
 ['military uniform' 'bow tie' 'suit' 'bearskin' 'pickelhaube']

La predicción más alta para esta imagen es "uniforme militar".

mobilenet_save_path = os.path.join(tmpdir, "mobilenet/1/")
tf.saved_model.save(pretrained_model, mobilenet_save_path)
INFO:tensorflow:Assets written to: /tmp/tmpfcgkddlh/mobilenet/1/assets

La ruta de guardado sigue una convención utilizada por TensorFlow Serving donde el último componente de ruta ( 1/ aquí) es un número de versión para su modelo; permite que herramientas como Tensorflow Serving razonen sobre la actualización relativa.

Puede volver a cargar el modelo guardado en Python con tf.saved_model.load y ver cómo se clasifica la imagen de Admiral Hopper.

loaded = tf.saved_model.load(mobilenet_save_path)
print(list(loaded.signatures.keys()))  # ["serving_default"]
['serving_default']

Las firmas importadas siempre devuelven diccionarios. Para personalizar los nombres de las firmas y las claves del diccionario de salida, consulte Especificación de firmas durante la exportación .

infer = loaded.signatures["serving_default"]
print(infer.structured_outputs)
{'predictions': TensorSpec(shape=(None, 1000), dtype=tf.float32, name='predictions')}

Ejecutar inferencia desde el modelo guardado da el mismo resultado que el modelo original.

labeling = infer(tf.constant(x))[pretrained_model.output_names[0]]

decoded = imagenet_labels[np.argsort(labeling)[0,::-1][:5]+1]

print("Result after saving and loading:\n", decoded)
Result after saving and loading:
 ['military uniform' 'bow tie' 'suit' 'bearskin' 'pickelhaube']

Ejecutar un modelo guardado en la publicación de TensorFlow

Los modelos guardados se pueden utilizar desde Python (más sobre eso a continuación), pero los entornos de producción suelen utilizar un servicio dedicado para la inferencia sin ejecutar código Python. Esto es fácil de configurar desde un modelo guardado usando TensorFlow Serving.

Consulta el instructivo de REST de TensorFlow Serving para ver un ejemplo de servicio de Tensorflow de extremo a extremo.

El formato de modelo guardado en el disco

Un modelo guardado es un directorio que contiene firmas serializadas y el estado necesario para ejecutarlas, incluidos los valores de las variables y los vocabularios.

ls {mobilenet_save_path}
assets  saved_model.pb  variables

El archivo saved_model.pb almacena el programa o modelo de TensorFlow real y un conjunto de firmas con nombre, cada una identificando una función que acepta entradas de tensor y produce salidas de tensor.

SavedModels puede contener múltiples variantes del modelo (múltiples v1.MetaGraphDefs , identificadas con la --tag_set en saved_model_cli ), pero esto es raro. Las API que crean múltiples variantes de un modelo incluyen tf.Estimator.experimental_export_all_saved_models y en TensorFlow 1.x tf.saved_model.Builder .

saved_model_cli show --dir {mobilenet_save_path} --tag_set serve
2021-02-11 02:25:22.757135: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
The given SavedModel MetaGraphDef contains SignatureDefs with the following keys:
SignatureDef key: "__saved_model_init_op"
SignatureDef key: "serving_default"

El directorio de variables contiene un punto de control de entrenamiento estándar (consulte la guía de puntos de control de entrenamiento ).

ls {mobilenet_save_path}/variables
variables.data-00000-of-00001  variables.index

El directorio de assets contiene archivos que usa el gráfico de TensorFlow, por ejemplo, archivos de texto que se usan para inicializar tablas de vocabulario. No se utiliza en este ejemplo.

SavedModels puede tener un directorio assets.extra para cualquier archivo no utilizado por el gráfico de TensorFlow, por ejemplo, información para los consumidores sobre qué hacer con SavedModel. TensorFlow en sí no usa este directorio.

Guardar un modelo personalizado

tf.saved_model.save admite guardar objetos tf.Module y sus subclases, como tf.keras.Layer y tf.keras.Model .

Veamos un ejemplo de tf.Module guardar y restaurar un tf.Module .

class CustomModule(tf.Module):

  def __init__(self):
    super(CustomModule, self).__init__()
    self.v = tf.Variable(1.)

  @tf.function
  def __call__(self, x):
    print('Tracing with', x)
    return x * self.v

  @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])
  def mutate(self, new_v):
    self.v.assign(new_v)

module = CustomModule()

Cuando guarda un tf.Module , se tf.Variable atributos tf.function , los métodos tf.Module y los tf.Module encuentran a través del recorrido recursivo. (Consulte el tutorial de Checkpoint para obtener más información sobre este recorrido recursivo). Sin embargo, todos los atributos, funciones y datos de Python se pierden. Esto significa que cuando se guarda una función tf.function se guarda ningún código Python.

Si no se guarda ningún código Python, ¿cómo sabe SavedModel cómo restaurar la función?

Brevemente, tf.function funciona rastreando el código Python para generar una ConcreteFunction (un contenedor invocable alrededor de tf.Graph ). Al guardar una función tf.function , realmente está guardando la tf.function caché de ConcreteFunctions de la función tf.function .

Para obtener más información sobre la relación entre tf.function y ConcreteFunctions, consulte la guía tf.function .

module_no_signatures_path = os.path.join(tmpdir, 'module_no_signatures')
module(tf.constant(0.))
print('Saving model...')
tf.saved_model.save(module, module_no_signatures_path)
Tracing with Tensor("x:0", shape=(), dtype=float32)
Saving model...
Tracing with Tensor("x:0", shape=(), dtype=float32)
INFO:tensorflow:Assets written to: /tmp/tmpfcgkddlh/module_no_signatures/assets

Carga y uso de un modelo personalizado

Cuando se carga un SavedModel en Python, todos tf.Variable atributos, tf.function -decorated métodos y tf.Module s se restauran en la misma estructura del objeto que el original guardado tf.Module .

imported = tf.saved_model.load(module_no_signatures_path)
assert imported(tf.constant(3.)).numpy() == 3
imported.mutate(tf.constant(2.))
assert imported(tf.constant(3.)).numpy() == 6

Debido a que no se guarda ningún código Python, la llamada a tf.function con una nueva firma de entrada fallará:

imported(tf.constant([3.]))
ValueError: Could not find matching function to call for canonicalized inputs ((,), {}). Only existing signatures are [((TensorSpec(shape=(), dtype=tf.float32, name=u'x'),), {})].

Ajuste fino básico

Los objetos variables están disponibles y puede retroceder a través de funciones importadas. Eso es suficiente para ajustar (es decir, volver a entrenar) un modelo guardado en casos simples.

optimizer = tf.optimizers.SGD(0.05)

def train_step():
  with tf.GradientTape() as tape:
    loss = (10. - imported(tf.constant(2.))) ** 2
  variables = tape.watched_variables()
  grads = tape.gradient(loss, variables)
  optimizer.apply_gradients(zip(grads, variables))
  return loss
for _ in range(10):
  # "v" approaches 5, "loss" approaches 0
  print("loss={:.2f} v={:.2f}".format(train_step(), imported.v.numpy()))
loss=36.00 v=3.20
loss=12.96 v=3.92
loss=4.67 v=4.35
loss=1.68 v=4.61
loss=0.60 v=4.77
loss=0.22 v=4.86
loss=0.08 v=4.92
loss=0.03 v=4.95
loss=0.01 v=4.97
loss=0.00 v=4.98

Ajuste fino general

Un modelo guardado de Keras proporciona más detalles que una simple __call__ para abordar casos más avanzados de ajuste fino. TensorFlow Hub recomienda proporcionar lo siguiente, si corresponde, en SavedModels compartidos con el fin de ajustar:

  • Si el modelo usa abandono u otra técnica en la que el pase directo difiere entre el entrenamiento y la inferencia (como la normalización por lotes), el método __call__ toma un argumento training= opcional valuado en Python que por defecto es False pero que se puede establecer en True .
  • Junto al atributo __call__ , hay atributos .variable y .trainable_variable con las correspondientes listas de variables. Una variable que originalmente se podía entrenar pero que debía congelarse durante el ajuste fino se omite de .trainable_variables .
  • Por el bien de marcos como Keras que representan regularizadores de peso como atributos de capas o submodelos, también puede haber un atributo .regularization_losses . Contiene una lista de funciones de argumento cero cuyos valores se deben sumar a la pérdida total.

Volviendo al ejemplo inicial de MobileNet, puede ver algunos de ellos en acción:

loaded = tf.saved_model.load(mobilenet_save_path)
print("MobileNet has {} trainable variables: {}, ...".format(
          len(loaded.trainable_variables),
          ", ".join([v.name for v in loaded.trainable_variables[:5]])))
MobileNet has 83 trainable variables: conv1/kernel:0, conv1_bn/gamma:0, conv1_bn/beta:0, conv_dw_1/depthwise_kernel:0, conv_dw_1_bn/gamma:0, ...
trainable_variable_ids = {id(v) for v in loaded.trainable_variables}
non_trainable_variables = [v for v in loaded.variables
                           if id(v) not in trainable_variable_ids]
print("MobileNet also has {} non-trainable variables: {}, ...".format(
          len(non_trainable_variables),
          ", ".join([v.name for v in non_trainable_variables[:3]])))
MobileNet also has 54 non-trainable variables: conv1_bn/moving_mean:0, conv1_bn/moving_variance:0, conv_dw_1_bn/moving_mean:0, ...

Especificar firmas durante la exportación

Herramientas como TensorFlow Serving y saved_model_cli pueden interactuar con SavedModels. Para ayudar a estas herramientas a determinar qué ConcreteFunctions utilizar, debe especificar las firmas de servicio. tf.keras.Model s especifica automáticamente las firmas de servicio, pero tendrá que declarar explícitamente una firma de servicio para nuestros módulos personalizados.

De forma predeterminada, no se declaran firmas en un tf.Module personalizado.

assert len(imported.signatures) == 0

Para declarar una firma de servicio, especifique una función ConcreteFunction utilizando las signatures kwarg. Al especificar una sola firma, su clave de firma será 'serving_default' , que se guarda como la constante tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY .

module_with_signature_path = os.path.join(tmpdir, 'module_with_signature')
call = module.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))
tf.saved_model.save(module, module_with_signature_path, signatures=call)
Tracing with Tensor("x:0", dtype=float32)
Tracing with Tensor("x:0", dtype=float32)
INFO:tensorflow:Assets written to: /tmp/tmpfcgkddlh/module_with_signature/assets
imported_with_signatures = tf.saved_model.load(module_with_signature_path)
list(imported_with_signatures.signatures.keys())
['serving_default']

Para exportar varias firmas, pase un diccionario de claves de firma a ConcreteFunctions. Cada clave de firma corresponde a una función concreta.

module_multiple_signatures_path = os.path.join(tmpdir, 'module_with_multiple_signatures')
signatures = {"serving_default": call,
              "array_input": module.__call__.get_concrete_function(tf.TensorSpec([None], tf.float32))}

tf.saved_model.save(module, module_multiple_signatures_path, signatures=signatures)
Tracing with Tensor("x:0", shape=(None,), dtype=float32)
Tracing with Tensor("x:0", shape=(None,), dtype=float32)
INFO:tensorflow:Assets written to: /tmp/tmpfcgkddlh/module_with_multiple_signatures/assets
imported_with_multiple_signatures = tf.saved_model.load(module_multiple_signatures_path)
list(imported_with_multiple_signatures.signatures.keys())
['serving_default', 'array_input']

De forma predeterminada, los nombres de los tensores de salida son bastante genéricos, como output_0 . Para controlar los nombres de las salidas, modifique su función tf.function para devolver un diccionario que tf.function los nombres de las salidas a las salidas. Los nombres de las entradas se derivan de los nombres arg de la función Python.

class CustomModuleWithOutputName(tf.Module):
  def __init__(self):
    super(CustomModuleWithOutputName, self).__init__()
    self.v = tf.Variable(1.)

  @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])
  def __call__(self, x):
    return {'custom_output_name': x * self.v}

module_output = CustomModuleWithOutputName()
call_output = module_output.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))
module_output_path = os.path.join(tmpdir, 'module_with_output_name')
tf.saved_model.save(module_output, module_output_path,
                    signatures={'serving_default': call_output})
INFO:tensorflow:Assets written to: /tmp/tmpfcgkddlh/module_with_output_name/assets
imported_with_output_name = tf.saved_model.load(module_output_path)
imported_with_output_name.signatures['serving_default'].structured_outputs
{'custom_output_name': TensorSpec(shape=(), dtype=tf.float32, name='custom_output_name')}

Cargar un modelo guardado en C ++

La versión C ++ del cargador de SavedModel proporciona una API para cargar un SavedModel desde una ruta, al tiempo que permite SessionOptions y RunOptions. Debe especificar las etiquetas asociadas con el gráfico que se cargará. La versión cargada de SavedModel se conoce como SavedModelBundle y contiene MetaGraphDef y la sesión dentro de la cual se carga.

const string export_dir = ...
SavedModelBundle bundle;
...
LoadSavedModel(session_options, run_options, export_dir, {kSavedModelTagTrain},
               &bundle);

Detalles de la interfaz de línea de comandos del modelo guardado

Puede utilizar la interfaz de línea de comandos (CLI) del modelo guardado para inspeccionar y ejecutar un modelo guardado. Por ejemplo, puede utilizar la CLI para inspeccionar los SignatureDef del modelo. La CLI le permite confirmar rápidamente que el tipo y la forma del tensor de entrada coinciden con el modelo. Además, si desea probar su modelo, puede usar la CLI para hacer una verificación de cordura pasando entradas de muestra en varios formatos (por ejemplo, expresiones de Python) y luego recuperando la salida.

Instalar la CLI de SavedModel

En términos generales, puede instalar TensorFlow de cualquiera de las dos formas siguientes:

  • Instalando un binario de TensorFlow prediseñado.
  • Compilando TensorFlow a partir del código fuente.

Si instaló TensorFlow a través de un binario de TensorFlow prediseñado, la CLI de SavedModel ya está instalada en su sistema en la ruta bin/saved_model_cli .

Si compiló TensorFlow a partir del código fuente, debe ejecutar el siguiente comando adicional para compilar saved_model_cli :

$ bazel build tensorflow/python/tools:saved_model_cli

Resumen de comandos

La CLI SavedModel admite los dos comandos siguientes en un modelo guardado:

  • show , que muestra los cálculos disponibles de un modelo guardado.
  • run , que ejecuta un cálculo desde un modelo guardado.

show comando

Un modelo guardado contiene una o más variantes de modelo (técnicamente, v1.MetaGraphDef s), identificadas por sus conjuntos de etiquetas. Para servir un modelo, podría preguntarse qué tipo de SignatureDef hay en cada variante del modelo y cuáles son sus entradas y salidas. El comando show permite examinar el contenido del modelo guardado en orden jerárquico. Esta es la sintaxis:

usage: saved_model_cli show [-h] --dir DIR [--all]
[--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]

Por ejemplo, el siguiente comando muestra todos los conjuntos de etiquetas disponibles en el modelo guardado:

$ saved_model_cli show --dir /tmp/saved_model_dir
The given SavedModel contains the following tag-sets:
serve
serve, gpu

El siguiente comando muestra todas las claves SignatureDef disponibles para un conjunto de etiquetas:

$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve
The given SavedModel `MetaGraphDef` contains `SignatureDefs` with the
following keys:
SignatureDef key: "classify_x2_to_y3"
SignatureDef key: "classify_x_to_y"
SignatureDef key: "regress_x2_to_y3"
SignatureDef key: "regress_x_to_y"
SignatureDef key: "regress_x_to_y2"
SignatureDef key: "serving_default"

Si hay varias etiquetas en el conjunto de etiquetas, debe especificar todas las etiquetas, cada etiqueta separada por una coma. Por ejemplo:

$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve,gpu

Para mostrar todas las entradas y salidas de TensorInfo para una SignatureDef específica, pase la clave SignatureDef a la opción signature_def . Esto es muy útil cuando desea conocer el valor de la clave del tensor, el tipo d y la forma de los tensores de entrada para ejecutar el gráfico de cálculo más tarde. Por ejemplo:

$ saved_model_cli show --dir \
/tmp/saved_model_dir --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
  inputs['x'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 1)
      name: x:0
The given SavedModel SignatureDef contains the following output(s):
  outputs['y'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 1)
      name: y:0
Method name is: tensorflow/serving/predict

Para mostrar toda la información disponible en el modelo guardado, use la opción --all . Por ejemplo:

$ saved_model_cli show --dir /tmp/saved_model_dir --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['classify_x2_to_y3']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['inputs'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: x2:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['scores'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: y3:0
  Method name is: tensorflow/serving/classify

...

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['x'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: x:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['y'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: y:0
  Method name is: tensorflow/serving/predict

run comando

Invocar la run de comandos para ejecutar un cálculo gráfico, pasando entradas y, a continuación se presentan (y opcionalmente guardar) las salidas. Esta es la sintaxis:

usage: saved_model_cli run [-h] --dir DIR --tag_set TAG_SET --signature_def
                           SIGNATURE_DEF_KEY [--inputs INPUTS]
                           [--input_exprs INPUT_EXPRS]
                           [--input_examples INPUT_EXAMPLES] [--outdir OUTDIR]
                           [--overwrite] [--tf_debug]

El comando de run proporciona las siguientes tres formas de pasar entradas al modelo:

  • --inputs opción --inputs permite pasar un gran número de ndarray en los archivos.
  • --input_exprs opción --input_exprs permite pasar expresiones de Python.
  • --input_examples opción --input_examples permite pasar tf.train.Example .

--inputs

Para pasar datos de entrada en archivos, especifique la opción --inputs , que tiene el siguiente formato general:

--inputs <INPUTS>

donde INPUTS es uno de los siguientes formatos:

  • <input_key>=<filename>
  • <input_key>=<filename>[<variable_name>]

Puede pasar múltiples ENTRADAS . Si pasa varias entradas, use un punto y coma para separar cada una de las ENTRADAS .

saved_model_cli usa numpy.load para cargar el nombre del archivo . El nombre del archivo puede estar en cualquiera de los siguientes formatos:

  • .npy
  • .npz
  • formato de salmuera

Un archivo .npy siempre contiene un ndarray numpy. Por lo tanto, al cargar desde un archivo .npy , el contenido se asignará directamente al tensor de entrada especificado. Si especifica un variable_name con ese .npy archivo, el variable_name tendrá en cuenta y se emitirá una advertencia.

Cuando la carga de un .npz archivo (zip), puede especificar un variable_name para identificar la variable dentro del archivo zip para cargar la clave para tensor de entrada. Si no se especifica un variable_name, la CLI SavedModel comprobará que sólo un archivo se incluye en el archivo zip y cargarlo para la llave tensor de entrada especificado.

Cuando se carga desde un archivo pickle, si no se especifica variable_name entre corchetes, lo que esté dentro del archivo pickle se pasará a la clave de tensor de entrada especificada. De lo contrario, la CLI SavedModel asumirá que hay un diccionario almacenado en el archivo pickle y se utilizará el valor correspondiente a variable_name .

--input_exprs

Para pasar entradas a través de expresiones de Python, especifique la opción --input_exprs . Esto puede ser útil cuando no tiene archivos de datos por ahí, pero aún desea verificar la cordura del modelo con algunas entradas simples que coincidan con el tipo y la forma de los SignatureDef del modelo. Por ejemplo:

`<input_key>=[[1],[2],[3]]`

Además de las expresiones de Python, también puede pasar numerosas funciones. Por ejemplo:

`<input_key>=np.ones((32,32,3))`

(Tenga en cuenta que el módulo numpy ya está disponible como np .)

--input_examples

Para pasar tf.train.Example como entradas, especifique la opción --input_examples . Para cada clave de entrada, toma una lista de diccionario, donde cada diccionario es una instancia de tf.train.Example . Las claves del diccionario son las características y los valores son las listas de valores para cada característica. Por ejemplo:

`<input_key>=[{"age":[22,24],"education":["BS","MS"]}]`

Guardar salida

De forma predeterminada, la CLI SavedModel escribe la salida en stdout. Si se pasa un directorio a la opción --outdir , las salidas se guardarán como archivos .npy nombre de las claves del tensor de salida en el directorio dado.

Utilice --overwrite para sobrescribir los archivos de salida existentes.