O Dia da Comunidade de ML é dia 9 de novembro! Junte-nos para atualização de TensorFlow, JAX, e mais Saiba mais

Usando o formato SavedModel

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Um SavedModel contém um programa TensorFlow completa, incluindo parâmetros formados (isto é, tf.Variable s) e cálculo. Ele não requer o código de construção do modelo original para corrida, o que o torna útil para a partilha ou implantando com TFLite , TensorFlow.js , TensorFlow Servindo , ou TensorFlow Hub .

Você pode salvar e carregar um modelo no formato SavedModel usando as seguintes APIs:

Criando um SavedModel de Keras

Para uma introdução rápida, esta seção exporta um modelo Keras pré-treinado e atende a solicitações de classificação de imagem com ele. O restante do guia preencherá os detalhes e discutirá outras maneiras de criar SavedModels.

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
73728/61306 [====================================] - 0s 0us/step

png

Você usará uma imagem de Grace Hopper como exemplo de execução e um modelo de classificação de imagem pré-treinado Keras, pois é fácil de usar. Os modelos personalizados também funcionam e são abordados em detalhes posteriormente.

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

A previsão principal para esta imagem é "uniforme militar".

mobilenet_save_path = os.path.join(tmpdir, "mobilenet/1/")
tf.saved_model.save(pretrained_model, mobilenet_save_path)
2021-09-22 20:37:56.476712: 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/tmpiq7_1gwc/mobilenet/1/assets

O caminho-de economia segue uma convenção usada por TensorFlow Servindo onde o último componente do caminho ( 1/ aqui) é um número de versão para o seu modelo - ele permite que ferramentas como Tensorflow Servindo à razão sobre a frescura relativa.

Você pode carregar a volta SavedModel em Python com tf.saved_model.load e ver como imagem do almirante Hopper é classificada.

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

As assinaturas importadas sempre retornam dicionários. Para personalizar nomes de assinatura e chaves de dicionário de saída, consulte Especificando assinaturas durante a exportação .

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

A execução da inferência do SavedModel fornece o mesmo resultado do 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']

Executando um SavedModel no TensorFlow Serving

SavedModels podem ser usados ​​em Python (mais sobre isso a seguir), mas os ambientes de produção normalmente usam um serviço dedicado para inferência sem executar o código Python. Isso é fácil de configurar a partir de um SavedModel usando o TensorFlow Serving.

Veja a TensorFlow Servindo RESTO tutorial para um exemplo-servindo tensorflow end-to-end.

O formato SavedModel no disco

Um SavedModel é um diretório que contém assinaturas serializadas e o estado necessário para executá-las, incluindo valores de variáveis ​​e vocabulários.

ls {mobilenet_save_path}
assets  saved_model.pb  variables

Os saved_model.pb arquivo armazena o programa real TensorFlow, ou modelo, e um conjunto de assinaturas nomeados, cada um identificar uma função que aceita entradas de tensores e produz tensor saídas.

SavedModels pode conter múltiplas variantes do modelo (vários v1.MetaGraphDefs , identificados com o --tag_set bandeira para saved_model_cli ), mas isso é raro. APIs que criam múltiplas variantes de um modelo incluem tf.Estimator.experimental_export_all_saved_models e em 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"

A variables diretório contém um posto de controle formação padrão (veja o guia para pontos de verificação de treinamento ).

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

O assets diretório contém arquivos usados pelo gráfico TensorFlow, por exemplo, arquivos de texto usados para inicializar tabelas de vocabulário. Não é usado neste exemplo.

SavedModels pode ter um assets.extra diretório para todos os arquivos não utilizados pelo gráfico TensorFlow, por exemplo, informações para os consumidores sobre o que fazer com o SavedModel. O próprio TensorFlow não usa esse diretório.

Salvando um modelo personalizado

tf.saved_model.save suporta a gravação tf.Module objetos e suas subclasses, como tf.keras.Layer e tf.keras.Model .

Vamos olhar um exemplo de salvar e restaurar um 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()

Quando você salva um tf.Module , nenhum tf.Variable atributos, tf.function -decorated métodos e tf.Module s encontrados através travessia recursiva são salvos. (Veja o tutorial Checkpoint para saber mais sobre esta travessia recursiva.) No entanto, atribui qualquer Python, as funções e os dados são perdidos. Isto significa que quando um tf.function é salvo, nenhum código Python é salvo.

Se nenhum código Python for salvo, como SavedModel sabe como restaurar a função?

