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

Création de nouveaux calques et modèles via le sous-classement

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

Installer

import tensorflow as tf
from tensorflow import keras

La classe Layer : la combinaison de l'état (poids) et du calcul

L'une des abstractions centrales de Keras est la classe Layer . Une couche encapsule à la fois un état (les «poids» de la couche) et une transformation des entrées en sorties (un «appel», la passe avant de la couche).

Voici une couche densément connectée. Il a un état: les variables w et b .

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        w_init = tf.random_normal_initializer()
        self.w = tf.Variable(
            initial_value=w_init(shape=(input_dim, units), dtype="float32"),
            trainable=True,
        )
        b_init = tf.zeros_initializer()
        self.b = tf.Variable(
            initial_value=b_init(shape=(units,), dtype="float32"), trainable=True
        )

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

Vous utiliseriez une couche en l'appelant sur certaines entrées tensorielles, un peu comme une fonction Python.

x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[0.06229124 0.02534804 0.00851583 0.00457605]
 [0.06229124 0.02534804 0.00851583 0.00457605]], shape=(2, 4), dtype=float32)

Notez que les poids w et b sont automatiquement suivis par le calque lorsqu'ils sont définis comme attributs de calque:

assert linear_layer.weights == [linear_layer.w, linear_layer.b]

Notez que vous avez également accès à un raccourci plus rapide pour ajouter du poids à un calque: la méthode add_weight() :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        self.w = self.add_weight(
            shape=(input_dim, units), initializer="random_normal", trainable=True
        )
        self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True)

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


x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[ 0.06930272  0.03181376 -0.07157768  0.07838295]
 [ 0.06930272  0.03181376 -0.07157768  0.07838295]], shape=(2, 4), dtype=float32)

Les couches peuvent avoir des poids non entraînables

Outre les poids entraînables, vous pouvez également ajouter des poids non entraînables à une couche. Ces poids ne doivent pas être pris en compte lors de la rétropropagation, lorsque vous entraînez la couche.

Voici comment ajouter et utiliser un poids non entraînable:

class ComputeSum(keras.layers.Layer):
    def __init__(self, input_dim):
        super(ComputeSum, self).__init__()
        self.total = tf.Variable(initial_value=tf.zeros((input_dim,)), trainable=False)

    def call(self, inputs):
        self.total.assign_add(tf.reduce_sum(inputs, axis=0))
        return self.total


x = tf.ones((2, 2))
my_sum = ComputeSum(2)
y = my_sum(x)
print(y.numpy())
y = my_sum(x)
print(y.numpy())
[2. 2.]
[4. 4.]

Cela fait partie de layer.weights , mais il est classé comme un poids non entraînable:

print("weights:", len(my_sum.weights))
print("non-trainable weights:", len(my_sum.non_trainable_weights))

# It's not included in the trainable weights:
print("trainable_weights:", my_sum.trainable_weights)
weights: 1
non-trainable weights: 1
trainable_weights: []

Bonne pratique: différer la création de poids jusqu'à ce que la forme des entrées soit connue

Notre couche Linear ci-dessus a pris un argument input_dim qui a été utilisé pour calculer la forme des poids w et b dans __init__() :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        self.w = self.add_weight(
            shape=(input_dim, units), initializer="random_normal", trainable=True
        )
        self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True)

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

Dans de nombreux cas, vous ne connaissez peut-être pas à l'avance la taille de vos entrées et vous souhaitez créer paresseusement des pondérations lorsque cette valeur devient connue, quelque temps après l'instanciation du calque.

Dans l'API Keras, nous vous recommandons de créer des pondérations de couche dans la méthode build(self, inputs_shape) de votre couche. Comme ça:

class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, self).__init__()
        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

La __call__() de votre couche exécutera automatiquement build la première fois qu'elle sera appelée. Vous avez maintenant une couche paresseuse et donc plus facile à utiliser:

# At instantiation, we don't know on what inputs this is going to get called
linear_layer = Linear(32)

# The layer's weights are created dynamically the first time the layer is called
y = linear_layer(x)

