天天看点

使用MNIST数据集编写深度卷积生成对抗网络(DCGAN)

上一篇文章介绍了DCGANS基本原理,那么这篇文章我们就利用MNIST数据集进行DCGAN的实例。

        生成对抗网络(GANs),通过两个模型通过对抗过程同时训练。一个生成器G学习创造看起来真实的图像,而判别器(D家”)学习区分真假图像 。训练过程中,生成器在生成逼真图像方面逐渐变强,而判别器在辨别这些图像的能力上逐渐变强。当判别器不再能够区分真实图片和伪造图片时,训练过程达到平衡。

       下方动画展示了当训练了 50 个epoch (全部数据集迭代50次) 时生成器所生成的一系列图片。图片从随机噪声开始,随着时间的推移越来越像手写数字。

使用MNIST数据集编写深度卷积生成对抗网络(DCGAN)

       那么我们开始例子的编写,该例子是用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时)

使用MNIST数据集编写深度卷积生成对抗网络(DCGAN)

     上图为ReLU函数图像,线性的函数,做梯度下降时候收敛速度较快,但是当小于0的值,梯度永远都会是0。为了防止这种情况,使用LeakyReLU激活函数。

使用MNIST数据集编写深度卷积生成对抗网络(DCGAN)

     上图为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网络模型就训练完成了。

继续阅读