Creazione di nuovi livelli e modelli tramite sottoclassi

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica il taccuino

Impostare

import tensorflow as tf
from tensorflow import keras

Il Layer classe: la combinazione di stato (pesi) e alcuni computazione

Uno di astrazione centrale Keras è il Layer di classe. Un livello incapsula sia uno stato (i "pesi" del livello) sia 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

Utilizzeresti un livello chiamandolo su alcuni input del tensore, proprio come una funzione 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)

Si noti che i pesi w e b sono tracciati automaticamente dallo strato su essere insieme come attributi di livello:

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

Nota è anche avere accesso ad un collegamento più rapido per l'aggiunta di peso a un livello: add_weight() Metodo:

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)

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 retropropagazione, quando si addestra il livello.

Ecco come aggiungere e utilizzare un peso non allenabile:

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 del 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: posticipare 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.

Nel API Keras, si consiglia di creare pesi strato nella build(self, inputs_shape) il metodo del vostro 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__() il metodo del vostro livello verrà eseguito automaticamente costruire 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)

Attuazione build() separatamente come mostrato sopra ben separa creando pesi sola volta dal utilizzando pesi in ogni chiamata. Tuttavia, per alcuni livelli personalizzati avanzati, può diventare poco pratico separare la creazione e il calcolo dello stato. Esecutori di livello sono autorizzati a rinviare la creazione di peso per la prima __call__() , ma necessità di prendersi cura che le chiamate successive utilizzano gli stessi pesi. Inoltre, poiché __call__() è suscettibile di essere eseguita per la prima volta in un tf.function , ogni creazione variabile che si svolge in __call__() dovrebbe essere avvolto in un tf.init_scope .

I livelli sono componibili in modo ricorsivo

Se assegni un'istanza di livello come attributo di un altro livello, il livello esterno inizierà a tenere traccia dei pesi creati dal livello interno.

Si consiglia di creare tali sottolivelli nel __init__() metodo e lasciare che sia il primo __call__() per innescare la costruzione di loro pesi.

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

add_loss() metodo

Quando si scrive il call() il metodo di un livello, è possibile creare tensori di perdita che si desidera utilizzare in seguito, quando si scrive il ciclo di formazione. Questo è fattibile 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 (compresi quelli creati da qualsiasi strato interno) possono essere recuperati tramite layer.losses . Questa proprietà è reimpostato all'inizio di ogni __call__() per lo strato di livello superiore, in modo che layer.losses contiene sempre i valori di perdita creati durante l'ultimo passo 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 loss proprietà contiene anche perdite di regolarizzazione creati 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.0024520475>]

Queste perdite devono essere prese in considerazione quando si scrivono loop di allenamento, in questo modo:

# 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 su come scrivere i cicli di formazione, consultare la guida alla scrittura di un ciclo di formazione da zero .

Queste perdite anche lavorare senza problemi con fit() (ottengono automaticamente sommati e aggiunti alla perdita principale, se del 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>

add_metric() metodo

Analogamente a add_loss() , strati hanno un add_metric() metodo per il tracciamento della media mobile di una quantità durante l'allenamento.

Considera il livello seguente: un livello "endpoint logistico". Prende come ingressi previsioni e obiettivi, si calcola un pregiudizio che ascolti tramite add_loss() , e calcola uno scalare 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)

Metriche monitorati 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: [<keras.metrics.BinaryAccuracy object at 0x7fce90578490>]
current accuracy value: 1.0

Proprio come per add_loss() , questi parametri sono monitorati dal 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>

Puoi opzionalmente abilitare la serializzazione sui tuoi livelli

Se avete bisogno dei vostri livelli personalizzati per essere serializzabile come parte di un modello di funzionamento , si può opzionalmente implementare una get_config() Metodo:

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 __init__() metodo della base Layer classe prende alcuni argomenti a parola chiave, in particolare un name e un dtype . E 'buona pratica per passare questi argomenti alla classe padre a __init__() e per includerli nella configurazione strato:

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 avete bisogno di più flessibilità quando si deserializzazione lo strato dalla sua configurazione, si può anche ignorare il from_config() metodo di classe. Questa è la base di attuazione from_config() :

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

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

