Semi-Supervised Learning with Generative Adversarial Networks

       如果你曾经听过或研究过深度学习,你可能听说过MNIST,SVHN,ImageNet,PascalVoc等。 每个数据集都有一个共同点。 它们由数百和数千个标记数据组成。 换句话说,这些集合由(x,y)对组成,其中(x)是原始数据,例如图像矩阵,(y)是数据点(x)代表什么的描述。

       以MNIST数据集为例。 60,000个数据点中的每一个都是(输入,标签)对。 输入是28x28灰度图像,标签是表示输入内容的符号。 对于MNIST数据集,输入图像可以是一个,两个,三个等等,直到九个可能的类别。

       像这样的数据集的最常见用法是开发监督模型。 为了训练这样的算法,我们通常提供大量的数据样本。

       这种类型的分类器占用标记数据的一小部分和大量未标记数据(来自同一域)。 目标是组合这些数据源以训练深度卷积神经网络(DCNN)以学习能够将新数据点映射到其期望结果的推断函数。

 

        在这个前沿领域,我们提出了一个GAN模型,使用一个非常小的带标签的训练集来分类街道视图的门牌号。事实上,这个模型大约使用了原始SVHN训练标签的1.3%,即1000(1000)个标记的例子。 

Intuition

        在构建用于生成图像的GAN时,我们同时训练了生成器和鉴别器。经过训练后,我们可以丢弃鉴别器,因为我们只用它来训练生成器。 

在构建用于生成图像的GAN时,我们同时训练了生成器和鉴别器。经过训练后,我们可以丢弃鉴别器,因为我们只用它来训练生成器。

另外,这一次,在训练结束时,我们可以扔掉生成器。注意,角色发生了变化。现在,生成器只用于训练时帮助鉴别器。

   换句话说,生成器充当不同的信息源,鉴别器从中获取原始的未标记训练数据。我们将看到,这些未标记数据是提高鉴别器性能的关键。

   此外,对于常规图像生成GAN,鉴别器仅具有一个角色。 计算其输入是否真实的概率 - 让我们称之为GAN问题。

   然而,为了将鉴别器变为半监督分类器,除了GAN问题之外,鉴别器还必须学习每个原始数据集类的概率。

   换句话说,对于每个输入图像,鉴别器必须知道它的概率是1 2 3等等。

   回想一下,对于图像生成GAN鉴别器,我们有一个sigmoid单位输出。 此值表示输入图像为真实(值接近1)或伪造(值接近0)的概率。

  换句话说,从鉴别器的角度来看,接近1的值意味着样本可能来自于训练集。同样,接近0的值意味着样本来自于生成器网络的变化更高。

  通过使用该概率,鉴别器能够给生成器发送一个信号。 该信号允许发生器在训练期间调整其参数,从而可以提高其创建逼真图像的能力。

  我们必须将鉴别器(从之前的GAN)转换为11类分类器。 为此,我们可以将其sigmoid输出转换为具有11类输出的softmax。 SVHN数据集(0到9)的各个类概率的前10个,以及来自发生器的所有伪图像的第11个类。

    注意,如果我们将第11类概率设置为0,然后,前10个概率的和表示使用sigmoid函数计算的相同概率。

最后,我们需要设置损失,以便鉴别器可以同时执行以下两种操作:

- (i)帮助生成器学习生成逼真的图像。为了做到这一点,我们必须指示鉴别器区分真实样品和虚假样品。

- (ii)使用生成器的图像以及标记和未标记的训练数据来帮助对数据集进行分类。

总之,鉴别器有三个不同的训练数据来源。

  • 带有标签的真实图像。这些是图像标签对就像任何常规监督分类问题一样。

  • 没有标签的真实图像。对于这些,分类器只知道这些图像是真实的。

  • 来自生成器的图像。对于这些,鉴别器学习将它们分类为假货。

这些不同数据来源的组合将使分类器能够从更广泛的角度进行学习。这反过来又使模型能够比仅仅使用1000个标记的示例进行训练时更精确地执行推理。

Generator

生成器遵循DCGAN论文中描述的非常标准的实现。这种方法包括以一个随机向量z作为输入。将它重塑为一个4D张量,然后将它输入到转置卷积序列、批规格化(BN)和leaky ReLU操作。  then feed it to a sequence of transpose convolutions, Batch Normalization (BN) and leaky ReLU operations.

