Cette page a été traduite par l'API Cloud Translation.
Switch to English

Enregistrer et charger des modèles Keras

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher la source sur GitHub Télécharger le carnet

introduction

Un modèle Keras se compose de plusieurs composants:

  • Une architecture, ou configuration, qui spécifie quelles couches le modèle contient et comment elles sont connectées.
  • Un ensemble de valeurs de poids ("l'état du modèle").
  • Un optimiseur (défini en compilant le modèle).
  • Un ensemble de pertes et de métriques (définies en compilant le modèle ou en appelant add_loss() ou add_metric() ).

L'API Keras permet d'enregistrer ces éléments sur le disque en une seule fois, ou d'en enregistrer uniquement certains de manière sélective:

  • Tout enregistrer dans une seule archive au format TensorFlow SavedModel (ou dans l'ancien format Keras H5). C'est la pratique courante.
  • Enregistrement de l'architecture / de la configuration uniquement, généralement sous forme de fichier JSON.
  • Enregistrement des valeurs de poids uniquement. Ceci est généralement utilisé lors de la formation du modèle.

Jetons un coup d'œil à chacune de ces options: quand utiliseriez-vous l'une ou l'autre? Comment travaillent-ils?

La réponse courte à l'enregistrement et au chargement

Si vous ne disposez que de 10 secondes pour lire ce guide, voici ce que vous devez savoir.

Enregistrement d'un modèle Keras:

 model = ...  # Get model (Sequential, Functional Model, or Model subclass)
model.save('path/to/location')
 

Rechargement du modèle:

 from tensorflow import keras
model = keras.models.load_model('path/to/location')
 

Maintenant, regardons les détails.

Installer

 import numpy as np
import tensorflow as tf
from tensorflow import keras
 

Sauvegarde et chargement du modèle entier

Vous pouvez enregistrer un modèle entier dans un seul artefact. Il comprendra:

  • L'architecture / la configuration du modèle
  • Les valeurs de poids du modèle (qui ont été apprises pendant l'entraînement)
  • Les informations de compilation du modèle (si compile() ) a été appelée
  • L'optimiseur et son état, le cas échéant (cela vous permet de redémarrer l'entraînement là où vous l'avez laissé)

Apis

Il existe deux formats que vous pouvez utiliser pour enregistrer un modèle entier sur le disque: le format TensorFlow SavedModel et l'ancien format Keras H5 . Le format recommandé est SavedModel. C'est la valeur par défaut lorsque vous utilisez model.save() .

Vous pouvez passer au format H5 en:

  • Passer save_format='h5' à save() .
  • Passer un nom de fichier qui se termine par .h5 ou .keras à save() .

Format du modèle enregistré

Exemple:

 def get_model():
    # Create a simple model.
    inputs = keras.Input(shape=(32,))
    outputs = keras.layers.Dense(1)(inputs)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer="adam", loss="mean_squared_error")
    return model


model = get_model()

# Train the model.
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)

# Calling `save('my_model')` creates a SavedModel folder `my_model`.
model.save("my_model")

# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_model")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)

# The reconstructed model is already compiled and has retained the optimizer
# state, so training can resume:
reconstructed_model.fit(test_input, test_target)
 
4/4 [==============================] - 0s 1ms/step - loss: 1.1917
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1817: 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.
INFO:tensorflow:Assets written to: my_model/assets
4/4 [==============================] - 0s 1ms/step - loss: 1.0581

<tensorflow.python.keras.callbacks.History at 0x7f13700096d8>

Ce que contient le SavedModel

L'appel de model.save('my_model') crée un dossier nommé my_model , contenant les éléments suivants:

ls my_model
assets  saved_model.pb  variables

L'architecture du modèle et la configuration de la formation (y compris l'optimiseur, les pertes et les métriques) sont stockées dans saved_model.pb . Les poids sont enregistrés dans le répertoire variables/ .

Pour des informations détaillées sur le format SavedModel, consultez le guide SavedModel ( Le format SavedModel sur le disque ) .

