其他分享
首页 > 其他分享> > 生成式对抗网络(GAN)

生成式对抗网络(GAN)

作者:互联网

生成式对抗网络

Ian Goodfellow等人在2014年的论文中提出了生成式对抗网络,尽管这个想法立刻使研究人员们兴奋不已,但还是花了几年时间才克服了训练GAN的一些困难。就像许多伟大的想法一样,事后看起来似乎很简单:让神经网络竞争,希望这种竞争能够促使它们变得更好。GAN由两个神经网络组成

在训练过程中,生成器和判别器有相反的目标:判别器试图从真实图像中分别出虚假图像,而生成器则试图产生看起来足够真实的图像来欺骗判别器。由于GAN由不同目标的两个网络组成,因此无法像常规神经网络一样对其进行训练。每个训练迭代都分为两个阶段

生成器实际上从未看到过任何真实的图像,但是它会逐渐学会令人信服的伪图像。它所得到的是流经判别器的回流梯度。幸运的是,判别器越好,这些二手梯度中包含的真实图像信息越多,因此生成器可以取得很大的进步

现在给Fashion MNIST构建一个简单的GAN

首先,需要构建生成器和判别器。生成器类似于自动编码器的解码器,判别器是常规的二元分类器(它以图像作为输入,包含单个神经元和使用sigmoid激活函数的Dense层)。对于每个训练迭代的第二阶段,还需要一个完整的GAN模型,其中包含生成器,后面跟随一个判别器:

import tensorflow as tf
from tensorflow import keras

codings_size = 30
generator = keras.models.Sequential([
    keras.layers.Dense(100, activation='gelu', input_shape=[codings_size]),
    keras.layers.Dense(150, activation='gelu'),
    keras.layers.Dense(28 * 28, activation='sigmoid'),
    keras.layers.Reshape([28, 28])
])
discriminator = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(150, activation='gelu'),
    keras.layers.Dense(100, activation='gelu'),
    keras.layers.Dense(1, activation='sigmoid')
])
gan = keras.models.Sequential([generator, discriminator])

接下来,需要编译模型。由于判别器是二元分类器,可以使用二元交叉熵损失。生成器仅通过gan模型进行训练,因此不需要对其进行编译。gan模型也是二元分类器,因此可以使用二元交叉熵损失。重要的是,判别器不应该在第二阶段进行训练,因此在训练gan模型之前,将其设为不可训练:

discriminator.compile(loss='binary_crossentropy', optimizer='rmsprop')
discriminator.trainable = False
gan.compile(loss='binary_crossentropy', optimizer='rmsprop')

Keras仅在编译模型时才考虑可训练属性,因此在运行此代码后,如果调用其fit()方法或其train_on_batch()方法,则判别器是可以训练的;当在gan模型上调用这些方法时,则判别器是不可训练的

由于训练循环不寻常,因此不能使用常规的fit()方法,相反,需要写一个自定义训练循环。为此,首先需要创建一个数据集来遍历数据集

fashion_mnist = keras.datasets.fashion_mnist
(X_train_all, y_train_all), (X_test, y_test) = fashion_mnist.load_data()
X_train_all, y_train_all, X_test, y_test = tf.cast(X_train_all, tf.float32), tf.cast(y_train_all, tf.float32), tf.cast(
    X_test, tf.float32), tf.cast(y_test, tf.float32)
batch_size = 32
dataset = tf.data.Dataset.from_tensor_slices(X_train_all / 255.).shuffle(1000)
dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1)

现在编写训练循环,将其包装在train_gan()函数中

def train_gan(gan, dataset, batch_size, codings_size, n_epochs=50):
    generator, discriminator = gan.layers
    for epoch in range(n_epochs):
        for X_batch in dataset:
            noise = tf.random.normal(shape=[batch_size, codings_size])
            generated_images = generator(noise)
            X_fake_and_real = tf.concat([generated_images, X_batch], axis=0)
            y1 = tf.constant([[0.]] * batch_size + [[1.]] * batch_size)
            discriminator.trainable = True
            discriminator.train_on_batch(X_fake_and_real, y1)
            noise = tf.random.normal(shape=[batch_size, codings_size])
            y2 = tf.constant([[1.]] * batch_size)
            discriminator.trainable = False
            gan.train_on_batch(noise, y2)


train_gan(gan, dataset, batch_size, codings_size)
coding = tf.random.normal(shape=[12, codings_size])
images = generator(coding).numpy()
import matplotlib.pyplot as plt


def plot_image(image):
    plt.imshow(image, cmap='binary')
    plt.axis('off')


fig = plt.figure(figsize=(12 * 1.5, 3))
for image_index in range(12):
    plt.subplot(3, 4, image_index + 1)
    plot_image(images[image_index])

GAN的训练难点

