Эта часть блога состоит из объяснения вариационного автоэнкодера, математики, кода и результата.
Этот блог состоит из следующих тем по порядку:
- Резюме — Автоэнкодер
- Вариационный автоэнкодер
- Код — Генерация изображения
- Вывод
- использованная литература
Резюме — Автоэнкодер
В прошлом блоге мы разобрались, как работает автоэнкодер. Вариационный автоэнкодер работает аналогично автоэнкодеру, но с небольшими изменениями. Этот вариант помогает нам генерировать изображения, используя скрытое пространство. Давайте углубимся в это и попытаемся понять концепции вариационного автоэнкодера.
Напомним, что 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) можно использовать для создания большего количества изображений. Это будет опробовано в дальнейших блогах.