Ayuda a proteger la Gran Barrera de Coral con TensorFlow en Kaggle Únete Challenge

Crear nuevas capas y modelos mediante subclases

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Configuración

import tensorflow as tf
from tensorflow import keras

La Layer clase: la combinación de Estado (pesos) y algunos cómputo

Uno de los abstracción central en Keras es la Layer la clase. Una capa encapsula tanto un estado (los "pesos" de la capa) como una transformación de entradas a salidas (una "llamada", el pase directo de la capa).

Aquí hay una capa densamente conectada. Tiene un estado: las variables w y 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

Usaría una capa llamándola en algunas entradas de tensor, como una función de 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)

Tenga en cuenta que los pesos w y b se realiza un seguimiento de forma automática por la capa en su constitución como atributos de capa:

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

Tenga en cuenta que también tiene acceso a un atajo más rápido para añadir peso a una capa: la add_weight() método:

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)

Las capas pueden tener pesos no entrenables

Además de los pesos entrenables, también puede agregar pesos no entrenables a una capa. Estos pesos no deben tenerse en cuenta durante la propagación hacia atrás, cuando está entrenando la capa.

A continuación, le indicamos cómo agregar y usar un peso no entrenable:

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 parte de layer.weights , pero se clasifica como un peso no entrenable:

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

Práctica recomendada: diferir la creación del peso hasta que se conozca la forma de las entradas

Nuestra Linear capa por encima tomó una input_dim argumento que se utilizó para calcular la forma de los pesos w y b en __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

En muchos casos, es posible que no sepa de antemano el tamaño de sus entradas y le gustaría crear ponderaciones de manera perezosa cuando ese valor se conozca, algún tiempo después de crear una instancia de la capa.

En la API Keras, se recomienda la creación de pesos de capa en la build(self, inputs_shape) método de su capa. Como esto:

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

El __call__() método de su capa se ejecutará automáticamente construir la primera vez que se llama. Ahora tiene una capa que es perezosa y, por lo tanto, más fácil de usar:

# 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 implementación de build() por separado como se muestra arriba bien separa la creación de pesas sólo una vez desde el uso de pesos en cada llamada. Sin embargo, para algunas capas personalizadas avanzadas, puede resultar poco práctico separar la creación del estado y el cálculo. Ejecutores de capa se permite aplazar la creación peso a la primera __call__() , pero es necesario tener cuidado de que las llamadas posteriores utilizan los mismos pesos. Además, puesto que __call__() es susceptible de ser ejecutado por primera vez dentro de una tf.function , cualquier creación variable que tiene lugar en __call__() debe ser envuelto en un tf.init_scope .

Las capas se pueden componer de forma recursiva

Si asigna una instancia de Capa como atributo de otra Capa, la capa externa comenzará a rastrear los pesos creados por la capa interna.

Recomendamos la creación de tales subcapas en la __init__() método y dejarlo a la primera __call__() de desencadenar la construcción de sus pesos.

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

El add_loss() método

Al escribir la call() método de una capa, puede crear tensores de pérdida que usted tendrá que usar más tarde, al escribir su bucle de entrenamiento. Esto es factible llamando 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

Estas pérdidas (incluyendo los creados por cualquier capa interior) pueden ser recuperados a través layer.losses . Esta propiedad se pone a cero al comienzo de cada __call__() a la capa de nivel superior, de manera que layer.losses contiene siempre los valores de pérdida creados durante el último pase hacia adelante.

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

Además, la loss propiedad contiene también las pérdidas de regularización creadas para los pesos de cualquier capa interna:

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

Estas pérdidas deben tenerse en cuenta al escribir bucles de entrenamiento, como este:

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

Para una guía detallada sobre cómo escribir bucles de formación, consulte la guía para escribir un bucle de formación a partir de cero .

Estas pérdidas también funcionan a la perfección con fit() (consiguen suman automáticamente y se agregan a la pérdida principal, en su caso):

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>

El add_metric() método

De manera similar a add_loss() , las capas también tienen un add_metric() método para el seguimiento de la media móvil de una cantidad durante el entrenamiento.

Considere la siguiente capa: una capa de "punto final logístico". Se toma como entradas las predicciones y objetivos, se calcula una pérdida que hace un seguimiento de vía add_loss() , y se calcula un escalar precisión, lo que hace un seguimiento de vía 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)

Métricas de orugas de esta manera se puede acceder a través de 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

Al igual que para add_loss() , estas métricas se realiza por 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>

Opcionalmente, puede habilitar la serialización en sus capas.

Si necesita que sus capas personalizadas para ser serializable como parte de un modelo funcional , se puede aplicar opcionalmente un get_config() método:

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}

Tenga en cuenta que el __init__() método de la base de Layer clase tiene algunos argumentos de palabras clave, en particular, un name y una dtype . Es una buena práctica para pasar estos argumentos a la clase padre en __init__() e incluirlos en la configuración de la capa:

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 necesita más flexibilidad al deserializar la capa de su configuración, también puede anular la from_config() método de clase. Esta es la implementación base de from_config() :

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