Comment SavedModel gère les objets personnalisés

Lors de l'enregistrement du modèle et de ses couches, le format SavedModel stocke le nom de la classe, la fonction d'appel , les pertes et les poids (et la configuration, si elle est implémentée). La fonction d'appel définit le graphe de calcul du modèle / couche.

En l'absence de la configuration modèle / couche, la fonction d'appel est utilisée pour créer un modèle qui existe comme le modèle d'origine qui peut être entraîné, évalué et utilisé pour l'inférence.

Néanmoins, il est toujours get_config de définir les méthodes get_config et from_config lors de l'écriture d'un modèle personnalisé ou d'une classe de couches. Cela vous permet de mettre à jour facilement le calcul ultérieurement si nécessaire. Consultez la section sur les objets personnalisés pour plus d'informations.

Voici un exemple de ce qui se passe lors du chargement de couches personnalisées à partir du format SavedModel sans écraser les méthodes de configuration.

 class CustomModel(keras.Model):
    def __init__(self, hidden_units):
        super(CustomModel, self).__init__()
        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


model = CustomModel([16, 16, 10])
# Build the model by calling it
input_arr = tf.random.uniform((1, 5))
outputs = model(input_arr)
model.save("my_model")

# Delete the custom-defined model class to ensure that the loader does not have
# access to it.
del CustomModel

loaded = keras.models.load_model("my_model")
np.testing.assert_allclose(loaded(input_arr), outputs)

print("Original model:", model)
print("Loaded model:", loaded)
 
INFO:tensorflow:Assets written to: my_model/assets
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Original model: <__main__.CustomModel object at 0x7f1370081550>
Loaded model: <tensorflow.python.keras.saving.saved_model.load.CustomModel object at 0x7f1328722e48>

Comme le montre l'exemple ci-dessus, le chargeur crée dynamiquement une nouvelle classe de modèle qui agit comme le modèle d'origine.

Format Keras H5

Keras prend également en charge l'enregistrement d'un seul fichier HDF5 contenant l'architecture du modèle, les valeurs de pondération et les informations compile() . C'est une alternative légère à SavedModel.

Exemple:

 model = get_model()

# Train the model.
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)

# Calling `save('my_model.h5')` creates a h5 file `my_model.h5`.
model.save("my_h5_model.h5")

# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_h5_model.h5")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)

# The reconstructed model is already compiled and has retained the optimizer
# state, so training can resume:
reconstructed_model.fit(test_input, test_target)
 
4/4 [==============================] - 0s 1ms/step - loss: 4.1064
4/4 [==============================] - 0s 1ms/step - loss: 3.8469

<tensorflow.python.keras.callbacks.History at 0x7f14171934a8>

Limites

Par rapport au format SavedModel, il y a deux choses qui ne sont pas incluses dans le fichier H5:

  • Les pertes externes et les métriques ajoutées via model.add_loss() et model.add_metric() ne sont pas sauvegardées (contrairement à SavedModel). Si vous avez de telles pertes et métriques sur votre modèle et que vous souhaitez reprendre l'entraînement, vous devez ajouter ces pertes vous-même après le chargement du modèle. Notez que cela ne s'applique pas aux pertes / métriques créées à l' intérieur des couches via self.add_loss() & self.add_metric() . Tant que la couche est chargée, ces pertes et métriques sont conservées, car elles font partie de la méthode d' call de la couche.
  • Le graphique de calcul des objets personnalisés tels que les calques personnalisés n'est pas inclus dans le fichier enregistré. Au moment du chargement, Keras aura besoin d'accéder aux classes / fonctions Python de ces objets afin de reconstruire le modèle. Voir Objets personnalisés .

Sauver l'architecture

