Статьи TensorFlow | TensorFlow 2.x Сохранение и реконструкция модели на основе Keras

TensorFlow

"Введение«Очень важным шагом после обучения модели является сохранение информации о модели для последующего переобучения и онлайн-обслуживания. Помимо личного использования, сохраненный файл модели также можно отправить в TensorFlow Hub, чтобы другие могли легко разрабатывать и обучать на основе предварительно обученной модели.

Обзор модели Керас

общийKerasМодель обычно состоит из следующих частей:

  1. Структура или конфигурация модели: определяет слои, включенные в модель, и способ их соединения.

  2. Последовательность значений весовой матрицы: используется для записи состояния модели.

  3. Оптимизатор: используется для оптимизации функции потерь, которую можно скомпилировать в модели (compile) уточняется.

  4. Ряд потерь и метрик: используется для регулирования процесса обучения, как потери и метрики, указанные во время компиляции, так иadd_loss()а такжеadd_metric()Метод добавления потерь и метрик.

использоватьKeras APIВы можете сохранить все или некоторые из вышеперечисленных компонентов модели на диск.Keras APIПредусмотрены следующие три варианта:

  1. Сохраните все содержимое модели: обычно какTensorFlow SavedModelформат илиKeras H5Это наиболее часто используемый способ сохранения моделей.

  2. Сохраняется только структура модели: обычно какjsonсохранить в виде файла.

  3. Сохраняйте только значение матрицы весов модели: обычно в видеnumpyОн сохраняется в виде массива, который обычно используется при переобучении модели или трансферном обучении.

Построение модели и обучение

Перед сохранением модели нам нужно построить модель и обучить ее, чтобы потери и метрики модели достигли определенного состояния. Рассмотрим следующую простую модель, в которой используетсяFunctional APIпонял один3Слои глубоких нейронных сетей.

from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

inputs = keras.Input(shape=(784, ), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs, name='3_layer_mlp')
model.summary()

x_train, y_train = (
    np.random.random((60000, 784)),
    np.random.randint(10, size=(60000, 1)),
)
x_test, y_test = (
    np.random.random((10000, 784)),
    np.random.randint(10, size=(10000, 1)),
)

model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop())
history = model.fit(x_train, y_train, batch_size=64, epochs=1)

# Save predictions for future checks
predictions = model.predict(x_test)

После одного раунда итеративного обучения модели со случайными данными значение матрицы весов модели и состояние оптимизатора изменились.На этом этапе мы можем сохранить модель.Конечно, вы также можете сделать это перед обучением Модель сохраняется, но делать это особого смысла нет.

сохранить всю модель

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

Сохраненный файл модели содержит следующую информацию:

  1. Структура модели.

  2. Матрица весов для модели, представляющая собой информацию, полученную моделью во время обучения.

  3. Сборка модели (compile) Информация.

  4. Информация о состоянии оптимизатора для модели, позволяющая продолжить обучение с того места, где закончилось последнее обучение.

существуетKeras, ты можешь использоватьmodel.save()метод илиtf.keras.models.save_model()метод сохранения модели, используйтеtf.keras.models.load_model()метод для загрузки сохраненного файла модели и перестроения модели.

Формат файла модели можно сохранить в2вид, один дляTensorFlow SavedModelформат, другойKeras H5Формат, официально рекомендуемый к использованиюSavedModelформат сохранения модели, он жеmodel.save()Формат сохранения, используемый методом по умолчанию. может быть установленsave()параметры в методеformat='h5'или укажитеsave()Суффикс имени файла в методе.h5или.kerasперейти на использованиеH5Формат для сохранения модели.

Сохранено в формате SavedModel

использоватьSavedModelКод для сохранения и реконструкции модели в формате выглядит следующим образом:

# Export the model to a SavedModel
model.save('path_to_saved_model')

# Recreate the exact same model
new_model = keras.models.load_model('path_to_saved_model')

