在TensorFlow中对比两大生成模型:VAE与GAN

变分自编码器(VAE)与生成对抗网络(GAN)是复杂分布上无监督学习最具前景的两类方法。本文中,作者在 MNIST 上对这两类生成模型的性能进行了对比测试。


项目链接:https://github.com/kvmanohar22/Generative-Models


本项目总结了使用变分自编码器(Variational Autoencode,VAE)和生成对抗网络(GAN)对给定数据分布进行建模,并且对比了这些模型的性能。你可能会问:我们已经有了数百万张图像,为什么还要从给定数据分布中生成图像呢?正如 Ian Goodfellow 在 NIPS 2016 教程中指出的那样,实际上有很多应用。我觉得比较有趣的一种是使用 GAN 模拟可能的未来,就像强化学习中使用策略梯度的智能体那样。


 本文组织架构:

  •  变分自编码器(VAE)
  •  生成对抗网络(GAN)
  •  训练普通 GAN 的难点
  •  训练细节
  •  在 MNIST 上进行 VAE 和 GAN 对比实验

        在无标签的情况下训练 GAN 判别器


        在有标签的情况下训练 GAN 判别器

  •  在 CIFAR 上进行 VAE 和 GAN 实验
  •  延伸阅读


VAE


 变分自编码器可用于对先验数据分布进行建模。从名字上就可以看出,它包括两部分:编码器和解码器。编码器将数据分布的高级特征映射到数据的低级表征,低级表征叫作本征向量(latent vector)。解码器吸收数据的低级表征,然后输出同样数据的高级表征。


 从数学上来讲,让 X 作为编码器的输入,z 作为本征向量,X′作为解码器的输出。


 图 1 是 VAE 的可视化图。



图 1:VAE 的架构


 这与标准自编码器有何不同?关键区别在于我们对本征向量的约束。如果是标准自编码器,那么我们主要关注重建损失(reconstruction loss),即:



而在变分自编码器的情况中,我们希望本征向量遵循特定的分布,通常是单位高斯分布(unit Gaussian distribution),使下列损失得到优化:



p(z′)∼N(0,I) 中 I 指单位矩阵(identity matrx),q(z∣X) 是本征向量的分布,其中由神经网络来计算。KL(A,B) 是分布 B 到 A 的 KL 散度。


 由于损失函数中还有其他项,因此存在模型生成图像的精度和本征向量的分布与单位高斯分布的接近程度之间存在权衡(trade-off)。这两部分由两个超参数λ_1 和λ_2 来控制。


GAN


 GAN 是根据给定的先验分布生成数据的另一种方式,包括同时进行的两部分:判别器和生成器。

 判别器用于对「真」图像和「伪」图像进行分类,生成器从随机噪声中生成图像(随机噪声通常叫作本征向量或代码,该噪声通常从均匀分布(uniform distribution)或高斯分布中获取)。生成器的任务是生成可以以假乱真的图像,令判别器也无法区分出来。也就是说,生成器和判别器是互相对抗的。判别器非常努力地尝试区分真伪图像,同时生成器尽力生成更加逼真的图像,使判别器将这些图像也分类为「真」图像。


 图 2 是 GAN 的典型结构。



图 2:GAN


 生成器包括利用代码输出图像的解卷积层。图 3 是生成器的架构图。


图 3:典型 GAN 的生成器图示(图像来源:OpenAI)


训练 GAN 的难点


 训练 GAN 时我们会遇到一些挑战,我认为其中最大的挑战在于本征向量/代码的采样。代码只是从先验分布中对本征变量的噪声采样。有很多种方法可以克服该挑战,包括:使用 VAE 对本征变量进行编码,学习数据的先验分布。这听起来要好一些,因为编码器能够学习数据分布,现在我们可以从分布中进行采样,而不是生成随机噪声。


训练细节


 我们知道两个分布 p(真实分布)和 q(估计分布)之间的交叉熵通过以下公式计算:


对于二元分类,


对于 GAN,我们假设分布的一半来自真实数据分布,一半来自估计分布,因此:


训练 GAN 需要同时优化两个损失函数。

 

按照极小极大值算法,



这里,判别器需要区分图像的真伪,不管图像是否包含真实物体,都没有注意力。当我们在 CIFAR 上检查 GAN 生成的图像时会明显看到这一点。


 我们可以重新定义判别器损失目标,使之包含标签。这被证明可以提高主观样本的质量。

 如:在 MNIST 或 CIFAR-10(两个数据集都有 10 个类别)。

 

