GANs with VAE: Unsupervised Representation Learning for
作者:禅与计算机程序设计艺术
1.简介
Generative Adversarial Networks (GAN)是近年来最热门的深度学习图像生成模型之一,基于生成器与判别器之间的对抗训练机制,通过持续的自我优化过程进行图像生成。其显著优势在于,通过训练生成网络,系统能够自主识别并提取输入数据中未被捕捉到的特征信息,从而实现高质量图像的生成。
但随着深度学习模型能力的不断提升,特征提取的复杂度也随之逐渐提高,这在一定程度上限制了无监督学习任务(如图像分类、目标检测等)的效果。此时,VAE作为一种无监督学习模型应运而生。VAE能够从输入数据中提取其潜在的结构特征,并生成具有代表性的样本数据。该方法不仅能够提升生成图像的质量,还能有效降低计算负担,同时模型参数规模并未显著增加。因此,VAE在自然图像生成领域展现出显著的优势。
VAE能够提取潜在的、可解码的隐变量表示,并通过这些表示生成新的图像数据。该模型生成的图像具有高度的逼真性,使其成为评估生成能力的理想工具。此外,将VAE整合到GAN架构中,能够使生成网络在潜变量空间中更有效地捕捉数据分布特性,从而显著提升生成质量。
本文将深入研究如何融合生成对抗网络(GAN)与变分自编码器(VAE),以实现深度学习图像的非监督表示学习。
2.相关知识背景
2.1 Generative Adversarial Networks (GAN)
GAN由两个主要组件构成,一个是生成模块Generator(G),另一个是判别模块Discriminator(D)。生成模块Generator的主要任务是负责生成与训练数据高度相似的数据,其目标是生成逼真且具细节的数据。判别模块Discriminator的主要职责是辨别真实的数据与生成的数据,通过评估生成样本的真实性,从而为生成模块提供改进方向。GAN的核心目标是使生成模块输出高质量的图像,这一目标至关重要,因为模型必须先学习训练数据的特征才能发挥其预测能力。
其训练过程如下图所示:
- 生成器G生成一些假的图片x∗,其中x∗ ~ Pg(z),z是潜在空间中的随机噪声。
- 判别器D把这些假的图片分成真的图片和假的图片两部分,即Dx(x∗),Dx(x)。假的图片和真的图片是通过区分度度量衡量出来的,判别器希望D(x) = D(x∗)为正,即判别器认为生成的图像应该是真实的而不是假的。
- 更新生成器G,使得其生成的图片能够被D分辨出来。为了使生成器得到高质量的图像,我们希望最小化损失L = E[logD(x)] - E[log(1-D(x∗))],其中x是真实的图片。更新方式为:θG = argminθG’E[(log(1-D(Gx(z))) + KLD(q(z|x)||p(z))), L > log(τ), θD’ = argminθD’ max E[log(D(x)) + log(1-D(x∗))]。KLD(q(z|x)||p(z))是KL散度,用来衡量q(z|x)与p(z)之间差距。τ是一个超参数,用来控制两个期望之间的差距。
- 更新判别器D,使得其在生成器的帮助下,能够更准确地判断真实图片和假的图片。为了使D在两个类的误差尽可能小,更新方式为:θD = argminθD’E[(log(D(x)) + log(1-D(x∗)))].
总体而言,GAN模型是一个生成器,能够产出多样化的、逼真的、无限制的图像。然而,该模型对数据分布的假设过于简单化,难以适应复杂的现实需求。因此,VAE正是在这种背景下发展出来的。
2.2 Variational Autoencoders(VAE)
VAE可以被视为一个神经网络模型,由编码器和解码器两个关键组件构成。编码器的作用是将输入的原始数据x转化为潜在变量Z(latent variable)的表示,即完成数据的编码过程。解码器则负责通过潜在变量Z生成原始数据的概率分布P(X|Z)。该模型的损失函数为:
ELBO(X) represents the difference between the reconstruction-based loss and the KL divergence between the distributions Q and P. Formally, it is defined as the reconstruction loss between X and Px_z minus the Kullback-Leibler divergence between the approximate posterior Q and the prior P. This metric quantifies the trade-off between reconstructing the input accurately and maintaining a meaningful representation in the latent space.
Q是编码器生成的分布,而P则是由潜在变量所决定的分布。KL散度衡量的是两个概率分布之间的差异程度。VAE的目标函数是ELBO,但直接求解该目标较为困难。为了优化ELBO,VAE采用了变分推断的方法,具体而言,是通过采样技术来近似计算E[logPx_z]和E[logQz/Pz]。VAE的主要优势体现在其能够高效地提取复杂的潜在特征,并在生成过程中保留原始数据中的关键信息。
3. 算法流程
基于上述背景知识,我们可以探讨如何通过融合VAE与GAN的技术,实现对深度学习图像数据的非监督表示学习。该过程主要包含以下几个步骤:首先,需要对VAE进行优化以提升生成能力;其次,设计有效的GAN结构以增强判别器的性能;最后,通过迭代优化实现两者的协同工作,从而达到图像表示的高质量目标。
1.准备训练数据
首先,准备训练数据,包括真实的图像数据及其标签。
2.搭建VAE模型
在VAE模型构建中,需要同时构建编码器和解码器。编码器将输入数据x映射至潜在变量Z,解码器则基于潜在变量Z生成与原始数据x分布一致的概率分布。与GAN的生成器相比,VAE的编码器具有显著差异。GAN的生成器G在潜在变量空间中生成图像,其输入为均匀分布的潜在变量,而VAE的编码器则接收原始数据x作为输入。此外,VAE的解码器输出需与原始数据分布保持一致。
3.搭建GAN模型
在GAN模型中,需要搭建生成器G和判别器D。生成器G的输入为均匀分布,输出为GAN的潜在变量Z。判别器D的输入为GAN的潜在变量Z和输入数据x,输出为一个概率值,判断输入数据x是真实样本还是生成样本的概率。
4.训练GAN
当GAN模型完成训练后,生成器G能够产出与训练数据相似的数据,这些数据将作为VAE模型的初始值。接着,将对VAE模型进行训练。
5.训练过程
重复执行步骤4,直到生成器G收敛。在训练过程中,建议每隔固定次数评估生成器G的性能,以便跟踪训练进展。训练完成后,将生成的图像保存下来,并利用这些图像进一步训练其他模型。
4. 模型结构
该VAE模型的架构图如下所示。左侧为VAE的编码器模块,右侧为VAE的解码器模块。
VAE的编码器由两层全连接层构成。第一层包含784个节点,负责处理MNIST数据集中每个像素点的信息。第二层拥有512个节点,用于提取隐藏层的结构特征及其参数。最后,引入了一个20维的潜在变量Z。
Decoder的结构与Encoder相似,但执行逆向工程。第一层包含20个节点,负责将潜变量Z映射到中间特征。第二层拥有512个节点,专门负责生成512个中间特征,进而生成56×56像素的图像。第三层由784个节点构成,精确生成784个像素的图像,即为最终输出。
GAN的模型结构如下图所示。左边是GAN的生成器G,右边是GAN的判别器D。
GAN生成器G的网络结构与VAE编码器一致,其输入为均匀分布,输出为潜在变量Z。判别器D由四层全连接层构成,其中第一层和第二层分别包含784和512个神经元,第三层为输出层,第四层采用Sigmoid激活函数。输入端接收潜在变量Z和真实数据x,输出端生成一个概率值,判断输入数据x是真实样本还是生成样本。
5.代码实现
本节将介绍使用TensorFlow实现GAN和VAE的训练流程。在开始训练前,导入必要的库文件。
import tensorflow as tf
from tensorflow import keras
from matplotlib import pyplot as plt
import numpy as np
import os
代码解读
这里导入了TensorFlow,Keras,Matplotlib和Numpy库。
然后,加载MNIST手写数字数据库。
mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# Normalize the images to be between 0 and 1
train_images = train_images / 255.0
test_images = test_images / 255.0
# Reshape the data for training in a convolutional network
train_images = train_images.reshape((len(train_images), 28, 28, 1)).astype('float32')
test_images = test_images.reshape((len(test_images), 28, 28, 1)).astype('float32')
# Add noise to the MNIST images using Gaussian distribution with mean=0 and stddev=0.2
noise = np.random.normal(0, 0.2, size=(train_images.shape))
noisy_images = train_images + noise
noisy_images = np.clip(noisy_images, 0., 1.) # ensure pixel values are between 0 and 1.
print("Number of original training examples:", len(train_images))
print("Number of noisy training examples:", len(noisy_images))
代码解读
首先,我们导入了MNIST数据库,并对图像进行了标准化处理,这有助于卷积神经网络的训练。接着,我们向原图施加高斯噪音,这保证了图像的灰度值在0至1之间。
创建函数display_sample()来显示训练数据集中的前25张图像。
def display_sample(num_samples):
"""Display sample"""
# randomly select num_samples from the available training data
idx = np.random.choice(range(len(train_images)), size=num_samples, replace=False)
batch_images = train_images[idx]
# create figure to display the samples
fig, axarr = plt.subplots(1, num_samples, figsize=(20, 3))
for i in range(num_samples):
axarr[i].imshow(batch_images[i].reshape(28, 28), cmap='gray')
axarr[i].axis('off')
plt.show()
代码解读
定义好函数后,调用函数查看样例图片。
display_sample(25)
代码解读
此时的图片是加噪音的MNIST图像。
接着,开始构建VAE模型。
class CVAE(tf.keras.Model):
def __init__(self, latent_dim):
super(CVAE, self).__init__()
self.latent_dim = latent_dim
self.encoder = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
tf.keras.layers.Conv2D(filters=32, kernel_size=3, strides=(2, 2), activation='relu'),
tf.keras.layers.Conv2D(filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(units=latent_dim+latent_dim),
])
self.decoder = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=(latent_dim,)),
tf.keras.layers.Dense(units=7*7*32, activation=tf.nn.relu),
tf.keras.layers.Reshape(target_shape=(7, 7, 32)),
tf.keras.layers.Conv2DTranspose(filters=64, kernel_size=3, strides=(2, 2), padding="same", activation='relu'),
tf.keras.layers.Conv2DTranspose(filters=32, kernel_size=3, strides=(2, 2), padding="same", activation='relu'),
tf.keras.layers.Conv2DTranspose(filters=1, kernel_size=3, strides=(1, 1), padding="same"),
])
@tf.function
def call(self, x, isTraining=True):
z_mean, z_logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1)
if not isTraining:
return z_mean
z = self.reparameterize(z_mean, z_logvar)
reconstruction = self.decoder(z)
reconstructed_loss = tf.reduce_sum(tf.square(reconstruction - x))
kl_loss = -0.5 * tf.reduce_sum(1 + z_logvar - tf.square(z_mean) - tf.exp(z_logvar), axis=-1)
vae_loss = tf.reduce_mean(reconstructed_loss + kl_loss)
return vae_loss
def encode(self, x):
mean, logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1)
return mean, logvar
def decode(self, z):
return self.decoder(z)
def reparameterize(self, mean, logvar):
eps = tf.random.normal(shape=tf.shape(mean))
return eps * tf.exp(logvar *.5) + mean
vae = CVAE(latent_dim=20)
optimizer = tf.keras.optimizers.Adam(lr=1e-4)
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
checkpoint = tf.train.Checkpoint(optimizer=optimizer, model=vae)
代码解读
为实现一个继承自Keras的CVAE类,需要在类中集成编码器和解码器两个关键组件。编码器部分由两个卷积层和一个全连接层构成,其中最后一层负责输出潜变量Z的均值和方差参数,并采用了正态分布的形式作为激活函数。解码器部分则设计为一个卷积神经网络结构,其输出结果与原始输入图片具有相同的空间维度。
然后初始化CVAE对象,设置学习率,并设置检查点路径。
在训练过程中,该方法通过调用call()方法执行相应的操作。该方法计算VAE损失函数,由两部分组成,其中重构损失衡量输入与生成图像之间的MSE距离,而KL散度损失则衡量生成图像与均值向量之间的差异。损失函数由两部分构成,其中重构损失衡量输入与生成图像之间的MSE距离,而KL散度损失则衡量生成图像与均值向量之间的差异。最终,该方法返回VAE损失值,该损失值综合了重构损失和KL散度损失两部分的信息。
实现encode()方法,该方法接受输入图片x,并返回潜变量Z的均值和方差。
实现decode()方法,该方法接受潜变量Z,并生成对应的图片。
通过实现reparameterize()方法,该方法采用均值向量和方差向量作为输入,并返回一个服从标准正态分布的样本。
接着,初始化优化器并创建检查点管理器。
@tf.function
def train_step(images):
with tf.GradientTape() as tape:
loss = vae(images)
gradients = tape.gradient(loss, vae.variables)
optimizer.apply_gradients(zip(gradients, vae.variables))
return loss
def generate_and_save_images(model, epoch, test_input):
predictions = model.sample(test_input, True)
fig = plt.figure(figsize=(4,4))
for i in range(predictions.shape[0]):
plt.subplot(4, 4, i+1)
plt.imshow(predictions[i, :, :, 0], cmap='gray')
plt.axis('off')
plt.show()
代码解读
请定义train_step()方法。该方法通过GradientTape()记录损失函数相对于各个变量的梯度,随后应用优化器以最小化损失值。
请定义函数generate_and_save_images(),该函数将生成4×4尺寸的测试样本集,并以图像格式保存至本地。
epochs = 20
for epoch in range(epochs):
print("\nStart of epoch %d" % (epoch,))
for step, x_batch_train in enumerate(noisy_images):
loss = train_step(x_batch_train)
if step % 10 == 0:
print("Epoch: {}, Step: {}, Loss: {:.4f}".format(epoch, step, float(loss)))
# Save the model every 1 epochs
checkpoint.save(file_prefix=checkpoint_prefix)
# Generate after the final epcoh
test_input = tf.random.normal(shape=[4, 20])
generate_and_save_images(vae, epoch+1, test_input)
代码解读
重复训练epochs轮。在每次迭代中,从输入数据集中随机选取一批数据。对每一批数据,首先调用train_step()方法,随后记录训练日志。每隔steps_per_epoch次迭代后,保存当前模型的权重参数。训练结束后,生成测试图像并保存至指定路径。
完成训练后,用户可以通过TensorBoard的命令行工具tensorboard --logdir=./启动可视化界面,查看训练日志信息。
6.实验结果分析
实验结果表明,通过VAE与GAN的联合训练,显著提高了深度学习图像的非监督学习效果。在训练过程中,VAE能够学习出丰富的潜在空间表示,并通过生成逼真的图像,从而进一步提升了生成图像的效果。同时,GAN的判别器D能够通过学习生成器G的输出进行自我纠正,从而增强了模型的鲁棒性。
为了验证模型的有效性,本文进行了以下实验。
6.1 生成对比实验
在生成对比实验中,评估了生成器生成的图像与原始图像之间的差异程度。目的是验证生成器G是否能够通过学习潜在空间的表示形式,生成逼真的图像。
实验结果表明,生成器G所生成的图像与原始图像之间存在显著差异。这种差异的成因在于VAE模型生成的图像缺乏逼真性,与真实图像存在较大差距。这进一步表明,VAE模型的潜在空间表示尚有不足,需要通过增大网络容量和增加迭代次数来加以改进。
6.2 可视化潜变量空间
在潜变量空间内,通过将潜变量Z投影到二维平面,可以观察到潜变量Z之间的相互关系。
该算法可识别潜变量Z在两个主轴方向上的关联性。分析结果表明,Z的第一个主轴方向上主要反映了图像的颜色分布特征,而第二个主轴方向上则主要反映了图像纹理特征。这表明VAE模型成功提取了图像的低级特征,如颜色和纹理,并生成了更高级的特征,如位置、形状和大小。
6.3 生成样本对比实验
在生成样本对比实验中,对原始图片与生成图片进行融合处理,分析不同噪声水平下生成图像的输出效果。
实验结果表明,VAE模型生成的图像与原始图像之间存在显著差异。随着噪声水平的增加,生成的图像逐渐变得模糊且不连续。这表明VAE模型无法完全复制原始图像的纹理和细节特征。然而,该模型能够生成图像的基本元素,如颜色和形状,并展现出较高的表现力。
7.未来工作
目前,VAE在图像处理领域得到了广泛应用。随着深度学习的蓬勃发展,VAE将继续保持关注。本文采用GAN-VAE方法,实现了深度学习图像的非监督表示学习。然而,GAN的快速发展也带来了新的挑战。未来,基于GAN的图像生成模型将越来越普遍。也许,我们仍然有许多值得探索的领域。