这一系列的计算增加了输入向量的空间维度,同时减少了它的通道数量。因此,网络通过双曲正切函数输出一个在-1和1之间压扁的32x32x3 RGB张量形状。

def generator(z, output_dim, reuse=False, alpha=0.2, training=True, size_mult=128):
    with tf.variable_scope('generator', reuse=reuse):
        # First fully connected layer
        x1 = tf.layers.dense(z, 4 * 4 * size_mult * 4)
        # Reshape it to start the convolutional stack
        x1 = tf.reshape(x1, (-1, 4, 4, size_mult * 4))
        x1 = tf.layers.batch_normalization(x1, training=training)
        x1 = tf.maximum(alpha * x1, x1)

        x2 = tf.layers.conv2d_transpose(x1, size_mult * 2, 5, strides=2, padding='same')
        x2 = tf.layers.batch_normalization(x2, training=training)
        x2 = tf.maximum(alpha * x2, x2)

        x3 = tf.layers.conv2d_transpose(x2, size_mult, 5, strides=2, padding='same')
        x3 = tf.layers.batch_normalization(x3, training=training)
        x3 = tf.maximum(alpha * x3, x3)

        # Output layer
        logits = tf.layers.conv2d_transpose(x3, output_dim, 5, strides=2, padding='same')

        out = tf.tanh(logits)

        return out

Discriminator

         鉴别器,现在是一个多类分类器,是最相关的网络。在这里,我们设置了一个类似的DCGAN体系结构,其中我们使用了BN和ReLU的一组卷积。我们使用跨越卷积来减少特征向量的维数。注意,不是所有的卷积都执行这种类型的计算。当我们想保持特征向量的维数不变时,我们用1的步长,否则我们用2的步长。最后,为了稳定学习,我们广泛使用了BN(除了网络的第一层)。

 

        2D卷积窗口(kernel或filter)被设置为所有卷积的宽度和高度为3。另外,注意我们有一些层是dropout。重要的是要了解我们的鉴别器的行为(部分)像任何其他常规分类器。因此,它可能会遇到任何一个分类器如果设计不好都会遇到的问题。

        在一个非常有限的数据集上训练一个大的分类器时,一个人可能会遇到的一个最可能的缺点是过度拟合的巨大。要注意的一件事是,“经过训练”的分类器通常在训练错误(较小)和测试错误(较高)之间显示出显著的差异。

        这种情况表明模型很好地捕获了训练数据集的结构。然而,由于它过于相信训练数据,它无法对看不见的例子进行概括。

       为了防止这种情况,我们通过dropout广泛使用正规化。 即使是网络的第一层。

       最后,我们没有在卷积堆栈的顶部应用完全连接层,而是执行全局平均池化(GAP)。在GAP中,我们取特征向量的空间维数的平均值。这个操作导致将张量维压缩到一个值。

def discriminator(x, reuse=False, alpha=0.2, drop_rate=0., num_classes=10, size_mult=64):
    with tf.variable_scope('discriminator', reuse=reuse):
        x = tf.layers.dropout(x, rate=drop_rate/2.5)

        # Input layer is ?x32x32x3
        x1 = tf.layers.conv2d(x, size_mult, 3, strides=2, padding='same')
        relu1 = tf.maximum(alpha * x1, x1)
        relu1 = tf.layers.dropout(relu1, rate=drop_rate) # [?x16x16x?]

        x2 = tf.layers.conv2d(relu1, size_mult, 3, strides=2, padding='same')
        bn2 = tf.layers.batch_normalization(x2, training=True) # [?x8x8x?]
        relu2 = tf.maximum(alpha * bn2, bn2)

        x3 = tf.layers.conv2d(relu2, size_mult, 3, strides=2, padding='same') # [?x4x4x?]
        bn3 = tf.layers.batch_normalization(x3, training=True)
        relu3 = tf.maximum(alpha * bn3, bn3)
        relu3 = tf.layers.dropout(relu3, rate=drop_rate)

        x4 = tf.layers.conv2d(relu3, 2 * size_mult, 3, strides=1, padding='same') # [?x4x4x?]
        bn4 = tf.layers.batch_normalization(x4, training=True)
        relu4 = tf.maximum(alpha * bn4, bn4)

        x5 = tf.layers.conv2d(relu4, 2 * size_mult, 3, strides=1, padding='same') # [?x4x4x?]
        bn5 = tf.layers.batch_normalization(x5, training=True)
        relu5 = tf.maximum(alpha * bn5, bn5)

        x6 = tf.layers.conv2d(relu5, 2 * size_mult, 3, strides=2, padding='same') # [?x2x2x?]
        bn6 = tf.layers.batch_normalization(x6, training=True)
        relu6 = tf.maximum(alpha * bn6, bn6)
        relu6 = tf.layers.dropout(relu6, rate=drop_rate)
