天天看點

使用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網絡模型就訓練完成了。

繼續閱讀