Les couches sont composables de manière récursive

Si vous affectez une occurrence de calque comme attribut d'un autre calque, le calque externe commencera à suivre les poids du calque interne.

Nous vous recommandons de créer de telles sous-couches dans la __init__() (puisque les sous-couches auront généralement une méthode de construction, elles seront construites lorsque la couche externe sera construite).

# Let's assume we are reusing the Linear class
# with a `build` method that we defined above.


class MLPBlock(keras.layers.Layer):
    def __init__(self):
        super(MLPBlock, self).__init__()
        self.linear_1 = Linear(32)
        self.linear_2 = Linear(32)
        self.linear_3 = Linear(1)

    def call(self, inputs):
        x = self.linear_1(inputs)
        x = tf.nn.relu(x)
        x = self.linear_2(x)
        x = tf.nn.relu(x)
        return self.linear_3(x)


mlp = MLPBlock()
y = mlp(tf.ones(shape=(3, 64)))  # The first call to the `mlp` will create the weights
print("weights:", len(mlp.weights))
print("trainable weights:", len(mlp.trainable_weights))
weights: 6
trainable weights: 6

La méthode add_loss()

Lors de l'écriture de la méthode call() d'une couche, vous pouvez créer des tenseurs de perte que vous voudrez utiliser plus tard, lors de l'écriture de votre boucle d'entraînement. Ceci est faisable en appelant self.add_loss(value) :

# A layer that creates an activity regularization loss
class ActivityRegularizationLayer(keras.layers.Layer):
    def __init__(self, rate=1e-2):
        super(ActivityRegularizationLayer, self).__init__()
        self.rate = rate

    def call(self, inputs):
        self.add_loss(self.rate * tf.reduce_sum(inputs))
        return inputs

Ces pertes (y compris celles créées par n'importe quelle couche interne) peuvent être récupérées via layer.losses . Cette propriété est réinitialisée au début de chaque __call__() vers la couche de niveau supérieur, de sorte que layer.losses contienne toujours les valeurs de perte créées lors de la dernière passe avant.

class OuterLayer(keras.layers.Layer):
    def __init__(self):
        super(OuterLayer, self).__init__()
        self.activity_reg = ActivityRegularizationLayer(1e-2)

    def call(self, inputs):
        return self.activity_reg(inputs)


layer = OuterLayer()
assert len(layer.losses) == 0  # No losses yet since the layer has never been called

_ = layer(tf.zeros(1, 1))
assert len(layer.losses) == 1  # We created one loss value

# `layer.losses` gets reset at the start of each __call__
_ = layer(tf.zeros(1, 1))
assert len(layer.losses) == 1  # This is the loss created during the call above

En outre, la propriété de loss contient également des pertes de régularisation créées pour les poids de toute couche interne:

class OuterLayerWithKernelRegularizer(keras.layers.Layer):
    def __init__(self):
        super(OuterLayerWithKernelRegularizer, self).__init__()
        self.dense = keras.layers.Dense(
            32, kernel_regularizer=tf.keras.regularizers.l2(1e-3)
        )

    def call(self, inputs):
        return self.dense(inputs)


layer = OuterLayerWithKernelRegularizer()
_ = layer(tf.zeros((1, 1)))

# This is `1e-3 * sum(layer.dense.kernel ** 2)`,
# created by the `kernel_regularizer` above.
print(layer.losses)
[<tf.Tensor: shape=(), dtype=float32, numpy=0.002361052>]

Ces pertes sont destinées à être prises en compte lors de l'écriture de boucles d'entraînement, comme ceci:

# Instantiate an optimizer.
optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Iterate over the batches of a dataset.
for x_batch_train, y_batch_train in train_dataset:
  with tf.GradientTape() as tape:
    logits = layer(x_batch_train)  # Logits for this minibatch
    # Loss value for this minibatch
    loss_value = loss_fn(y_batch_train, logits)
    # Add extra losses created during this forward pass:
    loss_value += sum(model.losses)

  grads = tape.gradient(loss_value, model.trainable_weights)
  optimizer.apply_gradients(zip(grads, model.trainable_weights))

Pour un guide détaillé sur l'écriture de boucles d'entraînement, consultez le guide d'écriture d'une boucle d'entraînement à partir de zéro .

Ces pertes fonctionnent également de manière transparente avec fit() (elles sont automatiquement additionnées et ajoutées à la perte principale, le cas échéant):

import numpy as np

inputs = keras.Input(shape=(3,))
outputs = ActivityRegularizationLayer()(inputs)
model = keras.Model(inputs, outputs)

# If there is a loss passed in `compile`, thee regularization
# losses get added to it
model.compile(optimizer="adam", loss="mse")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))