...
In the end, instead of applying a fully connected layer on top of the convolution stack, we perform Global Average Pooling (GAP). In GAP, we take the average over the spatial dimensions of a feature vector. This operation results in squashing the tensor dimensions to a single value.

...
# Flatten it by global average pooling
# In global average pooling, for every feature map we take the average over all the spatial
# domain and return a single value
# In: [BATCH_SIZE,HEIGHT X WIDTH X CHANNELS] --> [BATCH_SIZE, CHANNELS]
features = tf.reduce_mean(relu7, axis=[1,2])

# Set class_logits to be the inputs to a softmax distribution over the different classes
class_logits = tf.layers.dense(features, num_classes)
...

例如,假设在一系列的卷积之后,我们得到一个形状张量[BATCH_SIZE, 8,8, NUM_CHANNELS]。为了应用间隙,我们取平均值除以[8x8]张量片。这导致了一个形状张量[BATCH_SIZE, 1,1, NUM_CHANNELS],可以被重新塑造成[BATCH_SIZE, NUM_CHANNELS]。

         在《网络中的网络》一书中,作者描述了GAP相对于传统的全连通层的一些优点。这包括:对空间转换具有更高的鲁棒性和较少的过度拟合问题。在GAP之后,我们应用一个完全连接的层来输出最终的物流。它们具有shape [BATCH_SIZE, NUM_CLASSES],对应于未缩放的最终类值。

      为了获得分类概率,我们通过softmax函数提供logits。 但是,我们仍然需要一种方法来表示输入图像是真实的而不是假的概率。 也就是说,我们仍然需要考虑常规GAN的二进制分类问题。

...
# Get the probability that the input is real rather than fake
out = tf.nn.softmax(class_logits) # class probabilities for the 10 real classes
...

       我们知道逻辑是用softmax概率值表示的。然而,我们还需要一种将它们表示为s状逻辑的方法。我们知道输入为实数的概率对应于所有实数类逻辑的和。有了这个想法,我们可以将这些值提供给LogSumExp函数,该函数将对二进制分类值进行建模。然后,我们将LogSumExp的结果提供给一个sigmoid函数。

       我们可以使用Tensorflow的LogSumExp内置函数来避免数值问题。这个例程可以防止当LogSumExp遇到非常极端的值(无论是正值还是负值)时可能出现的over/under flow问题。

...
# This function is more numerically stable than log(sum(exp(input))).
# It avoids overflows caused by taking the exp of large inputs and underflows
# caused by taking the log of small inputs.
gan_logits = tf.reduce_logsumexp(class_logits, 1)
...

Model Loss

如前所述,我们可以将鉴别器损耗分为两部分。一个代表GAN问题,无监督损失。另一个是计算单个真实类概率,监督损失。

 

对于无监督损耗,鉴别器必须从生成器中区分真实的训练图像和虚假图像。

 

对于普通GAN,有一半的时间鉴别器接收来自训练集的未标记图像,另一半时间接收来自生成器的虚构未标记图像。

 

在这两种情况下,我们都在处理二进制分类问题。由于我们希望真实图像的概率值接近1,而非真实图像的概率值接近0,所以我们可以使用s形交叉熵函数来计算损失。

 

对于来自训练集的图像,我们通过分配1的标签来最大化它们的真实概率。对于来自生成器的合成图像,我们通过给它们贴上0的标签来最大限度地提高它们的伪概率。

 

...
# Here we compute `d_loss`, the loss for the discriminator.
# This should combine two different losses:
# 1. The loss for the GAN problem, where we minimize the cross-entropy for the binary
#    real-vs-fake classification problem.
tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=gan_logits_on_data,
                                                        labels=tf.ones_like(gan_logits_on_data) * (1 - smooth)))

