GAN学习报告

GAN 启发自博弈论中的二人零和博弈(two-player game),
论文:Generative Adversarial Networks(arxiv:https://arxiv.org/abs/1406.2661)
GAN 模型中的两位博弈方分别由生成式模型G(generative model)和判别式模型N(discriminative model)组成。

基本原理

onenote
G是一个生成图片的网络,它接收一个随机的噪声z,通过这个噪声生成图片,记做G(z)。
D是一个判别网络,判别一张图片是不是“真实的”。它的输入参数是x,x代表一张图片,输出D(x)代表x为真实图片的概率,如果为1,就代表100%是真实的图片,而输出为0,就代表不可能是真实的图片。

在训练过程中,生成网络G的目标就是尽量生成真实的图片去欺骗判别网络D。而D的目标就是尽量把G生成的图片和真实的图片分别开来。这样,G和D构成了一个动态的“博弈过程”。

最后对抗网网络的目标:对于输入一个随机噪声z,生成网络G生成的假样本G(z)进去了判别网络以后,判别网络D给出的结果D(G(z))是一个接近 0.5 的值,也就是说无法分辨是真(1)还是假(0),达到了平衡。从而我们的G可以生成以假乱真的图片。

模型

判别模型,直观来看就是一个简单的神经网络结构,输入就是一副图像,输出就是一个概率值,用于判断真假使用(概率值大于 0.5 那就是真,小于 0.5 那就是假)。
生成模型,生成模型同样也可以看成是一个神经网络模型,输入是一组随机噪声z,输出是一个仿样本G(z)。

实现方法

onenote

生成模型与对抗模型可以说是完全独立的两个模型

但是训练时让他们:单独交替迭代训练

先用真实数据集和生成模型产生的数据集进行标签,来训练判别模型。

而训练生成模型时,把生成数据标签成真实数据,让判别模型去判别。(注意,不更新判别模型里面的参数)把误差一直传,传到生成网络那块后更新生成网络的参数。

重复上述过程。

简单来说生成器的结果会有两种应用,一种是标明为假,和真实数据混合起来为判别模型。一种把自己标注为真,用当前的判别模型来进行训练

目标公式

onenote

公式详解在下图

onenote
整个式子由两部分构成。x表示真实图片,z表示输入G网络的噪声,而G(z)表示G网络生成的图片。
D(x)表示D网络判断真实图片是否真实的概率(因为x就是真实的,所以对于D来说,这个值越接近1越好)。而D(G(z))是D网络判断G生成的图片的是否真实的概率。

单独优化D时,想要让真实数据x的输出让D(x)越大越好,让生成数据D(G(z))越小越好。公式中将D(G(z))写为1-D(G(z))是可以让整个公式统一,V(D)越越好。

单独优化G时,只和公式第二部分相关,其目标就是让D(G(z)越大越好,为了整合统一写为1-D(G(z),就是V(G)越越好。

用tensorflow实现简单的GAN

about tensorflow:tensorflow过程主要分为前向传播和反向传播。

前向传播搭建网络结构。

反向传播训练网络参数。

本次目的:实现通过mnist数据集,训练得到生成数字图像

每训练1000 次保存16张生成图片

共训练500000轮。

参考代码:https://github.com/liuzheng007/GAN/blob/master/gan.py

KL散度又称相对熵,用来衡量两个分布的差异。

KL 散度有很多有用的性质,最重要的是它是非负的。KL 散度为 0时,是 当且仅当 P 和 Q 在离散型变量的情况下是相同的分布,或者在连续型变量的情况下是 『几乎处处』 相同的。因为 KL 散度是非负的并且衡量的是两个分布之间的差异,它经常被用作分布之间的某种距离。然而,它并不是真的距离因为它不是对称的。

最优判别器

在上面极小极大博弈的第一步中,给定生成器 G,最大化 V(D,G) 而得出最优判别器 D。其中,最大化 V(D,G) 评估了 P(G(z)) 和 P(x) 之间的差异或距离。因为在原论文中价值函数可写为在 x 上的积分,即将数学期望展开为积分形式:

其实求积分的最大值可以转化为求被积函数的最大值。而求被积函数的最大值是为了求得最优判别器 D,因此不涉及判别器的项都可以看作为常数项。如图,因此被积函数可表示为 aD(x)+blog(1-D(x))
【 a,b∈(0,1)】

令判别器 D(x) 等于 y,那么被积函数可以写为:
为了找到最优的极值点,如果 a+b≠0,我们可以对y求一阶导,令导数为零。

继续求表达式 f(y) 在该驻点的二阶导,

其中 a,b∈(0,1)。因为一阶导等于零、二阶导小于零,所以我们知道 a/(a+b) 为极大值。将 a=P(x)、b=P(G(x)) 代入该极值,最优判别器 D(x)=P(x)/(P(x)+P(G(x))),此时价值函数 V(G,D) 取极大值。
其实该最优的 D 在实践中并不是可计算的,但在数学上十分重要。我们并不知道先验的 P(x),所以我们在训练中永远不会用到它。另一方面,它的存在令我们可以证明最优的 G 是存在的,并且在训练中我们只需要逼近 D(x)。

代码解析:

生成模型
首先需要定义一个生成器 G,该生成器需要将输入的随机噪声变换为图像。以下是定义的生成模型,该模型首先输入有 100 个元素的向量,该向量随机生成于某分布。随后利用两个全连接层接连将该输入向量扩展到 1024 维和 12877 维,后面就开始将全连接层所产生的一维张量重新塑造成二维张量,即 MNIST 中的灰度图。我们注意到该模型采用的激活函数为 tanh,所以也尝试过将其转换为 relu 函数,但发现生成模型如果转化为 relu 函数,那么它的输出就会成为一片灰色。

由全连接传递的数据会经过几个上采样层和卷积层,我们注意到最后一个卷积层所采用的卷积核为 1,所以经过最后卷积层所生成的图像是一张二维灰度图像,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def generator_model():
#下面搭建生成器的架构,首先导入序贯模型(sequential),即多个网络层的线性堆叠
model = Sequential()
#添加一个全连接层,输入为100维向量,输出为1024维
model.add(Dense(input_dim=100, output_dim=1024))
#添加一个激活函数tanh
model.add(Activation('tanh'))
#添加一个全连接层,输出为128×7×7维度
model.add(Dense(128*7*7))
#添加一个批量归一化层,该层在每个batch上将前一层的激活值重新规范化,即使得其输出数据的均值接近0,其标准差接近1
model.add(BatchNormalization())
model.add(Activation('tanh'))
#Reshape层用来将输入shape转换为特定的shape,将含有128*7*7个元素的向量转化为7×7×128张量
model.add(Reshape((7, 7, 128), input_shape=(128*7*7,)))
#2维上采样层,即将数据的行和列分别重复2次
model.add(UpSampling2D(size=(2, 2)))
#添加一个2维卷积层,卷积核大小为5×5,激活函数为tanh,共64个卷积核,并采用padding以保持图像尺寸不变
model.add(Conv2D(64, (5, 5), padding='same'))
model.add(Activation('tanh'))
model.add(UpSampling2D(size=(2, 2)))
#卷积核设为1即输出图像的维度
model.add(Conv2D(1, (5, 5), padding='same'))
model.add(Activation('tanh'))
return model

拼接
前面定义的是可生成图像的模型 G(z;θ_g),而我们在训练生成模型时,需要固定判别模型 D 以极小化价值函数而寻求更好的生成模型,这就意味着我们需要将生成模型与判别模型拼接在一起,并固定 D 的权重以训练 G 的权重。下面就定义了这一过程,我们先添加前面定义的生成模型,再将定义的判别模型拼接在生成模型下方,并且我们将判别模型设置为不可训练。因此,训练这个组合模型才能真正更新生成模型的参数。

1
2
3
4
5
6
7
8
9
def generator_containing_discriminator(g, d):
#将前面定义的生成器架构和判别器架构组拼接成一个大的神经网络,用于判别生成的图片
model = Sequential()
#先添加生成器架构,再令d不可训练,即固定d
#因此在给定d的情况下训练生成器,即通过将生成的结果投入到判别器进行辨别而优化生成器
model.add(g)
d.trainable = False
model.add(d)
return model

判别模型
判别模型相对来说就是比较传统的图像识别模型,前面我们可以按照经典的方法采用几个卷积层与最大池化层,而后再展开为一维张量并采用几个全连接层作为架构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def discriminator_model():
#下面搭建判别器架构,同样采用序贯模型
model = Sequential()

#添加2维卷积层,卷积核大小为5×5,激活函数为tanh,输入shape在‘channels_first’模式下为(samples,channels,rows,cols)
#在‘channels_last’模式下为(samples,rows,cols,channels),输出为64维
model.add(
Conv2D(64, (5, 5),
padding='same',
input_shape=(28, 28, 1))
)
model.add(Activation('tanh'))
#为空域信号施加最大值池化,pool_size取(2,2)代表使图片在两个维度上均变为原长的一半
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(128, (5, 5)))
model.add(Activation('tanh'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#Flatten层把多维输入一维化,常用在从卷积层到全连接层的过渡
model.add(Flatten())
model.add(Dense(1024))
model.add(Activation('tanh'))
#一个结点进行二值分类,并采用sigmoid函数的输出作为概念
model.add(Dense(1))
model.add(Activation('sigmoid'))
return model

训练
训练过程可简述为:

加载 MNIST 数据
将数据分割为训练与测试集,并赋值给变量
设置训练模型的超参数
编译模型的训练过程
在每一次迭代内,抽取生成图像与真实图像,并打上标注
随后将数据投入到判别模型中,并进行训练与计算损失
固定判别模型,训练生成模型并计算损失,结束这一次迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def train(BATCH_SIZE):


(X_train, y_train), (X_test, y_test) = mnist.load_data()
#iamge_data_format选择"channels_last"或"channels_first",该选项指定了Keras将要使用的维度顺序。
#"channels_first"假定2D数据的维度顺序为(channels, rows, cols),3D数据的维度顺序为(channels, conv_dim1, conv_dim2, conv_dim3)

#转换字段类型,并将数据导入变量中
X_train = (X_train.astype(np.float32) - 127.5)/127.5
X_train = X_train[:, :, :, None]
X_test = X_test[:, :, :, None]
# X_train = X_train.reshape((X_train.shape, 1) + X_train.shape[1:])

#将定义好的模型架构赋值给特定的变量
d = discriminator_model()
g = generator_model()
d_on_g = generator_containing_discriminator(g, d)

#定义生成器模型判别器模型更新所使用的优化算法及超参数
d_optim = SGD(lr=0.001, momentum=0.9, nesterov=True)
g_optim = SGD(lr=0.001, momentum=0.9, nesterov=True)

#编译三个神经网络并设置损失函数和优化算法,其中损失函数都是用的是二元分类交叉熵函数。编译是用来配置模型学习过程的
g.compile(loss='binary_crossentropy', optimizer="SGD")
d_on_g.compile(loss='binary_crossentropy', optimizer=g_optim)

#前一个架构在固定判别器的情况下训练了生成器,所以在训练判别器之前先要设定其为可训练。
d.trainable = True
d.compile(loss='binary_crossentropy', optimizer=d_optim)

#下面在满足epoch条件下进行训练
for epoch in range(30):
print("Epoch is", epoch)

#计算一个epoch所需要的迭代数量,即训练样本数除批量大小数的值取整;其中shape[0]就是读取矩阵第一维度的长度
print("Number of batches", int(X_train.shape[0]/BATCH_SIZE))

#在一个epoch内进行迭代训练
for index in range(int(X_train.shape[0]/BATCH_SIZE)):

#随机生成的噪声服从均匀分布,且采样下界为-1、采样上界为1,输出BATCH_SIZE×100个样本;即抽取一个批量的随机样本
noise = np.random.uniform(-1, 1, size=(BATCH_SIZE, 100))

#抽取一个批量的真实图片
image_batch = X_train[index*BATCH_SIZE:(index+1)*BATCH_SIZE]

#生成的图片使用生成器对随机噪声进行推断;verbose为日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录
generated_images = g.predict(noise, verbose=0)

#每经过100次迭代输出一张生成的图片
if index % 100 == 0:
image = combine_images(generated_images)
image = image*127.5+127.5
Image.fromarray(image.astype(np.uint8)).save(
"./GAN/"+str(epoch)+"_"+str(index)+".png")

#将真实的图片和生成的图片以多维数组的形式拼接在一起,真实图片在上,生成图片在下
X = np.concatenate((image_batch, generated_images))

#生成图片真假标签,即一个包含两倍批量大小的列表;前一个批量大小都是1,代表真实图片,后一个批量大小都是0,代表伪造图片
y = [1] * BATCH_SIZE + [0] * BATCH_SIZE

#判别器的损失;在一个batch的数据上进行一次参数更新
d_loss = d.train_on_batch(X, y)
print("batch %d d_loss : %f" % (index, d_loss))

#随机生成的噪声服从均匀分布
noise = np.random.uniform(-1, 1, (BATCH_SIZE, 100))

#固定判别器
d.trainable = False

#计算生成器损失;在一个batch的数据上进行一次参数更新
g_loss = d_on_g.train_on_batch(noise, [1] * BATCH_SIZE)

#令判别器可训练
d.trainable = True
print("batch %d g_loss : %f" % (index, g_loss))

#每100次迭代保存一次生成器和判别器的权重
if index % 100 == 9:
g.save_weights('generator', True)
d.save_weights('discriminator', True)

文章目录
  1. 1. 基本原理
  2. 2. 模型
  3. 3. 实现方法
  4. 4. 目标公式
  5. 5. 用tensorflow实现简单的GAN
  6. 6. 最优判别器
  7. 7. 代码解析:
,