Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шолле Ф. - Глубокое обучение на Python (Библиотека программиста) - 2023.pdf
Скачиваний:
4
Добавлен:
07.04.2024
Размер:
11.34 Mб
Скачать

12.4. Генерирование изображений с автокодировщиками    487

данных..Давайте.пройдемся.по.реализации.вариационного.автокодировщика. в.Keras..Схематически.она.выглядит.так:

Кодирование входа в среднее и дисперсию

z_mean, z_log_variance = encoder(input_img)

z = z_mean + exp(z_log_variance) * epsilon reconstructed_img = decoder(z) model = Model(input_img, reconstructed_img)

Создание экземпляра модели автокодировщика, которая отобразит входное изображение в его реконструкцию

Извлечение скрытой точки с использованием небольшой случайной величины epsilon

Декодирование z обратно в изображение

Затем.можно.обучить.модель,.использовав.потери.восстановления.и.потери. регуляризации..Для.вычисления.потери.регуляризации.обычно.применяется. выражение.(расхождение.Кульбака.—.Лейблера),.предназначенное.для.под- талкивания.распределения.выходных.данных.кодировщика.к.нормальному. распределению.с.центром.в.точке.0..Это.дает.кодировщику.разумное.предположение.о.структуре.скрытого.пространства,.которое.он.моделирует.

А.теперь.давайте.посмотрим,.как.выглядит.реализация.вариационного.автокодировщика.(VAE).на.практике!

12.4.4. Реализация VAE в Keras

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

.сети.кодировщика,.превращающей.реальное.изображение.в.среднее.и.дисперсию.в.скрытом.пространстве;

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

.сети.декодера,.превращающей.точки.из.скрытого.пространства.обратно. в.изображения.

В.следующем.листинге.демонстрируется.используемая.нами.сеть.кодировщика,. отображающая.изображения.в.параметры.распределения.вероятности.в.скрытом. пространстве..Эта.простая.сверточная.сеть.отображает.входное.изображение.x . в.два.вектора,.z_mean .и.z_log_var..Важно.отметить,.что.для.уменьшения.разрешения.карт.признаков.вместо.выбора.максимального.из.соседних.значений.мы. будем.выполнять.выборку.с.определенным.шагом..В.последний.раз.мы.делали. это.в.примере.сегментации.изображения.в.главе.9..Напомню,.что.в.общем.случае.выборка.с.определенным.шагом.предпочтительнее.выбора.максимального.

Входное изображение кодируется в эти два параметра

488    Глава 12. Генеративное глубокое обучение

из.соседних.значений,.когда.важно.сохранить.информацию о местоположении,. то.есть.о.том,.где.что-то.находится.в.изображении..Это.как.раз.наш.случай,.ведь. модель.должна.закодировать.изображение.так,.чтобы.потом.кодировку.можно. было.использовать.для.восстановления.действительного.изображения.

Листинг 12.24. Сеть кодировщика VAE

from

tensorflow import keras

 

from

tensorflow.keras import layers

Размерность скрытого пространства:

 

 

 

latent_dim = 2

 

двумерная плоскость

 

 

encoder_inputs = keras.Input(shape=(28, 28, 1)) x = layers.Conv2D(

32, 3, activation="relu", strides=2, padding="same")(encoder_inputs) x = layers.Conv2D(64, 3, activation="relu", strides=2, padding="same")(x) x = layers.Flatten()(x)

x = layers.Dense(16, activation="relu")(x)

z_mean = layers.Dense(latent_dim, name="z_mean")(x) z_log_var = layers.Dense(latent_dim, name="z_log_var")(x)

encoder = keras.Model(encoder_inputs, [z_mean, z_log_var], name="encoder")

Вот.сводная.информация.о.получившейся.модели:

>>> encoder.summary() Model: "encoder"

___________________________________________________________________________________

Layer (type) Output Shape Param # Connected to

===================================================================================

input_1 (InputLayer) [(None, 28, 28, 1)] 0

___________________________________________________________________________________

conv2d (Conv2D) (None, 14, 14, 32) 320 input_1[0][0]

___________________________________________________________________________________

conv2d_1 (Conv2D) (None, 7, 7, 64) 18496 conv2d[0][0]

___________________________________________________________________________________

flatten (Flatten) (None, 3136) 0 conv2d_1[0][0]

___________________________________________________________________________________

dense (Dense) (None, 16) 50192 flatten[0][0]

___________________________________________________________________________________

z_mean (Dense) (None, 2) 34 dense[0][0]

___________________________________________________________________________________

z_log_var (Dense) (None, 2) 34 dense[0][0]

===================================================================================