# It's also possible not to pass any loss in `compile`,
# since the model already has a loss to minimize, via the `add_loss`
# call during the forward pass!
model.compile(optimizer="adam")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))
1/1 [==============================] - 0s 1ms/step - loss: 0.2552
1/1 [==============================] - 0s 893us/step - loss: 0.0427

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

La méthode add_metric()

Comme pour add_loss() , les couches ont également une méthode add_metric() pour suivre la moyenne mobile d'une quantité pendant l'entraînement.

Considérez la couche suivante: une couche de «point final logistique». Il prend comme entrées des prédictions et des cibles, il calcule une perte qu'il suit via add_loss() , et il calcule un scalaire de précision, qu'il suit via add_metric() .

class LogisticEndpoint(keras.layers.Layer):
    def __init__(self, name=None):
        super(LogisticEndpoint, self).__init__(name=name)
        self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
        self.accuracy_fn = keras.metrics.BinaryAccuracy()

    def call(self, targets, logits, sample_weights=None):
        # Compute the training-time loss value and add it
        # to the layer using `self.add_loss()`.
        loss = self.loss_fn(targets, logits, sample_weights)
        self.add_loss(loss)

        # Log accuracy as a metric and add it
        # to the layer using `self.add_metric()`.
        acc = self.accuracy_fn(targets, logits, sample_weights)
        self.add_metric(acc, name="accuracy")

        # Return the inference-time prediction tensor (for `.predict()`).
        return tf.nn.softmax(logits)

Les métriques suivies de cette manière sont accessibles via layer.metrics :

layer = LogisticEndpoint()

targets = tf.ones((2, 2))
logits = tf.ones((2, 2))
y = layer(targets, logits)

print("layer.metrics:", layer.metrics)
print("current accuracy value:", float(layer.metrics[0].result()))
layer.metrics: [<tensorflow.python.keras.metrics.BinaryAccuracy object at 0x7ff68cc73208>]
current accuracy value: 1.0

Tout comme pour add_loss() , ces métriques sont suivies par fit() :

inputs = keras.Input(shape=(3,), name="inputs")
targets = keras.Input(shape=(10,), name="targets")
logits = keras.layers.Dense(10)(inputs)
predictions = LogisticEndpoint(name="predictions")(logits, targets)

model = keras.Model(inputs=[inputs, targets], outputs=predictions)
model.compile(optimizer="adam")

data = {
    "inputs": np.random.random((3, 3)),
    "targets": np.random.random((3, 10)),
}
model.fit(data)
1/1 [==============================] - 0s 2ms/step - loss: 1.0043 - binary_accuracy: 0.0000e+00

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

Vous pouvez éventuellement activer la sérialisation sur vos couches

Si vous avez besoin que vos couches personnalisées soient sérialisables dans le cadre d'un modèle fonctionnel , vous pouvez éventuellement implémenter une méthode get_config() :

class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, self).__init__()
        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):
        return {"units": self.units}


# Now you can recreate the layer from its config:
layer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'units': 64}

Notez que la __init__() de la classe de base Layer prend certains arguments de mot-clé, en particulier un name et un dtype . Il est __init__() de transmettre ces arguments à la classe parente dans __init__() et de les inclure dans la configuration de la couche:

