Это 16-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления
Deep Learning with Python
Эта статья — одна из серии заметок, которые я написал, изучая Deep Learning with Python (второе издание, Франсуа Шолле). Содержимое статьи конвертировано из блокнотов Jupyter в Markdown, вы можете перейти наGitHubилиGiteeнайти оригинал.ipynb
ноутбук.
ты можешь идтиЧитайте оригинальный текст этой книги онлайн на этом сайте(Английский). Автор этой книги также дает соответствиеJupyter notebooks.
Эта статьяГлава 8 Генеративное глубокое обучение (Chapter 8. Generative deep learning) одной из записок.
Генерация изображений с помощью вариационных автоэнкодеров
8.4 Generating images with variational autoencoders
И DeepDream, и Neural Style Transfer, представленные в первых двух статьях, лишь ограниченно «модифицируют» существующие работы. GAN и VAE, которые мы представим ниже, являются более творческими, оба из них представляют собой методы, которые выбирают из скрытого пространства изображения и создают совершенно новые изображения или редактируют существующие.
- VAE: вариационный автоэнкодер
- GAN: генеративно-состязательная сеть
Выборка из скрытого пространства
Скрытое пространство — это векторное пространство, в котором любая точка может быть отображена в реалистичное изображение. Модуль, который реализует это сопоставление (скрытая точка -> изображение), является генератором GAN или декодером VAE.
Ключом к созданию изображений с помощью GAN и VAE является поиск низкоразмерного «скрытого пространства представлений». Как только такое скрытое пространство найдено, взято из него и сопоставлено с пространством изображения, может быть сгенерировано совершенно новое изображение.
Существует большая разница в скрытом пространстве, изученном GAN и VAE:
- VAE хороши для изучения хорошо структурированного скрытого пространства, где определенная ориентация кодирует (представляет) ось значимого изменения данных.
- Изображения, генерируемые GAN, могут быть очень реалистичными, но скрытому пространству не хватает хорошей структуры и недостаточной непрерывности.
Концептуальный вектор
Концептуальный вектор: учитывая скрытое пространство представлений или пространство вложений, определенные направления в пространстве могут представлять значимые оси изменения исходных данных. Например, для изображения может быть вектор (называемый вектором улыбки), представляющий понятие «улыбка» в скрытом пространстве изображения лица: для потенциальной точки z, представляющей лицо, z+s — это тот же человек. Представление с улыбкой на лице.
Найдя несколько таких векторов понятий, мы можем отредактировать изображение следующим образом: спроецировав изображение в скрытое пространство, оперируя вектором понятий для перемещения его представления, а затем декодируя его в пространство изображения, мы можем изменить изображение на определенное понятие - например, степень улыбки:
Вариационный автоэнкодер
Автоэнкодер — это тип сети, которая берет изображение, отображает его в «скрытом пространстве» через модуль кодировщика, а затем декодирует его в выходные данные того же размера, что и исходное изображение, через модуль декодера. Цель обучения этой штуке — сделать вывод и ввод одинаковыми, поэтому мы используем одно и то же изображение для ввода и вывода. То, что узнает автоэнкодер, — это реконструкция исходного ввода.
Налагая ограничения на кодирование (выход кодировщика), автокодировщик может изучить полезное скрытое представление данных. Например, ограничьте кодирование низкоразмерным и разреженным, чтобы кодировщик мог сжимать входные данные до меньшего количества битов информации:
Вариационный автоэнкодер VAE — современный автоэнкодер. Это генеративная модель, специально предназначенная для редактирования изображений с использованием концептуальных векторов. По сравнению с классическими автоэнкодерами VAE могут изучать более непрерывное, хорошо структурированное скрытое пространство.
Вместо того, чтобы сжимать входное изображение в фиксированную кодировку в скрытом пространстве, VAE преобразует изображение в параметры статистического распределения — среднее значение и дисперсию. Декодирование VAE использует среднее значение и дисперсию для случайной выборки элемента из распределения и декодирования этого элемента в исходный вход. Поэтому процесс кодирования/декодирования VAE имеет определенную случайность.
Случайность этого процесса повышает надежность скрытого пространства VAE: VAE должно гарантировать, что каждая точка, отобранная в скрытом пространстве, может быть декодирована в действительный вывод, что заставляет любое местоположение в скрытом пространстве соответствовать осмысленному представлению.
На приведенной выше диаграмме показано, как работает VAE:
- Блок Encoder будет вводить сэмплы
input_img
Преобразовано для представления параметров в скрытом пространствеz_mean
иz_log_variance
; - Случайным образом выберите точку z из этого основного нормального распределения:
z = z_mean + exp(z_log_variance) * epsilon
, где эпсилон — небольшой случайный тензор; - Модуль декодера сопоставляет эту скрытую точку с исходным входным изображением.
epsilon является случайным, поэтому каждая точка, которая должна быть близка к скрытой позиции (z-mean), закодированной input_img, может быть декодирована как изображение, подобное input_img, Это свойство заставляет скрытое пространство постоянно иметь смысл: любые две точки в скрытое пространство Каждая соседняя точка будет декодирована как очень похожее изображение. Непрерывность, а также низкая размерность латентного пространства, в свою очередь, вынуждают каждое направление в латентном пространстве представлять осмысленную ось изменения данных, которой затем можно манипулировать с помощью концептуальных векторов.
Псевдокод для реализации VAE с помощью 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, reconstruced_img)
Для обучения VAE требуются две функции потерь:
- Потеря реконструкции: сделайте так, чтобы декодированные образцы соответствовали исходному входу;
- Потеря регуляризации: сделайте скрытое пространство хорошей структурой (непрерывность, доступность концептуального вектора), а также уменьшите переоснащение обучающих данных;
Прежде чем приступить к написанию кода, отключите режим выполнения «точно в срок»:
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
Мы специально реализуем сеть кодировщика: через свёрточную нейронную сеть входное изображение x сопоставляется с двумя векторами z_mean и z_log_var:
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import backend as K
from tensorflow.keras.models import Model
import numpy as np
img_shape = (28, 28, 1)
batch_size = 16
latent_dim = 2 # 潜在空间的维度:2D平面
input_img = keras.Input(shape=img_shape)
x = layers.Conv2D(32, 3, padding='same', activation='relu')(input_img)
x = layers.Conv2D(64, 3, padding='same', activation='relu', strides=(2, 2))(x)
x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
shape_before_flattening = K.int_shape(x)
x = layers.Flatten()(x)
x = layers.Dense(32, activation='relu')(x)
z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)
Следующий код будет использовать z_mean и z_log_var для генерации (выборки) скрытой точки пространства z.
# 潜在空间采样的函数
def sampling(args):
z_mean, z_log_var = args
epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim),
mean=0.,
stddev=1.)
return z_mean + K.exp(z_log_var) * epsilon
z = layers.Lambda(sampling)([z_mean, z_log_var]) # 封装为层
Затем идет реализация декодера: изменение размера вектора z до размера изображения, затем использование нескольких сверточных слоев для получения окончательного вывода изображения.
# VAE 解码器网络
decoder_input = layers.Input(K.int_shape(z)[1:])
x = layers.Dense(np.prod(shape_before_flattening[1:]),
activation='relu')(decoder_input)
x = layers.Reshape(shape_before_flattening[1:])(x)
x = layers.Conv2DTranspose(32, 3,
padding='same',
activation='relu',
strides=(2, 2))(x)
x = layers.Conv2D(1, 3,
padding='same',
activation='sigmoid')(x)
decoder = Model(decoder_input, x)
z_decoded = decoder(z)
VAE использует две потери, поэтому его нельзя записать напрямую какloss(input, target)
, нам нужно написать собственный слой, в котором мы используем встроенныйadd_loss
способ создания требуемых убытков.
Пользовательский слой для расчета потерь VAE:
class CustomVariationalLayer(keras.layers.Layer):
def vae_loss(self, x, z_decoded):
x = K.flatten(x)
z_decoded = K.flatten(z_decoded)
xent_loss = keras.metrics.binary_crossentropy(x, z_decoded)
kl_loss = -5e-4 * K.mean(
1 + z_log_var - K.square(z_mean) - K.exp(z_log_var),
axis=-1)
return K.mean(xent_loss + kl_loss)
def call(self, inputs):
x = inputs[0]
z_decoded = inputs[1]
loss = self.vae_loss(x, z_decoded)
self.add_loss(loss, inputs=inputs)
return x
y = CustomVariationalLayer()([input_img, z_decoded])
Наконец, создайте экземпляр модели и начните обучение. Поскольку наши потери также включены в пользовательский слой, нет необходимости указывать внешние потери во время компиляции (loss=None
), поэтому нет необходимости во внешних целевых данных (y=None
).
Здесь мы используем MNIST для его обучения, что является скрытым пространством для генерации рукописных цифр:
from tensorflow.keras.datasets import mnist
vae = Model(input_img, y)
vae.compile(optimizer='rmsprop', loss=None)
vae.summary()
(x_train, _), (x_test, y_test) = mnist.load_data()
x_train = x_train.astype('float32') / 255.
x_train = x_train.reshape(x_train.shape + (1,))
x_test = x_test.astype('float32') / 255.
x_test = x_test.reshape(x_test.shape + (1,))
vae.fit(x=x_train, y=None,
shuffle=True,
epochs=10,
batch_size=batch_size,
validation_data=(x_test, None))
С обученной моделью мы можем использовать декодер для преобразования векторов в любом скрытом пространстве в изображения:
import matplotlib.pyplot as plt
from scipy.stats import norm
n = 15 # 显示 15x15个数
digit_size = 28
figure = np.zeros((digit_size * n, digit_size * n))
grid_x = norm.ppf(np.linspace(0.05, 0.95, n)) # ppf 函数对线性分隔的坐标进行变换,以生成潜在变量 z 的值
grid_y = norm.ppf(np.linspace(0.05, 0.95, n))
for i, yi in enumerate(grid_x):
for j, xi in enumerate(grid_y):
z_simple = np.array([[xi, yi]])
z_simple = np.tile(z_simple, batch_size).reshape(batch_size, 2)
x_decoded = decoder.predict(z_simple, batch_size=batch_size)
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
plt.figure(figsize=(10, 10))
plt.imshow(figure, cmap='Greys_r')
plt.show()
На этом книга заканчивается, а о применении концепт-вектора, упомянутого ранее, я так и не написал подробно, а жаль.