Total params: 69,076 Trainable params: 69,076 Non-trainable params: 0

___________________________________________________________________________________

Далее.приводится.код,.использующий.z_mean.и.z_log_var,.параметры.статистического.распределения,.которое,.как.предполагается,.произвело.input_img,.для. создания.точки.z .скрытого.пространства.

12.4. Генерирование изображений с автокодировщиками    489

Листинг 12.25. Слой выбора точки из скрытого пространства

class Sampler(layers.Layer):

Выбрать набор

 

случайных векторов

def call(self, z_mean, z_log_var):

 

 

import tensorflow as tf

 

 

batch_size = tf.shape(z_mean)[0]

из нормального

распределения

z_size = tf.shape(z_mean)[1]

 

 

epsilon = tf.random.normal(shape=(batch_size, z_size))

 

 

return z_mean + tf.exp(0.5 * z_log_var) * epsilon

Применить

формулу выборки VAE

Следующий.листинг.демонстрирует.реализацию.декодера..Здесь.мы.приводим. размерность.вектора.z .в.соответствие.с.размерами.изображения.и.затем.используем.несколько.сверточных.слоев,.чтобы.получить.выходное.изображение. с.теми.же.размерами,.что.и.оригинальное.input_img.

Листинг 12.26. Сеть декодера VAE, отображающая точки из скрытого пространства в изображения

Произвести столько же коэффициентов, сколько

 

Восстановить

 

имеется на уровне слоя Flatten в кодировщике

 

слой Conv2D

latent_inputs = keras.Input(shape=(latent_dim,))

 

 

Передача z на вход

 

кодировщика

 

 

 

 

x = layers.Dense(7 * 7 * 64, activation="relu")(latent_inputs)

 

 

 

 

 

 

x = layers.Reshape((7, 7, 64))(x)

 

 

 

Восстановить слой Flatten кодировщика

 

 

 

x = layers.Conv2DTranspose(64, 3, activation="relu", strides=2, padding="same")(x) x = layers.Conv2DTranspose(32, 3, activation="relu", strides=2, padding="same")(x)

decoder_outputs = layers.Conv2D(1, 3, activation="sigmoid", padding="same")(x) decoder = keras.Model(latent_inputs, decoder_outputs, name="decoder")

Выход будет иметь форму (28, 28, 1)

Вот.сводная.информация.о.получившейся.модели:

>>> decoder.summary() Model: "decoder"

_________________________________________________________________

Layer (type) Output Shape Param #

=================================================================

input_2 (InputLayer) [(None, 2)] 0

_________________________________________________________________

dense_1 (Dense) (None, 3136) 9408

_________________________________________________________________

reshape (Reshape) (None, 7, 7, 64) 0

_________________________________________________________________

