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

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

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

Installer

import tensorflow as tf
from tensorflow import keras

La Layer de classe: la combinaison de l' État (poids) et un certain calcul

L' un de l'abstraction centrale dans Keras est la Layer classe. 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 dispose d' un: 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 de tenseur, 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.00962844 -0.01307489 -0.1452128   0.0538918 ]
 [ 0.00962844 -0.01307489 -0.1452128   0.0538918 ]], shape=(2, 4), dtype=float32)

On notera que les coefficients de pondération w et b sont détectés automatiquement par la couche à être mis sous forme d' attributs de couche:

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

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

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.05790994  0.060931   -0.0402256  -0.09450993]
 [ 0.05790994  0.060931   -0.0402256  -0.09450993]], 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. De tels poids sont destinés à ne 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 se catégorisé comme un poids non trainable:

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 intrants soit connue

Notre Linear couche ci - dessus a un input_dim argument 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 poids lorsque cette valeur est connue, quelque temps après l'instanciation de la couche.

Dans l'API Keras, nous vous recommandons de créer des poids de la couche dans la build(self, inputs_shape) méthode 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__() méthode de votre couche construirons exécute automatiquement la première fois qu'il est appelé. Vous avez maintenant un calque paresseux 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)

La mise en œuvre build() séparément , comme indiqué ci - dessus se sépare bien que la création de poids une fois d'utiliser des poids dans tous les appels. Cependant, pour certaines couches personnalisées avancées, il peut devenir difficile de séparer la création et le calcul de l'état. Layer implémenteurs sont autorisés à différer la création de poids à la première __call__() , mais besoin de prendre soin que plus tard les appels utilisent les mêmes poids. De plus, étant donné que __call__() est susceptible d'être exécuté pour la première fois dans une tf.function , toute création de variable qui a lieu dans __call__() devrait être enveloppé dans un tf.init_scope .

Les calques sont récursivement composables

Si vous attribuez une instance de couche en tant qu'attribut d'une autre couche, la couche externe commencera à suivre les poids créés par la couche interne.

Nous vous recommandons de créer ces sous - couches dans la __init__() méthode et le laisser à la première __call__() pour déclencher la construction de leur poids.

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

Le add_loss() Méthode

Lors de l' écriture l' call() méthode d'une couche, vous pouvez créer des tenseurs de perte que vous voulez utiliser plus tard, lors de l' écriture de votre boucle de formation. 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 ceux qui sont créés par une couche interne) peuvent être récupérées par l' intermédiaire d' layer.losses . Cette propriété est remis à zéro au début de chaque __call__() à la couche de niveau supérieur, de sorte que layer.losses contient toujours les valeurs de perte créées au cours de la dernière passe en 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 loss propriété 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.0024520475>]

Ces pertes sont censé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 des boucles de formation, consultez le guide pour écrire une boucle de formation à partir de zéro .

