Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Making new Layers & Models via subclassing

Ansicht auf TensorFlow.org Führen Sie in Google Colab aus Quelle auf GitHub anzeigen Notizbuch herunterladen

Konfiguration

import tensorflow as tf
from tensorflow import keras

Die Layer Klasse: Die Kombination aus Status (Gewichten) und einigen Berechnungen

Eine der zentralen Abstraktionen in Keras ist die Layer Klasse. Eine Ebene kapselt sowohl einen Zustand (die "Gewichte" der Ebene)) als auch eine Transformation von Eingaben zu Ausgaben (ein "Aufruf", den Vorwärtsdurchlauf der Ebene).

Hier ist eine dicht verbundene Schicht. Es hat einen Zustand: die Variablen w und 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

Sie würden eine Ebene verwenden, indem Sie sie für einige Tensoreingaben aufrufen, ähnlich wie bei einer Python-Funktion.

x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[-0.00892124  0.03003723  0.01141541 -0.13389507]
 [-0.00892124  0.03003723  0.01141541 -0.13389507]], shape=(2, 4), dtype=float32)

Beachten Sie, dass die Gewichte w und b automatisch von der Ebene verfolgt werden, wenn sie als Ebenenattribute festgelegt werden:

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

Beachten Sie, dass Sie auch Zugriff auf eine schnellere Verknüpfung zum Hinzufügen von Gewicht zu einer Ebene haben: die Methode 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.01266684  0.01941528 -0.09573359  0.03471692]
 [-0.01266684  0.01941528 -0.09573359  0.03471692]], shape=(2, 4), dtype=float32)

Ebenen können nicht trainierbare Gewichte haben

Neben trainierbaren Gewichten können Sie einer Ebene auch nicht trainierbare Gewichte hinzufügen. Solche Gewichte sollten beim Backpropagation beim Training der Schicht nicht berücksichtigt werden.

So fügen Sie ein nicht trainierbares Gewicht hinzu und verwenden es:

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.]

Es ist Teil von layer.weights , wird jedoch als nicht trainierbares Gewicht eingestuft:

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: []

Best Practice: Verschieben der Gewichtserstellung, bis die Form der Eingaben bekannt ist

Unsere obige Linear Ebene hat ein input_dim Argument verwendet, mit dem die Form der Gewichte w und b in __init__() berechnet wurde:

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

In vielen Fällen kennen Sie die Größe Ihrer Eingaben möglicherweise nicht im Voraus und möchten einige Zeit nach dem Instanziieren der Ebene träge Gewichte erstellen, wenn dieser Wert bekannt wird.

In der Keras-API empfehlen wir build(self, inputs_shape) in der build(self, inputs_shape) Methode build(self, inputs_shape) Ihrer Ebene zu build(self, inputs_shape) . So was:

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

Die __call__() -Methode Ihrer Ebene wird beim ersten Aufruf automatisch erstellt. Sie haben jetzt eine Ebene, die faul und damit einfacher zu verwenden ist:

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

Ebenen sind rekursiv zusammensetzbar

Wenn Sie eine Ebeneninstanz als Attribut einer anderen Ebene zuweisen, beginnt die äußere Ebene mit der Verfolgung der Gewichte der inneren Ebene.

Wir empfehlen, solche Unterschichten in der Methode __init__() (da die Unterschichten normalerweise eine Erstellungsmethode haben, werden sie erstellt, wenn die äußere Schicht erstellt wird).

# 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

Die Methode add_loss()

Wenn Sie die call() -Methode einer Ebene schreiben, können Sie Verlusttensoren erstellen, die Sie später beim Schreiben Ihrer Trainingsschleife verwenden möchten. Dies ist durch Aufrufen von 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

Diese Verluste (einschließlich der Verluste, die durch eine innere Schicht verursacht werden) können über layer.losses abgerufen werden. Diese Eigenschaft wird zu Beginn jedes __call__() auf die oberste Ebene zurückgesetzt, sodass layer.losses immer die Verlustwerte enthält, die während des letzten Vorwärtsdurchlaufs erstellt wurden.

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