# Check that the state is preserved
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Note that the optimizer state is preserved as well:
# you can resume training where you left off.
new_model.fit(x_train, y_train, batch_size=64, epochs=1)

Выполнение приведенного выше кода создаст файл с именемpath_to_saved_modelкаталог, вся информация о модели будет сохранена в этом каталоге, структура каталога следующая:

.
├── assets
├── saved_model.pb
└── variables
    ├── variables.data-00000-of-00001
    └── variables.index

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

Сохранить в формате H5

Kerasтакже поддерживает использованиеH5Формат сохранения и реконструкции модели.Сохраненный файл содержит структуру модели, значение матрицы весов и информацию о компиляции.По сравнению сSavedModelФормат,H5Формат является более легкой альтернативой.

использоватьH5Код для сохранения и реконструкции модели в формате выглядит следующим образом:

# Save the model
model.save('path_to_my_model.h5')

# Recreate the exact same model purely from the file
new_model = keras.models.load_model('path_to_my_model.h5')

# Check that the state is preserved
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Note that the optimizer state is preserved as well:
# you can resume training where you left off.
new_model.fit(x_train, y_train, batch_size=64, epochs=1)

но использоватьH5Формат имеет свои ограничения по сравнению сSavedModel,H5Следующие два вида информации не могут быть сохранены в файле модели формата:

  1. Внешне добавленная информация об убытках и индикаторах: Passmodel.add_loss()метод иmodel.add_metric()Информация о потерях и метриках, добавленная методом, не сохраняется вH5в файле. Если вы хотите загрузить модели и продолжить использовать их в обучении, вам нужно добавить их снова с помощью описанного выше метода. Следует отметить, что вKerasпройти черезself.add_loss()илиself.add_metric()Добавленные потери и показателиH5Файл автоматически сохраняется.

  2. Информация вычислительного графика пользовательских объектов: таких как пользовательские слои (layers) не будет сохранен вH5в файле. В этот момент, если вы используетеH5Файл напрямую загружает модель и сообщаетValueError: Unknown layerОшибка.

Так что при сохранении всей модели, особенно для пользовательских (Subclassed) модель, нам лучше использоватьSavedModelформат для сохранения и реконструкции модели.

Кроме того, как видно из кода, посколькуmodel.save()Метод сохраняет всю информацию о модели, поэтому после перестроения модели вы можете продолжить обучение на основе исходной модели без каких-либо дополнительных настроек.

сохранить только структуру модели

Иногда нас может интересовать только структура модели, и мы не хотим сохранять такую ​​информацию, как значения веса модели и состояние оптимизатора. В этом случае мы можем использовать модель配置方法 (config), чтобы сохранить и перестроить структуру модели.

Sequential/Functional

