Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Haciendo nuevas capas & amp; Modelos por subclases

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

Preparar

 import tensorflow as tf
from tensorflow import keras
 

La clase de Layer : la combinación de estado (pesos) y algunos cálculos

Una de las abstracciones centrales en Keras es la clase Layer . Una capa encapsula tanto un estado (los "pesos" de la capa) como una transformación de entradas a salidas (una "llamada", el paso 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.00892124  0.03003723  0.01141541 -0.13389507]
 [-0.00892124  0.03003723  0.01141541 -0.13389507]], shape=(2, 4), dtype=float32)

Tenga en cuenta que la capa rastrea automáticamente los pesos w y b al establecerse como atributos de capa:

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

Tenga en cuenta que también tiene acceso a un acceso directo más rápido para agregar peso a una capa: el método 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)

Las capas pueden tener pesos no entrenables

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

Aquí le mostramos 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: []

Mejor práctica: aplazar la creación de peso hasta que se conozca la forma de las entradas

Nuestra capa Linear anterior tomó un argumento input_dim 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 pesos de forma perezosa cuando se conozca ese valor, algún tiempo después de crear una instancia de la capa.

En la API de Keras, recomendamos crear pesos de capa en el método de build(self, inputs_shape) de su capa. Me gusta 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__() de su capa se ejecutará automáticamente la primera vez que se llame. 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)
 

Las capas son recursivamente compostables

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

Recomendamos crear tales subcapas en el __init__() (dado que las subcapas generalmente tendrán un método de construcción, se construirán cuando se construya la capa externa).

 # 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

El método add_loss()

Al escribir el método call() de una capa, puede crear tensores de pérdida que deseará usar más adelante, al escribir su ciclo de entrenamiento. Esto se puede hacer llamando a 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 (incluidas las creadas por cualquier capa interna) se pueden recuperar a través de layer.losses . Esta propiedad se restablece al comienzo de cada __call__() en la capa de nivel superior, de modo que layer.losses siempre contiene los valores de pérdida creados durante el último paso 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 propiedad de loss también contiene 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.0019264814>]

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 obtener una guía detallada sobre cómo escribir bucles de capacitación, consulte la guía para escribir un bucle de capacitación desde cero .

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

 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>

El método add_metric()

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

Considere la siguiente capa: una capa de "punto final logístico". Toma como entradas predicciones y objetivos, calcula una pérdida que rastrea a través de add_loss() , y calcula un escalar de precisión, que rastrea a través de 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)

 

Las métricas rastreadas de esta manera son accesibles 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: [<tensorflow.python.keras.metrics.BinaryAccuracy object at 0x7fa7f03601d0>]
current accuracy value: 1.0

Al igual que para add_loss() , estas métricas son rastreadas 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 1ms/step - loss: 0.9958 - binary_accuracy: 0.0000e+00

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

Opcionalmente, puede habilitar la serialización en sus capas

Si necesita que sus capas personalizadas sean serializables como parte de un modelo funcional , puede implementar opcionalmente un método get_config() :

 class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, self).__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

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

    def get_config(self):
        return {"units": self.units}


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

Tenga en cuenta que el __init__() de la clase de Layer base toma algunos argumentos de palabras clave, en particular un name y un tipo de dtype . Es una buena práctica 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 desde su configuración, también puede anular el método de la clase from_config() . 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 guardado, consulte la guía completa para guardar y serializar modelos .

Argumento de training privilegiado en el método call()

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

Al exponer este argumento en call() , habilita los bucles integrados de entrenamiento y evaluación (por ejemplo, fit() ) para usar correctamente la capa en el entrenamiento 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

 

Argumento de mask privilegiado en el método call()

El otro argumento privilegiado soportado por call() es el argumento de mask .

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

Keras pasará automáticamente el argumento de mask correcto a __call__() para las capas que lo admiten, cuando una capa anterior genera una máscara. Las capas generadoras de máscaras son la capa de Embedding configurada con mask_zero=True y la capa de Masking .

Para obtener más información sobre el enmascaramiento y cómo escribir capas habilitadas para el enmascaramiento, consulte la guía "comprensión del relleno y el enmascaramiento" .

La clase Model

En general, usará la clase Layer para definir bloques de cálculo internos, y usará la clase Model para definir el modelo externo, el objeto que entrenará.

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

La clase Model tiene la misma API que Layer , con las siguientes diferencias:

  • Expone bucles incorporados de entrenamiento, evaluación y predicción ( model.fit() , model.evaluate() , model.predict() ).
  • Expone la lista de sus capas internas, a través de la propiedad model.layers .
  • Expone las API de almacenamiento y serialización ( save() , save_weights() ...)

Efectivamente, la clase Layer corresponde a lo que en la literatura denominamos "capa" (como en "capa de convolución" o "capa recurrente") o como "bloque" (como en "bloque ResNet" o "bloque de inicio" )

Mientras tanto, la clase Model corresponde a lo que en la literatura se denomina "modelo" (como en "modelo de aprendizaje profundo") o como "red" (como en "red neuronal profunda").

Entonces, si se está preguntando, "¿debería usar la clase Layer o la clase Model ?", Pregúntese: ¿tendré que llamar a fit() en él? ¿Tendré que llamar a save() en él? Si es así, ve con el Model . Si no es así (ya sea porque su clase es solo un bloque en un sistema más grande o porque usted mismo está escribiendo un código de entrenamiento y guardado), use Layer .

Por ejemplo, podríamos tomar nuestro ejemplo de mini-resnet anterior, y usarlo para construir un Model que podríamos entrenar con fit() , y que podríamos guardar con 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)
 

Poniendo todo junto: un ejemplo de extremo a extremo

Esto es lo que has aprendido hasta ahora:

  • Una Layer encapsula un estado (creado en __init__() o build() ) y algunos cálculos (definidos en call() ).
  • Las capas se pueden anidar recursivamente para crear nuevos bloques de cálculo más grandes.
  • Las capas pueden crear y rastrear pérdidas (generalmente pérdidas de regularización), así como métricas, a través de add_loss() y add_metric()
  • El contenedor externo, lo que quieres entrenar, es un Model . Un Model es como una Layer , pero con capacitación adicional y utilidades de serialización.

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

Nuestro VAE será una subclase de Model , construida como una composición anidada de capas que subclase Layer . Contará con 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.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

Tenga en cuenta que, dado que el VAE está subclasificando el Model , presenta bucles de entrenamiento integrados. 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 [==============================] - 2s 2ms/step - loss: 0.0749
Epoch 2/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0676

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

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

¿Fue este ejemplo demasiado desarrollo orientado a objetos para usted? También puede 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 siguiente ejemplo de API funcional reutiliza la misma capa de Sampling que definimos 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 [==============================] - 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>

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