Resumidamente, tf.function funciona rastreando o código Python para gerar um ConcreteFunction (a callable wrapper em torno tf.Graph ). Ao salvar um tf.function , você está realmente salvar o tf.function cache do de ConcreteFunctions.

Para saber mais sobre a relação entre tf.function e ConcreteFunctions, consulte o guia 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/tmpiq7_1gwc/module_no_signatures/assets

Carregando e usando um modelo personalizado

Quando você carrega um SavedModel em Python, todos os tf.Variable atributos, tf.function -decorated métodos e tf.Module s são restaurados na mesma estrutura do objeto que o original salvo 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

Porque nenhum código Python é salva, chamando um tf.function com uma nova assinatura de entrada irá falhar:

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

Objetos variáveis ​​estão disponíveis e você pode fazer backprop por meio de funções importadas. Isso é o suficiente para ajustar (ou seja, treinar novamente) um SavedModel em 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 geral

A SavedModel de Keras fornece mais detalhes do que uma simples __call__ para tratar casos mais avançados de ajuste fino. O TensorFlow Hub recomenda fornecer o seguinte, se aplicável, em SavedModels compartilhados para fins de ajuste fino:

  • Se os usos modelo dropout ou outra técnica em que os difere passe para frente entre a formação e inferência (como a normalização lote), a __call__ método leva, um opcional Python-valorizado training= argumento de que o padrão é False , mas pode ser configurado para True .
  • Ao lado do __call__ atributo, há .variable e .trainable_variable atributos com as listas correspondentes de variáveis. Uma variável que foi originalmente treinável, mas é para ser congelado durante o ajuste fino é omitida .trainable_variables .
  • Por uma questão de estruturas como Keras que representam regularizers peso como atributos de camadas ou sub-modelos, também pode haver um .regularization_losses atributo. Ele contém uma lista de funções de argumento zero cujos valores são destinados à adição à perda total.

Voltando ao exemplo inicial do MobileNet, você pode ver alguns deles em ação:

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, ...

Especificando assinaturas durante a exportação

Ferramentas como TensorFlow Servindo e saved_model_cli pode interagir com SavedModels. Para ajudar essas ferramentas a determinar quais ConcreteFunctions usar, você precisa especificar as assinaturas de serviço. tf.keras.Model s especificar automaticamente servindo assinaturas, mas você vai ter que declarar explicitamente uma assinatura servir para os nossos módulos personalizados.

Por padrão, há assinaturas são declaradas em um costume tf.Module .

assert len(imported.signatures) == 0

Para declarar uma assinatura de servir, especificar um ConcreteFunction usando o signatures kwarg. Ao especificar uma única assinatura, a sua chave de assinatura será 'serving_default' , que é salvo como a 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/tmpiq7_1gwc/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 várias assinaturas, passe um dicionário de chaves de assinatura para ConcreteFunctions. Cada chave de assinatura corresponde a um ConcreteFunction.

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/tmpiq7_1gwc/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']

Por padrão, os nomes de tensores de saída são bastante genérico, como output_0 . Para controlar os nomes das saídas, modificar seu tf.function para retornar um dicionário que mapeia os nomes de saída para saídas. Os nomes das entradas são derivados dos nomes dos argumentos da função 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/tmpiq7_1gwc/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')}

Carregar um SavedModel em C ++

A versão C ++ da SavedModel carregador fornece uma API para carregar um SavedModel a partir de um caminho, ao mesmo tempo permitindo que SessionOptions e RunOptions. Você deve especificar as tags associadas ao gráfico a ser carregado. A versão carregada de SavedModel é conhecida como SavedModelBundle e contém o MetaGraphDef e a sessão na qual é carregado.

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

Detalhes da interface da linha de comando SavedModel

Você pode usar o SavedModel Command Line Interface (CLI) para inspecionar e executar um SavedModel. Por exemplo, você pode usar o CLI para inspecionar o modelo SignatureDef s. A CLI permite que você confirme rapidamente se o tipo e a forma do tensor de entrada correspondem ao modelo. Além disso, se quiser testar seu modelo, você pode usar a CLI para fazer uma verificação de sanidade, passando entradas de amostra em vários formatos (por exemplo, expressões Python) e, em seguida, buscando a saída.

Instale o SavedModel CLI

Em termos gerais, você pode instalar o TensorFlow de uma das seguintes maneiras:

  • Instalando um binário pré-construído do TensorFlow.
  • Ao construir o TensorFlow a partir do código-fonte.

Se você instalou TensorFlow através de um pré-construído TensorFlow binário, então o SavedModel CLI já está instalado em seu sistema a caminho bin/saved_model_cli .

