Hai una domanda? Connettiti con la community al forum TensorFlow Visita il forum

Creazione di nuovi livelli e modelli tramite la creazione di sottoclassi

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub Scarica taccuino

Impostare

import tensorflow as tf
from tensorflow import keras

La classe Layer : la combinazione di stato (pesi) e alcuni calcoli

Una delle astrazioni centrali di Keras è la classe Layer . Un livello incapsula sia uno stato (i "pesi" del livello) e una trasformazione da input a output (una "chiamata", il passaggio in avanti del livello).

Ecco uno strato densamente connesso. Ha uno stato: le variabili w e 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

Usereste un livello chiamandolo su alcuni input tensoriali, in modo molto simile a una funzione Python.

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)

Notare che i pesi w e b vengono tracciati automaticamente dal livello una volta impostati come attributi del livello:

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

Nota che hai anche accesso a una scorciatoia più rapida per aggiungere peso a un livello: il metodo 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)

I livelli possono avere pesi non allenabili

Oltre ai pesi allenabili, puoi aggiungere anche pesi non allenabili a un livello. Tali pesi non devono essere presi in considerazione durante la propagazione all'indietro, quando si allena il livello.

Ecco come aggiungere e utilizzare un peso non addestrabile:

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

Fa parte di layer.weights , ma viene classificato come un peso non addestrabile:

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: differire la creazione del peso fino a quando non si conosce la forma degli input

La nostra Linear livello sopra ha preso una input_dim argomento che è stato utilizzato per calcolare la forma dei pesi w e b a __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

In molti casi, potresti non conoscere in anticipo la dimensione dei tuoi input e vorresti creare pigramente pesi quando quel valore diventa noto, qualche tempo dopo aver istanziato il livello.

Nell'API di Keras, ti consigliamo di creare pesi dei livelli nel metodo build(self, inputs_shape) del tuo livello. Come questo:

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

Il __call__() del tuo livello verrà eseguito automaticamente build la prima volta che viene chiamato. Ora hai un livello pigro e quindi più facile da usare:

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

I livelli sono componibili in modo ricorsivo

Se si assegna un'istanza di livello come attributo di un altro livello, il livello esterno inizierà a tracciare i pesi del livello interno.

Raccomandiamo di creare tali sottolivelli nel metodo __init__() (poiché i sottolivelli avranno tipicamente un metodo di compilazione, verranno creati quando verrà costruito il livello esterno).

# 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

Il metodo add_loss()

Quando si scrive il metodo call() di un livello, è possibile creare tensori di perdita che si vorranno utilizzare in seguito, durante la scrittura del ciclo di addestramento. Ciò è possibile chiamando 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

Queste perdite (comprese quelle create da qualsiasi strato interno) possono essere recuperate tramite layer.losses . Questa proprietà viene ripristinata all'inizio di ogni __call__() al livello di livello superiore, in modo che layer.losses contenga sempre i valori di perdita creati durante l'ultimo passaggio in avanti.

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

Inoltre, la proprietà loss contiene anche le perdite di regolarizzazione create per i pesi di qualsiasi strato interno:

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

Queste perdite dovrebbero essere prese in considerazione quando si scrivono cicli di addestramento, come questo:

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

Per una guida dettagliata sulla scrittura di cicli di addestramento, vedere la guida per scrivere un ciclo di addestramento da zero .

Queste perdite funzionano anche perfettamente con fit() (vengono automaticamente sommate e aggiunte alla perdita principale, se presente):

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>

Il metodo add_metric()

Analogamente a add_loss() , i layer hanno anche un metodo add_metric() per monitorare la media mobile di una quantità durante l'addestramento.

Considera il livello seguente: un livello "endpoint logistico". Prende come input previsioni e obiettivi, calcola una perdita che tiene traccia tramite add_loss() e calcola uno scalare di precisione, che tiene traccia tramite 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)

Le metriche tracciate in questo modo sono accessibili tramite 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 0x7f5578831518>]
current accuracy value: 1.0

Proprio come per add_loss() , queste metriche vengono tracciate da 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>

Facoltativamente, puoi abilitare la serializzazione sui tuoi livelli

Se hai bisogno che i tuoi livelli personalizzati siano serializzabili come parte di un modello funzionale , puoi opzionalmente implementare un metodo 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}

Si noti che il metodo __init__() della classe Layer base accetta alcuni argomenti di parole chiave, in particolare un name e un dtype . È buona norma passare questi argomenti alla classe genitore in __init__() e includerli nella configurazione del livello:

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}

Se hai bisogno di maggiore flessibilità durante la deserializzazione del livello dalla sua configurazione, puoi anche sovrascrivere il metodo della classe from_config() . Questa è l'implementazione di base di from_config() :

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

Per ulteriori informazioni sulla serializzazione e il salvataggio, vedere la guida completa al salvataggio e alla serializzazione dei modelli .

