Guardando y Serializando Modelos con TensorFlow Keras

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

La primera parte de esta guia cubre el guardado y serialización para modelos secuenciales y modelos creados con la API funcional y para modelos secuenciales. Las APIs de guardado y serialización son exactamente las mismas para ambos tipos de modelos.

El guardado para las subclases personalizadas de Modelo se explica en la sección "Guardar modelos de subclases". Las APIs en este caso son ligeramente diferentes a las de los modelos secuenciales o funcionales.

Configurar

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf

tf.keras.backend.clear_session()  # Para restablecer fácilmente el estado del portátil.

Parte I: Guardar modelos secuenciales o modelos funcionales

Consideremos el siguiente modelo:

from tensorflow import keras
from tensorflow.keras import layers

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, activation='softmax', name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs, name='3_layer_mlp')
model.summary()
Model: "3_layer_mlp"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
digits (InputLayer)          [(None, 784)]             0         
_________________________________________________________________
dense_1 (Dense)              (None, 64)                50240     
_________________________________________________________________
dense_2 (Dense)              (None, 64)                4160      
_________________________________________________________________
predictions (Dense)          (None, 10)                650       
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________

Opcionalmente, entrenaremos este modelo, solo para que tenga valores de peso para guardarlos, así como un estado optimizador. Por supuesto, tambien puede guardar modelos que nunca ha entrenado, pero obviamente eso es menos interesante.

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255
x_test = x_test.reshape(10000, 784).astype('float32') / 255

model.compile(loss='sparse_categorical_crossentropy',
              optimizer=keras.optimizers.RMSprop())
history = model.fit(x_train, y_train,
                    batch_size=64,
                    epochs=1)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Train on 60000 samples
60000/60000 [==============================] - 3s 47us/sample - loss: 0.3208
# Guardar predicciones para futuras verificaciones
predictions = model.predict(x_test)

Modelo-Completo Guardando

Puede guardar un modelo creado con la API funcional en un solo archivo. Posteriormente, puede volver a crear el mismo modelo a partir de este archivo, incluso si ya no tiene acceso al codigo que creo el modelo.

Este archivo incluye:

  • Los modelos de arquitectura
  • Los valores de peso del modelo (que se aprendieron durante el entrenamiento)
  • La configuración de entrenamiento del modelo (lo que pasó a 'compilar'), si corresponde
  • El optimizador y su estado, si corresponde (esto le permite reiniciar el entrenamiento donde lo dejó)
# Guardar el Modelo
model.save('path_to_my_model.h5')

# Recrea exactamente el mismo modelo solo desde el archivo
new_model = keras.models.load_model('path_to_my_model.h5')
import numpy as np

# Verifique que el estado esté preservado
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Tenga en cuenta que el estado del optimizador también se conserva:
# puede reanudar el entrenamiento donde lo dejó.

Exportar a 'SavedModel'

Tambien puede exportar un modelo completo al formato 'SavedModel' de TensorFlow. 'SavedModel' es un formato de serialización independiente para objetos de Tensorflow, compatible con el servicio de TensorFlow y las implementaciones de TensorFlow que no sean Python.

# Exportar el modelo a 'SavedModel'
keras.experimental.export_saved_model(model, 'path_to_saved_model')

# Recrea exactamente el mismo modelo
new_model = keras.experimental.load_from_saved_model('path_to_saved_model')

# Verifique que el estado esté guardado
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Tenga en cuenta que el estado del optimizador también se conserva:
# puede reanudar el entrenamiento donde lo dejó.
WARNING:tensorflow:From <ipython-input-8-1d8bca516ff6>:2: export_saved_model (from tensorflow.python.keras.saving.saved_model_experimental) is deprecated and will be removed in a future version.
Instructions for updating:
Please use `model.save(..., save_format="tf")` or `tf.keras.models.save_model(..., save_format="tf")`.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow_core/python/ops/resource_variable_ops.py:1630: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow_core/python/saved_model/signature_def_utils_impl.py:253: build_tensor_info (from tensorflow.python.saved_model.utils_impl) is deprecated and will be removed in a future version.
Instructions for updating:
This function will only be available through the v1 compatibility library as tf.compat.v1.saved_model.utils.build_tensor_info or tf.compat.v1.saved_model.build_tensor_info.
INFO:tensorflow:Signatures INCLUDED in export for Classify: None
INFO:tensorflow:Signatures INCLUDED in export for Regress: None
INFO:tensorflow:Signatures INCLUDED in export for Predict: None
INFO:tensorflow:Signatures INCLUDED in export for Train: ['train']
INFO:tensorflow:Signatures INCLUDED in export for Eval: None
WARNING:tensorflow:Export includes no default signature!
INFO:tensorflow:No assets to save.
INFO:tensorflow:No assets to write.
INFO:tensorflow:Signatures INCLUDED in export for Classify: None
INFO:tensorflow:Signatures INCLUDED in export for Regress: None
INFO:tensorflow:Signatures INCLUDED in export for Predict: None
INFO:tensorflow:Signatures INCLUDED in export for Train: None
INFO:tensorflow:Signatures INCLUDED in export for Eval: ['eval']
WARNING:tensorflow:Export includes no default signature!
INFO:tensorflow:No assets to save.
INFO:tensorflow:No assets to write.
INFO:tensorflow:Signatures INCLUDED in export for Classify: None
INFO:tensorflow:Signatures INCLUDED in export for Regress: None
INFO:tensorflow:Signatures INCLUDED in export for Predict: ['serving_default']
INFO:tensorflow:Signatures INCLUDED in export for Train: None
INFO:tensorflow:Signatures INCLUDED in export for Eval: None
INFO:tensorflow:No assets to save.
INFO:tensorflow:No assets to write.
INFO:tensorflow:SavedModel written to: path_to_saved_model/saved_model.pb
WARNING:tensorflow:From <ipython-input-8-1d8bca516ff6>:5: load_from_saved_model (from tensorflow.python.keras.saving.saved_model_experimental) is deprecated and will be removed in a future version.
Instructions for updating:
The experimental save and load functions have been  deprecated. Please switch to `tf.keras.models.load_model`.