class Linear(keras.layers.Layer):
    def __init__(self, units=32, **kwargs):
        super(Linear, 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(Linear, self).get_config()
        config.update({"units": self.units})
        return config


layer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'name': 'linear_8', 'trainable': True, 'dtype': 'float32', 'units': 64}

Si vous avez besoin de plus de flexibilité lors de la désérialisation de la couche de sa configuration, vous pouvez également remplacer la méthode de classe from_config() . C'est l'implémentation de base de from_config() :

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

Pour en savoir plus sur la sérialisation et l'enregistrement, consultez le guide complet d'enregistrement et de sérialisation des modèles .

Argument d' training privilégié dans la méthode call()

Certaines couches, en particulier la couche BatchNormalization et la couche Dropout , ont des comportements différents pendant l'entraînement et l'inférence. Pour de telles couches, il est courant d'exposer un argument de training (booléen) dans la méthode call() .

En exposant cet argument dans call() , vous activez les boucles d'entraînement et d'évaluation intégrées (par exemple, fit() ) pour utiliser correctement la couche dans l'apprentissage et l'inférence.

class CustomDropout(keras.layers.Layer):
    def __init__(self, rate, **kwargs):
        super(CustomDropout, self).__init__(**kwargs)
        self.rate = rate

    def call(self, inputs, training=None):
        if training:
            return tf.nn.dropout(inputs, rate=self.rate)
        return inputs

Argument de mask privilégié dans la méthode call()

L'autre argument privilégié pris en charge par call() est l'argument mask .

