GAN是如何工作的?在MNIST数据集上如何演示GAN的一个简单实现?
从伪造活动门票的故事中,可以非常直观地看出GAN的思想。为了清楚地理解GAN是如何工作的以及如何实现它们,本节将会在MNIST数据集上演示GAN的一个简单实现。
首先,需要构建GAN网络的核心,它由两个主要部分组成:生成器和判别器。生成器将会尝试从某个特定的概率分布中想象或者伪造数据样本;而可以访问和查看实际数据样本的判别器将会判断生成器的输出是在设计中存在缺陷还是它与原始数据样本非常接近。与前面的活动场景相似,生成器的整个目的就是使得判别器相信生成的图像是来自真实数据集的,以此来试图欺骗判别器。
训练过程和前面的故事有着相似的结尾,生成器最终将会设法生成与原始数据样本看起来非常相似的图像。
图14.2显示了GAN的典型结构,将在MNIST数据集上训练GAN。图14.2中的隐藏样本部分是一个随机想法或者向量,生成器将会使用它来从真实图像中复制出虚假图像。
图14.2 针对MNIST数据集的通用GAN架构
正如前文提到的,作为一个判断者,判别器将尝试从生成器设计的虚假图像中分辨出真实图像。所以这个网络将产生一个二值输出,二值输出可以使用sigmoid函数来表示(0表示输入的是虚假图像,1表示输入的是真实图像)。
现在继续实现这个架构,看它在MNIST数据集上的表现如何。
从导入此实现所需的库开始。
%matplotlib inline
import matplotlib.pyplot as plt
import pickle as pkl
import numpy as np
import tensorflow as tf
因为这里使用了MNIST数据集,所以将会使用TensorFlow辅助函数来获取数据集,并将它存储在某处。
from tensorflow.examples.tutorials.mnist import input_data
mnist_dataset = input_data.read_data_sets('MNIST_data')
输出如下。
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
14.2.1 模型输入
在深入构建由生成器和判别器表示的GAN的核心之前,先定义计算图的输入。如图14.3所示,需要两个输入:一个输入是真实图像,会把它提供给判别器;另一个输入称为隐空间,会将它提供给生成器,并用于生成虚假图像。
# Defining the model input for the generator and discrimator
def inputs_placeholders(discrimator_real_dim, gen_z_dim):
real_discrminator_input = tf.placeholder(tf.float32, (None,
discrimator_real_dim), name="real_discrminator_input")
generator_inputs_z = tf.placeholder(tf.float32, (None, gen_z_dim),
name="generator_input_z")
return real_discrminator_input, generator_inputs_z
图14.3 在MNIST数据集上实现的GAN架构
下面开始深入构建GAN架构的两个核心组件。首先从构建生成器部分开始。如图14.3所示,生成器将包含至少一个隐藏层,它将作为一个近似器。此外,将会采用一种称为Leaky ReLU的**函数,而不是通用的ReLU**函数。这将允许梯度值在层与层之间随意流动(关于Leaky ReLU的更多信息将会在14.2.3节中介绍)。
14.2.2 变量作用域
变量作用域是TensorFlow中的一个特性,作用域有助于执行如下操作。
- 确保有一些命名约定,以便后续检索变量。例如,通过使变量以单词generator或discriminator开头,这在网络训练期间将有所帮助。其实也可以使用名字作用域特性,但是这个特性不能帮助我们实现第二个目的。
- 能够重复使用或重复训练有不同输入的相同网络。例如,我们将从生成器中对虚假图像进行采样,来查看生成器复制原始图像的性能如何。此外,判别器可以访问真实图像和虚假图像,这使得在构建计算图时可以轻松地重用变量而不是创建新变量。
以下语句将说明如何使用TensorFlow中的变量作用域特性。
with tf.variable_scope('scopeName', reuse=False):
# Write your code here
读者可以在TensorFlow官网中搜索“variable scope”来了解关于使用变量作用域特性的更多好处。
14.2.3 Leaky ReLU
前文提到,使用与ReLU**函数不同版本的**函数——Leaky ReLU。传统版本的ReLU**函数通过其他方式将负值截断为零,只会取输入值和零值中的最大值。而这里使用的Leaky ReLU版本允许存在一些负值,因此得名Leaky ReLU。
有时使用传统的ReLU**函数,网络会陷入一种常态——死亡状态,这是因为网络所有的输出全为零。
Leaky ReLU的思想是通过允许一些负值传递来阻止这种死亡状态。
使生成器工作的整个思想就是从判别器接收梯度值,并且如果网络陷入死亡状态,学习过程就不会出现。
图14.4和图14.5显示了传统ReLU与Leaky ReLU**函数之间的不同。
图14.4 ReLU**函数
图14.5 Leaky ReLU**函数
因为Leaky ReLU**函数并没有在TensorFlow中实现,所以需要我们自己去实现它。如果输入为正数,此**函数的输出也为正数;如果输入为负数,则此**函数的输出将是一个受控制的负值。这里将使用一个称为alpha的参数来控制负值,通过允许传递一些负值来引入网络的容错性。
下面的等式表示需要实现的Leaky ReLU函数。
f(x) = max(ax,x)
14.2.4 生成器
把MNIST图像归一化到0~1,从而使得sigmoid**函数充分发挥作用。但实际上,我们发现tanh**函数比其他任何函数都具有更好的性能。因此为了使用tanh**函数,需要将这些图像的像素值范围重新缩放到−1~1。
def generator(gen_z, gen_out_dim, num_hiddern_units=128, reuse_vars=False,
leaky_relu_alpha=0.01):
''' Building the generator part of the network
Function arguments
---------
gen_z : the generator input tensor
gen_out_dim : the output shape of the generator
num_hiddern_units : Number of neurons/units in the hidden layer
reuse_vars : Reuse variables with tf.variable_scope
leaky_relu_alpha : leaky ReLU parameter
Function Returns
-------
tanh_output, logits_layer:
'''
with tf.variable_scope('generator', reuse=reuse_vars):
# Defining the generator hidden layer
hidden_layer_1 = tf.layers.dense(gen_z, num_hiddern_units,
activation=None)
# Feeding the output of hidden_layer_1 to leaky relu
hidden_layer_1 = tf.maximum(hidden_layer_1,
leaky_relu_alpha*hidden_layer_1)
# Getting the logits and tanh layer output
logits_layer = tf.layers.dense(hidden_layer_1, gen_out_dim,
activation=None)
tanh_output = tf.nn.tanh(logits_layer)
return tanh_output, logits_layer
现在我们已经准备好了生成器部分,下面继续定义GAN的第二个组件。
14.2.5 判别器
接下来,构建生成对抗网络中的第二个主要组件,即判别器。判别器与生成器基本相同,但不使用tanh**函数,而使用sigmoid**函数;它将产生一个二值输出,代表判别器对输入图像的判断。
def discriminator(disc_input, num_hiddern_units=128, reuse_vars=False,
leaky_relu_alpha=0.01):
''' Building the discriminator part of the network
Function Arguments
---------
disc_input : discrminator input tensor
num_hiddern_units : Number of neurons/units in the hidden layer
reuse_vars : Reuse variables with tf.variable_scope
leaky_relu_alpha : leaky ReLU parameter
Function Returns
-------
sigmoid_out, logits_layer:
'''
with tf.variable_scope('discriminator', reuse=reuse_vars):
# Defining the generator hidden layer
hidden_layer_1 = tf.layers.dense(disc_input, num_hiddern_units,
activation=None)
# Feeding the output of hidden_layer_1 to leaky relu
hidden_layer_1 = tf.maximum(hidden_layer_1,
leaky_relu_alpha*hidden_layer_1)
logits_layer = tf.layers.dense(hidden_layer_1, 1, activation=None)
sigmoid_out = tf.nn.sigmoid(logits_layer)
return sigmoid_out, logits_layer
14.2.6 构建GAN网络
在定义了构建生成器和判别器组件的主要函数之后,下面将它们堆叠起来,然后为此实现定义模型损失和优化器。
可以通过改变下面一组超参数来微调GAN。
# size of discriminator input image
#28 by 28 will flattened to be 784
input_img_size = 784
# size of the generator latent vector
gen_z_size = 100
# number of hidden units for the generator and discriminator hidden layers
gen_hidden_size = 128
disc_hidden_size = 128
#leaky ReLU alpha parameter which controls the leak of the function
leaky_relu_alpha = 0.01
# smoothness of the label
label_smooth = 0.1
在定义了用于生成虚假MNIST图像(看起来和真实图像基本相同)的GAN架构的两个主要组件之后,下面使用目前已经定义的函数来构建网络。构建网络将遵循以下步骤。
(1)定义模型输入,输入包含两个变量。其中一个变量是真实图像,把它输入判别器,另一个变量是生成器用于复制原始图像的隐空间。
(2)调用前面定义的生成器函数来构建网络的生成器部分。
(3)调用前面定义的判别器函数来构建网络的判别器部分,但是这里会调用该函数两次。第一次调用针对真实数据,第二次调用针对生成器生成的虚假数据。
(4)通过重用变量来保持真实图像和虚假图像的权重是一样的。
tf.reset_default_graph()
# creating the input placeholders for the discrminator and
generator
real_discrminator_input, generator_input_z =
inputs_placeholders(input_img_size, gen_z_size)
# Create the generator network
gen_model, gen_logits = generator(generator_input_z,
input_img_size, gen_hidden_size, reuse_vars=False,
leaky_relu_alpha=leaky_relu_alpha)
# gen_model is the output of the generator
# Create the generator network
disc_model_real, disc_logits_real =
discriminator(real_discrminator_input, disc_hidden_size,
reuse_vars=False, leaky_relu_alpha=leaky_relu_alpha)
disc_model_fake, disc_logits_fake = discriminator(gen_model,
disc_hidden_size, reuse_vars=True,
leaky_relu_alpha=leaky_relu_alpha)
这一部分需要定义判别器和生成器损失,可以认为这是此实现中最富有技巧的部分。
我们知道生成器试图伪造原始图像,并且判别器作为判断者,同时接收来自生成器和原始输入的图像。因此在为每一部分设计损失时,需要关注两件事。
首先,网络的判别器部分要能够区分由生成器生成的虚假图像和来自原始训练样本的真实图像。在训练时,将给判别器部分提供一批分为两类的数据。第一类是来自原始输入的图像,第二类是生成器生成的虚假图像。
因此,判别器最终的总损失将是它接受真实图像为真实图像并且检测假图像为虚假图像的能力之和。最终的总损失如下。
disc_loss = disc_loss_real + disc_loss_fake
tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_layer,
labels=labels))
然后,需要计算两个损失才能得到最终的判别器损失。
第一个损失disc_loss_real将会根据从判别器和labels获得的logits值计算出来。在这种情况下,labels的值全都为1,因为此时最小批次中所有图像都来自MNIST数据集中的真实输入图像。为了增强模型在测试集上的泛化能力并给出更好的结果,我们发现其实将labels的值从1改为0.9会更好。标签的这种改变称为标签平滑。
labels = tf.ones_like(tensor) * (1 - smooth)
判别器损失的第二部分是判别器能够检测虚假图像的能力,损失介于从判别器获得的logits值和labels值之间。此时,所有的labels值都是零,因为已知这个最小批次中的所有图像都来自生成器,而不是来自原始输入。
既然已经讨论了判别器损失,那么同样也需要计算生成器损失。生成器损失称为gen_loss,它介于disc_logits_fake(判别器对于虚假图像的输出)和标签(全都为1,因为生成器试图使判别器相信它生成的虚假图像)之间。
# calculating the losses of the discrimnator and generator
disc_labels_real = tf.ones_like(disc_logits_real) * (1 - label_smooth)
disc_labels_fake = tf.zeros_like(disc_logits_fake)
disc_loss_real =
tf.nn.sigmoid_cross_entropy_with_logits(labels=disc_labels_real,
logits=disc_logits_real)
disc_loss_fake =
tf.nn.sigmoid_cross_entropy_with_logits(labels=disc_labels_fake,
logits=disc_logits_fake)
#averaging the disc loss
disc_loss = tf.reduce_mean(disc_loss_real + disc_loss_fake)
#averaging the gen loss
gen_loss = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
labels=tf.ones_like(disc_logits_fake),
logits=disc_logits_fake))
最后是优化器部分。在本节中,将会定义训练过程中使用的优化标准。首先,将分别更新生成器和判别器的变量,因此需要能够检索每一部分的变量。
对于第一个优化器(即生成器1),将从计算图中的可训练变量中检索以generator名称开头的所有变量,然后通过参考其名称来检查每个变量属于哪一模块。
同样也要对判别器的变量做同样的操作,方法是令其所有变量都以discriminator开头。在这之后,就可以将想要优化的变量列表传递给优化器。
TensorFlow的变量作用域特性使得我们能够检索以某个字符串开头的变量,然后会有两个不同的变量列表,一个用于生成器,另一个用于判别器。
# building the model optimizer
learning_rate = 0.002
# Getting the trainable_variables of the computational graph, split into
Generator and Discrimnator parts
trainable_vars = tf.trainable_variables()
gen_vars = [var for var in trainable_vars if
var.name.startswith("generator")]
disc_vars = [var for var in trainable_vars if
var.name.startswith("discriminator")]
disc_train_optimizer = tf.train.AdamOptimizer().minimize(disc_loss,
var_list=disc_vars)
gen_train_optimizer = tf.train.AdamOptimizer().minimize(gen_loss,
var_list=gen_vars)
本文截选自《深度学习案例精粹》第14章,[爱尔兰] 艾哈迈德·曼肖伊(Ahmed Menshawy) 著,洪志伟,曹檑,廖钊坡 译。
本书使用目前广泛应用的深度学习框架之一—TensorFlow以及非常流行的Python语言进行代码示例,想要进一步学习的读者将会有极多的社区资源。
本书主要讲述了深度学习中的重要概念和技术,并展示了如何使用TensorFlow实现高级机器学习算法和神经网络。本书首先介绍了数据科学和机器学习中的基本概念,然后讲述如何使用TensorFlow训练深度学习模型,以及如何通过训练深度前馈神经网络对数字进行分类,如何通过深度学习架构解决计算机视觉、语言处理、语义分析等方面的实际问题,最后讨论了高级的深度学习模型,如生成对抗网络及其应用。