Los archivos 'SavedModel' que se crearon contienen:

  • Un punto de control TensorFlow que contiene los pesos del modelo.
  • Un prototipo 'SavedModel' que contiene el grafico subyacente de Tensorflow. Separar los graficos que se guardan para prediccion (servicio), capacitacion y evaluacion. Si el modelo no se compilo antes, solo el grafico de inferencia se exporta
  • La configuracion de arquitectura del modelo, si esta disponible.

Solo arquitectura de guardado

A veces, solo esta interesado en la arquitectura del modelo y no necesita guardar los valores de peso o el optimizador. En este caso, puede recuperar la "configuracion" del modelo mediante el metodo get_config (). La configuracion es un dict de Python que le permite recrear el mismo modelo, inicializado desde cero, sin ninguna de la información aprendida previamente durante el entrenamiento.

config = model.get_config()
reinitialized_model = keras.Model.from_config(config)

# ¡Tenga en cuenta que el estado del modelo no se conserva! Solo guardamos la arquitectura.
new_predictions = reinitialized_model.predict(x_test)
assert abs(np.sum(predictions - new_predictions)) > 0.
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.decay
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.learning_rate
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.momentum
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.rho
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-0.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-0.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-1.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-1.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-2.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-2.bias
WARNING:tensorflow:A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.iter
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.decay
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.learning_rate
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.momentum
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.rho
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-0.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-0.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-1.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-1.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-2.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-2.bias
WARNING:tensorflow:A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.

Alternativamente, puede usar 'to_json()' de 'from_json ()', que usa una cadena JSON para almacenar la configuracion en lugar de un 'dict' de Python. Esto es util para guardar la configuracion en el disco.

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

Guardando solo con pesos

A veces, solo le interesa el estado del modelo, sus valores de peso, y no la arquitectura. En este caso, puede recuperar los valores de pesos como una lista de matrices Numpy a traves de 'get_weights()', y establecer el estado del modelo a través de 'set_weights':

weights = model.get_weights()  # Recupera el estado del modelo.
model.set_weights(weights)  # Establece el estado del modelo.

Puede combinar get_config()/from_config() y get_weights()/set_weights() para recrear su modelo en el mismo estado. Sin embargo, a diferencia de model.save(), esto no incluira la configuracion de entrenamiento y el optimizado tendría que volver a llamar a compile() antes de usar el modelo para el entrenamiento.

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

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

# Verifique que el estado esté preservado
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Tenga en cuenta que el optimizador no se conserva
# entonces el modelo debe compilarse nuevamente antes de entrenar
# (y el optimizador comenzará desde un estado en blanco).

La alternativa de guardar en disco a 'get_weights()' y 'set_weights(weights)' es 'save_weights(fpath)' y 'load_weights(fpath)'.

Aquí hay un ejemplo que guarda en el disco:

# Guardar configuración JSON en el disco
json_config = model.to_json()
with open('model_config.json', 'w') as json_file:
    json_file.write(json_config)
# Guardar pesos en el disco
model.save_weights('path_to_my_weights.h5')

# Recargue el modelo de los 2 archivos que guardamos
with open('model_config.json') as json_file:
    json_config = json_file.read()
new_model = keras.models.model_from_json(json_config)
new_model.load_weights('path_to_my_weights.h5')

# Verifique que el estado esté preservado
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# Tenga en cuenta que el optimizador no se conservo.

Pero recuerde que la forma más simple y recomendada es solo esto:

model.save('path_to_my_model.h5')
del model
model = keras.models.load_model('path_to_my_model.h5')

Weights-only Guardando en formato 'SavedModel'

Tenga en cuenta que 'save_weights' puede crear archivos en el formato Keras HDF5 o en el formato TensorFlow 'SavedModel'. El formato se infiere de la extension de archivo que proporciona: si es ".h5" o ".keras", el marco utiliza el formato Keras HDF5. Cualquier otra cosa por defecto es 'SavedModel'.

