Создание новых слоев и моделей через создание подклассов

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Настраивать

import tensorflow as tf
from tensorflow import keras

Layer класс: сочетание государственных (весы) и некоторые вычисления

Один из центральной абстракции Keras является Layer класса. Слой инкапсулирует как состояние («веса» слоя), так и преобразование входных данных в выходные («вызов», прямой проход уровня).

Вот плотносвязный слой. Он имеет состояние: переменные w и 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

Вы могли бы использовать слой, вызывая его на некоторых входных данных тензора, очень похоже на функцию 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)

Обратите внимание , что вес w и b автоматически отслеживаются слоем при задаются как атрибуты слоя:

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

Обратите внимание , у вас также есть доступ к более быстрому ярлыка для добавления веса к слою: в 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.05790994  0.060931   -0.0402256  -0.09450993]
 [ 0.05790994  0.060931   -0.0402256  -0.09450993]], shape=(2, 4), dtype=float32)

Слои могут иметь необучаемые веса

Помимо обучаемых весов, вы также можете добавить к слою необучаемые веса. Такие веса не должны учитываться во время обратного распространения, когда вы тренируете слой.

Вот как добавить и использовать необучаемый вес:

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

Это часть layer.weights , но он получает классифицируется как не обучаемый вес:

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

Лучшая практика: откладывание создания весов до тех пор, пока не станет известна форма входных данных.

Наши Linear слой над принимал input_dim аргумент , который был использован для вычисления формы весов w и b в __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

Во многих случаях вы можете не знать заранее размер ваших входных данных, и вы хотели бы лениво создавать веса, когда это значение становится известным, через некоторое время после создания экземпляра слоя.

В API Keras, мы рекомендуем создавать вес слоя в build(self, inputs_shape) метода вашего слоя. Нравится:

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

__call__() метод вашего слоя будет автоматически запускаться построить первый раз , когда он называется. Теперь у вас есть ленивый слой, который проще в использовании:

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

Реализация build() отдельно , как показано выше , хорошо отделяет создание весов только один раз с использованием весов в каждом вызове. Однако для некоторых расширенных пользовательских слоев может оказаться непрактичным разделять создание состояния и вычисление. Исполнители слоя разрешены отложить создание веса до первого __call__() , но нужно позаботиться о том , последующие вызовы используют одни и тот же вес. Кроме того, так как __call__() , вероятно, будет выполнена в первый раз внутри tf.function , любое создание переменной , которая имеет место в __call__() должны быть завернуты в tf.init_scope .

Слои рекурсивно компонуются

Если вы назначите экземпляр слоя как атрибут другого слоя, внешний слой начнет отслеживать веса, созданные внутренним слоем.

Мы рекомендуем создавать такие подуровни в __init__() метод и оставить его на первый __call__() для запуска здания их веса.

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() метод

При написании call() метода слоя, вы можете создать тензоры потери , которые вы хотите использовать в дальнейшем, при написании цикла обучения. Это выполнимо по телефону 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

Эти потери ( в том числе тех , которые созданы любой внутренний слой) могут быть получены с помощью layer.losses . Это свойство сбрасывается в начале каждого __call__() к слою верхнего уровня, так что layer.losses всегда содержит значение потери , созданное в течение последнего прохода вперед.

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

Кроме того, loss свойство также содержит потери регуляризации , созданные для весов любого внутреннего слоя:

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

Эти потери должны учитываться при написании тренировочных циклов, например:

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

Для подробного руководства о написании учебных циклов, см руководство по написанию учебного цикла с нуля .

Эти потери также работать совместно с fit() (они получают автоматически суммируются и добавляются к основной потери, если таковые имеются):

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() метод

Аналогично add_loss() , слои также имеют add_metric() метод для отслеживания скользящего среднего значения некоторого количества во время тренировки.

Рассмотрим следующий уровень: уровень «логической конечной точки». Он принимает в качестве входных сигналов предсказания и цели, он вычисляет потери , которые он отслеживает с помощью add_loss() , и он вычисляет точность скаляр, который она отслеживает с помощью 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)

Метрики отслеживаемые таким образом доступны через 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

Так же , как и для add_loss() , эти показатели отслеживаются 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>

При желании вы можете включить сериализацию на своих слоях.

Если вам нужны пользовательские слои сериализовать в рамках функциональной модели можно дополнительно реализовать 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}

Обратите внимание , что __init__() метод базового Layer класса принимает некоторые именованные аргументы, в частности, name и dtype . Это хорошая практика , чтобы передать эти аргументы родительского класса в __init__() и включить их в слой конфигурации:

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}

