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

Этот блог состоит из следующих тем по порядку:

  1. Резюме — Автоэнкодер
  2. Вариационный автоэнкодер
  3. Код — Генерация изображения
  4. Вывод
  5. использованная литература

Резюме — Автоэнкодер

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

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

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

В вариационном автоэнкодере вектор узкого места заменяется двумя отдельными векторами среднего значения распределения и ошибки стандартного отклонения распределения. Таким образом, всякий раз, когда данные подаются в декодер, образцы распределения проходят через декодер. Функция потерь вариационного автоэнкодера состоит из 2 членов. Во-первых, это потери при реконструкции, это то же самое, что автоэнкодер ожидает, что у нас есть срок ожидания, потому что мы делаем выборку из распределения. Второй член - это член дивергенции KL. Второй член гарантирует, что он остается в пределах нормального распределения. В основном мы тренируемся держать скрытое пространство близким к среднему значению 0 и стандартному отклонению 1, что эквивалентно нормальному распределению.

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

Итак, трюк заключается в следующем: если вы видите скрытый вектор, то его можно рассматривать как сумму mu, который является параметром, который вы изучаете, sigma, который равен также параметр, который мы изучаем и умножаем на эпсилон,в этот эпсилон мы помещаем стохастическую часть. Этот эпсилон всегда будет гауссовым с нулевым средним значением и стандартным отклонением 1. Таким образом, мы будем выбирать из эпсилон, умноженного на сигма. и добавьте его с помощью mu, чтобы получить скрытый вектор. Таким образом, mu и sigma — это единственные вещи, которые нам нужно обучить, и можно было бы увеличить градиенты, чтобы уменьшить ошибку и обучить сеть. эпсилон можно не дрессировать. Нам нужна стохастичность, которая поможет нам в создании изображений. Это можно увидеть на рисунке ниже 3.

Код — Генерация изображения

Здесь я буду использовать платформу Python Tensorflow-Keras для обучения VAE и создания изображений. Для начала импортируем необходимые библиотеки.

import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf; tf.compat.v1.disable_eager_execution()
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Input, Dense, Conv2D, Conv2DTranspose, Flatten, Lambda, Reshape
from tensorflow.keras.models import Model
from tensorflow.keras.losses import binary_crossentropy
from tensorflow.keras.datasets import mnist
np.random.seed(25)
tf.executing_eagerly()

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

# A function to compute the value of latent space using mu and sigma
def compute_latent(x):
    mu, sigma = x
    batch = K.shape(mu)[0]
    dim = K.int_shape(mu)[1]
    eps = K.random_normal(shape=(batch,dim))
    return mu + K.exp(sigma/2)*eps
# The loss function for VAE
def kl_reconstruction_loss(true, pred):
    # Reconstruction loss (binary crossentropy)
    reconstruction_loss = binary_crossentropy(K.flatten(true), K.flatten(pred)) * img_width * img_height
# KL divergence loss
    kl_loss = 1 + sigma - K.square(mu) - K.exp(sigma)
    kl_loss = K.sum(kl_loss, axis=-1)
    kl_loss *= -0.5
    # Total loss = 50% rec + 50% KL divergence loss
    return K.mean(reconstruction_loss + kl_loss)
# A function to display image sequence
def display_image_sequence(x_start, y_start, x_end, y_end, no_of_imgs):
    x_axis = np.linspace(x_start,x_end,no_of_imgs)
    y_axis = np.linspace(y_start,y_end,no_of_imgs)
    
    x_axis = x_axis[:, np.newaxis]
    y_axis = y_axis[:, np.newaxis]
    
    new_points = np.hstack((x_axis, y_axis))
    new_images = decoder.predict(new_points)
    new_images = new_images.reshape(new_images.shape[0], new_images.shape[1], new_images.shape[2])
    
    # Display some images
    fig, axes = plt.subplots(ncols=no_of_imgs, sharex=False, sharey=True, figsize=(20, 7))
    counter = 0
    for i in range(no_of_imgs):
        axes[counter].imshow(new_images[i], cmap='gray')
        axes[counter].get_xaxis().set_visible(False)
        axes[counter].get_yaxis().set_visible(False)
        counter += 1
    plt.show()