上述 Python 损失函数在 TensorFlow 中的实现:

 

          def VAE_loss(true_images, logits, mean, std):
      """
        Args:
          true_images : batch of input images
          logits      : linear output of the decoder network (the constructed images)
          mean        : mean of the latent code
          std         : standard deviation of the latent code
      """
      imgs_flat    = tf.reshape(true_images, [-1, img_h*img_w*img_d])
      encoder_loss = 0.5 * tf.reduce_sum(tf.square(mean)+tf.square(std)
                     -tf.log(tf.square(std))-1, 1)
      decoder_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(
                     logits=logits, labels=img_flat), 1)
      return tf.reduce_mean(encoder_loss + decoder_loss)
  
  
  def GAN_loss_without_labels(true_logit, fake_logit):
      """
        Args:
          true_logit : Given data from true distribution,
                      `true_logit` is the output of Discriminator (a column vector)
          fake_logit : Given data generated from Generator,
                      `fake_logit` is the output of Discriminator (a column vector)
      """

      true_prob = tf.nn.sigmoid(true_logit)
      fake_prob = tf.nn.sigmoid(fake_logit)
      d_loss = tf.reduce_mean(-tf.log(true_prob)-tf.log(1-fake_prob))
      g_loss = tf.reduce_mean(-tf.log(fake_prob))
      return d_loss, g_loss
  
  
  def GAN_loss_with_labels(true_logit, fake_logit):
      """
        Args:
          true_logit : Given data from true distribution,
                      `true_logit` is the output of Discriminator (a matrix now)
          fake_logit : Given data generated from Generator,
                      `fake_logit` is the output of Discriminator (a matrix now)
      """
      d_true_loss = tf.nn.softmax_cross_entropy_with_logits(
                    labels=self.labels, logits=self.true_logit, dim=1)
      d_fake_loss = tf.nn.softmax_cross_entropy_with_logits(
                    labels=1-self.labels, logits=self.fake_logit, dim=1)
      g_loss = tf.nn.softmax_cross_entropy_with_logits(
                    labels=self.labels, logits=self.fake_logit, dim=1)

      d_loss = d_true_loss + d_fake_loss
      return tf.reduce_mean(d_loss), tf.reduce_mean(g_loss)
  
      


在 MNIST 上进行 VAE 与 GAN 对比实验


#1 不使用标签训练判别器


我在 MNIST 上训练了一个 VAE。代码地址:https://github.com/kvmanohar22/Generative-Models


实验使用了 MNIST 的 28×28 图像,下图中:


  • 左侧:数据分布的 64 张原始图像
  • 中间:VAE 生成的 64 张图像
  • 右侧:GAN 生成的 64 张图像


第 1 次迭代



第 2 次迭代



第 3 次迭代


第 4 次迭代


第 100 次迭代


VAE(125)和 GAN(368)训练的最终结果


以下动图展示了 GAN 生成图像的过程(模型训练了 368 个 epoch)。


显然,VAE 生成的图像与 GAN 生成的图像相比,前者更加模糊。这个结果在预料之中,因为 VAE 模型生成的所有输出都是分布的平均。为了减少图像的模糊,我们可以使用 L1 损失来代替 L2 损失。


在第一个实验后,作者还将在近期研究使用标签训练判别器,并在 CIFAR 数据集上测试 VAE 与 GAN 的性能。


使用


  • 下载 MNIST 和 CIFAR 数据集

使用 MNIST 训练 VAE 请运行:

        python main.py --train --model vae --dataset mnist
      

使用 MNIST 训练 GAN 请运行:

        python main.py --train --model gan --dataset mnist
      

想要获取完整的命令行选项,请运行:

        python main.py --help
      

该模型由 generate_frq 决定生成图片的频率,默认值为 1。


GAN 在 MNIST 上的训练结果


MNIST 数据集中的样本图像:


上方是 VAE 生成的图像,下方的动图展示了 GAN 生成图像的过程:




  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GAN生成对抗网络)可以用于生成序列数据,例如文本、音频和图像等。 对于生成序列数据,通常使用条件GAN(CGAN)或变分自编码器(VAE)。 在CGAN生成器将随机噪声向量和条件向量作为输入,输出生成的序列数据。条件向量可以是任何与序列相关的信息,例如序列的类别或标签。 在VAE,编码器将输入序列数据转换为潜在空间的向量表示,然后解码器将潜在向量转换为生成的序列数据。VAE通常比CGAN更稳定,因为它使用潜在变量来编码数据分布的结构。 下面是一个简单的示例,使用Keras和TensorFlow实现基于CGAN的文本生成: 1. 导入库和数据集 ```python import numpy as np import tensorflow as tf from tensorflow import keras # 数据集:莎士比亚的诗歌 path_to_file = keras.utils.get_file( 'shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt' ) with open(path_to_file) as f: text = f.read() print('Length of text: {} characters'.format(len(text))) ``` 2. 预处理数据 ```python # 构建字符级别的标记 vocab = sorted(set(text)) char2idx = {c:i for i, c in enumerate(vocab)} idx2char = np.array(vocab) # 将文本转换为整数序列 text_as_int = np.array([char2idx[c] for c in text]) # 创建训练样本和目标 seq_length = 100 examples_per_epoch = len(text) // (seq_length + 1) char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int) sequences = char_dataset.batch(seq_length + 1, drop_remainder=True) def split_input_target(chunk): input_text = chunk[:-1] target_text = chunk[1:] return input_text, target_text dataset = sequences.map(split_input_target) ``` 3. 构建生成器和鉴别器 ```python # 生成器 def build_generator(vocab_size, embedding_dim, rnn_units): model = keras.Sequential([ keras.layers.Embedding(vocab_size, embedding_dim), keras.layers.LSTM(rnn_units, return_sequences=True), keras.layers.Dense(vocab_size, activation='softmax') ]) return model # 鉴别器 def build_discriminator(vocab_size, embedding_dim, rnn_units): model = keras.Sequential([ keras.layers.Embedding(vocab_size, embedding_dim), keras.layers.LSTM(rnn_units), keras.layers.Dense(1, activation='sigmoid') ]) return model ``` 4. 定义损失函数和优化器 ```python # 交叉熵损失函数 def cross_entropy_loss(logits, labels): return tf.reduce_mean( tf.nn.sparse_softmax_cross_entropy_with_logits( logits=logits, labels=labels ) ) # 优化器 generator_optimizer = keras.optimizers.Adam(1e-4) discriminator_optimizer = keras.optimizers.Adam(1e-4) ``` 5. 定义训练循环 ```python # 训练循环 def train_step(generator, discriminator, x, y, noise_dim): # 训练鉴别器 with tf.GradientTape() as tape: generated_seq = generator(x, noise_dim) real_output = discriminator(y) fake_output = discriminator(generated_seq) d_loss_real = cross_entropy_loss(real_output, tf.ones_like(real_output)) d_loss_fake = cross_entropy_loss(fake_output, tf.zeros_like(fake_output)) d_loss = d_loss_real + d_loss_fake grads = tape.gradient(d_loss, discriminator.trainable_variables) discriminator_optimizer.apply_gradients(zip(grads, discriminator.trainable_variables)) # 训练生成器 with tf.GradientTape() as tape: generated_seq = generator(x, noise_dim) fake_output = discriminator(generated_seq) g_loss = cross_entropy_loss(fake_output, tf.ones_like(fake_output)) grads = tape.gradient(g_loss, generator.trainable_variables) generator_optimizer.apply_gradients(zip(grads, generator.trainable_variables)) return d_loss, g_loss ``` 6. 训练模型 ```python # 训练模型 vocab_size = len(vocab) embedding_dim = 256 rnn_units = 1024 noise_dim = 100 num_epochs = 20 generator = build_generator(vocab_size, embedding_dim, rnn_units) discriminator = build_discriminator(vocab_size, embedding_dim, rnn_units) for epoch in range(num_epochs): print('Epoch {}/{}'.format(epoch+1, num_epochs)) for i, (input_seq, target_seq) in enumerate(dataset): d_loss, g_loss = train_step(generator, discriminator, input_seq, target_seq, noise_dim) if i % 100 == 0: print('Step {}: d_loss={}, g_loss={}'.format(i, d_loss, g_loss)) ``` 7. 生成文本 ```python # 生成文本 def generate_text(generator, start_string, num_generate, temperature): input_eval = [char2idx[c] for c in start_string] input_eval = tf.expand_dims(input_eval, 0) generated_seq = [] for i in range(num_generate): predictions = generator(input_eval, temperature) predictions = tf.squeeze(predictions, 0) predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy() generated_seq.append(idx2char[predicted_id]) input_eval = tf.expand_dims([predicted_id], 0) return start_string + ''.join(generated_seq) print(generate_text(generator, start_string='ROMEO:', num_generate=1000, temperature=0.5)) ``` 这个例子是基于字符级别的文本生成,你可以根据需要修改、调整代码来适应其他类型的序列数据生成任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值