Darüber hinaus enthält die loss auch Regularisierungsverluste, die für die Gewichte einer inneren Schicht erzeugt werden:

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.0019264814>]

Diese Verluste sollen beim Schreiben von Trainingsschleifen wie folgt berücksichtigt werden:

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

Eine ausführliche Anleitung zum Schreiben von Trainingsschleifen finden Sie in der Anleitung zum Schreiben einer Trainingsschleife von Grund auf neu .

Diese Verluste funktionieren auch nahtlos mit fit() (sie werden automatisch summiert und gegebenenfalls zum Hauptverlust addiert):

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.2169
1/1 [==============================] - 0s 875us/step - loss: 0.0396

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

Die Methode add_metric()

Ähnlich wie bei add_loss() verfügen Layer auch über eine add_metric() -Methode zum Verfolgen des gleitenden Durchschnitts einer Menge während des Trainings.

Betrachten Sie die folgende Ebene: eine Ebene "Logistischer Endpunkt". Als Eingaben werden Vorhersagen und Ziele verwendet, ein Verlust berechnet, den es über add_loss() , und es wird ein Genauigkeitsskalar berechnet, den es über 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)

Auf diese Weise verfolgte Metriken sind über layer.metrics zugänglich:

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 0x7fa7f03601d0>]
current accuracy value: 1.0

Genau wie bei add_loss() werden diese Metriken von 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 1ms/step - loss: 0.9958 - binary_accuracy: 0.0000e+00

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

Sie können optional die Serialisierung auf Ihren Ebenen aktivieren

Wenn Ihre benutzerdefinierten Ebenen als Teil eines Funktionsmodells serialisierbar sein müssen, können Sie optional eine get_config() -Methode implementieren:

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}

Beachten Sie, dass die __init__() Methode der Layer Klasse einige Keyword Argumente annimmt, insbesondere einen name und ein dtype . Es wird __init__() , diese Argumente in __init__() an die übergeordnete Klasse zu __init__() und sie in die Layerkonfiguration aufzunehmen:

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}

Wenn Sie mehr Flexibilität beim Deserialisieren der Ebene aus ihrer Konfiguration benötigen, können Sie auch die from_config() überschreiben. Dies ist die from_config() von from_config() :

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

Weitere Informationen zum Serialisieren und Speichern finden Sie in der vollständigen Anleitung zum Speichern und Serialisieren von Modellen .

Privilegiertes training in der call() -Methode

Einige Ebenen, insbesondere die BatchNormalization Ebene und die Dropout Ebene, verhalten sich während des Trainings und der Inferenz unterschiedlich. Für solche Ebenen ist es üblich, ein training (boolesches Argument) in der call() -Methode verfügbar zu machen.

Indem Sie dieses Argument in call() verfügbar machen, aktivieren Sie die integrierten Trainings- und Bewertungsschleifen (z. B. fit() ), um die Ebene für Training und Inferenz korrekt zu verwenden.

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

Privilegiertes mask in der call() -Methode

Das andere privilegierte Argument, das von call() unterstützt wird call() ist das mask .

Sie finden es in allen Keras RNN-Schichten. Eine Maske ist ein boolescher Tensor (ein boolescher Wert pro Zeitschritt in der Eingabe), der zum Überspringen bestimmter Eingabezeitschritte bei der Verarbeitung von Zeitreihendaten verwendet wird.

Keras wird automatisch die richtige passieren mask Argument __call__() für die Schichten , die sie unterstützen, wenn eine Maske durch eine vorherige Schicht erzeugt wird. mask_zero=True Ebenen sind die mit mask_zero=True konfigurierte Embedding und die Masking .

Weitere Informationen zum Maskieren und zum Schreiben maskierungsfähiger Ebenen finden Sie in der Anleitung "Grundlegendes zum Auffüllen und Maskieren" .

Die Model

Im Allgemeinen verwenden Sie die Layer Klasse, um innere Berechnungsblöcke zu definieren, und verwenden die Model Klasse, um das äußere Modell zu definieren - das Objekt, das Sie trainieren werden.