заSequentialмодель иFunctional APIмодели, так как они в основном состоят из предопределенных слоев (layers), поэтому их информация о конфигурации существует в структурированной форме, что позволяет легко сохранить структуру модели. Мы можем использовать модельget_config()метод, чтобы получить конфигурацию модели, затем передатьfrom_config(config)метод восстановления модели.

  1. Для первого представленногоFunctional APIДля модели код сохранения и реконструкции ее структуры выглядит следующим образом:

    config = model.get_config()
    new_model = keras.Model.from_config(config)
    
    # Note that the model state is not preserved! We only saved the architecture.
    new_predictions = new_model.predict(x_test)
    assert abs(np.sum(predictions - new_predictions)) > 0.
    

    вget_config()Результат, возвращаемый методом, представляет собойpythonСловарь, который содержит всю информацию о структуре модели, поэтому ее можно легко реконструировать.pythonСодержание словаря следующее:

    {
        'name':
        '3_layer_mlp',
        'layers': [{
            'class_name': 'InputLayer',
            'config': {
                'batch_input_shape': (None, 784),
                'dtype': 'float32',
                'sparse': False,
                'ragged': False,
                'name': 'digits'
            },
            'name': 'digits',
            'inbound_nodes': []
        }, {
            'class_name': 'Dense',
            'config': {
                'name': 'dense_1',
                'trainable': True,
                'dtype': 'float32',
                'units': 64,
                'activation': 'relu',
                'use_bias': True,
                'kernel_initializer': {
                    'class_name': 'GlorotUniform',
                    'config': {
                        'seed': None
                    }
                },
                'bias_initializer': {
                    'class_name': 'Zeros',
                    'config': {}
                },
                'kernel_regularizer': None,
                'bias_regularizer': None,
                'activity_regularizer': None,
                'kernel_constraint': None,
                'bias_constraint': None
            },
            'name': 'dense_1',
            'inbound_nodes': [[['digits', 0, 0, {}]]]
        }, {
            'class_name': 'Dense',
            'config': {
                'name': 'dense_2',
                'trainable': True,
                'dtype': 'float32',
                'units': 64,
                'activation': 'relu',
                'use_bias': True,
                'kernel_initializer': {
                    'class_name': 'GlorotUniform',
                    'config': {
                        'seed': None
                    }
                },
                'bias_initializer': {
                    'class_name': 'Zeros',
                    'config': {}
                },
                'kernel_regularizer': None,
                'bias_regularizer': None,
                'activity_regularizer': None,
                'kernel_constraint': None,
                'bias_constraint': None
            },
            'name': 'dense_2',
            'inbound_nodes': [[['dense_1', 0, 0, {}]]]
        }, {
            'class_name': 'Dense',
            'config': {
                'name': 'predictions',
                'trainable': True,
                'dtype': 'float32',
                'units': 10,
                'activation': 'linear',
                'use_bias': True,
                'kernel_initializer': {
                    'class_name': 'GlorotUniform',
                    'config': {
                        'seed': None
                    }
                },
                'bias_initializer': {
                    'class_name': 'Zeros',
                    'config': {}
                },
                'kernel_regularizer': None,
                'bias_regularizer': None,
                'activity_regularizer': None,
                'kernel_constraint': None,
                'bias_constraint': None
            },
            'name': 'predictions',
            'inbound_nodes': [[['dense_2', 0, 0, {}]]]
        }],
        'input_layers': [['digits', 0, 0]],
        'output_layers': [['predictions', 0, 0]]
    }
    
  2. заSequentialДля модели пример кода для сохранения и перестроения структуры модели выглядит следующим образом:

    from tensorflow import keras
    
    model = keras.Sequential([keras.Input((32, )), keras.layers.Dense(1)])
    config = model.get_config()
    new_model = keras.Sequential.from_config(config)
    new_model.summary()
    

Чтобы облегчить постоянное хранение структуры модели, вы можете напрямую использоватьto_json()метод сериализует информацию модели какjsonЗатем формат сохраняется локально и может использоваться при перестроении модели.from_json()метод разбораjsonсодержимое файла для завершения восстановления. Пример кода выглядит следующим образом:

json_config = model.to_json()
new_model = keras.models.model_from_json(json_config)

Следует отметить, что поскольку сохраняется только структура модели, после перестроения модели новая модель не содержит информации о компиляции и информации о состоянии предыдущей модели, поэтому новую модель необходимо перекомпилировать для последующих операций обучения. .

Подклассы моделей и слоев

SubclassedСтруктурная информация модели находится в__init__иcallметод, они рассматриваются какpythonбайт-код, не имея возможности сериализовать его какjsonФормат. На момент написания этой статьи нет возможности直接Для пользовательских моделей (унаследованных отkeras.Model)из结构信息Выполнение операций сохранения и восстановления.

И дляSubclassedслой (унаследован отkeras.Layer) для сохранения и реконструкции структуры нам нужно переписать ееget_config()Методы иfrom_config()(необязательный) метод для достижения этого.