Vous le trouverez dans toutes les couches Keras RNN. Un masque est un tenseur booléen (une valeur booléenne par pas de temps dans l'entrée) utilisé pour ignorer certains pas de temps d'entrée lors du traitement des données de séries temporelles.

Keras passera automatiquement l'argument de mask correct à __call__() pour les calques qui le supportent, lorsqu'un masque est généré par un calque précédent. Les calques générant un masque sont le calque d' Embedding configuré avec mask_zero=True et le calque de Masking .

Pour en savoir plus sur le masquage et comment écrire des calques activés pour le masquage, consultez le guide «Comprendre le remplissage et le masquage» .

La classe Model

En général, vous utiliserez la classe Layer pour définir des blocs de calcul internes et vous utiliserez la classe Model pour définir le modèle externe - l'objet que vous entraînerez.

Par exemple, dans un modèle ResNet50, vous auriez plusieurs blocs ResNet sous-classant la Layer et un seul Model englobant l'ensemble du réseau ResNet50.

La classe Model a la même API que Layer , avec les différences suivantes:

  • Il expose des boucles model.fit() formation, d'évaluation et de prédiction ( model.fit() , model.evaluate() , model.predict() ).
  • Il expose la liste de ses couches internes, via la propriété model.layers .
  • Il expose des API de sauvegarde et de sérialisation ( save() , save_weights() ...)

En effet, la classe Layer correspond à ce que l'on appelle dans la littérature une "couche" (comme dans "convolution layer" ou "recurrent layer") ou comme un "block" (comme dans "ResNet block" ou "Inception block" ).

Pendant ce temps, la classe Model correspond à ce que la littérature appelle un "modèle" (comme dans "deep learning model") ou comme un "network" (comme dans "deep neural network").

Donc, si vous vous demandez, "devrais-je utiliser la classe Layer ou la classe Model ?", Demandez-vous: est-ce que je devrai appeler fit() dessus? Dois-je appeler save() dessus? Si c'est le cas, optez pour Model . Sinon (soit parce que votre classe n'est qu'un bloc dans un système plus grand, soit parce que vous écrivez vous-même la formation et enregistrez le code), utilisez Layer .

Par exemple, nous pourrions prendre notre exemple de mini-resnet ci-dessus et l'utiliser pour construire un Model que nous pourrions entraîner avec fit() , et que nous pourrions enregistrer avec save_weights() :

class ResNet(tf.keras.Model):

    def __init__(self):
        super(ResNet, self).__init__()
        self.block_1 = ResNetBlock()
        self.block_2 = ResNetBlock()
        self.global_pool = layers.GlobalAveragePooling2D()
        self.classifier = Dense(num_classes)

    def call(self, inputs):
        x = self.block_1(inputs)
        x = self.block_2(x)
        x = self.global_pool(x)
        return self.classifier(x)


resnet = ResNet()
dataset = ...
resnet.fit(dataset, epochs=10)
resnet.save(filepath)

Tout rassembler: un exemple de bout en bout

Voici ce que vous avez appris jusqu'à présent:

  • Une Layer encapsule un état (créé dans __init__() ou build() ) et un certain calcul (défini dans call() ).
  • Les couches peuvent être imbriquées de manière récursive pour créer de nouveaux blocs de calcul plus gros.
  • Les couches peuvent créer et suivre des pertes (généralement des pertes de régularisation) ainsi que des métriques, via add_loss() et add_metric()
  • Le conteneur extérieur, ce que vous voulez entraîner, est un Model . Un Model est comme un Layer , mais avec des utilitaires de formation et de sérialisation supplémentaires.

Mettons toutes ces choses ensemble dans un exemple de bout en bout: nous allons implémenter un Variational AutoEncoder (VAE). Nous allons l'entraîner sur les chiffres MNIST.

Notre VAE sera une sous-classe de Model , construite comme une composition imbriquée de couches qui sous-classe Layer . Il comportera une perte de régularisation (divergence KL).

from tensorflow.keras import layers


class Sampling(layers.Layer):
    """Uses (z_mean, z_log_var) to sample z, the vector encoding a digit."""

    def call(self, inputs):
        z_mean, z_log_var = inputs
        batch = tf.shape(z_mean)[0]
        dim = tf.shape(z_mean)[1]
        epsilon = tf.keras.backend.random_normal(shape=(batch, dim))
        return z_mean + tf.exp(0.5 * z_log_var) * epsilon


class Encoder(layers.Layer):
    """Maps MNIST digits to a triplet (z_mean, z_log_var, z)."""

    def __init__(self, latent_dim=32, intermediate_dim=64, name="encoder", **kwargs):
        super(Encoder, self).__init__(name=name, **kwargs)
        self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
        self.dense_mean = layers.Dense(latent_dim)
        self.dense_log_var = layers.Dense(latent_dim)
        self.sampling = Sampling()

    def call(self, inputs):
        x = self.dense_proj(inputs)
        z_mean = self.dense_mean(x)
        z_log_var = self.dense_log_var(x)
        z = self.sampling((z_mean, z_log_var))
        return z_mean, z_log_var, z


class Decoder(layers.Layer):
    """Converts z, the encoded digit vector, back into a readable digit."""

    def __init__(self, original_dim, intermediate_dim=64, name="decoder", **kwargs):
        super(Decoder, self).__init__(name=name, **kwargs)
        self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
        self.dense_output = layers.Dense(original_dim, activation="sigmoid")

    def call(self, inputs):
        x = self.dense_proj(inputs)
        return self.dense_output(x)


class VariationalAutoEncoder(keras.Model):
    """Combines the encoder and decoder into an end-to-end model for training."""

    def __init__(
        self,
        original_dim,
        intermediate_dim=64,
        latent_dim=32,
        name="autoencoder",
        **kwargs
    ):
        super(VariationalAutoEncoder, self).__init__(name=name, **kwargs)
        self.original_dim = original_dim
        self.encoder = Encoder(latent_dim=latent_dim, intermediate_dim=intermediate_dim)
        self.decoder = Decoder(original_dim, intermediate_dim=intermediate_dim)

    def call(self, inputs):
        z_mean, z_log_var, z = self.encoder(inputs)
        reconstructed = self.decoder(z)
        # Add KL divergence regularization loss.
        kl_loss = -0.5 * tf.reduce_mean(
            z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1
        )
        self.add_loss(kl_loss)
        return reconstructed

Écrivons une simple boucle d'entraînement sur MNIST:

original_dim = 784
vae = VariationalAutoEncoder(original_dim, 64, 32)

optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
mse_loss_fn = tf.keras.losses.MeanSquaredError()

loss_metric = tf.keras.metrics.Mean()

(x_train, _), _ = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype("float32") / 255

train_dataset = tf.data.Dataset.from_tensor_slices(x_train)
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

epochs = 2

# Iterate over epochs.
for epoch in range(epochs):
    print("Start of epoch %d" % (epoch,))

    # Iterate over the batches of the dataset.
    for step, x_batch_train in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            reconstructed = vae(x_batch_train)
            # Compute reconstruction loss
            loss = mse_loss_fn(x_batch_train, reconstructed)
            loss += sum(vae.losses)  # Add KLD regularization loss

        grads = tape.gradient(loss, vae.trainable_weights)
        optimizer.apply_gradients(zip(grads, vae.trainable_weights))

        loss_metric(loss)

        if step % 100 == 0:
            print("step %d: mean loss = %.4f" % (step, loss_metric.result()))
Start of epoch 0
step 0: mean loss = 0.3187
step 100: mean loss = 0.1247
step 200: mean loss = 0.0987
step 300: mean loss = 0.0888
step 400: mean loss = 0.0840
step 500: mean loss = 0.0807
step 600: mean loss = 0.0786
step 700: mean loss = 0.0770
step 800: mean loss = 0.0759
step 900: mean loss = 0.0749
Start of epoch 1
step 0: mean loss = 0.0746
step 100: mean loss = 0.0739
step 200: mean loss = 0.0734
step 300: mean loss = 0.0730
step 400: mean loss = 0.0726
step 500: mean loss = 0.0722
step 600: mean loss = 0.0720
step 700: mean loss = 0.0717
step 800: mean loss = 0.0714
step 900: mean loss = 0.0712

Notez que dans la mesure où le VAE sous-classe le Model , il comporte des boucles d'apprentissage intégrées. Vous auriez donc pu aussi l'entraîner comme ceci:

vae = VariationalAutoEncoder(784, 64, 32)

optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)

vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
vae.fit(x_train, x_train, epochs=2, batch_size=64)
Epoch 1/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0747
Epoch 2/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0676

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

Au-delà du développement orienté objet: l'API fonctionnelle

Cet exemple était-il trop de développement orienté objet pour vous? Vous pouvez également créer des modèles à l'aide de l' API fonctionnelle . Surtout, choisir un style ou un autre ne vous empêche pas de tirer parti des composants écrits dans l'autre style: vous pouvez toujours mélanger et assortir.

Par exemple, l'exemple d'API fonctionnelle ci-dessous réutilise la même couche d' Sampling nous avons définie dans l'exemple ci-dessus:

original_dim = 784
intermediate_dim = 64
latent_dim = 32

# Define encoder model.
original_inputs = tf.keras.Input(shape=(original_dim,), name="encoder_input")
x = layers.Dense(intermediate_dim, activation="relu")(original_inputs)
z_mean = layers.Dense(latent_dim, name="z_mean")(x)
z_log_var = layers.Dense(latent_dim, name="z_log_var")(x)
z = Sampling()((z_mean, z_log_var))
encoder = tf.keras.Model(inputs=original_inputs, outputs=z, name="encoder")

# Define decoder model.
latent_inputs = tf.keras.Input(shape=(latent_dim,), name="z_sampling")
x = layers.Dense(intermediate_dim, activation="relu")(latent_inputs)
outputs = layers.Dense(original_dim, activation="sigmoid")(x)
decoder = tf.keras.Model(inputs=latent_inputs, outputs=outputs, name="decoder")

# Define VAE model.
outputs = decoder(z)
vae = tf.keras.Model(inputs=original_inputs, outputs=outputs, name="vae")

# Add KL divergence regularization loss.
kl_loss = -0.5 * tf.reduce_mean(z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1)
vae.add_loss(kl_loss)

# Train.
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
vae.fit(x_train, x_train, epochs=3, batch_size=64)
Epoch 1/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0746
Epoch 2/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0676
Epoch 3/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0676

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

Pour plus d'informations, assurez-vous de lire le guide de l'API fonctionnelle .