Enregistrer et charger les modèles Keras

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

introduction

Un modèle Keras se compose de plusieurs composants :

  • L'architecture, ou configuration, qui spécifie les couches contenues dans le modèle et la manière dont elles sont connectées.
  • Un ensemble de valeurs de poids (l'"état du modèle").
  • Un optimiseur (défini en compilant le modèle).
  • Une série de pertes et de mesures (définies par la compilation du modèle ou en appelant add_loss() ou add_metric() ).

L'API Keras permet de sauvegarder toutes ces pièces sur le disque en une seule fois, ou de ne sauvegarder que de manière sélective certaines d'entre elles :

  • 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.
  • Sauvegarde 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'un ou l'autre et comment fonctionnent-ils ?

Comment enregistrer et charger un modèle

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/configuration du modèle
  • Les valeurs de poids du modèle (qui ont été apprises pendant l'entraînement)
  • Informations de compilation du modèle (si la compile() a été appelé)
  • L'optimiseur et son état, le cas échéant (cela permet de reprendre 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 plus Keras le format H5. Le format recommandé est SavedModel. Il est la valeur par défaut lorsque vous utilisez model.save() .

Vous pouvez passer au format H5 en :

  • Passant save_format='h5' pour save() .
  • Le passage d' un nom de fichier qui se termine par .h5 ou .keras pour save() .

Format du modèle enregistré

SavedModel est le format de sauvegarde le plus complet qui enregistre l'architecture du modèle, les poids et les sous-graphes Tensorflow tracés des fonctions d'appel. Cela permet à Keras de restaurer à la fois les calques intégrés et les objets personnalisés.

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 [==============================] - 1s 2ms/step - loss: 0.5884
2021-08-25 17:49:05.320893: 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: my_model/assets
4/4 [==============================] - 0s 2ms/step - loss: 0.5197
<keras.callbacks.History at 0x7f99486ad490>

Ce que contient le modèle enregistré

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

ls my_model
assets  keras_metadata.pb  saved_model.pb  variables

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

Pour plus d' informations 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 le poids (et la configuration, si elles sont appliquées). 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 une bonne pratique pour définir la get_config et from_config méthodes lors de l' écriture d' un modèle personnalisé ou une classe de couche. Cela vous permet de mettre à jour facilement le calcul ultérieurement si nécessaire. Voir la section sur les objets personnalisés pour plus d' informations.

Exemple:

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

    @classmethod
    def from_config(cls, config):
        return cls(**config)


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

# Option 1: Load with the custom_object argument.
loaded_1 = keras.models.load_model(
    "my_model", custom_objects={"CustomModel": CustomModel}
)

# Option 2: Load without the CustomModel class.

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

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

print("Original model:", model)
print("Model Loaded with custom objects:", loaded_1)
print("Model loaded without the custom object class:", loaded_2)
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.
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Original model: <__main__.CustomModel object at 0x7f9949c86810>
Model Loaded with custom objects: <__main__.CustomModel object at 0x7f99681f61d0>
Model loaded without the custom object class: <keras.saving.saved_model.load.CustomModel object at 0x7f9aaceefd10>

Le premier modèle chargé est chargé à l' aide de la configuration et CustomModel classe. Le deuxième modèle est chargé en créant dynamiquement la classe de modèle qui agit comme le modèle d'origine.

Configuration du modèle enregistré

Nouveau dans TensoFlow 2.4 L'argument save_traces a été ajouté à model.save , ce qui vous permet de basculer le traçage de fonction SavedModel. Les fonctions sont pour permettre au Keras d'objets personnalisés re-charge sans les DEFINITIONS de classe d' origine, alors quand save_traces=False , tous les objets personnalisés doivent avoir défini get_config / from_config méthodes. Lors du chargement, les objets personnalisés doivent être transmis à l' custom_objects argument. save_traces=False réduit l'espace disque utilisé par le SavedModel et gain de temps.

Format Keras H5

Keras prend également en charge la sauvegarde d' un seul fichier contenant HDF5 l'architecture, les valeurs de poids et du modèle de compile() informations. 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: 1.6322
4/4 [==============================] - 0s 1ms/step - loss: 1.4790
<keras.callbacks.History at 0x7f9aacc0fd50>

Limites

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

  • Pertes externes et paramètres ajoutés par model.add_loss() et model.add_metric() ne sont pas enregistré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 rajouter ces pertes vous-même après avoir chargé le modèle. Notez que cela ne concerne pas les pertes / métriques créées à l' intérieur des couches par self.add_loss() et self.add_metric() . Tant que la couche se charge, ces pertes et paramètres sont conservés, car ils font partie de l' call méthode de la couche.
  • Le graphique de calcul des objets personnalisés tels que des couches personnalisées ne sont pas incluses 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 les objets personnalisés .

Sauvegarder l'architecture

La configuration (ou 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 ne s'applique qu'aux modèles définis à l'aide des API fonctionnelles ou séquentielles et non des modèles sous-classés.

Configuration d'un modèle Séquentiel ou d'un modèle API Fonctionnel

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()

Appel config = model.get_config() retourne un dict Python contenant la configuration du modèle. Le même modèle peut alors être reconstruit par Sequential.from_config(config) (pour un Sequential modèle) ou Model.from_config(config) (pour un modèle API fonctionnelle).

Le même workflow 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 alors être chargé sans la classe modèle original. 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 - classées sont définies dans les méthodes __init__ et call . Ils sont considérés comme bytecode Python, qui ne peut être sérialisé dans une configuration compatible JSON - vous pouvez essayer sérialisation le bytecode (par exemple via pickle ), mais il est tout à fait dangereux et signifie que votre modèle ne peut pas être chargé sur un autre système.

Afin de sauvegarder / charger un modèle avec des couches définies sur mesure, ou d' un modèle sous - classé, vous devez remplacer le get_config et éventuellement from_config méthodes. En outre, vous devez utiliser enregistrer l'objet personnalisé afin que Keras en soit informé.

Fonctions personnalisées

Les fonctions personnalisées définies (par exemple perte ou initialisation activation) ne nécessitent pas de get_config méthode. 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 graphique TensorFlow généré par le Keras. Si vous le faites, vous aurez pas besoin de fournir des 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()
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
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 retourné par tf.saved_model.load n'est pas un modèle Keras. Ce n'est donc pas aussi simple à utiliser. Par exemple, vous ne pourrez pas avoir accès à .predict() ou .fit()

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

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

Définir les méthodes de configuration

Caractéristiques:

  • get_config doit retourner un dictionnaire JSON-sérialisable afin d'être compatible avec l'API et Keras Architecture- d' économie d'modèle.
  • from_config(config) ( classmethod ) doit renvoyer un nouvel objet de couche ou d'un modèle qui est créé à partir de la configuration. Les retours d'implémentation par défaut 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. Dans l'exemple ci - dessus, tf.keras.layers.serialize génère une forme sérialisée de la couche de mesure:

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

Keras conserve une liste maîtresse de tous intégrés dans la couche, le modèle, optimiseur, et les classes métriques, qui est utilisé pour trouver la bonne classe à l' appel from_config . Si la classe ne peut pas être trouvée, une erreur est élevée ( Value Error: Unknown layer ). Il existe plusieurs façons d'enregistrer des classes personnalisées dans cette liste :

  1. Réglage custom_objects argument de 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 personnalisée et de fonction

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 (ainsi, cela ne préserve 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 de ne sauvegarder et de charger que les poids d'un modèle. Cela peut être utile si :

  • Vous n'avez besoin du modèle que 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 entraînerez 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 les différents objets à l'aide 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 1 to layer 2
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 modifient pas l'ordre ou le nombre de poids, 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 sauvegardé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 checkpoint tensorflow. Il existe deux manières de spécifier le format de sauvegarde :

  1. save_format argument: Définir la valeur à save_format="tf" ou save_format="h5" .
  2. path Argument: Si les extrémités de chemin avec .h5 ou .hdf5 , le format HDF5 est utilisé. D' autres suffixes se traduira par un point de contrôle de tensorflow à moins save_format est réglé.

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 qui sont 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 0x7f9aaca4ced0>

Détails du format

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

Notez que le bord attribut / graphique est nommé d' après le nom utilisé dans l' objet parent, pas le nom de la variable. Considérons 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 de transfert d'apprentissage

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 (Functional)      (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 0x7f9aaca76990>

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

La question suivante est la suivante : comment les poids peuvent-ils être enregistrés et chargés dans différents modèles si les architectures de modèle sont très différentes ? La solution est d'utiliser tf.train.Checkpoint pour sauvegarder 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()
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/engine/base_layer.py:2223: UserWarning: `layer.add_variable` is deprecated and will be removed in a future version. Please use `layer.add_weight` method instead.
  warnings.warn('`layer.add_variable` is deprecated and '
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f9aaca6f390>

format HDF5

Le format HDF5 contient des poids regroupés par noms de couche. Les coefficients de pondération sont des listes ordonnées en concaténant la liste des masses 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 possède les mêmes couches et statuts pouvant être entraînés 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 la modification layer.trainable peut entraîner une autre layer.weights commande lorsque le modèle contient des calques imbriqués.

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 de transfert d'apprentissage

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
_________________________________________________________________