Se você construiu TensorFlow a partir do código fonte, você deve executar o seguinte comando adicional para construir saved_model_cli :

$ bazel build tensorflow/python/tools:saved_model_cli

Visão geral dos comandos

A CLI SavedModel oferece suporte aos dois comandos a seguir em um SavedModel:

  • show , que mostra os cálculos disponível a partir de uma SavedModel.
  • run , que é executado um cálculo a partir de um SavedModel.

show comando

Um SavedModel contém uma ou mais variantes de modelo (tecnicamente, v1.MetaGraphDef s), identificadas pelos seus tag-conjuntos. Para servir um modelo, você pode se perguntar que tipo de SignatureDef s estão em cada variante do modelo, e quais são as suas entradas e saídas. O show comando permitem que você examinar o conteúdo do SavedModel em ordem hierárquica. Esta é a sintaxe:

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

Por exemplo, o seguinte comando mostra todos os conjuntos de tags disponíveis no SavedModel:

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

As seguintes mostras de comando todos os disponíveis SignatureDef chaves para um conjunto tag:

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

Se houver vários tags na tag-set, você deve especificar todas as tags, cada tag separados por uma vírgula. Por exemplo:

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

Para mostrar todas as entradas e saídas TensorInfo para uma específica SignatureDef , passar na SignatureDef chave para signature_def opção. Isso é muito útil quando você deseja saber o valor da chave do tensor, o tipo d e a forma dos tensores de entrada para executar o gráfico de computação posteriormente. Por exemplo:

$ 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 todas as informações disponíveis na SavedModel, utilize o --all opção. Por exemplo:

$ 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 a run de comandos para executar um cálculo de gráfico, passando entradas e então visualizadas (e, opcionalmente, poupando) as saídas. Esta é a sintaxe:

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]

O run de comando fornece os seguintes três maneiras de passar informações para o modelo:

  • --inputs opção permite que você passe ndarray numpy em arquivos.
  • --input_exprs opção permite que você passe expressões Python.
  • --input_examples opção permite que você passe tf.train.Example .

--inputs

Para passar dados de entrada nos arquivos, especifique o --inputs opção, que leva o seguinte formato geral:

--inputs <INPUTS>

onde as entradas é um dos seguintes formatos:

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

Você pode passar várias entradas. Se você passar várias entradas, use um ponto e vírgula para separar cada uma das entradas.

saved_model_cli usos numpy.load para carregar o nome do arquivo. O nome do arquivo pode estar em qualquer um dos seguintes formatos:

  • .npy
  • .npz
  • formato de picles

A .npy arquivo contém sempre uma ndarray numpy. Portanto, quando o carregamento de um .npy arquivo, o conteúdo será atribuído diretamente ao tensor de entrada especificado. Se você especificar um variable_name com que .npy arquivo, o variable_name será ignorado e será emitido um aviso.

Quando o carregamento de um .npz arquivo (zip), você pode, opcionalmente, especificar um variable_name para identificar a variável dentro do arquivo zip para carregar para a chave de entrada tensor. Se você não especificar um variable_name, o SavedModel CLI irá verificar que apenas um arquivo é incluído no arquivo zip e carregá-lo para a chave de entrada tensor especificado.

Quando o carregamento de um arquivo de picles, se não variable_name é especificado entre colchetes, o que quer que está dentro do arquivo picles será passado para a chave de entrada tensor especificado. Caso contrário, o SavedModel CLI irá assumir um dicionário é armazenado no arquivo de picles e será usado o valor correspondente ao variable_name.

--input_exprs

Para passar entradas através de expressões Python, especifique o --input_exprs opção. Isto pode ser útil para quando você não tem arquivos de dados em torno de mentir, mas ainda quero sanidade verificar o modelo com algumas entradas simples que correspondem a dtipo e forma do da modelo SignatureDef s. Por exemplo:

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

Além das expressões Python, você também pode passar funções numpy. Por exemplo:

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

(Note que o numpy módulo já está disponível para você como np .)

--input_examples

Para passar tf.train.Example como entradas, especificar o --input_examples opção. Para cada chave de entrada, leva-se uma lista de dicionário, onde cada dicionário é uma instância de tf.train.Example . As chaves do dicionário são os recursos e os valores são as listas de valores para cada recurso. Por exemplo:

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

Salvar saída

Por padrão, o SavedModel CLI grava a saída em stdout. Se um diretório é passado para --outdir opção, as saídas serão salvos como .npy arquivos com nomes de chaves tensor de saída sob o diretório dado.

Use --overwrite para sobrescrever arquivos de saída existentes.