Использование формата SavedModel

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

SavedModel содержит полную программу TensorFlow, включая обученные параметры (например, tf.Variable s) и вычисления. Для запуска не требуется исходный код построения модели, что делает его полезным для совместного использования или развертывания с помощью TFLite , TensorFlow.js , TensorFlow Serving или TensorFlow Hub .

Вы можете сохранить и загрузить модель в формате SavedModel, используя следующие API:

Создание SavedModel из Keras

Для краткого ознакомления в этом разделе экспортируется предварительно обученная модель Keras и с ней обслуживаются запросы на классификацию изображений. Остальная часть руководства будет содержать детали и обсуждать другие способы создания 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

png

Вы будете использовать изображение Грейс Хоппер в качестве работающего примера и предварительно обученную модель классификации изображений Keras, так как она проста в использовании. Пользовательские модели также работают и подробно рассматриваются позже.

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

Верхний прогноз для этого изображения — «военная форма».

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

Путь сохранения следует соглашению, используемому TensorFlow Serving, где последний компонент пути (здесь 1/ ) является номером версии вашей модели — это позволяет таким инструментам, как Tensorflow Serving, рассуждать об относительной свежести.

Вы можете загрузить SavedModel обратно в Python с помощью tf.saved_model.load и посмотреть, как классифицируется изображение адмирала Хоппера.

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

Импортированные подписи всегда возвращают словари. Чтобы настроить имена подписей и ключи выходного словаря, см. раздел Указание подписей во время экспорта .

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

Запуск вывода из SavedModel дает тот же результат, что и исходная модель.

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

Запуск SavedModel в TensorFlow Serving

SavedModels можно использовать из Python (подробнее об этом ниже), но в производственных средах обычно используется выделенная служба для логического вывода без запуска кода Python. Это легко настроить из SavedModel с помощью TensorFlow Serving.

См. руководство TensorFlow Serving REST для примера сквозного обслуживания tensorflow.

Формат SavedModel на диске

SavedModel — это каталог, содержащий сериализованные подписи и состояние, необходимое для их запуска, включая значения переменных и словари.

ls {mobilenet_save_path}
assets  saved_model.pb  variables

В файле saved_model.pb хранится фактическая программа или модель TensorFlow и набор именованных сигнатур, каждая из которых идентифицирует функцию, которая принимает тензорные входные данные и создает тензорные выходные данные.

SavedModels может содержать несколько вариантов модели (несколько v1.MetaGraphDefs , определяемых флагом --tag_set для saved_model_cli ), но это бывает редко. API-интерфейсы, которые создают несколько вариантов модели, включают tf.Estimator.experimental_export_all_saved_models и tf.saved_model.Builder в tf.saved_model.Builder 1.x.

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"

Каталог variables содержит стандартную обучающую контрольную точку (см. руководство по обучающим контрольным точкам ).

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

Каталог assets содержит файлы, используемые графом TensorFlow, например текстовые файлы, используемые для инициализации словарных таблиц. В данном примере он не используется.

SavedModels может иметь каталог assets.extra для любых файлов, не используемых графом TensorFlow, например, информацию для потребителей о том, что делать с SavedModel. Сам TensorFlow не использует этот каталог.

Сохранение пользовательской модели

tf.saved_model.save поддерживает сохранение объектов tf.Module и его подклассов, таких как tf.keras.Layer и tf.keras.Model .

Давайте рассмотрим пример сохранения и восстановления 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()

Когда вы сохраняете tf.Module , все атрибуты tf.Variable , методы, декорированные tf.function , и tf.Module , найденные в результате рекурсивного обхода, сохраняются. (Подробнее об этом рекурсивном обходе см. в учебнике Checkpoint .) Однако все атрибуты, функции и данные Python теряются. Это означает, что при сохранении tf.function код Python не сохраняется.

Если код Python не сохранен, как SavedModel узнает, как восстановить функцию?

Вкратце, tf.function работает, отслеживая код Python для создания ConcreteFunction (вызываемая оболочка вокруг tf.Graph ). При сохранении tf.function вы действительно сохраняете кэш ConcreteFunctions tf.function .

Чтобы узнать больше о взаимосвязи между tf.function и ConcreteFunctions, см. руководство по 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

Загрузка и использование пользовательской модели