Privilegiata training argomento nella call() metodo

Alcuni strati, in particolare la BatchNormalization livello e il Dropout strato, hanno comportamenti diversi durante l'allenamento e l'inferenza. Per tali strati, è pratica standard per esporre una training argomento (booleano) nella call() metodo.

Esponendo questo argomento call() , si attiva il (ad esempio integrato nei cicli di formazione e valutazione fit() ) per utilizzare correttamente il livello di formazione e di 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

Privilegiata mask argomento nella call() metodo

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

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 di dati di serie temporali.

Keras passerà automaticamente la corretta mask argomento __call__() per strati che lo supportano, quando una maschera viene generato da uno strato precedente. Livelli maschera generatrici sono Embedding strato configurato con mask_zero=True , e la Masking strato.

Per saperne di più su di mascheramento e come scrivere strati mascheramento abilitato, si prega di consultare la guida "la comprensione imbottitura e mascheramento" .

Il Model di classe

In generale, si utilizzerà il Layer classe per definire blocchi di calcolo interne, e utilizzerà il Model di classe per definire il modello esterno - l'oggetto che si formerà.

Per esempio, in un modello ResNet50, si avrebbe diversi blocchi RESNET sottoclasse Layer , e un singolo Model che comprende l'intera rete ResNet50.

Il Model classe ha la stessa API come Layer , con le seguenti differenze:

  • Espone built-in cicli di formazione, di valutazione e di previsione ( model.fit() , model.evaluate() , model.predict() ).
  • Espone l'elenco dei suoi strati più interni, attraverso la model.layers proprietà.
  • Espone il risparmio e le API di serializzazione ( save() , save_weights() ...)

Effettivamente, i Layer corrisponde classe a ciò ci riferiamo in letteratura come "strato" (come in "strato convoluzione" o "strato ricorrente") o come "blocco" (come in "RESNET blocco" o "blocco Inception" ).

Nel frattempo, le Model corrisponde classe per quello che viene definito in letteratura come un "modello" (come in "modello di apprendimento profondo") o come una "rete" (come in "rete neurale profondo").

Quindi, se vi state chiedendo, "devo usare il Layer classe o il Model di classe?", Chiedetevi: avrò bisogno di chiamare fit() su di esso? Avrò bisogno di chiamare save() su di esso? Se è così, andare con Model . In caso contrario (o perché la classe è solo un blocco in un sistema più grande, o perché si sta scrivendo la formazione e il salvataggio di codice da soli), l'uso Layer .

Per esempio, abbiamo potuto portare il nostro esempio mini-RESNET sopra, e utilizzarlo per costruire un Model che potremmo allenarsi con fit() , e che potremmo risparmiare 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:

  • Uno Layer incapsula uno stato (creato nel __init__() o build() ) e qualche calcolo (definito nella call() ).
  • I livelli possono essere annidati in modo ricorsivo per creare nuovi blocchi di calcolo più grandi.
  • Livelli possono creare e perdite di pista (tipicamente perdite di regolarizzazione), così come metriche, tramite add_loss() e add_metric()
  • Il contenitore esterno, la cosa si vuole allenare, è un Model . Un Model è proprio come un Layer , ma con le utility di formazione e di serializzazione aggiunto.

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

Il nostro VAE sarà una sottoclasse di Model , costruito come una composizione nidificata di livelli 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()))
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

Si noti che, poiché il VAE è sottoclasse Model , è dotato di built-in cicli di formazione. 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 3ms/step - loss: 0.0745
Epoch 2/2
938/938 [==============================] - 3s 3ms/step - loss: 0.0676
<keras.callbacks.History at 0x7fce90282750>

Oltre lo sviluppo orientato agli oggetti: l'API funzionale

Questo esempio è stato uno sviluppo troppo orientato agli oggetti per te? È inoltre possibile 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 combinare e abbinare.

Ad esempio, l'esempio API funzionale sotto riutilizzi lo stesso Sampling strato abbiamo definito nell'esempio precedente:

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>

Per ulteriori informazioni, assicurarsi di leggere la guida API funzionale .