La configuration (ou l'architecture) du modèle spécifie quelles couches le modèle contient et comment ces couches sont connectées *. Si vous avez la configuration d'un modèle, le modèle peut être créé avec un état fraîchement initialisé pour les poids et aucune information de compilation.

* Notez que cela s'applique uniquement aux modèles définis à l'aide des API fonctionnelles ou séquentielles non sous-classées.

Configuration d'un modèle séquentiel ou d'un modèle d'API fonctionnelle

Ces types de modèles sont des graphes explicites de couches: leur configuration est toujours disponible sous une forme structurée.

Apis

get_config() et from_config()

L'appel de config = model.get_config() retournera un dict Python contenant la configuration du modèle. Le même modèle peut ensuite être reconstruit via Sequential.from_config(config) (pour un modèle Sequential ) ou Model.from_config(config) (pour un modèle d'API fonctionnelle).

Le même flux de travail fonctionne également pour toute couche sérialisable.

Exemple de calque:

 layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
new_layer = keras.layers.Dense.from_config(layer_config)
 

Exemple de modèle séquentiel:

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

Exemple de modèle fonctionnel:

 inputs = keras.Input((32,))
outputs = keras.layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)
config = model.get_config()
new_model = keras.Model.from_config(config)
 

to_json() et tf.keras.models.model_from_json()

Ceci est similaire à get_config / from_config , sauf qu'il transforme le modèle en une chaîne JSON, qui peut ensuite être chargée sans la classe de modèle d'origine. Il est également spécifique aux modèles, il n'est pas destiné aux calques.

Exemple:

 model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
json_config = model.to_json()
new_model = keras.models.model_from_json(json_config)
 

Objets personnalisés

Modèles et couches

L'architecture des modèles et des couches sous- __init__ est définie dans les méthodes __init__ et call . Ils sont considérés comme du bytecode Python, qui ne peut pas être sérialisé dans une configuration compatible JSON - vous pouvez essayer de sérialiser le bytecode (par exemple via pickle ), mais il est complètement dangereux et signifie que votre modèle ne peut pas être chargé sur un système différent.

Pour enregistrer / charger un modèle avec des couches personnalisées, ou un modèle sous- get_config , vous devez remplacer les get_config et éventuellement from_config . En outre, vous devez utiliser enregistrer l'objet personnalisé afin que Keras en soit conscient.

Fonctions personnalisées

Les fonctions personnalisées (par exemple, la perte d'activation ou l'initialisation) n'ont pas besoin d'une méthode get_config . Le nom de la fonction est suffisant pour le chargement tant qu'il est enregistré en tant qu'objet personnalisé.

Chargement du graphique TensorFlow uniquement

Il est possible de charger le graphe TensorFlow généré par le Keras. Si vous le faites, vous n'aurez pas besoin de fournir d' custom_objects . Vous pouvez le faire comme ceci:

 model.save("my_model")
tensorflow_graph = tf.saved_model.load("my_model")
x = np.random.uniform(size=(4, 32)).astype(np.float32)
predicted = tensorflow_graph(x).numpy()
 
INFO:tensorflow:Assets written to: my_model/assets

Notez que cette méthode présente plusieurs inconvénients:

  • Pour des raisons de traçabilité, vous devez toujours avoir accès aux objets personnalisés qui ont été utilisés. Vous ne voudriez pas mettre en production un modèle que vous ne pouvez pas recréer.
  • L'objet renvoyé par tf.saved_model.load n'est pas un modèle Keras. Ce n'est donc pas aussi simple à utiliser. Par exemple, vous n'aurez pas accès à .predict() ou .fit()

Même si son utilisation est déconseillée, elle peut vous aider si vous êtes dans une situation difficile, par exemple, si vous avez perdu le code de vos objets personnalisés ou avez des problèmes de chargement du modèle avec tf.keras.models.load_model() .

Vous pouvez en savoir plus sur la page sur tf.saved_model.load

Définition des méthodes de configuration

Caractéristiques:

  • get_config doit renvoyer un dictionnaire sérialisable JSON afin d'être compatible avec les API de sauvegarde d'architecture et de modèle Keras.
  • from_config(config) ( classmethod ) doit renvoyer un nouveau calque ou objet modèle créé à partir de la configuration. L'implémentation par défaut renvoie cls(**config) .

