大数据实验学习打卡第N次记录
生成式对抗网络(Generative Adversarial Network)是由Goodfellow等人于2014年提出的,它可以替代VAE来学习图像的潜在空间,能够迫使生成图像与真实图像在统计上几乎无法区分从而生成相当逼真的合成图像!
举个通俗的栗子:
一名伪造者试图伪造一副达芬奇的画作,于是他将自己的一些赝品与达芬奇的真迹混在一起,并将其展示给一位艺术商人,艺术商人对每幅画的真实性进行评估,并向伪造者给出反馈,告诉他达芬奇的真迹应该具有什么什么特点,于是伪造者回到自己工作室按照他的建议制作一些新的赝品,随着这个过程的叠加,伪造者的赝品越来越像达芬奇的风格,艺术商人也变得越来越擅长找出赝品。
从上面的栗子我们可以总结出GAN的工作原理:一个伪造者网络,一个专家网络,二者训练的目的都是为了打败彼此。
值得注意的是:GAN网络的优化最小值是不固定的,梯度下降是沿着静态的损失地形滚下山坡,但对于GAN而言,每下一步山,都会对整个地形造成一点改变,它是一个动态的系统,其最优化过程寻找的不是一个最小值,而是两股力量之间的平衡,因此,GAN的训练极其困难,想要让GAN正常运行,需要对模型构架和训练参数进行大量的仔细调整。
下面总结了实现GAN生成器和判别器时用到的一些技巧:
GAN属于高级应用,本节具体实现一个形式最简单的深度卷积生成式对抗网络(DCGAN,deep convolution GAN),即生成器和判别器都是深度卷积神经网络的GAN,在生成器中使用Conv2DTranspose层进行图像采样。
在CIFAR10数据集的图像上训练GAN,这个数据集包含50000张32 x 32的RGB图像,这些图像属于10个类别(每个类别5000张图像),为了简化,这里只使用属于“frog”类别的图像。
开发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')
训练循环的大致流程如下,每轮都进行一下操作:
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