Veja no TensorFlow.org | Executar no Google Colab | Ver fonte no GitHub | Baixar caderno |
Um SavedModel contém um programa TensorFlow completo, incluindo parâmetros treinados (ou seja, tf.Variable
s) e computação. Ele não exige que o código de construção do modelo original seja executado, o que o torna útil para compartilhamento ou implantação com TFLite , TensorFlow.js , TensorFlow Serving ou TensorFlow Hub .
Você pode salvar e carregar um modelo no formato SavedModel usando as seguintes APIs:
- API
tf.saved_model
de baixo nível. Este documento descreve como usar essa API em detalhes.- Salvar:
tf.saved_model.save(model, path_to_dir)
- Carregar:
model = tf.saved_model.load(path_to_dir)
- Salvar:
- API
tf.keras.Model
de alto nível. Consulte o guia de salvar e serializar keras . - Se você deseja apenas salvar/carregar pesos durante o treinamento, consulte o guia de pontos de verificação .
Criando um SavedModel do 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.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
Você usará uma imagem de Grace Hopper como exemplo de execução e um modelo de classificação de imagem pré-treinado da Keras, pois é fácil de usar. 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 principal previsão para esta imagem é "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
O caminho de salvamento segue uma convenção usada pelo TensorFlow Serving, em que o último componente do caminho ( 1/
aqui) é um número de versão para o seu modelo - ele permite que ferramentas como o Tensorflow Serving raciocinem sobre a atualização relativa.
Você pode carregar o SavedModel de volta ao Python com tf.saved_model.load
e ver como a imagem do Almirante Hopper é classificada.
loaded = tf.saved_model.load(mobilenet_save_path)
print(list(loaded.signatures.keys())) # ["serving_default"]
['serving_default']
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 que o 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 na veiculação do TensorFlow
Os SavedModels podem ser usados no Python (mais sobre isso abaixo), 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.
Consulte o tutorial REST de veiculação do TensorFlow para obter um exemplo de veiculação do tensorflow de ponta a ponta.
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
O arquivo saved_model.pb
armazena o programa TensorFlow real, ou modelo, e um conjunto de assinaturas nomeadas, cada uma identificando uma função que aceita entradas de tensor e produz saídas de tensor.
Os SavedModels podem conter várias variantes do modelo (vários v1.MetaGraphDefs
, identificados com o sinalizador --tag_set
para saved_model_cli
), mas isso é raro. As APIs que criam várias variantes de um modelo incluem tf.Estimator.experimental_export_all_saved_models
e no 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"
O diretório de variables
contém um ponto de verificação de treinamento padrão (consulte o guia para pontos de verificação de treinamento ).
ls {mobilenet_save_path}/variables
variables.data-00000-of-00001 variables.index
O diretório de assets
contém arquivos usados pelo gráfico do TensorFlow, por exemplo, arquivos de texto usados para inicializar tabelas de vocabulário. Ele não é usado neste exemplo.
SavedModels pode ter um diretório assets.extra
para qualquer arquivo não usado pelo gráfico do TensorFlow, por exemplo, informações para 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 salvar objetos tf.Module
e suas subclasses, como tf.keras.Layer
e tf.keras.Model
.
Vejamos um exemplo de como 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
, quaisquer atributos tf.Variable
, métodos tf.function
-decorated e tf.Module
s encontrados via travessia recursiva são salvos. (Consulte o tutorial Checkpoint para saber mais sobre essa travessia recursiva.) No entanto, todos os atributos, funções e dados do Python são perdidos. Isso 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 uma ConcreteFunction (um wrapper que pode ser chamado em torno tf.Graph
). Ao salvar um tf.function
, você está realmente salvando o cache do tf.function
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/tmpbf9fpzwt/module_no_signatures/assets
Carregando e usando um modelo personalizado
Quando você carrega um SavedModel em Python, todos os atributos tf.Variable
, métodos tf.function
-decorated e tf.Module
s são restaurados na mesma estrutura de objeto que o tf.Module
salvo original.
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
Como nenhum código Python é salvo, chamar um tf.function
com uma nova assinatura de entrada 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'),), {})].
Afinação básica
Objetos variáveis estão disponíveis e você pode fazer backprop por meio de funções importadas. Isso é suficiente para ajustar (ou seja, retreinar) 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
Afinação geral
Um SavedModel da Keras fornece mais detalhes do que um simples __call__
para tratar de 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 o modelo usar dropout ou outra técnica na qual a passagem direta difere entre treinamento e inferência (como normalização em lote), o método
__call__
recebe um argumentotraining=
opcional com valor Python que assume como padrãoFalse
, mas pode ser definido comoTrue
. - Ao lado do atributo
__call__
estão os atributos.variable
e.trainable_variable
com as respectivas listas de variáveis. Uma variável originalmente treinável, mas destinada a ser congelada durante o ajuste fino, é omitida de.trainable_variables
. - Para frameworks como Keras que representam os regularizadores de peso como atributos de camadas ou submodelos, também pode haver um atributo
.regularization_losses
. 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 Serving e saved_model_cli
podem interagir com SavedModels. Para ajudar essas ferramentas a determinar quais ConcreteFunctions usar, você precisa especificar as assinaturas de serviço. tf.keras.Model
s especificam automaticamente as assinaturas de serviço, mas você terá que declarar explicitamente uma assinatura de serviço para nossos módulos personalizados.
Por padrão, nenhuma assinatura é declarada em um tf.Module
personalizado.
assert len(imported.signatures) == 0
Para declarar uma assinatura de serviço, especifique uma ConcreteFunction usando as signatures
kwarg. Ao especificar uma única assinatura, sua chave de assinatura será 'serving_default'
, que é salva 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/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 várias assinaturas, passe um dicionário de chaves de assinatura para ConcreteFunctions. Cada chave de assinatura corresponde a uma 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/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']
Por padrão, os nomes dos tensores de saída são bastante genéricos, como output_0
. Para controlar os nomes das saídas, modifique seu tf.function
para retornar um dicionário que mapeie os nomes das saídas para as 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/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')}
Carregar um SavedModel em C++
A versão C++ do carregador SavedModel fornece uma API para carregar um SavedModel de um caminho, enquanto permite SessionOptions e RunOptions. Você deve especificar as tags associadas ao gráfico a ser carregado. A versão carregada do SavedModel é chamada de SavedModelBundle e contém o MetaGraphDef e a sessão na qual ele é carregado.
const string export_dir = ...
SavedModelBundle bundle;
...
LoadSavedModel(session_options, run_options, export_dir, {kSavedModelTagTrain},
&bundle);
Detalhes da interface de linha de comando SavedModel
Você pode usar a interface de linha de comando (CLI) SavedModel para inspecionar e executar um SavedModel. Por exemplo, você pode usar a CLI para inspecionar os SignatureDef
s do modelo. A CLI permite que você confirme rapidamente se o tipo e a forma do tensor de entrada correspondem ao modelo. Além disso, se você quiser testar seu modelo, poderá usar a CLI para fazer uma verificação de integridade passando entradas de amostra em vários formatos (por exemplo, expressões Python) e, em seguida, buscando a saída.
Instale a CLI SavedModel
Em termos gerais, você pode instalar o TensorFlow de uma das duas maneiras a seguir:
- Ao instalar um binário TensorFlow pré-criado.
- Construindo o TensorFlow a partir do código-fonte.
Se você instalou o TensorFlow por meio de um binário do TensorFlow pré-criado, a CLI SavedModel já está instalada em seu sistema em pathname bin/saved_model_cli
.
Se você criou o TensorFlow a partir do código-fonte, deverá executar o seguinte comando adicional para compilar o saved_model_cli
:
$ bazel build tensorflow/python/tools:saved_model_cli
Visão geral dos comandos
A CLI SavedModel suporta os dois comandos a seguir em um SavedModel:
-
show
, que mostra os cálculos disponíveis de um SavedModel. -
run
, que executa um cálculo de um SavedModel.
comando show
Um SavedModel contém uma ou mais variantes de modelo (tecnicamente, v1.MetaGraphDef
s), identificadas por seus conjuntos de tags. Para servir um modelo, você pode se perguntar que tipo de SignatureDef
s estão em cada variante de modelo e quais são suas entradas e saídas. O comando show
permite examinar o conteúdo do SavedModel em ordem hierárquica. Aqui está a sintaxe:
usage: saved_model_cli show [-h] --dir DIR [--all]
[--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]
Por exemplo, o comando a seguir 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
O comando a seguir mostra todas as chaves SignatureDef
disponíveis para um conjunto de tags:
$ 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árias tags no conjunto de tags, você deverá especificar todas as tags, cada tag separada 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 do TensorInfo para um SignatureDef
específico, passe a chave SignatureDef
para a opção signature_def
. Isso é muito útil quando você deseja saber o valor da chave do tensor, dtype 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 no SavedModel, use a opção --all
. 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
Invoque o comando run
para executar um cálculo gráfico, passando entradas e exibindo (e opcionalmente salvando) as saídas. Aqui está 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 comando run
fornece as três maneiras a seguir de passar entradas para o modelo:
-
--inputs
opção permite que você passe numpy ndarray em arquivos. - A opção
--input_exprs
permite passar expressões Python. - A opção
--input_examples
permite que você passetf.train.Example
.
--inputs
Para passar dados de entrada em arquivos, especifique a opção --inputs
, que tem o seguinte formato geral:
--inputs <INPUTS>
onde INPUTS é um dos seguintes formatos:
-
<input_key>=<filename>
-
<input_key>=<filename>[<variable_name>]
Você pode passar várias INPUTS . Se você passar várias entradas, use um ponto e vírgula para separar cada uma das INPUTS .
saved_model_cli
usa 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
Um arquivo .npy
sempre contém um numpy ndarray. Portanto, ao carregar de um arquivo .npy
, o conteúdo será atribuído diretamente ao tensor de entrada especificado. Se você especificar um nome_variável com esse arquivo .npy
, o nome_variável será ignorado e um aviso será emitido.
Ao carregar de um .npz
(zip), você pode opcionalmente especificar um variable_name para identificar a variável dentro do arquivo zip a ser carregada para a chave do tensor de entrada. Se você não especificar um variable_name , a CLI SavedModel verificará se apenas um arquivo está incluído no arquivo zip e o carregará para a chave de tensor de entrada especificada.
Ao carregar de um arquivo pickle, se nenhum variable_name
for especificado entre colchetes, o que estiver dentro do arquivo pickle será passado para a chave tensora de entrada especificada. Caso contrário, a CLI SavedModel assumirá que um dicionário está armazenado no arquivo pickle e o valor correspondente ao nome_da_variável será usado.
--input_exprs
Para passar entradas por meio de expressões Python, especifique a opção --input_exprs
. Isso pode ser útil quando você não tem arquivos de dados espalhados, mas ainda deseja verificar a sanidade do modelo com algumas entradas simples que correspondem ao dtype e à forma dos SignatureDef
s do modelo. 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))`
(Observe que o módulo numpy
já está disponível para você como np
.)
--input_examples
Para passar tf.train.Example
como entradas, especifique a opção --input_examples
. Para cada chave de entrada, leva 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, a CLI SavedModel grava a saída em stdout. Se um diretório for passado para a opção --outdir
, as saídas serão salvas como arquivos .npy
nomeados após as chaves do tensor de saída no diretório fornecido.
Use --overwrite
para substituir os arquivos de saída existentes.