Когда вы загружаете SavedModel в Python, все атрибуты tf.Variable , методы, декорированные tf.function , и tf.Module s восстанавливаются в той же структуре объекта, что и исходный сохраненный 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

Поскольку код Python не сохраняется, вызов tf.function с новой входной подписью завершится ошибкой:

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

Базовая тонкая настройка

Доступны переменные объекты, и вы можете выполнить резервное копирование через импортированные функции. Этого достаточно для тонкой настройки (т.е. переобучения) SavedModel в простых случаях.

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

Общая тонкая настройка

SavedModel от Keras предоставляет больше деталей , чем простой __call__ , для решения более сложных случаев тонкой настройки. TensorFlow Hub рекомендует предоставить следующие из них, если это применимо, в совместно используемых SavedModels для тонкой настройки:

  • Если в модели используется отсев или другой метод, в котором прямой проход отличается между обучением и выводом (например, пакетная нормализация), метод __call__ принимает необязательный аргумент training= со значением Python, который по умолчанию имеет значение False , но может быть установлен на True .
  • Рядом с атрибутом __call__ находятся .variable и .trainable_variable с соответствующими списками переменных. Переменная, которая изначально была обучаемой, но должна быть заморожена во время тонкой настройки, исключена из .trainable_variables .
  • Для таких фреймворков, как Keras, которые представляют регуляризаторы веса как атрибуты слоев или подмоделей, также может быть атрибут .regularization_losses . Он содержит список функций с нулевым аргументом, значения которых предназначены для добавления к общей потере.

Возвращаясь к первоначальному примеру MobileNet, вы можете увидеть некоторые из них в действии:

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

Указание подписи при экспорте

Такие инструменты, как TensorFlow Serving и saved_model_cli могут взаимодействовать с SavedModels. Чтобы помочь этим инструментам определить, какие ConcreteFunctions использовать, вам необходимо указать обслуживающие подписи. tf.keras.Model автоматически указывают обслуживающие подписи, но вам придется явно объявлять обслуживающие подписи для наших пользовательских модулей.

По умолчанию в пользовательском tf.Module не объявляются подписи.

assert len(imported.signatures) == 0

Чтобы объявить обслуживающую подпись, укажите ConcreteFunction с помощью signatures kwarg. При указании одной подписи ее ключ подписи будет 'serving_default' , который сохраняется как константа 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']

Чтобы экспортировать несколько подписей, передайте словарь ключей подписи в ConcreteFunctions. Каждый ключ подписи соответствует одной 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']

По умолчанию имена выходных тензоров довольно общие, например output_0 . Чтобы управлять именами выходов, измените tf.function , чтобы она возвращала словарь, который сопоставляет имена выходов с выходами. Имена входных данных получены из имен аргументов функций 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')}

Загрузите SavedModel в C++

Версия загрузчика SavedModel на C++ предоставляет API для загрузки SavedModel из пути, позволяя использовать SessionOptions и RunOptions. Вы должны указать теги, связанные с загружаемым графиком. Загруженная версия SavedModel называется SavedModelBundle и содержит MetaGraphDef и сеанс, в котором он загружается.

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

Подробная информация об интерфейсе командной строки SavedModel

Вы можете использовать интерфейс командной строки SavedModel (CLI) для проверки и выполнения SavedModel. Например, вы можете использовать интерфейс командной строки для проверки SignatureDef модели. Интерфейс командной строки позволяет быстро подтвердить, что входной тип тензора и форма соответствуют модели. Кроме того, если вы хотите протестировать свою модель, вы можете использовать интерфейс командной строки для проверки работоспособности, передав образцы входных данных в различных форматах (например, выражения Python), а затем извлекая выходные данные.

Установите интерфейс командной строки SavedModel

Вообще говоря, вы можете установить TensorFlow одним из следующих двух способов:

  • Установив предварительно созданный двоичный файл TensorFlow.
  • Создавая TensorFlow из исходного кода.

Если вы установили TensorFlow через предварительно созданный двоичный файл TensorFlow, CLI SavedModel уже установлен в вашей системе по пути bin/saved_model_cli .

Если вы собрали TensorFlow из исходного кода, вы должны запустить следующую дополнительную команду для сборки saved_model_cli :

