VAE для глубокого обучения Python

глубокое обучение Python

Это 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 — это тот же человек. Представление с улыбкой на лице.

Найдя несколько таких векторов понятий, мы можем отредактировать изображение следующим образом: спроецировав изображение в скрытое пространство, оперируя вектором понятий для перемещения его представления, а затем декодируя его в пространство изображения, мы можем изменить изображение на определенное понятие - например, степень улыбки:

微笑向量

Вариационный автоэнкодер

Автоэнкодер — это тип сети, которая берет изображение, отображает его в «скрытом пространстве» через модуль кодировщика, а затем декодирует его в выходные данные того же размера, что и исходное изображение, через модуль декодера. Цель обучения этой штуке — сделать вывод и ввод одинаковыми, поэтому мы используем одно и то же изображение для ввода и вывода. То, что узнает автоэнкодер, — это реконструкция исходного ввода.

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

自编码器:将输入x映射为压缩表示,然后再将其解码为x’

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

Вместо того, чтобы сжимать входное изображение в фиксированную кодировку в скрытом пространстве, VAE преобразует изображение в параметры статистического распределения — среднее значение и дисперсию. Декодирование VAE использует среднее значение и дисперсию для случайной выборки элемента из распределения и декодирования этого элемента в исходный вход. Поэтому процесс кодирования/декодирования VAE имеет определенную случайность.

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

VAE 将一张图像映射为两个向量 z_mean 和 z_log_var,二者定义了潜在 空间中的一个概率分布,用于采样一个潜在点并对其进行解码

На приведенной выше диаграмме показано, как работает VAE:

  1. Блок Encoder будет вводить сэмплыinput_imgПреобразовано для представления параметров в скрытом пространствеz_meanиz_log_variance;
  2. Случайным образом выберите точку z из этого основного нормального распределения:z = z_mean + exp(z_log_variance) * epsilon, где эпсилон — небольшой случайный тензор;
  3. Модуль декодера сопоставляет эту скрытую точку с исходным входным изображением.

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

截屏2021-08-16 14.26.55.png

С обученной моделью мы можем использовать декодер для преобразования векторов в любом скрытом пространстве в изображения:

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

png

На этом книга заканчивается, а о применении концепт-вектора, упомянутого ранее, я так и не написал подробно, а жаль.