Ces pertes fonctionnent également de manière transparente avec fit() (ils se résument automatiquement et ajoutés à 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`, the 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 209ms/step - loss: 0.1948
1/1 [==============================] - 0s 49ms/step - loss: 0.0298
<keras.callbacks.History at 0x7fce9052d290>

Le add_metric() Méthode

De manière similaire à add_loss() , les couches ont également un add_metric() méthode pour le suivi de la moyenne d'une quantité en mouvement pendant l' entraînement.

Considérez la couche suivante : une couche « point final logistique ». Il faut que les prévisions et les cibles entrées, il calcule une perte dont 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)

Metrics chenillés 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: [<keras.metrics.BinaryAccuracy object at 0x7fce90578490>]
current accuracy value: 1.0

Tout comme pour add_loss() , ces mesures 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 274ms/step - loss: 0.9291 - binary_accuracy: 0.0000e+00
<keras.callbacks.History at 0x7fce90448c50>

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

Si vous avez besoin de vos couches personnalisées sérialisable dans le cadre d'un modèle fonctionnel , vous pouvez éventuellement mettre en œuvre un get_config() méthode:

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__() méthode de la base Layer classe prend des arguments clés, notamment un name et un dtype . Il est bon 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 d'une plus grande flexibilité lors de la désérialisation la couche de sa configuration, vous pouvez également remplacer la from_config() méthode de classe. Ceci est la mise en œuvre de la base from_config() :

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

Pour en savoir plus sur la sérialisation et de sauver, voir le complet guide pour sauver et modèles sérialisation .

Privileged training argument dans l' call() méthode

Certaines couches, en particulier la BatchNormalization couche et la Dropout de Dropout couche, ont des comportements différents au cours de la formation et l' inférence. Pour ces couches, il est pratique courante d'exposer une training argument (booléen) dans l' call() méthode.

En exposant cet argument dans l' call() , vous activez intégré dans les boucles de formation et d' évaluation (par exemple en fit() ) d'utiliser correctement la couche dans la formation 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

Privilégié mask argument dans l' call() Méthode

L'autre argument privilégié soutenu par call() est le mask argument.

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

Keras passera automatiquement le bon mask argument pour __call__() pour les couches qui le supportent, lorsqu'un masque est générée par une couche antérieure. Masque couches génératrices sont Embedding couche configurée avec mask_zero=True , et le Masking couche.

Pour en savoir plus sur le masquage et la façon d'écrire des couches de masquage activé, s'il vous plaît consulter le guide « comprendre le rembourrage et le masquage » .

Le Model classe

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

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

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

  • Il expose intégré dans la formation, l' évaluation et des boucles de prédiction ( model.fit() , model.evaluate() , model.predict() ).
  • Il expose la liste de ses couches internes, par la model.layers propriété.
  • Il expose l' épargne et les API de sérialisation ( save() , save_weights() ...)

En effet, la Layer correspond de classe à ce que nous appelons dans la littérature comme une « couche » (comme dans « la couche de convolution » ou « couche récurrente ») ou comme un « bloc » (comme dans « ResNet bloc » ou « bloc Inception » ).

Pendant ce temps, le Model correspond de classe à ce qu'on appelle dans la littérature comme un « modèle » (comme dans « modèle d'apprentissage en profondeur ») ou comme un « réseau » (comme dans « réseau de neurones profond »).

Donc , si vous vous demandez, « dois - je utiliser la Layer de classe ou Model classe? », Demandez - vous: dois - je appeler en fit() là - dessus? Ai - je besoin d'appeler save() là - dessus? Si oui, rendez - vous avec le Model . Dans le cas contraire ( que ce soit parce que votre classe est juste un bloc dans un système plus grand, ou parce que vous écrivez vous - même le code et la formation d' économie), l' utilisation de Layer .

Par exemple, nous pourrions prendre notre exemple mini-ResNet ci - dessus, et l' utiliser pour construire un Model que nous pourrions former avec fit() , et que nous pourrions sauver avec save_weights() :

class ResNet(tf.keras.Model):

    def __init__(self, num_classes=1000):
        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 assembler : un exemple de bout en bout

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

  • Une Layer encapsuler un état (créé en __init__() ou build() ) et un certain calcul (défini dans l' 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 pertes de piste (généralement des pertes de régularisation), ainsi que des mesures, via add_loss() et add_metric()
  • Le récipient extérieur, la chose que vous voulez former, est un Model . Un Model est tout comme une Layer , mais avec formation ajoutée et les services publics sérialisation.

Regroupons toutes ces choses 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 , construit comme une composition imbriquée de couches que la 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 boucle d'entraînement simple 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.3184
step 100: mean loss = 0.1252
step 200: mean loss = 0.0989
step 300: mean loss = 0.0890
step 400: mean loss = 0.0841
step 500: mean loss = 0.0807
step 600: mean loss = 0.0787
step 700: mean loss = 0.0771
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.0740
step 200: mean loss = 0.0735
step 300: mean loss = 0.0730
step 400: mean loss = 0.0727
step 500: mean loss = 0.0723
step 600: mean loss = 0.0720
step 700: mean loss = 0.0717
step 800: mean loss = 0.0715
step 900: mean loss = 0.0712

Notez que depuis la VAE est le sous - classement Model , il fonctionnalités intégrées dans les boucles de formation. Donc, vous auriez 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 [==============================] - 3s 3ms/step - loss: 0.0745
Epoch 2/2
938/938 [==============================] - 3s 3ms/step - loss: 0.0676
<keras.callbacks.History at 0x7fce90282750>

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 en utilisant 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 de l' API fonctionnelle ci - dessous réutilise le même Sampling de la couche nous avons défini 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 [==============================] - 3s 3ms/step - loss: 0.0748
Epoch 2/3
938/938 [==============================] - 3s 3ms/step - loss: 0.0676
Epoch 3/3
938/938 [==============================] - 3s 3ms/step - loss: 0.0676
<keras.callbacks.History at 0x7fce90233cd0>

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