An intuitive introduction to Generative Adversarial Networks (GANs)

生成对抗网络(GAN)的直观介绍

  • Warm up

让我们说你附近有一个非常酷的派对,你真的想去。 但有个问题。 要进入聚会,你需要一张特价机票 - 这个机票已经售罄。

等等! 这不是Generative Adversarial Networks的文章吗? 是的。 但是现在忍受我,这是值得的。

好吧,由于期望很高,派对的组织者聘请了一个合格的安全机构。 他们的主要目标是不允许任何人崩溃。 为此,他们在场地入口处放了很多警卫,检查每个人的真实性门票。

由于你没有任何武术艺术礼品,唯一的办法就是用一张非常令人信服的假票欺骗他们。

这个计划存在一个很大的问题 - 你从未真正看到这张票的样子。
即使你根据自己的创造力设计了一张罚单,在第一次试用时几乎不可能愚弄守卫。 此外,在你有一个非常体面的派对的传球复制品之前,你不能露面。

为了帮助解决问题,您决定打电话给您的朋友Bob为您完成工作。

鲍勃的使命非常简单。 他会尝试用假通行证进入派对。 如果他被拒绝,他会回复你,提供关于机票应该如何的有用提示。

根据这些反馈,您可以制作新版本的故障单并将其交给Bob,后者再次尝试。 这个过程不断重复,直到您能够设计出完美的副本。

An intuitive introduction to Generative Adversarial Networks (GANs)
暂且不谈这个轶事中的“小漏洞”,这几乎就是Generative Adversarial Networks(GAN)的工作原理。

如今,GAN的大多数应用都属于计算机视觉领域。 一些应用包括训练半监督分类器,以及从低分辨率对应物生成高分辨率图像。

本文介绍了GAN,并采用实际操作方法来解决生成图像的问题。 您可以在此处克隆此帖子的笔记本。

  • Generative Adversarial Networks
    An intuitive introduction to Generative Adversarial Networks (GANs)
    Generative Adversarial Network framework.

GAN是Goodfellow等人设计的生成模型。 在GAN设置中,由神经网络表示的两个可区分函数被锁定在游戏中。 两个参与者(生成器和鉴别器)在此框架中具有不同的角色。

生成器尝试生成来自某些概率分布的数据。 那就是你试图重现派对的门票。

鉴别者就像一个法官。 它决定输入是来自发生器还是来自真正的训练集。 这将是派对的安全性,将您的假票与真实票证进行比较,以找到您设计中的缺陷。

  • In summary, the game follows with:
    试图最大化使鉴别器错误输入的概率的发生器是真实的。
    并且鉴别器引导发生器产生更逼真的图像。
    在完美均衡中,发电机将捕获一般的训练数据分布。 结果,鉴别器总是不确定其输入是否真实。

An intuitive introduction to Generative Adversarial Networks (GANs)
改编自DCGAN论文。 这里实现了Generator网络。 注意完全连接和池化层不存在。

在DCGAN论文中,作者描述了一些深度学习技术的组合作为训练GAN的关键。 这些技术包括:(i)所有卷积网和(ii)批量标准化(BN)。

第一个强调两个方面的跨步卷积(而不是汇集层):增加和减少特征的空间维度。 并且第二个将特征向量归一化以在所有层中具有零均值和单位方差。 这有助于稳定学习并处理重量不足的初始化问题。

不用多说,让我们深入了解实施细节,并在我们开始时更多地讨论GAN。 我们提出了深度卷积生成对抗网络(DCGAN)的实现。 我们的实现使用Tensorflow并遵循DCGAN文件中描述的一些实践。

  • Generator

该网络有4个卷积层,后面都是BN(输出层除外)和整流线性单元(ReLU)**。

它将随机向量z(从正态分布中绘制)作为输入。在重塑z以获得4D形状后,我们将其提供给启动一系列上采样层的生成器。

每个上采样层表示具有步幅2的转置卷积运算。转置卷积类似于常规卷积。

通常,常规卷积从宽层和浅层变为更窄和更深层。转置卷积是另一种方式。它们从深而窄的层变为更宽更浅。

转置卷积运算的步幅定义了输出层的大小。使用“相同”填充和2的步幅,输出功能将具有输入层的两倍大小。

之所以发生这种情况,是因为每当我们在输入层中移动一个像素时,我们就会在输出层上将卷积内核移动两个像素。换句话说,输入图像中的每个像素用于在输出图像中绘制正方形。

An intuitive introduction to Generative Adversarial Networks (GANs)

在2x2输入上使用步幅2对3x3内核进行转置,相当于在5x5输入上使用步幅2对3x3内核进行卷积。对于两者,不使用填充“VALID”。