Exemple:

 class CustomLayer(keras.layers.Layer):
    def __init__(self, a):
        self.var = tf.Variable(a, name="var_a")

    def call(self, inputs, training=False):
        if training:
            return inputs * self.var
        else:
            return inputs

    def get_config(self):
        return {"a": self.var.numpy()}

    # There's actually no need to define `from_config` here, since returning
    # `cls(**config)` is the default behavior.
    @classmethod
    def from_config(cls, config):
        return cls(**config)


layer = CustomLayer(5)
layer.var.assign(2)

serialized_layer = keras.layers.serialize(layer)
new_layer = keras.layers.deserialize(
    serialized_layer, custom_objects={"CustomLayer": CustomLayer}
)
 

Enregistrement de l'objet personnalisé

Keras garde une note de la classe qui a généré la configuration. À partir de l'exemple ci-dessus, tf.keras.layers.serialize génère une forme sérialisée de la couche personnalisée:

 {'class_name': 'CustomLayer', 'config': {'a': 2} }
 

Keras conserve une liste principale de toutes les classes intégrées de couche, modèle, optimiseur et métrique, qui est utilisée pour trouver la classe correcte à appeler from_config . Si la classe ne peut pas être trouvée, une erreur est générée ( Value Error: Unknown layer ). Il existe plusieurs façons d'enregistrer des classes personnalisées dans cette liste:

  1. Définition de l'argument custom_objects dans la fonction de chargement. (voir l'exemple dans la section ci-dessus "Définition des méthodes de configuration")
  2. tf.keras.utils.custom_object_scope ou tf.keras.utils.CustomObjectScope
  3. tf.keras.utils.register_keras_serializable

Exemple de couche et de fonction personnalisée

 class CustomLayer(keras.layers.Layer):
    def __init__(self, units=32, **kwargs):
        super(CustomLayer, self).__init__(**kwargs)
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

    def get_config(self):
        config = super(CustomLayer, self).get_config()
        config.update({"units": self.units})
        return config


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


# Make a model with the CustomLayer and custom_activation
inputs = keras.Input((32,))
x = CustomLayer(32)(inputs)
outputs = keras.layers.Activation(custom_activation)(x)
model = keras.Model(inputs, outputs)

# Retrieve the config
config = model.get_config()

# At loading time, register the custom objects with a `custom_object_scope`:
custom_objects = {"CustomLayer": CustomLayer, "custom_activation": custom_activation}
with keras.utils.custom_object_scope(custom_objects):
    new_model = keras.Model.from_config(config)
 

Clonage de modèle en mémoire

Vous pouvez également faire le clonage en mémoire d'un modèle via tf.keras.models.clone_model() . Cela équivaut à obtenir la configuration puis à recréer le modèle à partir de sa configuration (cela ne conserve donc pas les informations de compilation ou les valeurs de pondération des couches).

Exemple:

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

Enregistrer et charger uniquement les valeurs de poids du modèle

Vous pouvez choisir d'enregistrer et de charger uniquement les poids d'un modèle. Cela peut être utile si:

  • Vous n'avez besoin que du modèle pour l'inférence: dans ce cas, vous n'aurez pas besoin de redémarrer l'entraînement, vous n'avez donc pas besoin des informations de compilation ou de l'état de l'optimiseur.
  • Vous effectuez un apprentissage par transfert: dans ce cas, vous allez entraîner un nouveau modèle en réutilisant l'état d'un modèle précédent, vous n'avez donc pas besoin des informations de compilation du modèle précédent.

API pour le transfert de poids en mémoire

Les poids peuvent être copiés entre différents objets à l'aide de get_weights et set_weights :

Exemples ci-dessous.

Transfert de poids d'une couche à une autre, en mémoire

 def create_layer():
    layer = keras.layers.Dense(64, activation="relu", name="dense_2")
    layer.build((None, 784))
    return layer


layer_1 = create_layer()
layer_2 = create_layer()

# Copy weights from layer 2 to layer 1
layer_2.set_weights(layer_1.get_weights())
 

