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

Hacer nuevas capas y modelos mediante subclases

Ver en TensorFlow.org Ver fuente en GitHub Descargar cuaderno

Preparar

import tensorflow as tf
from tensorflow import keras

La clase 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 las entradas a las 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.06229124 0.02534804 0.00851583 0.00457605]
 [0.06229124 0.02534804 0.00851583 0.00457605]], shape=(2, 4), dtype=float32)

Tenga en cuenta que la capa realiza un seguimiento automático de 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 atajo 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.06930272  0.03181376 -0.07157768  0.07838295]
 [ 0.06930272  0.03181376 -0.07157768  0.07838295]], 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, se explica 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 capa Linear anterior tomó un argumento input_dim que se usó 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 de Keras, recomendamos crear pesos de capa en el método 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 ejecutará automáticamente build 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 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 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 querrá usar más adelante, cuando escriba su ciclo de entrenamiento. Esto es posible 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 mediante layer.losses . Esta propiedad se restablece al comienzo de cada __call__() a la capa de nivel superior, de modo que layer.losses siempre contiene los valores de pérdida creados durante la última pasada 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.002361052>]

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 ciclos de entrenamiento, consulte la guía para escribir un ciclo de entrenamiento desde cero .

Estas pérdidas también funcionan sin problemas 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.2552
1/1 [==============================] - 0s 893us/step - loss: 0.0427

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

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

Al igual que para add_loss() , fit() seguimiento de estas métricas:

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 2ms/step - loss: 1.0043 - binary_accuracy: 0.0000e+00

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

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

Si necesita que sus capas personalizadas sean serializables como parte de un modelo funcional , opcionalmente puede implementar 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 Layer base toma algunos argumentos de palabras clave, en particular un name y un dtype . Es una buena práctica pasar estos argumentos a la clase principal 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 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 de entrenamiento y evaluación integrados (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 privilegiada en el método call()

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

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 el argumento de mask correcto a __call__() para las capas que lo admitan, 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 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 ResNet subclasificando la Layer , y un solo Model abarcara toda la red ResNet50.

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

  • Expone bucles 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 llamamos en la literatura una "capa" (como en "capa de convolución" o "capa recurrente") o como un "bloque" (como en "Bloque ResNet" o "Bloque de inicio" ).

Mientras tanto, la clase Model corresponde a lo que se denomina en la literatura como un "modelo" (como en "modelo de aprendizaje profundo") o como una "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() ? ¿Tendré que llamar a save() ? Si es así, vaya con 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 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)

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

Esto es lo que ha 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 de forma recursiva para crear bloques de cálculo nuevos y más grandes.
  • Las capas pueden crear y rastrear pérdidas (típicamente pérdidas de regularización) así como métricas, a través de add_loss() y add_metric()
  • El contenedor exterior, lo que quieres entrenar, es un Model . Un Model es como una Layer , pero con utilidades de serialización y entrenamiento adicionales.

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.

Nuestro VAE será una subclase de Model , construida 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.3187
step 100: mean loss = 0.1247
step 200: mean loss = 0.0987
step 300: mean loss = 0.0888
step 400: mean loss = 0.0840
step 500: mean loss = 0.0807
step 600: mean loss = 0.0786
step 700: mean loss = 0.0770
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.0739
step 200: mean loss = 0.0734
step 300: mean loss = 0.0730
step 400: mean loss = 0.0726
step 500: mean loss = 0.0722
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 es un Model subclases, presenta bucles de entrenamiento integrados. Así que 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.0747
Epoch 2/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0676

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

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

¿Fue este ejemplo demasiado desarrollo orientado a objetos para usted? También puede crear 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.0746
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 0x7ff658204588>

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