Para obtener más información sobre la serialización y el ahorro, consulte la completa guía para el ahorro y la serialización de los modelos .

Privilegiado training argumento en la call() método

Algunas capas, en particular la BatchNormalization capa y la Dropout capa, tienen diferentes comportamientos durante el entrenamiento y la inferencia. Para tales capas, es una práctica estándar para exponer una training argumento (booleano) en el call() método.

Al exponer este argumento en call() , se habilita la (por ejemplo, una función de bucles de formación y evaluación fit() ) para utilizar correctamente la capa en la formación y la inferencia.

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

Privilegiada mask argumento en la call() método

El otro argumento privilegiada con el apoyo de call() es la mask argumento.

Lo encontrará en todas las capas de Keras RNN. Una máscara es un tensor booleano (un valor booleano por paso de tiempo en la entrada) que se usa para omitir ciertos pasos de tiempo de entrada al procesar datos de series temporales.

Keras pasará automáticamente la correcta mask argumento para __call__() para las capas que lo soportan, cuando una máscara se genera por una capa anterior. Las capas de máscara de generación son la Embedding capa configurado con mask_zero=True , y el Masking capa.

Para obtener más información sobre el enmascaramiento y la forma de escribir habilitado capas de enmascaramiento, por favor echa un vistazo a la guía de "entender el relleno y el enmascaramiento" .

El Model de clases

En general, se utiliza la Layer de clase para definir bloques de cálculo internas, y usará el Model de clase para definir el modelo exterior - el objeto que va a entrenar.

Por ejemplo, en un modelo ResNet50, que tendría varios bloques ResNet de subclases Layer , y un solo Model que abarca toda la red ResNet50.

El Model clase tiene el mismo API Layer , con las siguientes diferencias:

  • Expone una función de bucles de formación, evaluación y predicción ( model.fit() , model.evaluate() , model.predict() ).
  • Se expone la lista de sus capas internas, a través de la model.layers propiedad.
  • Expone el ahorro y la API de serialización ( save() , save_weights() ...)

Efectivamente, los Layer de clase corresponde a lo que nos referimos en la literatura como una "capa" (como en "capa de convolución" o "capa recurrente") o como un "bloque" (como en "ResNet bloque" o "bloque de inicio" ).

Mientras tanto, los Model corresponde a la clase lo que se conoce en la literatura como un "modelo" (como en "modelo de aprendizaje profundo") o como una "red" (como en "red neuronal") de profundidad.

Así que si usted se está preguntando, "debería utilizar la Layer la clase o el Model de clase?", Se pregunta: ¿necesitaré a la llamada de fit() en él? Voy a tener que llamar save() en él? Si es así, ir con Model . Si no es así (ya sea debido a que su clase es sólo un bloque en un sistema más grande, o porque usted está escribiendo la formación y el ahorro de código usted mismo), el uso Layer .

Por ejemplo, podríamos tener nuestro ejemplo mini-resnet arriba, y lo utilizan para construir un Model que podríamos entrenar con fit() , y que podríamos ahorrar con 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)

Poniéndolo todo junto: un ejemplo de principio a fin

Esto es lo que ha aprendido hasta ahora:

  • Una Layer encapsular un estado (creado en __init__() o de build() ) y algún cálculo (definido en call() ).
  • Las capas se pueden anidar de forma recursiva para crear bloques de cálculo nuevos y más grandes.
  • Capas pueden crear y pérdidas de pista (típicamente pérdidas de regularización), así como métricas, a través de add_loss() y add_metric()
  • El recipiente exterior, lo que desea entrenar, es un Model . Un Model es como una Layer , pero con los servicios públicos de formación y de serialización añadido.

Pongamos todas estas cosas juntas en un ejemplo de extremo a extremo: vamos a implementar un AutoEncoder Variacional (VAE). Lo entrenaremos en dígitos MNIST.

Nuestra VAE será una subclase de Model , construido como una composición anidada de capas que subclase Layer . Presentará una pérdida de regularización (divergencia 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

Escribamos un ciclo de entrenamiento simple en 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

Tenga en cuenta que, dado que el VAE se subclassing Model , que cuenta con una función de bucles de formación. Entonces también podrías haberlo entrenado así:

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>

Más allá del desarrollo orientado a objetos: la API funcional

¿Fue este ejemplo demasiado desarrollo orientado a objetos para usted? También es posible construir modelos utilizando la API funcional . Es importante destacar que elegir un estilo u otro no le impide aprovechar los componentes escritos en el otro estilo: siempre puede mezclar y combinar.

Por ejemplo, el ejemplo API funcional continuación vuelve a utilizar el mismo Sampling capa que se define en el ejemplo anterior:

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>

Para obtener más información, asegúrese de leer la guía API funcional .