Erstellen neuer Ebenen und Modelle über Unterklassen

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Einrichten

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(
[[ 8.9605771e-02 -5.8132906e-02 -2.3558782e-02 -5.9220940e-05]
 [ 8.9605771e-02 -5.8132906e-02 -2.3558782e-02 -5.9220940e-05]], 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.00164011 -0.02662886  0.00616307  0.0370644 ]
 [-0.00164011 -0.02662886  0.00616307  0.0370644 ]], 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 Unterebenen in der Methode __init__() (da die Unterebenen normalerweise eine Erstellungsmethode haben, werden sie erstellt, wenn die äußere Ebene 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.001774033>]

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`, 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 111ms/step - loss: 0.3057
1/1 [==============================] - 0s 44ms/step - loss: 0.0246
<tensorflow.python.keras.callbacks.History at 0x7f557862c4e0>

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". Es nimmt Vorhersagen und Ziele als add_loss() , berechnet einen Verlust, den es über add_loss() , und berechnet einen Genauigkeitsskalar, 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 0x7f5578831518>]
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 248ms/step - loss: 0.8016 - binary_accuracy: 0.0000e+00
<tensorflow.python.keras.callbacks.History at 0x7f557860d470>

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 Basis- Layer Klasse einige Schlüsselwortargumente verwendet, insbesondere einen name und einen 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 einzelnes 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 obiges Mini-Resnet-Beispiel verwenden, um ein Model zu erstellen, das wir mit fit() trainieren und mit save_weights() speichern 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)

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()))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Start of epoch 0
step 0: mean loss = 0.3603
step 100: mean loss = 0.1262
step 200: mean loss = 0.0997
step 300: mean loss = 0.0895
step 400: mean loss = 0.0845
step 500: mean loss = 0.0810
step 600: mean loss = 0.0789
step 700: mean loss = 0.0773
step 800: mean loss = 0.0761
step 900: mean loss = 0.0750
Start of epoch 1
step 0: mean loss = 0.0748
step 100: mean loss = 0.0741
step 200: mean loss = 0.0736
step 300: mean loss = 0.0731
step 400: mean loss = 0.0728
step 500: mean loss = 0.0724
step 600: mean loss = 0.0721
step 700: mean loss = 0.0718
step 800: mean loss = 0.0715
step 900: mean loss = 0.0713

Beachten Sie, dass die VAE, da sie ein Model Unterklasse ist, über integrierte Trainingsschleifen verfügt. 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 [==============================] - 3s 2ms/step - loss: 0.0945
Epoch 2/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0678
<tensorflow.python.keras.callbacks.History at 0x7f54dc15f550>

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 [==============================] - 3s 2ms/step - loss: 0.0950
Epoch 2/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0677
Epoch 3/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0676
<tensorflow.python.keras.callbacks.History at 0x7f54d00b9630>

Weitere Informationen finden Sie im Handbuch zur funktionalen API .