Advertisement

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的核心目标是使生成模块输出高质量的图像,这一目标至关重要,因为模型必须先学习训练数据的特征才能发挥其预测能力。

其训练过程如下图所示:

  1. 生成器G生成一些假的图片x∗,其中x∗ ~ Pg(z),z是潜在空间中的随机噪声。
  2. 判别器D把这些假的图片分成真的图片和假的图片两部分,即Dx(x∗),Dx(x)。假的图片和真的图片是通过区分度度量衡量出来的,判别器希望D(x) = D(x∗)为正,即判别器认为生成的图像应该是真实的而不是假的。
  3. 更新生成器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)之间差距。τ是一个超参数,用来控制两个期望之间的差距。
  4. 更新判别器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的图像生成模型将越来越普遍。也许,我们仍然有许多值得探索的领域。

全部评论 (0)

还没有任何评论哟~