GAN(生成式对抗网络)通过CIFAR-10实例理解

大数据实验学习打卡第N次记录

一、什么是GAN(生成式对抗网络)

生成式对抗网络(Generative Adversarial Network)是由Goodfellow等人于2014年提出的,它可以替代VAE来学习图像的潜在空间,能够迫使生成图像与真实图像在统计上几乎无法区分从而生成相当逼真的合成图像!

举个通俗的栗子:
一名伪造者试图伪造一副达芬奇的画作,于是他将自己的一些赝品与达芬奇的真迹混在一起,并将其展示给一位艺术商人,艺术商人对每幅画的真实性进行评估,并向伪造者给出反馈,告诉他达芬奇的真迹应该具有什么什么特点,于是伪造者回到自己工作室按照他的建议制作一些新的赝品,随着这个过程的叠加,伪造者的赝品越来越像达芬奇的风格,艺术商人也变得越来越擅长找出赝品。

从上面的栗子我们可以总结出GAN的工作原理:一个伪造者网络,一个专家网络,二者训练的目的都是为了打败彼此。

二、GAN的网络组成

  • 生成器网络(generator network):它以一个随机向量(潜在空间中的一个随机点)作为输入,并将其解码为一张合成图像。
  • 判别器网络(discriminator network)或对手(adversary):以一张图像(真实的或者合成的均可)作为输入,并预测该图像是来自训练集还是由生成器网络创建。

值得注意的是:GAN网络的优化最小值是不固定的,梯度下降是沿着静态的损失地形滚下山坡,但对于GAN而言,每下一步山,都会对整个地形造成一点改变,它是一个动态的系统,其最优化过程寻找的不是一个最小值,而是两股力量之间的平衡,因此,GAN的训练极其困难,想要让GAN正常运行,需要对模型构架和训练参数进行大量的仔细调整。

三、公认的技巧

下面总结了实现GAN生成器和判别器时用到的一些技巧:

  • 我们使用tanh作为生成器最后一层的激活,而不用sigmoid,后者在其他类型的模型中更常见。
  • 使用正态分布对潜在空间中的点进行采样,而不用均匀分布。
  • 随机性能够提高稳健性,训练GAN得到的是一个动态平衡,所以GAN可能以各种方式“卡住”/在训练过程中引入随机性有助于防止这种情况。两种方式引入随机性:一种是在判别器中使用droput,另一种是向判别器的标签添加随机噪声。
  • 稀疏的梯度会妨碍GAN的训练。在深度学习中,稀疏性通常是我们需要的属性,但在GAN中并非如此。有两件事情可能导致稀疏度稀疏:最大池化运算和ReLU激活。推荐使用步进卷积代替最大池化来进行下采样,还推荐使用LeakyReLU层来代替ReLU激活。LeakyReLU和ReLU类似,但它允许较小的负数激活值,从而放宽了稀疏性限制。
  • 在生成的图像中,经常会见到棋盘状伪影,这是由于生成器中像素空间的不均匀覆盖造成的(如下图2),为了解决这个问题,每当生成器和判别器中都使用步进的Conv2DTranspose或Conv2D时,使用的内核大小要能够被步幅大小整除。
    GAN(生成式对抗网络)通过CIFAR-10实例理解_第1张图片
    图2(由于步幅大小和内核大小不匹配而导致的棋盘状伪影,进而导致像素空间不均匀的覆盖)

四、GAN的简要实现流程(基于keras)

GAN属于高级应用,本节具体实现一个形式最简单的深度卷积生成式对抗网络(DCGAN,deep convolution GAN),即生成器和判别器都是深度卷积神经网络的GAN,在生成器中使用Conv2DTranspose层进行图像采样。

在CIFAR10数据集的图像上训练GAN,这个数据集包含50000张32 x 32的RGB图像,这些图像属于10个类别(每个类别5000张图像),为了简化,这里只使用属于“frog”类别的图像。

基于以上前提,GAN的简要流程如下:
GAN(生成式对抗网络)通过CIFAR-10实例理解_第2张图片

  1. generator网络将形状为(latent_dim,)的向量映射到形状为(32,32,3)的图像;
  2. discriminator网络将形状为(32,32,3)的图像映射到一个二进制分数,用于评估图像为真的概率;
  3. gan网络将generator网络和discriminator网络连接在一起:gan(x) = discriminator(generator(x))。生成器将潜在空间向量解码为图像,判别器对这些图像的真实性进行评估,因此这个gan网络是将这些潜在向量映射到判别器的评估结果。
  4. 我们使用带有“真”/“假”标签的真假图像样本来训练判别器,就和训练普通的图像分类模型一样。
  5. 为了训练生成器,我们要使用gan模型的损失相对于生成器权重的梯度。这意味着,在每一步都要移动生成器的权重,其移动方向是让判别器更有可能将生成器解码的图像划分为“真”。也就是训练生成器来欺骗判别器。

五、生成器

开发generator模型,它将一个向量(来自潜在空间,训练过程中对其随机采样)转换为一张候选图像。

GAN常见的诸多问题之一,就是生成器“卡在”看似噪声的生成图像上。一种可行的办法就是在生成器和判别器中都使用droput。

代码如下:

import keras
from keras import layers
import numpy as np

latent_dim = 32
height = 32
width = 32
channels = 3

generator_input = keras.Input(shape=(latent_dim,))

x = layers.Dense(128 * 16 * 16)(generator_input)
x = layers.LeakyReLU()(x)
x = layers.Reshape((16, 16, 128))(x)

x = layers.Conv2D(256, 5, padding = 'same')(x)
x = layers.LeakyReLU()(x)

x = layers.Conv2DTranspose(256, 4, strides = 2, padding = 'same')(x)
x = layers.LeakyReLU()(x)

