Usando el formato SavedModel

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

Un modelo guardado contiene un programa TensorFlow completo, incluidos los parámetros entrenados (es decir, tf.Variable s) y el cálculo. No requiere el código de creació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 el formato de modelo guardado utilizando las siguientes API:

Crear un modelo guardado de Keras

Para una introducción rápida, esta sección exporta un modelo de Keras previamente entrenado y atiende 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.utils.load_img(file, target_size=[224, 224])
plt.imshow(img)
plt.axis('off')
x = tf.keras.utils.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
73728/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
24576/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
17235968/17225924 [==============================] - 0s 0us/step
Result before saving:
 ['military uniform' 'bow tie' 'suit' 'bearskin' 'pickelhaube']

La principal predicción para esta imagen es "uniforme militar".

mobilenet_save_path = os.path.join(tmpdir, "mobilenet/1/")
tf.saved_model.save(pretrained_model, mobilenet_save_path)
2021-10-27 01:24:27.831628: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: /tmp/tmpbf9fpzwt/mobilenet/1/assets

La ruta de guardado sigue una convención utilizada por TensorFlow Serving donde el último componente de la 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 la 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 TensorFlow Serving

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

Consulte el tutorial REST de servicio de TensorFlow 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 valores de variables y vocabularios.

ls {mobilenet_save_path}
assets  saved_model.pb  variables

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

Los modelos guardados pueden contener múltiples variantes del modelo (múltiples v1.MetaGraphDefs , identificados con el indicador --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
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 utilizados por el gráfico de TensorFlow, por ejemplo, archivos de texto utilizados para inicializar tablas de vocabulario. No se utiliza en este ejemplo.

Los modelos guardados pueden tener un directorio assets.extra para cualquier archivo que no use el gráfico de TensorFlow, por ejemplo, información para los consumidores sobre qué hacer con el modelo guardado. 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 cómo 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 guardan todos los atributos de tf.Variable , tf.function métodos decorados con tf.function y los tf.Module encontrados a través del recorrido recursivo. (Consulte el tutorial de Checkpoint para obtener más información sobre este recorrido recursivo). Sin embargo, se pierden todos los atributos, funciones y datos de Python. Esto significa que cuando se guarda una tf.function ., no se guarda ningún código de Python.

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

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

Para obtener más información sobre la relación entre tf.function y ConcreteFunctions, consulte la guía de 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/tmpbf9fpzwt/module_no_signatures/assets

Cargar y usar un modelo personalizado

Cuando carga un modelo guardado en Python, todos los atributos tf.Variable , tf.function métodos decorados con tf.function y los tf.Module se restauran en la misma estructura de objeto que el tf.Module original guardado.

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 de Python, fallará la llamada a una tf.function con una nueva firma de entrada:

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'),), {})].

Puesta a punto básica

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

Ajustes generales

Un modelo guardado de Keras proporciona más detalles que una simple __call__ para abordar casos más avanzados de ajuste. TensorFlow Hub recomienda proporcionar lo siguiente, si corresponde, en los modelos guardados compartidos con fines de ajuste:

  • Si el modelo usa el abandono u otra técnica en la que el paso hacia adelante difiere entre el entrenamiento y la inferencia (como la normalización por lotes), el método __call__ toma un argumento training= opcional valorado en Python que por defecto es False pero se puede establecer en True .
  • Junto al atributo __call__ , se encuentran los atributos .variable y .trainable_variable con las correspondientes listas de variables. Una variable que originalmente era entrenable pero que debe 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 están destinados a sumarse 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, ...

Especificación de firmas durante la exportación

Herramientas como TensorFlow Serving y saved_model_cli pueden interactuar con los modelos guardados. Para ayudar a estas herramientas a determinar qué funciones concretas usar, debe especificar las firmas de publicación. tf.keras.Model s especifica automáticamente las firmas de publicación, pero tendrá que declarar explícitamente una firma de publicación 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 ConcreteFunction usando 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/tmpbf9fpzwt/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/tmpbf9fpzwt/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 tf.function para devolver un diccionario que asigne nombres de salida a salidas. Los nombres de las entradas se derivan de los nombres arg de la función de 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/tmpbf9fpzwt/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')}

Cargue un modelo guardado en C++

La versión C++ del cargador de modelos guardados proporciona una API para cargar un modelo guardado desde una ruta, al tiempo que permite SessionOptions y RunOptions. Debe especificar las etiquetas asociadas con el gráfico que se va a cargar. La versión cargada de SavedModel se conoce como SavedModelBundle y contiene MetaGraphDef y la sesión en la que 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 de SavedModel

Puede utilizar la interfaz de línea de comandos (CLI) del modelo guardado para inspeccionar y ejecutar un modelo guardado. Por ejemplo, puede usar 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 realizar una verificación de cordura al pasar entradas de muestra en varios formatos (por ejemplo, expresiones de Python) y luego obtener la salida.

Instale la CLI del modelo guardado

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

  • Al instalar un binario de TensorFlow preconstruido.
  • Construyendo TensorFlow a partir del código fuente.

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

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

$ bazel build tensorflow/python/tools:saved_model_cli

Descripción general de los comandos

La CLI del modelo guardado admite los siguientes dos comandos 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, es posible que se pregunte qué tipo de SignatureDef hay en cada variante del modelo y cuáles son sus entradas y salidas. El comando show le permite examinar el contenido del modelo guardado en orden jerárquico. Aquí está 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 un SignatureDef específico, 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 y la forma de los tensores de entrada para ejecutar el gráfico de cálculo más adelante. 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

Invoque el comando de run para ejecutar un cálculo gráfico, pasando entradas y luego mostrando (y opcionalmente guardando) las salidas. Aquí está 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 run proporciona las siguientes tres formas de pasar entradas al modelo:

  • La opción --inputs le permite pasar numpy ndarray en archivos.
  • La opción --input_exprs le permite pasar expresiones de Python.
  • La opción --input_examples le permite pasar tf.train.Example .

--inputs

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

--inputs <INPUTS>

donde ENTRADAS es cualquiera de los siguientes formatos:

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

Puede pasar varias 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 pepinillo

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 nombre_variable con ese archivo .npy , el nombre_variable se ignorará y se emitirá una advertencia.

Al cargar desde un .npz (zip), puede especificar opcionalmente un nombre_variable para identificar la variable dentro del archivo zip para cargar la clave de tensor de entrada. Si no especifica un nombre_variable , la CLI del modelo guardado verificará que solo se incluye un archivo en el archivo zip y lo cargará para la clave de tensor de entrada especificada.

Al cargar desde un archivo pickle, si no se especifica variable_name entre corchetes, lo que sea que esté dentro del archivo pickle se pasará a la clave de tensor de entrada especificada. De lo contrario, la CLI del modelo guardado supondrá que hay un diccionario almacenado en el archivo pickle y se usará 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 coinciden con el tipo de d y la forma de los SignatureDef s del modelo. Por ejemplo:

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

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

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

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

--input_examples

Para pasar tf.train.Example como entradas, especifique la opción --input_examples . Para cada tecla de entrada, se necesita una lista de diccionarios, donde cada diccionario es una instancia de tf.train.Example . Las claves del diccionario son las funciones y los valores son las listas de valores para cada función. Por ejemplo:

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

Guardar salida

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

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