вget_config()Метод должен возвращатьjsonсериализованные словари для адаптацииKerasСтруктура модели и интерфейс сохранения модели.from_config(config)Методы класса должны быть основаны наconfigПараметр возвращает новый объект слоя.

четко определенный слойconfigметод, мы можем использоватьserializeиdeserializeспособ сериализации (сохранения) и десериализации (перестроения) информации о структуре слоя, обратите внимание, что при перестроении слоя вам нужно начать сcustom_objectsспособ предоставления информации о пользовательских слоях. Пример кода выглядит следующим образом:

from tensorflow import keras

class ThreeLayerMLP(keras.layers.Layer):
    def __init__(self, hidden_units):
        super().__init__()
        self.hidden_units = hidden_units
        self.dense_layers = [keras.layers.Dense(u) for u in hidden_units]

    def call(self, inputs):
        x = inputs
        for layer in self.dense_layers:
            x = layer(x)
        return x

    def get_config(self):
        return {"hidden_units": self.hidden_units}

layer = ThreeLayerMLP([64, 64, 10])
serialized_layer = keras.layers.serialize(layer)
print(serialized_layer)
new_layer = keras.layers.deserialize(
    serialized_layer,
    custom_objects={"ThreeLayerMLP": ThreeLayerMLP},
)

вserializeРезультат, возвращаемый методом, выглядит следующим образом:

{'class_name': 'ThreeLayerMLP', 'config': {'hidden_units': ListWrapper([64, 64, 10])}}

Для конструкций, построенных с использованием пользовательских слоев и пользовательских функцийFunctional APIмодели, либо с использованием вышеуказанногоserialize/deserializeметоды сохранения и восстановления структуры модели, в том числе с помощьюget_config/keras.Model.from_configметод для завершения этой операции сохранения и восстановления, но с помощьюkeras.utils.custom_object_scopeметод для указания пользовательского объекта. Пример кода выглядит следующим образом:

def custom_activation(x):
    return tf.nn.tanh(x)**2

inputs = keras.Input((64, ))
x = layer(inputs)
outputs = keras.layers.Activation(custom_activation)(x)
model = keras.Model(inputs, outputs)

config = model.get_config()
print(config)
custom_objects = {
    "ThreeLayerMLP": ThreeLayerMLP,
    "custom_activation": custom_activation
}
with keras.utils.custom_object_scope(custom_objects):
    new_model = keras.Model.from_config(config)
    new_model.summary()

В дополнение к вышеперечисленномуFunctional APIМодель также можно скопировать непосредственно в память, что в основном аналогично процессу получения конфигурации и последующего перестроения модели через конфигурацию. Пример кода выглядит следующим образом:

with keras.utils.custom_object_scope(custom_objects):
    new_model = keras.models.clone_model(model)
    new_model.summary()

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

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

  1. Вам нужно использовать модель только для прогнозирования: в этом случае вам не нужно продолжать обучение существующей модели, и нет необходимости сохранять информацию о компиляции модели и информацию о состоянии оптимизатора.

  2. Вы выполняете трансферное обучение: в этом случае вы обучаете новую модель, используя состояние (весовую матрицу) существующей модели, поэтому информация о компиляции существующей модели также не требуется.

существуетKeras, можно передать модельget_weights()иset_weights()методы получения и установки значений весовой матрицы. Код выглядит следующим образом:

weights = model.get_weights()  # Retrieves the state of the model.
model.set_weights(weights)  # Sets the state of the model.

модельget_weights()метод возвращаетnumpy arrayсостоит изlist, который записывает информацию обо всех весовых матрицах модели, а сохраненную информацию о весе можно использовать какset_weights(weights)Параметры метода используются для задания состояния новой модели, эту операцию также можно понимать как перенос веса между моделями.