x = layers.Conv2D(256, 5, padding = 'same')(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(256, 5, padding = 'same')(x)
x = layers.LeakyReLU()(x)

x = layers.Conv2D(channels, 7, activation='tanh', padding = 'same')(x)
generator = keras.models.Model(generator_input, x)
generator.summary()

六、判别器

开发discriminator模型,接收一张候选图像(真实的或者合成的)作为输入,并将其划分到这两个类别之一:“生成图像”或“来自训练集的真实图像”

代码如下:

discriminator_input = layers.Input(shape = (height, width, channels))
x = layers.Conv2D(128, 3)(discriminator_input)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Flatten()(x)

x = layers.Dropout(0.4)(x)

x = layers.Dense(1, activation='sigmoid')(x) #分类层

discriminator = keras.models.Model(discriminator_input, x)  #将判别器模型实例化,将输入转换为一个二进制分类决策
discriminator.summary()

discriminator_optimizer = keras.optimizers.RMSprop(
	lr = 0.0008,
	clipvalue = 1.0,      #在优化器中使用梯度裁剪(限制梯度值的范围)
	decay = 1e - 8)       #为了稳定训练过程,使用学习率衰减

discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy')
	

七、对抗网络

最后,设置GAN,将生成器和判别器连在一起。训练时,这个模型将让生成器向某个方向移动,从而提高它欺骗判别器的能力。这个模型将潜在空间的点转换为一个分类决策(真或假),它训练的标签都是“真实图像”。

因此,训练gan将会更新generator的权重,使得discriminator在观察假图像时更有可能预测为“真”。

注意,在训练过程中需要将判别器设置为冻结(即不可训练),这样在训练gan时它的权重才不会更新。如果在此过程中可以对判别器的权重进行更新,那么我们就是在训练判别器始终预判为“真”,但这并不是我们想要的。

代码如下:

discriminator.trainable = False	#将判别器权重设置为不可训练

gan_input = keras.Input(shape=(latent_dim,))
gan_output = discriminator(generator(gan_input))
gan = keras.models.Model(gan_input, gan_output)
gan_optimizer = keras.optimizers.RMSprop(lr=0.0004, clipvalue=1.0, decay=le-8)
gan.compile(optimizer=gan_optimizer, loss='binary_crossentropy')

八、训练DDGAN

训练循环的大致流程如下,每轮都进行一下操作:

  1. 从潜在空间中抽取随机的点(随机噪声);
  2. 利用这个随机噪声用generator生成图像;
  3. 将生成图像与真实图像混合;
  4. 使用这些混合后的图像以及相应的标签(真实图像为“真”,生成图像为“假”)来训练discriminator;
  5. 在潜在空间中随机抽取新的点;
  6. 使用这些随机向量以及全部是“真实图像”的标签来训练gan,这会更新生成器的权重(只更新生成器的权重,因为判别器在gan中被冻结),其更新方向是使得判别器能够将生成图像预测为“真实图像”,这个训练过程是训练生成器去欺骗判别器。
import os
from keras.preprocessing import image

(x_train, y_train), (_, _) = keras.datasets.cifar10.load_data()	#加载cifar10数据

x_train = x_train[y_train.flatten() == 6]	#选择青蛙图像,类编号为6

x_train = x_train.reshape(
	(x_train.shape[0], ) +
	(height, width, channels)).astype('float32') / 255	#数据标准化

iterations = 10000
batch_size = 20
save_dir = 'your_dir'	#指定你的保存生成图像的目录

start = 0
for step in range(iterations):
	random_latent_vectors = np.random.normol(size=(batch_size, latent_dim))	#在潜在空间中采样随机点
	generated_images = generator.predict(random_latent_vectors)	#将这些点解码为虚假图像
	stop = start + batch_size
	real_images = x_train[start:stop]
	combined_images = np.concatenate([generated_images, real_images])	#将这些虚假图像与真实图像合在一起

labels = np.concatenate([np.ones((batch_size, 1)), np.zeros((batch_size,1))])	#合并标签,区分真实和虚假图像
labels += 0.05 * np.random.random(labels.shape)	#向标签中添加随机噪声

d_loss = discriminator.train_on_batch(combined_images, labels)
random_latent_vectors = np.random.normol(size=batch_size, latent_dim))	#在潜在空间采样随机点
misleading_targets = np.zeros((batch_size, 1))	#合并标签,全部是“真实图像”(这是在撒谎)
a_loss = gan.train_on_batch(random_latent_vectors, misleading_targets)	#通过gan模型来训练生成器(此时冻结判别器权重)

start += batch_size
if start > len(x_train) - batch_size:
	start = 0

if step % 100 == 0:	#每一百步保存并绘图
	gan.save_weights('gan.h5')	#保存模型权重
	
	print('discriminator loss:', d_loss)
	print('adversarial loss:', a_loss)	#将指标打印出来
	
	img = image.array_to_img(generated_image[0] * 255, scale=False)
	img.save(os.path.join(save_dir, 'generated_frog' + str(step) + '.png'))	#保存一张生成图像
	
	img = image.array_to_img(real_images[0] * 255, scale=False)
	img.save(os.path.join(save_dir, 'real_frog' + str(step) +'.png'))保存一张真实图像用于对比

训练时,可能会看到对抗损失开始大幅增加,而判别损失则趋向于0,即判别器最终支配了生成器,这种情况的解决办法是尝试减小判别器的学习率,并增大判别器的droput比率。

参考资料

《python深度学习》——弗朗索瓦·肖莱(Francois Chollet)
论文:“Generative adversarial network” by Goodfellow

你可能感兴趣的:(自学,机器学习,之路,深度学习,机器学习,神经网络,tensorflow,自然语言处理)