In einem ResNet50-Modell hätten Sie beispielsweise mehrere ResNet-Blöcke, die Layer unterordnen, und ein einziges Model das das gesamte ResNet50-Netzwerk umfasst.

Die Model Klasse hat dieselbe API wie Layer mit den folgenden Unterschieden:

  • Es stellt integrierte Trainings-, Evaluierungs- und Vorhersageschleifen model.fit() , model.evaluate() , model.predict() ).
  • Die Liste der inneren Ebenen wird über die Eigenschaft model.layers .
  • Es macht APIs zum Speichern und Serialisieren save_weights() save() , save_weights() ...)

Tatsächlich entspricht die Layer dem, was wir in der Literatur als "Schicht" (wie in "Faltungsschicht" oder "wiederkehrende Schicht") oder als "Block" (wie in "ResNet-Block" oder "Inception-Block") bezeichnen. ).

Inzwischen hat das Model der Klasse entspricht , was in der Literatur bezeichnet wird als „Modell“ (wie in „deep Lernmodell“) oder als „Netzwerk“ (wie in „deep neuronales Netz“).

Wenn Sie sich also fragen: "Soll ich die Layer Klasse oder die Model Klasse verwenden?", Fragen Sie sich: Muss ich fit() darauf aufrufen? Muss ich save() darauf aufrufen? Wenn ja, gehen Sie mit Model . Wenn nicht (entweder weil Ihre Klasse nur ein Block in einem größeren System ist oder weil Sie selbst Trainings- und Speichercode schreiben), verwenden Sie Layer .

Zum Beispiel könnten wir unser Beispiel für ein Mini-Resnet oben verwenden und daraus ein Model erstellen, das wir mit fit() trainieren und mit save_weights() speichern 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)

Alles zusammen: ein End-to-End-Beispiel

Folgendes haben Sie bisher gelernt:

  • Eine Layer kapselt einen Status (erstellt in __init__() oder build() ) und eine Berechnung (definiert in call() ).
  • Ebenen können rekursiv verschachtelt werden, um neue, größere Rechenblöcke zu erstellen.
  • Ebenen können Verluste (normalerweise Regularisierungsverluste) sowie Metriken über add_loss() und add_metric() erstellen und verfolgen.
  • Der äußere Container, das, was Sie trainieren möchten, ist ein Model . Ein Model ist wie eine Layer , verfügt jedoch über zusätzliche Dienstprogramme für Schulung und Serialisierung.

Lassen Sie uns all diese Dinge zu einem End-to-End-Beispiel zusammenfassen: Wir werden einen Variational AutoEncoder (VAE) implementieren. Wir werden es auf MNIST-Ziffern trainieren.

Unsere VAE wird eine Unterklasse von Model , die als verschachtelte Zusammensetzung von Ebenen dieser Unterklasse Layer . Es wird einen Regularisierungsverlust (KL-Divergenz) aufweisen.

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

Schreiben wir eine einfache Trainingsschleife auf 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.3052
step 100: mean loss = 0.1252
step 200: mean loss = 0.0990
step 300: mean loss = 0.0890
step 400: mean loss = 0.0841
step 500: mean loss = 0.0808
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.0714
step 900: mean loss = 0.0712

Da die VAE das Model Unterklasse ist, verfügt sie über integrierte Trainingsschleifen. Du hättest es also auch so trainieren können:

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.0749
Epoch 2/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0676

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

Jenseits der objektorientierten Entwicklung: die funktionale API

War Ihnen dieses Beispiel zu viel objektorientierte Entwicklung? Sie können Modelle auch mithilfe der Funktions-API erstellen. Wichtig ist, dass die Auswahl des einen oder anderen Stils Sie nicht daran hindert, Komponenten zu nutzen, die im anderen Stil geschrieben wurden: Sie können jederzeit mischen und anpassen.

Im folgenden Beispiel für die funktionale API wird beispielsweise dieselbe Sampling Schicht wiederverwendet, die wir im obigen Beispiel definiert haben:

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.0751
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 0x7fa7580f1668>

Weitere Informationen finden Sie im Handbuch zur funktionalen API .