fake_data_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=gan_logits_on_samples,
                                                     labels=tf.zeros_like(gan_logits_on_samples)))

# This way, the unsupervised
unsupervised_loss = real_data_loss + fake_data_loss
...

对于监督损失,我们需要使用来自鉴别器的物流。由于这是一个多类分类问题,我们可以使用softmax交叉熵函数与我们现有的实际标签。

 

注意,这部分类似于任何其他分类模型。最后,鉴别器损耗是监督损耗和非监督损耗的总和。另外,因为我们假装没有大部分的标签,我们需要在监督损失中忽略它们。为此,我们将损失乘以mask变量,该变量指示哪些标签可以使用。

#  2. The loss for the SVHN digit classification problem, where we minimize the cross-entropy
#     for the multi-class softmax. For this one we use the labels. Don't forget to ignore
#     use `label_mask` to ignore the examples that we are pretending are unlabeled for the
#     semi-supervised learning problem.
y = tf.squeeze(y)
suppervised_loss = tf.nn.softmax_cross_entropy_with_logits(logits=class_logits_on_data,
                                                              labels=tf.one_hot(y, num_classes, dtype=tf.float32))

label_mask = tf.squeeze(tf.to_float(label_mask))

# ignore the labels that we pretend does not exist for the loss
suppervised_loss = tf.reduce_sum(tf.multiply(suppervised_loss, label_mask))

# get the mean
suppervised_loss = suppervised_loss / tf.maximum(1.0, tf.reduce_sum(label_mask))
d_loss = unsupervised_loss + suppervised_loss

As described in the Improved Techniques for Training GANs paper, we use feature matching for the generator loss.

 As the authors describe:

特征匹配是指对训练数据上的某些特征的平均值与生成样本上该特征集的平均值之间的平均绝对误差进行惩罚。

为了做到这一点,我们从两个不同的来源获得一些统计数据(矩),并强迫它们是相似的。

 

首先,我们取在处理一个真正的训练小批量时从鉴别器中提取的特征的平均值。

 

第二,我们用同样的方法计算力矩,但是现在当一个由来自生成器的假图像组成的小批量被鉴别器分析时。

 

最后,对于这两组矩,生成器损耗是它们之间的平均绝对差。换句话说,正如本文所强调的:

我们训练生成器匹配鉴别器中间层特征的期望值。
# Here we set `g_loss` to the "feature matching" loss invented by Tim Salimans at OpenAI.
# This loss consists of minimizing the absolute difference between the expected features
# on the data and the expected features on the generated samples.
# This loss works better for semi-supervised learning than the tradition GAN losses.
# Make the Generator output features that are on average similar to the features
# that are found by applying the real data to the discriminator
data_moments = tf.reduce_mean(data_features, axis=0)
sample_moments = tf.reduce_mean(sample_features, axis=0)
g_loss = tf.reduce_mean(tf.abs(data_moments - sample_moments))
pred_class = tf.cast(tf.argmax(class_logits_on_data, 1), tf.int32)
eq = tf.equal(tf.squeeze(y), pred_class)
correct = tf.reduce_sum(tf.to_float(eq))
masked_correct = tf.reduce_sum(label_mask * tf.to_float(eq))

虽然特征匹配缺失在半监督学习中表现得很好,但是生成器生成的图像却不如上一篇文章中生成的图像好。

生成器网络使用特征匹配损耗创建的示例图像。

在GANs论文的改进技术中,OpenAI报告了在MNIST、CIFAR-10和SVHN上进行半监督分类学习的最新成果。

我们的实现分别达到训练和测试精度近93%和68%。这些结果比NIPS 2014年发表的论文中提到的结果要好。

 

这本笔记本不是用来演示这种交叉验证技术的最佳实践的。它只使用了最初OpenAI论文中描述的一些技术。

 

这本笔记本是基于我毕业的Udacity基础深度学习纳米学位课程。

 Concluding

许多研究认为无监督学习是一般人工智能系统缺失的一环。

 

要打破这些障碍,关键是尝试使用较少标记的数据来解决已经存在的问题。在这个场景中,GANs提供了一个学习复杂任务的真实替代方法,使用较少的标记样本。

 

然而,监督学习和半监督学习之间的性能差距还远未相等。但我们当然可以预期,随着新方法的出现,这种差距会变得越来越短。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值