Если вам нужно больше гибкости при десериализации слоя от его конфигурации, вы можете также переопределить from_config() метод класса. Это базовая реализация from_config() :

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

Чтобы узнать больше о сериализации и экономии, увидеть полное руководство по экономии и сериализации модели .

Льготное training аргумент в call() метод

Некоторые слои, в частности BatchNormalization слой и Dropout слой, имеют различные модели поведения во время тренировки и умозаключений. Для таких слоев, это является стандартной практикой подвергать training (булево) аргумент в call() метода.

Выставляя этот аргумент в call() , включить встроенную в обучении и оценке контуров (например , fit() ) , чтобы правильно использовать слой в обучении и умозаключений.

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

Привилегированное mask аргумент в call() метод

Другой привилегированный аргумент поддерживается call() является mask аргумента.

Вы найдете его во всех слоях Keras RNN. Маска — это логический тензор (одно логическое значение на каждый временной шаг во входных данных), используемый для пропуска определенных входных временных шагов при обработке данных временных рядов.

Keras автоматически передаст правильные mask аргумента __call__() для слоев , которые поддерживают его, когда маска генерируются предыдущим слоем. Маска генерирующие слои являются Embedding уровня , выполненный с mask_zero=True , и Masking слоя.

Чтобы узнать больше о маскирующих и как писать маскировки с поддержкой слоев, пожалуйста , проверьте руководство «понимание отступов и маскирующий» .

Model класса

В общем, вы будете использовать Layer класс для определения внутренних блоков вычислений, и будете использовать Model класс для определения внешней модели - объект , который будет обучать.

Например, в модели ResNet50, вы бы несколько RESNET блоков подклассов Layer , и один Model , охватывающем всю сеть ResNet50.

Model класс имеет тот же интерфейс , как Layer , со следующими отличиями:

  • Он выставляет встроенные циклы обучения, оценки и прогнозирования ( model.fit() , model.evaluate() , model.predict() ).
  • Он предоставляет список своих внутренних слоев, через model.layers свойства.
  • Он предоставляет сохранение и сериализации API ( save() , save_weights() ...)

Эффективно, что Layer класс соответствует тому , что мы называем в литературе как «слой» (как в «свертке слоя» или «рецидивирующий слой») , либо как «блок» (как в «RESNET блока» или «Начальный блок» ).

Между тем, Model класса соответствует тому , что упоминается в литературе как «модель» (как в «глубокой модели обучения») или как «сеть» (как в «глубокой нейронной сети»).

Так что если вам интересно, «я должен использовать Layer класс или Model класса?», Спросите себя: мне нужно , чтобы позвонить fit() на нем? Нужно ли мне позвонить save() на нем? Если да, то идти с Model . Если нет (либо потому , что ваш класс только блок в более крупной системе, или потому , что вы пишете обучение и сохранение коды самостоятельно), используйте Layer .

Например, мы могли бы взять наш пример мини-RESNET выше, и использовать его , чтобы построить Model , чтобы мы могли тренироваться с fit() , и что мы могли бы спасти с 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)

Собираем все вместе: сквозной пример

Вот что вы узнали на данный момент:

  • Layer инкапсулировать состояние (созданные в __init__() или build() ) и некоторые вычисления (определенные в call() ).
  • Слои могут быть рекурсивно вложены друг в друга для создания новых, более крупных вычислительных блоков.
  • Слои могут создавать и потерю дорожки ( как правило , потери регуляризирующих), а также показатели, с помощью add_loss() и add_metric()
  • Внешний контейнер, то , что вы хотите тренироваться, это Model . Model точно так же как Layer , но с дополнительными учебными и сериализации коммунальных услуг.

Давайте объединим все эти вещи в сквозной пример: мы собираемся реализовать вариационный автоэнкодер (VAE). Мы будем обучать его на цифрах MNIST.

Наш VAE будет подкласс Model , построенной в виде вложенного состава слоев этого подкласса Layer . Он будет иметь потерю регуляризации (расхождение 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

Напишем простой обучающий цикл на 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

Обратите внимание , что поскольку VAE является подклассов Model , он имеет встроенный в учебных циклах. Таким образом, вы могли бы также обучить его следующим образом:

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>

Помимо объектно-ориентированной разработки: функциональный API

Был ли этот пример слишком много объектно-ориентированной разработки для вас? Кроме того, можно строить модели с использованием функционального API . Важно отметить, что выбор того или иного стиля не мешает вам использовать компоненты, написанные в другом стиле: вы всегда можете смешивать и сочетать.

Например, функциональный пример API , ниже кратное использования того же Sampling слой мы определили в приведенном выше примере:

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>

Для получения дополнительной информации, не забудьте прочитать Functional руководство по API .