conv2d_transpose (Conv2DTran (None, 14, 14, 64)

36928

_________________________________________________________________

conv2d_transpose_1 (Conv2DTr (None, 28, 28, 32)

18464

_________________________________________________________________

conv2d_2 (Conv2D) (None, 28, 28, 1) 289

=================================================================

Total params: 65,089 Trainable params: 65,089 Non-trainable params: 0

_________________________________________________________________

Добавляем член регуляризации (расхождение Кульбака — Лейблера)
Перечисляем метрики
в свойстве metrics, чтобы модель могла сбрасывать их после каждой эпохи (или между вызовами fit()/evaluate())
Суммируем потери при реконструкции по пространственным
измерениям (оси 1 и 2) и берем их средние значения

490    Глава 12. Генеративное глубокое обучение

Теперь.реализуем.саму.модель.VAE..Это.наш.первый.пример.модели.с.обучением. без.учителя.(в.отличие.от.автокодировщика.—.примера.модели.с.самообучением,. поскольку.в.качестве.целей.он.использует.свои.входные.данные)..Всякий.раз.при. отступлении.от.классического.способа.обучения.с.учителем.обычно.создается. подкласс.класса.Model .и.реализуется.свой.метод.train_step(),.уточняющий. новую.логику.обучения,.—.рабочий.процесс,.с.которым.вы.познакомились. в.главе.7..Этим.мы.и.займемся.

Листинг 12.27. Модель VAE с нестандартным методом train_step()

class VAE(keras.Model):

def __init__(self, encoder, decoder, **kwargs):

super().__init__(**kwargs)

Эти метрики используются

self.encoder = encoder

для слежения за средними

self.decoder = decoder

значениями потерь в каждой эпохе

self.sampler = Sampler()

 

self.total_loss_tracker = keras.metrics.Mean(name="total_loss") self.reconstruction_loss_tracker = keras.metrics.Mean(

name="reconstruction_loss")

self.kl_loss_tracker = keras.metrics.Mean(name="kl_loss")

@property

def metrics(self): return [self.total_loss_tracker,

self.reconstruction_loss_tracker, self.kl_loss_tracker]

def train_step(self, data):

with tf.GradientTape() as tape:

z_mean, z_log_var = self.encoder(data) z = self.sampler(z_mean, z_log_var) reconstruction = decoder(z) reconstruction_loss = tf.reduce_mean(

tf.reduce_sum(

keras.losses.binary_crossentropy(data, reconstruction), axis=(1, 2)

)

)

kl_loss = -0.5 * (1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var))

total_loss = reconstruction_loss + tf.reduce_mean(kl_loss) grads = tape.gradient(total_loss, self.trainable_weights)

self.optimizer.apply_gradients(zip(grads, self.trainable_weights)) self.total_loss_tracker.update_state(total_loss) self.reconstruction_loss_tracker.update_state(reconstruction_loss) self.kl_loss_tracker.update_state(kl_loss)

return {

"total_loss": self.total_loss_tracker.result(), "reconstruction_loss": self.reconstruction_loss_tracker.result(), "kl_loss": self.kl_loss_tracker.result(),

}

Выбрать ячейки, линейно распределенные в двумерной сетке
Выполнить обход ячеек в сетке
Отобразить сетку 30 × 30 цифр (всего 900 цифр)
Обратите внимание, что мы не передаем аргумент loss в вызов compile(), потому что
вычисление потерь выполняется в методе train_step()

12.4. Генерирование изображений с автокодировщиками    491

Наконец,.мы.готовы.создать.и.обучить.экземпляр.модели.на.изображениях.цифр. из.набора.MNIST..Поскольку.вычислением.потерь.у.нас.занимается.собственный.слой,.мы.не.указываем.функцию.потерь.на.этапе.компиляции.(loss=None).. Это,.в.свою.очередь,.означает,.что.нам.не.нужно.передавать.целевые.данные. в.процесс.обучения.(как.можно.заметить,.в.метод.fit .обучаемой.модели.передается.только.x_train).

Листинг 12.28. Обучение VAE

 

Обучение выполняется на полном

 

наборе данных MNIST, поэтому

 

мы объединили обучающий

import numpy as np

и контрольный наборы

(x_train, _), (x_test, _) = keras.datasets.mnist.load_data() mnist_digits = np.concatenate([x_train, x_test], axis=0) mnist_digits = np.expand_dims(mnist_digits, -1).astype("float32") / 255

vae = VAE(encoder, decoder) vae.compile(optimizer=keras.optimizers.Adam(), run_eagerly=True) vae.fit(mnist_digits, epochs=30, batch_size=128)

Помните, что мы не передаем цели в метод fit(), поскольку они не используются в train_step()

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

Листинг 12.29. Выбор сетки с изображениями из двумерного скрытого пространства

import matplotlib.pyplot as plt

n = 30 digit_size = 28

figure = np.zeros((digit_size * n, digit_size * n))

grid_x = np.linspace(-1, 1, n) grid_y = np.linspace(-1, 1, n)[::-1]

for i, yi in enumerate(grid_y):

for j, xi in enumerate(grid_x): z_sample = np.array([[xi, yi]])

x_decoded = vae.decoder.predict(z_sample)

digit = x_decoded[0].reshape(digit_size, digit_size) figure[

i * digit_size : (i + 1) * digit_size, j * digit_size : (j + 1) * digit_size,

] = digit

Для каждой ячейки выбрать цифру и добавить в изображение

492    Глава 12. Генеративное глубокое обучение

plt.figure(figsize=(15, 15)) start_range = digit_size // 2

end_range = n * digit_size + start_range

pixel_range = np.arange(start_range, end_range, digit_size) sample_range_x = np.round(grid_x, 1)

sample_range_y = np.round(grid_y, 1) plt.xticks(pixel_range, sample_range_x) plt.yticks(pixel_range, sample_range_y) plt.xlabel("z[0]")

plt.ylabel("z[1]")

plt.axis("off") plt.imshow(figure, cmap="Greys_r")

Сетка.выбранных.цифр.(рис..12.18).демонстрирует.полностью.непрерывное. распределение.разных.классов.цифр,.где.одна.цифра.превращается.в.другую.по. пути.через.непрерывное.скрытое.пространство..Конкретные.направления.в.этом. пространстве.наделены.определенным.смыслом:.например,.есть.направление. «пятерочности»,.«единичности».и.т..д.

Рис. 12.18. Сетка с цифрами, декодированными из скрытого пространства