上一篇文章介紹了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網絡模型就訓練完成了。