В дополнение к переносу веса между похожими моделями операции переноса веса также могут выполняться между моделями с совместимыми архитектурами, например, мы можем получить предыдущийFunctionalИнформация о весовой матрице модели, а затем значение весовой матрицы передается черезset_weights()методы переносятся в соответствующиеSubclassedв модели. Однако следует отметить, что порядок, количество и размер весовых матриц в двух моделях должны быть согласованы. Пример кода выглядит следующим образом:

from tensorflow import keras
from tensorflow.keras import layers

class ThreeLayerMLP(keras.Model):
    def __init__(self, hidden_units):
        super().__init__()
        self.hidden_units = hidden_units
        self.dense_layers = [layers.Dense(u) for u in hidden_units]

    def call(self, inputs):
        x = inputs
        for layer in self.dense_layers:
            x = layer(x)
        return x

    def get_config(self):
        return {"hidden_units": self.hidden_units}

subclassed_model = ThreeLayerMLP([64, 64, 10])
subclassed_model(tf.ones((1, 784)))
subclassed_model.set_weights(functional_model.get_weights())

assert len(functional_model.weights) == len(subclassed_model.weights)
for a, b in zip(functional_model.weights, subclassed_model.weights):
  np.testing.assert_allclose(a.numpy(), b.numpy())

Кроме того, для слоя без состояния, поскольку он не содержит матрицы весов, он не меняет порядок и количество матриц весов в модели, поэтому даже если между разными моделями существуют дополнительные слои без состояния (например,Dropoutслой), они также могут иметь совместимые структуры моделей, а также могут использовать описанный выше метод для переноса весовой матрицы.

Если вы хотите сохранить информацию о матрице веса локально, вы можете использоватьsave_weights(fpath)способ локального сохранения, который можно использовать позжеload_weights(fpath)Метод загружает информацию о весе из локального файла, чтобы восстановить состояние модели. Пример кода выглядит следующим образом:

# Save weights to disk
model.save_weights('path_to_my_weights.h5')
new_model = ThreeLayerMLP([64, 64, 10])
new_model(tf.ones((1, 784)))
new_model.load_weights('path_to_my_weights.h5')

# Check that the state is preserved
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Note that the optimizer was not preserved.
new_model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop())
new_model.fit(x_train, y_train, batch_size=64, epochs=1)

Уведомление,save_weightsметоды могут быть сохранены какKeras HDF5файл формата, также может быть сохранен какTensorFlow Checkpointфайл формата. иmodel.saveметод аналогичен, он также содержит параметрsave_format, также есть два значения. По умолчаниюtfуказать использованиеCheckpointсохранение формата,h5указать использованиеHDF5сохранить формат. Если он не указан явно, он будет делать вывод на основе суффикса файла, заканчивающегося на.h5или.hdf5имена файлов заканчиваются наHDF5формат для сохранения.

можно использовать в комбинацииget_config()/from_config()иget_weights()/set_weights()перестроить модель и восстановить исходное состояние. Но с использованиемmodel.save()Таким образом, поскольку обучающая конфигурация модели и состояние оптимизатора не могут быть сохранены, необходимо повторно вызвать модель.compileметод для указания некоторой конфигурации, необходимой для процесса обучения. Код выглядит следующим образом:

config = model.get_config()
weights = model.get_weights()

new_model = keras.Model.from_config(config)
new_model.set_weights(weights)

# Check that the state is preserved
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Note that the optimizer was not preserved,
# so the model should be compiled anew before training
# (and the optimizer will start from a blank state).
new_model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop())
new_model.fit(x_train, y_train, batch_size=64, epochs=1)

TensorFlow Hub

TensorFlow Hubпредставляет собой репозиторий моделей и библиотеку функций для многоразового машинного обучения.TensorFlow HubМодели можно повторно использовать в различных учебных задачах (即迁移学习), что позволяет добиться хороших результатов, используя при обучении лишь небольшой набор данных, при этом улучшая способность модели к обобщению и ускоряя скорость обучения модели.