$ bazel build tensorflow/python/tools:saved_model_cli

Обзор команд

Интерфейс командной строки SavedModel поддерживает следующие две команды для SavedModel:

  • show , который показывает вычисления, доступные из SavedModel.
  • run , который запускает вычисления из SavedModel.

show команду

SavedModel содержит один или несколько вариантов модели (технически, v1.MetaGraphDef s), идентифицируемых их наборами тегов. Чтобы обслуживать модель, вы можете задаться вопросом, какие типы SignatureDef есть в каждом варианте модели и каковы их входы и выходы. Команда show позволяет просматривать содержимое SavedModel в иерархическом порядке. Вот синтаксис:

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

Например, следующая команда показывает все доступные наборы тегов в SavedModel:

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

Следующая команда показывает все доступные ключи SignatureDef для набора тегов:

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

Если в наборе тегов несколько тегов, вы должны указать все теги, разделив каждый тег запятой. Например:

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

Чтобы отобразить все входные и выходные данные TensorInfo для определенного SignatureDef , передайте ключ SignatureDef в параметр signature_def . Это очень полезно, когда вы хотите узнать значение ключа тензора, dtype и форму входных тензоров для последующего выполнения графа вычислений. Например:

$ 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

Чтобы отобразить всю доступную информацию в SavedModel, используйте параметр --all . Например:

$ 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 команду

Вызовите команду run , чтобы запустить вычисление графика, передав входные данные и затем отобразив (и, возможно, сохранив) выходные данные. Вот синтаксис:

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]

Команда run предоставляет следующие три способа передачи входных данных в модель:

  • --inputs позволяет передавать numpy ndarray в файлы.
  • --input_exprs позволяет передавать выражения Python.
  • --input_examples позволяет передать tf.train.Example .

--inputs

Чтобы передать входные данные в файлах, укажите параметр --inputs , который принимает следующий общий формат:

--inputs <INPUTS>

где INPUTS имеет один из следующих форматов:

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

Вы можете передать несколько INPUTS . Если вы передаете несколько входных данных, используйте точку с запятой для разделения каждого из входов .

saved_model_cli использует numpy.load для загрузки имени файла . Имя файла может быть в любом из следующих форматов:

  • .npy
  • .npz
  • формат рассола

Файл .npy всегда содержит пустой ndarray. Поэтому при загрузке из .npy файла содержимое будет напрямую привязано к указанному входному тензору. Если вы укажете имя_переменной в этом файле .npy , имя_переменной будет проигнорировано и будет выдано предупреждение.

При загрузке из .npz (zip) вы можете дополнительно указать имя_переменной , чтобы идентифицировать переменную в zip-файле для загрузки для входного тензорного ключа. Если вы не укажете variable_name , интерфейс командной строки SavedModel проверит, включен ли в zip-файл только один файл, и загрузит его для указанного входного тензорного ключа.

При загрузке из файла рассола, если в квадратных скобках не указано variable_name , все, что находится внутри файла рассола, будет передано указанному входному тензорному ключу. В противном случае интерфейс командной строки SavedModel будет считать, что словарь хранится в файле рассола, и будет использоваться значение, соответствующее имени переменной .

--input_exprs

Чтобы передать входные данные через выражения Python, укажите параметр --input_exprs . Это может быть полезно, когда у вас нет файлов данных, но вы все же хотите проверить работоспособность модели с помощью некоторых простых входных данных, которые соответствуют dtype и форме SignatureDef модели. Например:

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

В дополнение к выражениям Python вы также можете передавать функции numpy. Например:

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

(Обратите внимание, что модуль numpy уже доступен вам как np .)

--input_examples

Чтобы передать tf.train.Example в качестве входных данных, укажите параметр --input_examples . Для каждого входного ключа он принимает список словарей, где каждый словарь является экземпляром tf.train.Example . Ключи словаря — это функции, а значения — это списки значений для каждой функции. Например:

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

Сохранить вывод

По умолчанию интерфейс командной строки SavedModel записывает выходные данные в стандартный вывод. Если каталог передается параметру --outdir , выходные данные будут сохранены в виде файлов .npy , названных в честь выходных тензорных ключей в данном каталоге.

Используйте --overwrite , чтобы перезаписать существующие выходные файлы.