简而言之,生成器从这个非常深但很窄的输入向量开始。 在每个转置卷积之后,z变得更宽和更浅。 所有转置卷积使用5x5内核的大小,深度从512一直减少到3 - 表示RGB彩色图像。

def transpose_conv2d(x, output_space):
    return tf.layers.conv2d_transpose(x, output_space, 
      kernel_size=5, strides=2, padding='same',
      kernel_initializer=tf.random_normal_initializer(mean=0.0,
                                                      stddev=0.02))

最后一层输出一个32x32x3张量 - 通过双曲正切(tanh)函数在-1和1之间压扁。

最终输出形状由训练图像的大小定义。 在这种情况下,如果训练SVHN,则生成器生成32x32x3图像。 但是,如果训练MNIST,它将生成28x28灰度图像。

最后,请注意,在将输入向量z馈送到生成器之前,我们需要将其缩放到-1到1的间隔。这是遵循使用tanh函数的选择。

def generator(z, output_dim, reuse=False, alpha=0.2, training=True):
    """
    Defines the generator network
    :param z: input random vector z
    :param output_dim: output dimension of the network
    :param reuse: Indicates whether or not the existing model variables should be used or recreated
    :param alpha: scalar for lrelu activation function
    :param training: Boolean for controlling the batch normalization statistics
    :return: model's output
    """
    with tf.variable_scope('generator', reuse=reuse):
        fc1 = dense(z, 4*4*512)

        # Reshape it to start the convolutional stack
        fc1 = tf.reshape(fc1, (-1, 4, 4, 512))
        fc1 = batch_norm(fc1, training=training)
        fc1 = tf.nn.relu(fc1)

        t_conv1 = transpose_conv2d(fc1, 256)
        t_conv1 = batch_norm(t_conv1, training=training)
        t_conv1 = tf.nn.relu(t_conv1)

        t_conv2 = transpose_conv2d(t_conv1, 128)
        t_conv2 = batch_norm(t_conv2, training=training)
        t_conv2 = tf.nn.relu(t_conv2)

        logits = transpose_conv2d(t_conv2, output_dim)

        out = tf.tanh(logits)
        return out
  • Discriminator
    鉴别器也是具有BN(除了其输入层)和泄漏的ReLU**的4层CNN。 使用这种基本的GAN架构,许多**功能都可以正常工作。 但是,泄漏的ReLU非常受欢迎,因为它们有助于梯度更容易地通过架构流动。

常规ReLU功能通过将负值截断为0来工作。这具有阻止梯度流过网络的效果。 泄漏的ReLU允许小的负值通过,而不是函数为零。 也就是说,该函数计算特征与小因子之间的最大值。

def lrelu(x, alpha=0.2):
     # non-linear activation function
    return tf.maximum(alpha * x, x)

Leaky ReLUs代表了解决垂死的ReLU问题的尝试。 当神经元陷入ReLU单元总是为所有输入输出0的状态时,就会发生这种情况。 对于这些情况,梯度完全关闭以通过网络回流。

这对于GAN尤其重要,因为生成器必须学习的唯一方法是从鉴别器接收梯度。

An intuitive introduction to Generative Adversarial Networks (GANs)
An intuitive introduction to Generative Adversarial Networks (GANs)
鉴别器从接收32x32x3图像张量开始。 与发生器相反,鉴别器执行一系列跨步的2次卷积。 每个都通过将特征向量的空间维度减小一半的大小来工作,也使得学习过滤器的数量加倍。

最后,鉴别器需要输出概率。 为此,我们在最终的logits上使用Logistic Sigmoid**函数。

def discriminator(x, reuse=False, alpha=0.2, training=True):
    """
    Defines the discriminator network
    :param x: input for network
    :param reuse: Indicates whether or not the existing model variables should be used or recreated
    :param alpha: scalar for lrelu activation function
    :param training: Boolean for controlling the batch normalization statistics
    :return: A tuple of (sigmoid probabilities, logits)
    """
    with tf.variable_scope('discriminator', reuse=reuse):
        # Input layer is 32x32x?
        conv1 = conv2d(x, 64)
        conv1 = lrelu(conv1, alpha)

        conv2 = conv2d(conv1, 128)
        conv2 = batch_norm(conv2, training=training)
        conv2 = lrelu(conv2, alpha)

        conv3 = conv2d(conv2, 256)
        conv3 = batch_norm(conv3, training=training)
        conv3 = lrelu(conv3, alpha)

        # Flatten it
        flat = tf.reshape(conv3, (-1, 4*4*256))
        logits = dense(flat, 1)

        out = tf.sigmoid(logits)
        return out, logits