在训练过程中,生成器和判别器在零和游戏中不断地试图超越彼此。随着训练的进行,游戏可能会在一种博弈论者称为纳什均衡的状态中结束,这种均衡以数学家约翰·纳什的名字命名:在这种情况下,假设其他玩家都没有改变他们的策略,那么没有人会改变自己的策略使自己变得更好。例如,当每个人都在道路左侧行驶时,达到了纳什均衡:当么给人都在道路的右侧行驶时。不同的初始状态和动态发展可能会导致一个平衡或另一个平衡。在此示例中,一旦达到平衡(即与其他所有人在同一侧行驶),就存在一个最佳策略,但是一个纳什均衡可能涉及多种竞争策略(例如,捕食者追捕猎物,猎物试图逃避,改变它们的策略也不会变得更好)

那么如果适用于GAN,论文的作者证明了GAN只能达到单个纳什均衡:当生成器产生完美逼真的图像时,判别器只能被迫猜测(50%真实,50%伪造)。这个事实非常令人鼓舞:似乎只需要训练GAN足够长的时间,它最终就会达到均衡,从而给出一个完美的生成器。不幸的是,并不是那么简单:没有任何东西可以保证达到均衡

最大的困难被称为模式崩溃:就是当生成器的输入逐渐变得不太多样化时。这是怎么发生的?假设生成器在产生逼真的鞋子方面比其他任何类都更好。他就会用鞋子来更多地欺骗判别器,这会鼓励它生成更多地鞋子图像。逐渐地它会忘记如何产生其他任何东西。同时判别器看到的唯一伪造图像将是鞋子,因此它也会忘记如何辨别其他类别的伪造图像。最终当判别器在进行假鞋和真鞋区分时,生成器将被迫转移到另一类。这样以来,它可能会变得擅长于生成衬衫,而忘记了鞋子,然后判别器也会跟着生成器。GAN可能会逐渐在几个类别中循环,而不会擅长于生成任何一个类别

此外由于生成器和判别器不断地相互竞争,因此它们的参数可能最终会振荡并开始变得不稳定。训练开始时可能会很好,然后由于这些不稳定而突然发散,没有明显的原因。而且有许多因素会影响这些复杂的动态过程,因此GAN对超参数非常敏感:可能不得不花费大量的精力来微调它们

自2014年以来,研究人员忙于解决这些问题:针对该问题发表了许多论文,其中一些提出了新的成本函数(尽管Google研究人员在2018年发表了一篇论文质疑其效率)或技术来解决训练稳定性或避免模式崩溃的问题。例如,一种称为重播体验的流行技术包括将生成器在每次迭代中生成的图像存储在重播缓存区中(逐渐删除较早生成的图像),使用真实图像以及从该缓冲区中取出的伪图像来训练判别器(而不是由当前生成器生成的伪图像)。这减少了判别器过拟合最新生成输出的图像的机会。另一种常见的技术称为小批量判别:它可测量跨批次中相似图像的程度,并将此统计信息提供给判别器,因此判别器可以轻松拒绝缺乏多样性的一整个批次的伪图像。这会鼓励生成器生成更多样的图像,从而减少模式崩溃。其他论文只是提出了一些表现良好的特定网络架构

深度卷积GAN

2014年的GAN原始论文试验了卷积层,但只是生成了小图像。不久之后,许多研究人员试图基于更深的卷积网络为更大的图像构建GAN。由于训练非常不稳定,所以被证明是棘手的,但是Alec Radford等人在实验了许多不同的架构和超参数后,终于在2015年末取得了成功。他们称其架构为深度卷积GAN(DCGAN)。以下是他们为构建稳定的卷积GAN提出的主要指导

这些准则在许多情况下都会起作用,但并非总是如此,因此可能需要试验不同的超参数(实际上,仅仅更改随机种子并再次训练相同的模型有时会起作用),下面是一个小型DCGAN,在Fashion MNIST数据集上可以很好地工作:

codings_size = 100
generator = keras.models.Sequential([
    keras.layers.Dense(7 * 7 * 128, input_shape=[codings_size]),
    keras.layers.Reshape([7, 7, 128]),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2DTranspose(64, kernel_size=5, strides=2, padding='same', activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2DTranspose(1, kernel_size=5, strides=2, padding='same', activation='tanh')
])
discriminator = keras.models.Sequential([
    keras.layers.Conv2D(64, kernel_size=5, strides=2, padding='same', activation=keras.layers.LeakyReLU(.2),
                        input_shape=[28, 28, 1]),
    keras.layers.Dropout(.4),
    keras.layers.Conv2D(128, kernel_size=5, strides=2, padding='same', activation=keras.layers.LeakyReLU(.2)),
    keras.layers.Dropout(.4),
    keras.layers.Flatten(),
    keras.layers.Dense(1, activation='sigmoid')
])
gan = keras.models.Sequential([generator, discriminator])

生成器使用大小为100的编码,将其投影到6272维度(77128),对结果进行重构以获得77128张量。该张量被批量归一化后,馈入步幅为2的转置卷积层层,将其从77上采样至1414,将深度从128缩小至64.其结果在此被批量归一化,并馈入另一个步幅为2的转置卷积层,将其从1414上采样到2828,将深度从64减少到1.该层使用tanh激活函数,因此输入范围为-1。因此在训练GAN之前,需要将训练集重新按比例调整为相同的范围。还需要重构形状来添加通道维度:

X_train_all = tf.reshape(X_train_all, [-1, 28, 28, 1]) * 2. - 1.
discriminator.compile(loss='binary_crossentropy', optimizer='rmsprop')
discriminator.trainable = False
gan.compile(loss='binary_crossentropy', optimizer='rmsprop')
dataset = tf.data.Dataset.from_tensor_slices(X_train_all).shuffle(1000)
dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1)
train_gan(gan, dataset, batch_size, codings_size)
coding=tf.random.normal(shape=[batch_size, codings_size])
images = generator(coding).numpy()
fig = plt.figure(figsize=(12, 8))
for image_index in range(batch_size):
    plt.subplot(4, 8, image_index + 1)
    plot_image(images[image_index])

GAN的逐步增长

Nvidia的研究人员Tero Karras等人在2018年的一篇论文中提出了一项重要技术:他们建议在训练开始时生成小图像,然后向生成器和判别器中逐渐添加卷积层以生成越来越大的图像。这种方法类似于堆叠式自动编码器的贪婪分层训练。额外的层添加在生成的末尾和判别器的开始处,并且先前训练过的层仍是可训练的

例如,当生成器的输出从44增长到88时,一个上采样层(使用最近邻滤波)被添加到现有的卷积层中,因此它输出88特征图,然后将其馈送到新的卷积层(使用‘same’填充和1的步幅,因此其输出也为88)。这个新层之后是新的输出卷积层:这是内核为1的常规卷积层,它将输出向下投影到所需数量的颜色通道。为了避免在添加新卷积层时破坏第一个卷积层的训练权重,最终输出是原始输出层和新输出层的加权和。新输出的权重为a,而原始输出的权重是1-a,并且a从0缓慢增加到1.换句话说,新的卷积层逐渐增强,而原始输出层逐渐减弱。在新的卷积层添加到判别器时,使用类似的增强/减弱技术

这篇论文还介绍了其他几种旨在增加输出多样性(避免模式崩溃)和使训练稳定的技术:

StyleGAN

相同的Nvidia团队在2018年发表的一篇论文中提出了高分辨率图像生成的最新技术,该论文介绍了流行的StyleGAN架构。作者在生成器中使用了风格转换技术,以确保生成的图像在各个尺度上都具有与训练图像相同的局部结构,从而极大地提高了所生成的图像质量。判别器和损失函数没有被修改,仅仅修改了生成器

独立于编码来增加噪声的想法非常重要。图像的某些部分非常随机,例如每个雀斑或头发的确切位置。在较早的GAN中,这种随机性要么来自编码,要么是生成器自身产生的一些伪随机噪声。如果它来自编码,则意味着生成器使用了编码的表征力的很大一部分来存储噪声:这非常浪费。而且,噪声必须能够流经网络并达到生成器的最后一层:这似乎是不必要的约束,可能会减慢训练速度。最后,可能会出现一些人工视觉,因为在不同层次使用了相同的噪声。相反,如果生成器试图自己产生自己的伪随机噪声,该噪声可能看起来不那么令人信服,从而导致更多人工视觉。另外,生成器的权重的一部分用于产生伪随机噪声,这似乎又是浪费的。通过增加额外的噪声输入,可以避免所有这些问题。GAN能够使用所提供的噪声为图像的每个部分添加适当数量的随机性

每个级别增加的噪声都不相同。每个噪声输入由一个充满了高斯噪声的单个特征图组成,该噪声会广播到所有(给定级别的)的特征图,并在添加之前使用学习到的每个特征图的比例因子进行缩放

最后,StyleGAN使用一种称为混和正则化(或风格混合)的技术,使用两种不同的编码生成一定百分比的生成图像。具体来说,编码\(c_1\)和\(c_2\)通过映射网络发送,给出两个风格向量\(w_1\)和\(w_2\)。然后,合成网络基于第一个级别的风格\(w_1\)和其余级别的样式\(w_2\)生成图像。阶段级别是随机选择的。这可以防止网络假设相邻级别的风格是相关联的,这反过来又鼓励了GAN中的局部性,意味着每个风格向量仅影响所生成图像中有限数量的特征

标签:判别,训练,keras,生成式,生成器,GAN,图像,对抗,size
来源: https://www.cnblogs.com/Reion/p/15831801.html