Transfert de poids d'un modèle à un autre modèle avec une architecture compatible, en mémoire

 # Create a simple functional model
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

# Define a subclassed model with the same architecture
class SubclassedModel(keras.Model):
    def __init__(self, output_dim, name=None):
        super(SubclassedModel, self).__init__(name=name)
        self.output_dim = output_dim
        self.dense_1 = keras.layers.Dense(64, activation="relu", name="dense_1")
        self.dense_2 = keras.layers.Dense(64, activation="relu", name="dense_2")
        self.dense_3 = keras.layers.Dense(output_dim, name="predictions")

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

    def get_config(self):
        return {"output_dim": self.output_dim, "name": self.name}


subclassed_model = SubclassedModel(10)
# Call the subclassed model once to create the weights.
subclassed_model(tf.ones((1, 784)))

# Copy weights from functional_model to subclassed_model.
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())
 

Le cas des couches apatrides

Étant donné que les couches sans état ne changent pas l'ordre ou le nombre de pondérations, les modèles peuvent avoir des architectures compatibles même s'il existe des couches sans état supplémentaires / manquantes.

 inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

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

# Add a dropout layer, which does not contain any weights.
x = keras.layers.Dropout(0.5)(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model_with_dropout = keras.Model(
    inputs=inputs, outputs=outputs, name="3_layer_mlp"
)

functional_model_with_dropout.set_weights(functional_model.get_weights())
 

API pour enregistrer les poids sur le disque et les recharger

Les poids peuvent être enregistrés sur le disque en appelant model.save_weights dans les formats suivants:

  • Point de contrôle TensorFlow
  • HDF5

Le format par défaut pour model.save_weights est le point de contrôle TensorFlow. Il existe deux façons de spécifier le format d'enregistrement:

  1. Argument save_format : save_format la valeur sur save_format="tf" ou save_format="h5" .
  2. argument de path : si le chemin se termine par .h5 ou .hdf5 , le format HDF5 est utilisé. D'autres suffixes entraîneront un point de contrôle TensorFlow à moins que save_format ne soit défini.

Il existe également une option de récupération des poids sous forme de tableaux numpy en mémoire. Chaque API a ses avantages et ses inconvénients, détaillés ci-dessous.

Format de point de contrôle TF

Exemple:

 # Runnable example
sequential_model = keras.Sequential(
    [
        keras.Input(shape=(784,), name="digits"),
        keras.layers.Dense(64, activation="relu", name="dense_1"),
        keras.layers.Dense(64, activation="relu", name="dense_2"),
        keras.layers.Dense(10, name="predictions"),
    ]
)
sequential_model.save_weights("ckpt")
load_status = sequential_model.load_weights("ckpt")

# `assert_consumed` can be used as validation that all variable values have been
# restored from the checkpoint. See `tf.train.Checkpoint.restore` for other
# methods in the Status object.
load_status.assert_consumed()
 
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f1416793ba8>

Détails du format

Le format TensorFlow Checkpoint enregistre et restaure les pondérations à l'aide des noms d'attributs d'objet. Par exemple, considérez la couche tf.keras.layers.Dense . La couche contient deux poids: dense.kernel et dense.bias . Lorsque la couche est enregistrée au format tf , le point de contrôle résultant contient les clés "kernel" et "bias" et leurs valeurs de poids correspondantes. Pour plus d'informations, voir "Mécanique de chargement" dans le guide TF Checkpoint .

Notez que l'attribut / le bord du graphique est nommé d'après le nom utilisé dans l'objet parent, et non le nom de la variable . Considérez le CustomLayer dans l'exemple ci-dessous. La variable CustomLayer.var est enregistrée avec "var" dans le cadre de la clé, et non "var_a" .

 class CustomLayer(keras.layers.Layer):
    def __init__(self, a):
        self.var = tf.Variable(a, name="var_a")


layer = CustomLayer(5)
layer_ckpt = tf.train.Checkpoint(layer=layer).save("custom_layer")

ckpt_reader = tf.train.load_checkpoint(layer_ckpt)

ckpt_reader.get_variable_to_dtype_map()
 
{'save_counter/.ATTRIBUTES/VARIABLE_VALUE': tf.int64,
 '_CHECKPOINTABLE_OBJECT_GRAPH': tf.string,
 'layer/var/.ATTRIBUTES/VARIABLE_VALUE': tf.int32}

Exemple d'apprentissage par transfert

Essentiellement, tant que deux modèles ont la même architecture, ils peuvent partager le même point de contrôle.

Exemple:

 inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

# Extract a portion of the functional model defined in the Setup section.
# The following lines produce a new model that excludes the final output
# layer of the functional model.
pretrained = keras.Model(
    functional_model.inputs, functional_model.layers[-1].input, name="pretrained_model"
)
# Randomly assign "trained" weights.
for w in pretrained.weights:
    w.assign(tf.random.normal(w.shape))
pretrained.save_weights("pretrained_ckpt")
pretrained.summary()

# Assume this is a separate program where only 'pretrained_ckpt' exists.
# Create a new functional model with a different output dimension.
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(5, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="new_model")

# Load the weights from pretrained_ckpt into model.
model.load_weights("pretrained_ckpt")

# Check that all of the pretrained weights have been loaded.
for a, b in zip(pretrained.weights, model.weights):
    np.testing.assert_allclose(a.numpy(), b.numpy())

print("\n", "-" * 50)
model.summary()

# Example 2: Sequential model
# Recreate the pretrained model, and load the saved weights.
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
pretrained_model = keras.Model(inputs=inputs, outputs=x, name="pretrained")

# Sequential example:
model = keras.Sequential([pretrained_model, keras.layers.Dense(5, name="predictions")])
model.summary()

pretrained_model.load_weights("pretrained_ckpt")

# Warning! Calling `model.load_weights('pretrained_ckpt')` won't throw an error,
# but will *not* work as expected. If you inspect the weights, you'll see that
# none of the weights will have loaded. `pretrained_model.load_weights()` is the
# correct method to call.
 
Model: "pretrained_model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
digits (InputLayer)          [(None, 784)]             0         
_________________________________________________________________
dense_1 (Dense)              (None, 64)                50240     
_________________________________________________________________
dense_2 (Dense)              (None, 64)                4160      
=================================================================
Total params: 54,400
Trainable params: 54,400
Non-trainable params: 0
_________________________________________________________________

 --------------------------------------------------
Model: "new_model"
_________________________________________________________________
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, 5)                 325       
=================================================================
Total params: 54,725
Trainable params: 54,725
Non-trainable params: 0
_________________________________________________________________
Model: "sequential_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
pretrained (Model)           (None, 64)                54400     
_________________________________________________________________
predictions (Dense)          (None, 5)                 325       
=================================================================
Total params: 54,725
Trainable params: 54,725
Non-trainable params: 0
_________________________________________________________________