Импортируйте набор данных и нарисуйте несколько примеров

# Loading dataset
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# Displaying data
fig, axes = plt.subplots(ncols=10, sharex=False, sharey=True, figsize=(20, 7))
counter = 0
for i in range(120, 130):
    axes[counter].set_title(y_train[i])
    axes[counter].imshow(X_train[i], cmap='gray')
    axes[counter].get_xaxis().set_visible(False)
    axes[counter].get_yaxis().set_visible(False)
    counter += 1
plt.show()

Нормализуйте данные и определите переменные для дальнейшего использования

# Normalize values such that all numbers are within
# the range of 0 to 1
X_train = X_train/255
X_test = X_test/255
# Convert from (no_of_data, 28, 28) to (no_of_data, 28, 28, 1)
X_train_new = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)
X_test_new = X_test.reshape(X_test.shape[0], X_test.shape[1], X_test.shape[2], 1)
# Defining some variables
img_height   = X_train_new.shape[1]    # 28
img_width    = X_train_new.shape[2]    # 28
num_channels = X_train_new.shape[3]    # 1
input_shape =  (img_height, img_width, num_channels)   # (28,28,1)
latent_dim = 2    # Dimension of the latent space

Определите кодировщик, декодер и скрытое пространство. Это гиперпараметры. Слои кодировщика и измерения скрытого пространства.

# Constructing encoder
encoder_input = Input(shape=input_shape)
encoder_conv = Conv2D(filters=8, kernel_size=3, strides=2,padding='same', activation='relu')(encoder_input)
encoder = Flatten()(encoder_conv)
#latent space
mu = Dense(latent_dim)(encoder)
sigma = Dense(latent_dim)(encoder)
latent_space = Lambda(compute_latent, output_shape=(latent_dim,))([mu, sigma])
# Take the convolution shape to be used in the decoder
conv_shape = K.int_shape(encoder_conv)
#Decoder
# Constructing decoder
decoder_input = Input(shape=(latent_dim,))
decoder = Dense(conv_shape[1]*conv_shape[2]*conv_shape[3], activation='relu')(decoder_input)
decoder = Reshape((conv_shape[1], conv_shape[2], conv_shape[3]))(decoder)
decoder_conv = Conv2DTranspose(filters=8, kernel_size=3, strides=2, padding='same', activation='relu')(decoder)
decoder_conv =  Conv2DTranspose(filters=num_channels, kernel_size=3, padding='same', activation='sigmoid')(decoder_conv)

Определить модель, просмотреть сводку кодировщика, декодера и VAE

# Actually build encoder, decoder and the entire VAE
encoder = Model(encoder_input, latent_space)
decoder = Model(decoder_input, decoder_conv)
vae = Model(encoder_input, decoder(encoder(encoder_input)))

Теперь давайте обучим сеть и посмотрим результаты.

# Compile the model using KL loss
vae.compile(optimizer='adam', loss=kl_reconstruction_loss)
# Training VAE
history = vae.fit(x=X_train_new, y=X_train_new, epochs=30, batch_size=32, validation_data=(X_test_new,X_test_new))

Вот кривая обучения.

Код для генерации изображения с использованием 2D скрытого вектора. Обратите внимание, чем выше размерность скрытого вектора, тем лучше будут сгенерированные изображения.

Вывод

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

Существует расширение вариационного автоэнкодера, в котором используется термин бета. Это называется распутанный автоэнкодер. Кроме того, генеративно-состязательные сети (GAN) можно использовать для создания большего количества изображений. Это будет опробовано в дальнейших блогах.