Когда мы реализуем более общую модель, мы можем отдать приоритет использованиюTensorFlow HubСуществующая модель предварительного обучения в модели и дальнейшее моделирование и обучение на основе модели, чтобы уменьшить объем кода программы и время обучения модели.

TensorFlow 2.xРекомендуемое использованиеSavedModelфайл в форматеTensorFlow HubДелитесь предварительно обученными моделями на .tensorflow_hubБиблиотека предоставляет классhub.KerasLayer, к которому можно получить доступ с удаленного или локальногоSavedModelПрочитайте информацию о модели из файла и начните сKeras LayerРеконструированная модель содержит информацию о предварительно обученной матрице весов.

использоватьhub.KerasLayerКод для загрузки модели и прогнозирования выглядит следующим образом:

import tensorflow_hub as hub

new_layer = hub.KerasLayer(
    'path_to_my_model',
    trainable=True,
    input_shape=[784],
    dtype=tf.float32,
)

# Check that the state is preserved
new_predictions = new_layer(x_test.astype('float32'))
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

Делая прогнозы, следите за тем, чтобы тип данных весовой матрицы в сохраненном файле модели соответствовал типу входных данных. существуетTensorFlow 2.x, тип данных файла модели определяется какtf.keras.backend.floatx()получить, по умолчаниюfloat32. в состоянии пройтиtf.keras.backend.set_floatx('float64')Измените тип данных модели или преобразуйте входные данные, чтобы избежать ошибок из-за несоответствия типов.

Используйте этот метод для загрузки модели и использованияkeras.models.load_modelЕсть несколько отличий от загрузки модели:

  1. С одной стороны, это по существуKeras Layer, поэтому не используйте некоторые методы модели, напримерsummaryметод.

  2. Во-вторых, его внутренняя структура была скрыта, поэтому его конкретный состав не может быть просмотрен (layers).

  3. В-третьих, при обучении его нужно использовать как слой модели, а для продолжения обучения можно создать новую модель.

существуетKerasиспользуется в кодеhub.KerasLayerКод для обучения следующий:

new_layer = hub.KerasLayer(
    'path_to_my_model',
    trainable=True,
    input_shape=[784],
    dtype=tf.float32,
)

new_model = tf.keras.Sequential([new_layer])
new_model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop())
new_model.fit(x_train, y_train, batch_size=64, epochs=1)

Как видно из кода, мы можем использоватьKeras Layerиспользовать то же самоеhub.KerasLayer. по умолчанию,hub.KerasLayerЗначение весовой матрицы в не поддается обучению, то есть остается неизменным в новом процессе обучения.Если вам нужно доработать исходную модель, вы можетеtrainableпараметр установлен наTrue.

Кроме того, если вам нужно повторно экспортировать доработанную модель, вы можете использовать различные методы, описанные выше, такие какmodel.save()илиtf.keras.models.save_model()Экспортируйте по мере необходимости.

Если вы хотите поделиться своей предварительно обученной моделью сTensorFlow Hub, вам сначала нужно экспортировать модель какSavedModelОтформатируйте файл, а затем отправьте его в Интернет для совместного использования модели. При экспорте файлов модели нужно быть внимательным.Если вы хотите поделиться всей моделью, вызовите модельsaveметод требуетinclude_optimizerпараметр установлен наFalse, то есть общая модель не может содержать информацию о состоянии оптимизатора; если вы хотите поделиться только определенной частью модели, вы можете извлечь часть для совместного использования перед построением модели и сохранить ее после обучения завершено. Пример кода выглядит следующим образом:

piece_to_share = tf.keras.Model(...)
full_model = tf.keras.Sequential([piece_to_share, ...])
full_model.fit(...)
piece_to_share.save(..., include_optimizer=False)

использованная литература

  1. Save and load Keras models
  2. SavedModels from TF Hub in TensorFlow 2