<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f1416704278>

Il est généralement recommandé de s'en tenir à la même API pour la création de modèles. Si vous basculez entre Séquentiel et Fonctionnel, ou Fonctionnel et sous-classe, etc., reconstruisez toujours le modèle pré-entraîné et chargez les poids pré-formés dans ce modèle.

La question suivante est: comment les poids peuvent-ils être enregistrés et chargés dans différents modèles si les architectures du modèle sont assez différentes? La solution consiste à utiliser tf.train.Checkpoint pour enregistrer et restaurer les couches / variables exactes.

Exemple:

 # Create a subclassed model that essentially uses functional_model's first
# and last layers.
# First, save the weights of functional_model's first and last dense layers.
first_dense = functional_model.layers[1]
last_dense = functional_model.layers[-1]
ckpt_path = tf.train.Checkpoint(
    dense=first_dense, kernel=last_dense.kernel, bias=last_dense.bias
).save("ckpt")

# Define the subclassed model.
class ContrivedModel(keras.Model):
    def __init__(self):
        super(ContrivedModel, self).__init__()
        self.first_dense = keras.layers.Dense(64)
        self.kernel = self.add_variable("kernel", shape=(64, 10))
        self.bias = self.add_variable("bias", shape=(10,))

    def call(self, inputs):
        x = self.first_dense(inputs)
        return tf.matmul(x, self.kernel) + self.bias