Argomento di training privilegiato nel metodo call()

Alcuni livelli, in particolare il livello BatchNormalization e il livello Dropout , hanno comportamenti diversi durante l'addestramento e l'inferenza. Per tali livelli, è pratica standard esporre un argomento di training (booleano) nel metodo call() .

Esponendo questo argomento in call() , abiliti i cicli di addestramento e valutazione incorporati (ad esempio fit() ) per utilizzare correttamente il livello nell'addestramento e nell'inferenza.

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

Argomento mask privilegiato nel metodo call()

L'altro argomento privilegiato supportato da call() è l'argomento mask .

Lo troverai in tutti i livelli Keras RNN. Una maschera è un tensore booleano (un valore booleano per timestep nell'input) utilizzato per saltare determinati timestep di input durante l'elaborazione dei dati delle serie temporali.

Keras passerà automaticamente l'argomento mask corretto a __call__() per i livelli che lo supportano, quando una maschera viene generata da un livello precedente. I livelli che generano la maschera sono il livello di Embedding configurato con mask_zero=True e il livello di Masking .

Per saperne di più sul mascheramento e su come scrivere livelli abilitati al mascheramento, consultare la guida "Comprensione del riempimento e del mascheramento" .

La classe Model

In generale, utilizzerai la classe Layer per definire i blocchi di calcolo interni e utilizzerai la classe Model per definire il modello esterno, l'oggetto che addestrerai.

Ad esempio, in un modello ResNet50, avresti diversi blocchi ResNet sottoclasse Layer e un unico Model comprende l'intera rete ResNet50.

La classe Model ha la stessa API di Layer , con le seguenti differenze:

  • Espone cicli integrati di addestramento, valutazione e previsione ( model.fit() , model.evaluate() , model.predict() ).
  • Espone l'elenco dei suoi livelli interni, tramite la proprietà model.layers .
  • Espone le API di salvataggio e serializzazione ( save() , save_weights() ...)

In effetti, la classe Layer corrisponde a ciò a cui ci riferiamo in letteratura come "livello" (come in "livello di convoluzione" o "livello ricorrente") o come "blocco" (come in "blocco ResNet" o "blocco Inception" ).

Nel frattempo, la classe Model corrisponde a ciò che in letteratura viene definito un "modello" (come in "modello di apprendimento profondo") o come una "rete" (come in "rete neurale profonda").

Quindi, se ti stai chiedendo, "dovrei usare la classe Layer o la classe Model ?", Chiediti: dovrò chiamare fit() su di essa? Dovrò chiamare save() su di esso? Se è così, vai con Model . In caso contrario (o perché la tua classe è solo un blocco in un sistema più grande, o perché stai scrivendo formazione e salvando il codice da solo), usa Layer .

Ad esempio, potremmo prendere il nostro esempio di mini-resnet sopra e usarlo per costruire un Model che potremmo addestrare con fit() e che potremmo salvare 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)

Mettere tutto insieme: un esempio end-to-end

Ecco cosa hai imparato finora:

  • Un Layer incapsula uno stato (creato in __init__() o build() ) e alcuni calcoli (definiti in call() ).
  • I livelli possono essere nidificati ricorsivamente per creare blocchi di calcolo nuovi e più grandi.
  • I livelli possono creare e tracciare le perdite (in genere le perdite di regolarizzazione) e le metriche, tramite add_loss() e add_metric()
  • Il contenitore esterno, la cosa che vuoi addestrare, è un Model . Un Model è proprio come un Layer , ma con funzionalità aggiuntive di formazione e serializzazione.

Mettiamo insieme tutte queste cose in un esempio end-to-end: implementeremo un Variational AutoEncoder (VAE). Lo addestreremo su cifre MNIST.

Il nostro VAE sarà una sottoclasse di Model , costruita come una composizione nidificata di strati che sottoclasse Layer . Sarà caratterizzato da una perdita di regolarizzazione (divergenza 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

Scriviamo un semplice ciclo di allenamento su 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

Si noti che, poiché VAE è una sottoclasse del Model , presenta cicli di addestramento incorporati. Quindi potresti anche averlo addestrato in questo modo:

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>

Oltre lo sviluppo orientato agli oggetti: l'API funzionale

Questo esempio è stato troppo sviluppo orientato agli oggetti per te? Puoi anche creare modelli utilizzando l' API funzionale . È importante sottolineare che la scelta di uno stile o di un altro non ti impedisce di sfruttare i componenti scritti nell'altro stile: puoi sempre mescolare e abbinare.

Ad esempio, l'esempio di API funzionale di seguito riutilizza lo stesso livello di Sampling che abbiamo definito nell'esempio sopra:

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>

Per ulteriori informazioni, assicurati di leggere la Guida alle API funzionali .