请注意,在此框架中,鉴别器充当常规二进制分类器。 一半时间它从训练集接收图像而另一半从发生器接收图像。

回到我们的冒险之旅,重现派对的门票,你所拥有的唯一信息来源是我们的朋友鲍勃的反馈。 换句话说,Bob在每次试用时提供给您的反馈质量对于完成工作至关重要。

以同样的方式,每当鉴别器注意到真实和假图像之间的差异时,它就向发生器发送信号。 该信号是从鉴别器向发生器向后流动的梯度。 通过接收它,发生器能够调整其参数以更接近真实的数据分布。

这是鉴别器的重要性。 事实上,发生器将与产生数据一样好,因为鉴别器正在分辨它们。

  • Losses

现在,让我们描述一下这种架构中最棘手的部分 - 损失。 首先,我们知道鉴别器从训练集和生成器接收图像。

我们希望鉴别器能够区分真实和假图像。 每次我们通过鉴别器运行一个小批量时,我们都会得到logits。 这些是模型中未缩放的值。

但是,我们可以将鉴别器接收的小批量分为两种类型。 第一个,仅由来自训练集和第二个的真实图像组成,仅包含假图像 - 由生成器创建的图像。

def model_loss(input_real, input_z, output_dim, alpha=0.2, smooth=0.1):
    """
    Get the loss for the discriminator and generator
    :param input_real: Images from the real dataset
    :param input_z: random vector z
    :param out_channel_dim: The number of channels in the output image
    :param smooth: label smothing scalar
    :return: A tuple of (discriminator loss, generator loss)
    """
    g_model = generator(input_z, output_dim, alpha=alpha)
    d_model_real, d_logits_real = discriminator(input_real, alpha=alpha)

    d_model_fake, d_logits_fake = discriminator(g_model, reuse=True, alpha=alpha)

    # for the real images, we want them to be classified as positives,  
    # so we want their labels to be all ones.
    # notice here we use label smoothing for helping the discriminator to generalize better.
    # Label smoothing works by avoiding the classifier to make extreme predictions when extrapolating.
    d_loss_real = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, labels=tf.ones_like(d_logits_real) * (1 - smooth)))

    # for the fake images produced by the generator, we want the discriminator to clissify them as false images,
    # so we set their labels to be all zeros.
    d_loss_fake = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.zeros_like(d_model_fake)))

    # since the generator wants the discriminator to output 1s for its images, it uses the discriminator logits for the
    # fake images and assign labels of 1s to them.
    g_loss = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.ones_like(d_model_fake)))

    d_loss = d_loss_real + d_loss_fake

    return d_loss, g_loss

由于两个网络同时训练,GAN还需要两个优化器。 每一个都分别用于最小化鉴别器和发生器的损耗函数。

我们希望鉴别器为真实图像输出接近1的概率,对于假图像输出接近0的概率。 为此,鉴别器需要两次损失。 因此,鉴别器的总损失是这两个部分损失的总和。 一个用于最大化真实图像的概率,另一个用于最小化伪图像的概率。

An intuitive introduction to Generative Adversarial Networks (GANs)
比较真实(左)和生成(右)SVHN样本图像。 虽然有些图像看起来很模糊而有些图像难以识别,但很明显数据分布是由模型捕获的。

在训练开始时,会出现两种有趣的情况。 首先,生成器不知道如何创建类似于训练集中的图像的图像。 第二,鉴别器不知道如何将它接收的图像分类为真实或假的。

结果,鉴别器接收两种非常不同类型的批次。 一个由训练集的真实图像组成,另一个包含非常嘈杂的信号。 随着训练的进行,发生器开始输出看起来更接近训练集图像的图像。 发生这种情况,因为发电机训练以学习组成训练集图像的数据分布。

与此同时,鉴别者开始真正善于将样本分类为真实或假冒。 因此,两种类型的小批量开始在结构上看起来彼此相似。 结果,这使得鉴别器无法将图像识别为真实或假的。

对于损失,我们使用vanilla交叉熵与Adam作为优化器的良好选择。

An intuitive introduction to Generative Adversarial Networks (GANs)
比较实际(左)和生成(右)MNIST样本图像。 由于MNIST图像具有更简单的数据结构,因此与SVHN相比,该模型能够生成更逼真的样本。

  • Concluding

GAN是目前机器学习中最热门的课程之一。 这些模型有可能解开无监督的学习方法,将ML扩展到新的视野。

自创建以来,研究人员一直在开发许多训练GAN的技术。 在用于训练GAN的改进技术中,作者描述了用于图像生成和半监督学习的最先进技术。