model = ContrivedModel()
# Call model on inputs to create the variables of the dense layer.
_ = model(tf.ones((1, 784)))

# Create a Checkpoint with the same structure as before, and load the weights.
tf.train.Checkpoint(
    dense=model.first_dense, kernel=model.kernel, bias=model.bias
).restore(ckpt_path).assert_consumed()
 
WARNING:tensorflow:From <ipython-input-21-eec1d28bc826>:15: Layer.add_variable (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
Please use `layer.add_weight` method instead.

<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f1416713358>

Format HDF5

Le format HDF5 contient des poids regroupés par noms de couches. Les poids sont des listes ordonnées en concaténant la liste des poids entraînables à la liste des poids non entraînables (identique à layer.weights ). Ainsi, un modèle peut utiliser un point de contrôle hdf5 s'il a les mêmes couches et les mêmes statuts que ceux enregistrés dans le point de contrôle.

Exemple:

 # Runnable example
sequential_model = keras.Sequential(
    [
        keras.Input(shape=(784,), name="digits"),
        keras.layers.Dense(64, activation="relu", name="dense_1"),
        keras.layers.Dense(64, activation="relu", name="dense_2"),
        keras.layers.Dense(10, name="predictions"),
    ]
)
sequential_model.save_weights("weights.h5")
sequential_model.load_weights("weights.h5")
 

Notez que le changement de layer.trainable peut entraîner un ordre différent de layer.weights lorsque le modèle contient des couches imbriquées.

 class NestedDenseLayer(keras.layers.Layer):
    def __init__(self, units, name=None):
        super(NestedDenseLayer, self).__init__(name=name)
        self.dense_1 = keras.layers.Dense(units, name="dense_1")
        self.dense_2 = keras.layers.Dense(units, name="dense_2")

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


nested_model = keras.Sequential([keras.Input((784,)), NestedDenseLayer(10, "nested")])
variable_names = [v.name for v in nested_model.weights]
print("variables: {}".format(variable_names))

print("\nChanging trainable status of one of the nested layers...")
nested_model.get_layer("nested").dense_1.trainable = False

variable_names_2 = [v.name for v in nested_model.weights]
print("\nvariables: {}".format(variable_names_2))
print("variable ordering changed:", variable_names != variable_names_2)
 
variables: ['nested/dense_1/kernel:0', 'nested/dense_1/bias:0', 'nested/dense_2/kernel:0', 'nested/dense_2/bias:0']

Changing trainable status of one of the nested layers...

variables: ['nested/dense_2/kernel:0', 'nested/dense_2/bias:0', 'nested/dense_1/kernel:0', 'nested/dense_1/bias:0']
variable ordering changed: True

Exemple d'apprentissage par transfert

Lors du chargement de poids pré-entraînés à partir de HDF5, il est recommandé de charger les poids dans le modèle à points de contrôle d'origine, puis d'extraire les poids / couches souhaités dans un nouveau modèle.

Exemple:

 def create_functional_model():
    inputs = keras.Input(shape=(784,), name="digits")
    x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
    x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
    outputs = keras.layers.Dense(10, name="predictions")(x)
    return keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")


functional_model = create_functional_model()
functional_model.save_weights("pretrained_weights.h5")

# In a separate program:
pretrained_model = create_functional_model()
pretrained_model.load_weights("pretrained_weights.h5")

# Create a new model by extracting layers from the original model:
extracted_layers = pretrained_model.layers[:-1]
extracted_layers.append(keras.layers.Dense(5, name="dense_3"))
model = keras.Sequential(extracted_layers)
model.summary()
 
Model: "sequential_6"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 64)                50240     
_________________________________________________________________
dense_2 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_3 (Dense)              (None, 5)                 325       
=================================================================
Total params: 54,725
Trainable params: 54,725
Non-trainable params: 0
_________________________________________________________________