上一篇文章介绍了DCGANS基本原理,那么这篇文章我们就利用MNIST数据集进行DCGAN的实例。
生成对抗网络(GANs),通过两个模型通过对抗过程同时训练。一个生成器G学习创造看起来真实的图像,而判别器(D家”)学习区分真假图像 。训练过程中,生成器在生成逼真图像方面逐渐变强,而判别器在辨别这些图像的能力上逐渐变强。当判别器不再能够区分真实图片和伪造图片时,训练过程达到平衡。
下方动画展示了当训练了 50 个epoch (全部数据集迭代50次) 时生成器所生成的一系列图片。图片从随机噪声开始,随着时间的推移越来越像手写数字。

那么我们开始例子的编写,该例子是用python3,tensorflow2.0编写,可以直接复制使用。并且我在该例子的编写过程中,添加了详细的代码注释,并且解释部分tensorflow2的API使用说明,供大家参考,如有错误望大家指正^_^。
1、导入必要的依赖库
import tensorflow as tf
#python图片操作库
import glob
import imageio
import PIL
#训练基本库
import matplotlib.pyplot as plt
import os
import sys
import numpy as np
from tensorflow.keras import layers
import time
#python交互式shell
from IPython import display
2、载入MNIST数据集并生成模型
#生成器创建
#载入mnist数据集
(train_images,train_label),(_,_) = tf.keras.datasets.mnist.load_data()
3、DCGANS并没有做其他的image的预处理操作,只讲数据进行[-1,1]的压缩以便训练
图片数据归一化的解释可以参考https://blog.csdn.net/wind82465/article/details/108711150
train_images = train_images.reshape(train_images.shape[0],28,28,1).astype('float32')#转化为float
train_images = (train_images - 127.5)/127.5 #没有做其他预处理,只讲图片数据修改到[-1,1]的区间内
4、定义buffer size和batch size
#定义buffer size 和batch szie
BUFFER_SIZE = 60000
BATCH_SIZE = 256
5、打乱数据,减少过拟合
#打乱数据 防止过拟合
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
6、创建生成器G模型
#创建生成器
def make_generator_model():
model = tf.keras.Sequential()#创建模型实例
#第一层须指定维度 #batch无限制
model.add(layers.Dense(7*7*BATCH_SIZE, use_bias=False, input_shape=(100,)))#Desne第一层可以理解为全连接层输入,它的秩必须小于2
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Reshape((7,7,256)))
assert model.output_shape == (None,7,7,256)
#上采样图片 #转化为7*7*128
model.add(layers.Conv2DTranspose(128,(5,5),strides=(1,1),padding='same',use_bias=False))
assert model.output_shape == (None,7,7,128)
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
#转化为14*14*64
model.add(layers.Conv2DTranspose(64,(5,5),strides=(2,2),padding='same',use_bias=False))
assert model.output_shape == (None,14,14,64)
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
#转化为28*28*1
model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False,activation='tanh'))
assert model.output_shape == (None, 28, 28, 1)
return model
1-5步基本的接口都很好理解,就不多解释了。第六步创建生成器G的网络模型,详细解释一下,有兴趣的小伙伴可以阅读、参考。基本功扎实的小伙伴可以直接跳到第七步继续阅读^_^。
生成器模型创建步骤及重要接口解释如下:
1、创建生成器第一步创建了模型的实例:model = tf.keras.Sequential()
2、创建生成器G的第一层即输入层,DCGANS的生成器G中移除了全连接层和池化层,只有输入层为一个全连接层。
model.add(layers.Dense(7*7*BATCH_SIZE, use_bias=False, input_shape=(100,)))
Dense的解释可以参考我之前的文章:https://blog.csdn.net/wind82465/article/details/108711787
3、G除了输出层,每层都使用了BatchNormalization,采用LeakyReLU激活函数。
Batch Normalization 的解释 原文可以从arvix中下载,原文链接:https://arxiv.org/abs/1502.03167,关于Batch Normalization解释比较详细的可以参考https://www.cnblogs.com/wmr95/articles/9450252.html ,解释的还是比较详细,通俗易懂一些的可以参考https://blog.csdn.net/shuzfan/article/details/50723877 所写的,个人感觉这两篇都是解释的挺好的。
4、生成器G除了最外层都是用LeakyReLU激活函数。
LeakyReLU 是为了防止RelU激活函数出现梯度无法更新(输入值小于0时)
上图为ReLU函数图像,线性的函数,做梯度下降时候收敛速度较快,但是当小于0的值,梯度永远都会是0。为了防止这种情况,使用LeakyReLU激活函数。
上图为LeakyReLU函数图像,可以看到当输入值小于0时,使用收敛速度较慢的函数,但是梯度不为0,有效的防止梯度为0的情况。DCGANS使用的就是LeakyReLU作为激活函数。
5、Conv2DTranspose的操作可以参考我之前博文详细解释了该操作Conv2DTranspose操作详解
到此就已经把生成G的所有使用接口都讲述了一遍,接下来我们就设计一下判别器的模型。
7、创建生成器D模型
#定义判别器模型
def make_discriminator_model():
model = tf.keras.Sequential()
model.add(layers.Conv2D(64,(5,5),strides=(2,2),padding='same',input_shape=[28,28,1]))
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3))
model.add(layers.Conv2D(128,(5,5),strides=(2,2),padding='same'))
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3))
model.add(layers.Flatten())#全连接拉伸
model.add(layers.Dense(1))
return model
判别器D首先说一下Dropout。Dropout的作用是按照一定概率去掉隐藏层里面的某层的神经元,其实使用的时候你也不用管它的原理,只要知道它可以帮助你减少过拟合就可以了,它的参数是放弃神经元占比的概率值,这里用0.3,就是随机放弃百分之三十的神经元进行前向训练。想了解原理的小伙伴可以参考这篇博文,写的还是通俗易懂的:https://blog.csdn.net/program_developer/article/details/80737724
判别器是一个基于 CNN 的图片分类器,判别器其他接口函数就不做过多解释了。
到这里我们就已经把生成器G和判别器D的模型构建出来了,那么生成一张图像测试一下吧,虽然我们还没有训练它们^_^。
8、测试生成一张图片
#测试生成一张图片
generator = make_generator_model()#生成器模型
noise = tf.random.normal([1,100])#随机生成噪声(高斯分布的序列噪声)
generator_image = generator(noise,training = False)
plt.imshow(generator_image[0,:,:,0], cmap='gray')#画出第一张图像
9、使用判别器
使用(尚未训练的)判别器来对图片的真伪进行判断。模型将被训练为为真实图片输出正值,为伪造图片输出负值。
discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)
输出
tf.Tensor([[-0.00181542]], shape=(1, 1), dtype=float32)
10、定义损失函数
#定义交叉熵损失函数
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
这里只判断生成器的图片真或假的判断,定义交叉熵损失函数即可。
定义判别器的损失函数,判别器有判断真伪图片的能力。它将判别器对真实图片的预测值与值全为 1 的数组进行对比,将判别器对生成器生成图片的预测值与值全为 0 的数组进行对比。
#定义判别器损失函数
def discriminator_loss(real_output,fake_output):
real_loss = cross_entropy(tf.ones_like(real_output),real_output)
fake_loss = cross_entropy(tf.zeros_like(fake_output),fake_output)
total_loss = real_loss + fake_loss
return total_loss
定义生成器损失函数,生成器的预测值与全1进行对比。
#定义生成器loss函数
def generator_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output),fake_output)
11、为生成器和判别器定义损失函数和优化器
#定义生成器优化器
generator_optimizer = tf.keras.optimizers.Adam(1e-4)#定义学习率
#定义判别器优化器
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)
12、保存数据模型到本地
#保存模型(保存检查点)
checkpoint_dir = './training_checkpoints/'
checkpoint_prefix = os.path.join(checkpoint_dir,'ckpt')
checkpoint = tf.train.Checkpoint(generator_optimizer = generator_optimizer,
discriminator_optimizer = discriminator_optimizer,
generator = generator,
discriminator = discriminator)
13、定义准备训练参数
#定义训练循环
EPOCHS = 50 #训练50次
noise_dim = 100#生成器从随机噪声开始生成图片,深度为100,与生成器模型输入层一致
num_examples_to_generate = 16#生成16张图片显示
seed = tf.random.normal([num_examples_to_generate,noise_dim])#随机生成正态分布的数据
14、定义训练过程
@tf.function
def train_step(images):
noise = tf.random.normal([BATCH_SIZE,noise_dim])#定义噪声batch为256 深度为100 与输入生成器G输入相当
#分别计算生成器和判别器的梯度
with tf.GradientTape() as gen_tape , tf.GradientTape() as disc_tape:
generator_images = generator(noise,training = True)#生成器以噪声开始训练
real_output = discriminator(images,training = True)#判别器提取测试集特征
fake_output = discriminator(generator_images,training = True)#判别器判断生成器返回的图片
#计算G和D的loss值
gen_loss = generator_loss(fake_output)
disc_loss = discriminator_loss(real_output,fake_output)
gradients_of_generator = gen_tape.gradient(gen_loss,generator.trainable_variables)#计算生成器损失梯度
generator_optimizer.apply_gradients(zip(gradients_of_generator,generator.trainable_variables))
gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)#判别器梯度
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator,discriminator.trainable_variables))
15、保存生成图片
#定义生成器保存模型函数
def generate_and_save_images(model,epoch,test_input):
predicitons = model(test_input,training=False)
#创建图片
fig = plt.figure(figsize=(4,4))#igure的宽和高,单位为英寸;4英寸大小图片
#生成图片内容
for i in range(predicitons.shape[0]):
plt.subplot(4,4,i+1)#画子图共4行 4列,索引位置一次画出
plt.imshow(predicitons[i,:,:,0]*127.5 + 127.5,cmap='gray')#恢复到0-255的灰度图
plt.axis('off')
plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
plt.show()
16、定义训练过程
#定义训练过程
def train(dateset,epochs):
for epoch in range(epochs):
start = time.time()
for image_batch in dateset:
train_step(image_batch)
#显示生成器生成的图像
display.clear_output(wait=True)#清空显示的数据
generate_and_save_images(generator,epoch + 1,seed)
# 每 15 个 epoch 保存一次模型
if (epoch + 1) % 15 == 0:
checkpoint.save(file_prefix = checkpoint_prefix)
print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))
17、训练模型
%%time
#训练模型
train(train_dataset, EPOCHS)
我的GPU一半,训练一次EPOCH大概30多分钟,到此DCGAN网络模型就训练完成了。