model.save_weights('path_to_my_tf_savedmodel')

Para una total explicidad, el formato se puede pasar explicitamente a traves del argumento 'save_format', que puede tomar el valor "tf" o "h5":

model.save_weights('path_to_my_tf_savedmodel', save_format='tf')

Guardar modelos subclasificados

Los modelos secuenciales y los modelos funcionales son estructuras de datos que representan un DAG de capas. Como tal, se pueden serializar y deserializar de forma segura.

Un modelo subclasificado difiere en que no es una estructura de datos, es una pieza de código. La arquitectura del modelo se define a través del cuerpo del método de llamada. Esto significa que la arquitectura del modelo no se puede serializar de forma segura. Para cargar un modelo, deberá tener acceso al código que lo creó (el código de la subclase de modelo). Alternativamente, podría estar serializando este código como bytecode (por ejemplo, mediante pickling), pero eso no es seguro y, en general, no es portátil.

Para obtener más información sobre estas diferencias, vea el artículo "¿Qué son las API simbólicas e imperativas en TensorFlow 2.0?".

Consideremos el siguiente modelo subclasificado, que sigue la misma estructura que el modelo de la primera seccion:

class ThreeLayerMLP(keras.Model):

  def __init__(self, name=None):
    super(ThreeLayerMLP, self).__init__(name=name)
    self.dense_1 = layers.Dense(64, activation='relu', name='dense_1')
    self.dense_2 = layers.Dense(64, activation='relu', name='dense_2')
    self.pred_layer = layers.Dense(10, activation='softmax', name='predictions')

  def call(self, inputs):
    x = self.dense_1(inputs)
    x = self.dense_2(x)
    return self.pred_layer(x)

def get_model():
  return ThreeLayerMLP(name='3_layer_mlp')

model = get_model()

En primer lugar, un modelo subclasificado que nunca se ha utilizado no se puede guardar..

Esto se debe a que es necesario invocar un modelo subclasificado en algunos datos para crear sus pesos.

Hasta que se haya llamado al modelo, no conoce la forma y el tipo de datos de entrada que debería ser esperando, y por lo tanto no puede crear sus variables de peso. Puede recordar que en el modelo funcional de la primera sección, la forma y el tipo de las entradas se especificaron de antemano (a través de 'keras.Input (...)'), por eso los modelos funcionales tienen un estado tan pronto como Estás instanciado.

Vamos a entrenar el modelo para darle un estado:

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255
x_test = x_test.reshape(10000, 784).astype('float32') / 255

model.compile(loss='sparse_categorical_crossentropy',
              optimizer=keras.optimizers.RMSprop())
history = model.fit(x_train, y_train,
                    batch_size=64,
                    epochs=1)
Train on 60000 samples
21248/60000 [=========>....................] - ETA: 1s - loss: 0.4792WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.iter
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.decay
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.learning_rate
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.momentum
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.rho
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-0.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-0.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-1.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-1.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-2.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'rms' for (root).layer_with_weights-2.bias
WARNING:tensorflow:A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.
60000/60000 [==============================] - 3s 42us/sample - loss: 0.3088

La forma recomendada de guardar un modelo subclasificado es usar 'save_weights' para crear un punto de control TensorFlow 'SavedModel', que contendra el valor de todas las variables asociadas con el modelo:

  • Los pesos de las capas
  • El estado del optimizador
  • Cualquier variable asociada con métricas de modelo con estado (si las hay)
model.save_weights('path_to_my_weights', save_format='tf')
# Guardar predicciones para futuras verificaciones
predictions = model.predict(x_test)
# También guarde la pérdida en el primer lote
# para luego afirmar que el estado del optimizador fue preservado
first_batch_loss = model.train_on_batch(x_train[:64], y_train[:64])

Para restaurar su modelo, necesitará acceso al codigo que creó el objeto modelo.

Tenga en cuenta que para restaurar el estado del optimizador y el estado de cualquier métrica con estado, debe compila el modelo (con los mismos argumentos que antes) y llámalo con algunos datos antes de llamar a 'load_weights':

# Recrea el modelo
new_model = get_model()
new_model.compile(loss='sparse_categorical_crossentropy',
                  optimizer=keras.optimizers.RMSprop())

# Esto inicializa las variables utilizadas por los optimizadores,
# así como cualquier variable métrica con estado
new_model.train_on_batch(x_train[:1], y_train[:1])

# Cargue el estado del modelo anterior.
new_model.load_weights('path_to_my_weights')

# Compruebe que se ha conservado el estado del modelo.
new_predictions = new_model.predict(x_test)
np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)

# El estado del optimizador también se conserva,
# para que pueda reanudar el entrenamiento donde lo dejó
new_first_batch_loss = new_model.train_on_batch(x_train[:64], y_train[:64])
assert first_batch_loss == new_first_batch_loss

You've reached the end of this guide! This covers everything you need to know about saving and serializing models with tf.keras in TensorFlow 2.0.