本篇是《GAN實戰對抗生成網絡》一書的第一個例程,下載下傳書本源代碼連結,不過書本中的主程式的的損失函數部分代碼需要做些改動,詳情可參考GAN系列:代碼閱讀(2)——Generative Adversarial Networks & 李宏毅老師GAN課程P1+P4和詳解GAN代碼之逐行解析GAN代碼。
備注:本人此處的代碼以及我參考的兩處部落格代碼均可跑通,不過win7,8G運存大約需要數小時,運作結果圖可見本人下篇文章.
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec #gridspec module: 給圖像分格 #gridspec是圖檔排列工具,在訓練過程中用于輸出可視化結果
import os
from tensorflow.examples.tutorials.mnist import input_data #導入手寫數字資料集
# 定義服從正态分布的函數,産生參數
#初始化參數時使用的xavier_init函數
def xavier_init(size):
# 産生标準差的過程
input_dim = size[0]
#初始化标準差
xavier_variance = 1. / tf.sqrt(input_dim/2.)
# 噪聲服從正态分布,size[0]應該是噪聲向量長度
return tf.random_normal(shape=size, stddev=xavier_variance)
def save(saver, sess, logdir, step): #儲存模型的save函數
model_name = 'model' #模型名字首
checkpoint_path = os.path.join(logdir, model_name) #儲存路徑
saver.save(sess, checkpoint_path, global_step=step) #儲存模型
print('The checkpoint has been created.')
# 定義畫圖函數,samples是要畫的圖像集
def plot(samples):
# 在matplotlib中,整個圖像為一個Figure對象。在Figure對象中可以包含一個或者多個Axes對象。
# 每個Axes(ax)對象都是一個擁有自己坐标系統的繪圖區域
# 建立圖像視窗,定義尺寸4*4
fig = plt.figure(figsize=(4, 4))
# gridspec将整個圖像分格:4*4共16個
gs = gridspec.GridSpec(4, 4)
# 調整圖像布局參數,隻影響目前Gridspec對象
gs.update(wspace=0.05, hspace=0.05)
# 周遊圖像集,拼成一個大圖用來展示結果
for i, sample in enumerate(samples):
# title為圖像标題,Axis為坐标軸,Tick為刻度線,Tick Label為刻度注釋
ax = plt.subplot(gs[i])
plt.axis('off')
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_aspect('equal')
# 把圖像變回28*28,cmap表示使用'Greys_r'灰階圖
plt.imshow(sample.reshape(28, 28), cmap='Greys_r')
# 傳回整張大圖
return fig
# Random noise setting for Generator
# placeholder執行個體通常用來為算法的實際輸入值作占位符,指派時必須用sess.run中的optional argument:feed_dict進行指派
## Z為随機噪聲的batch,向量長度為100
Z = tf.placeholder(tf.float32, shape=[None, 100], name='Z')
#Generator parameter settings
# 生成器第一層權重和偏置變量
G_W1 = tf.Variable(xavier_init([100, 128]), name='G_W1')
G_b1 = tf.Variable(tf.zeros(shape=[128]), name='G_b1')
# 生成器第二層權重和偏置變量,輸出784維向量,作為圖檔
G_W2 = tf.Variable(xavier_init([128, 784]), name='G_W2')
G_b2 = tf.Variable(tf.zeros(shape=[784]), name='G_b2')
# 整合生成器參數
theta_G = [G_W1, G_W2, G_b1, G_b2]
# Generator Network
# 定義生成器函數:z是輸入的随機噪聲batch矩陣,網絡為全連接配接
#生成器generator(z)從随機分布中(本例采用統一分布)接收一個100維向量作為輸入,并産生一個符合MNIST規範(28X28)的784維向量。
#這裡的z是G(z)的先驗。通過這種方式可以學習到先驗空間和Pdata(真實資料分布)空間之間的映射
def generator(z):
# matmul:矩陣相乘
# 生成器激活函數為relu,隻有第一層激活
G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)
G_log_prob = tf.matmul(G_h1, G_W2) + G_b2
# 第二層輸出前用sigmoid函數,為什麼?答:為了進行二分類,或者說是為了輸出真假機率. 評:回答錯誤,判别器是為了二分類,生成器不是
G_prob = tf.nn.sigmoid(G_log_prob)
return G_prob
#Input Image MNIST setting for Discriminator [28x28=784]
#X表示真實圖檔batch,每個圖檔向量長度為784:28*28
X = tf.placeholder(tf.float32, shape=[None, 784], name='X')
#Discriminator parameter settings
#Variable建立變量後必須指派才能使用,在計算圖的運算過程中,其值會一直儲存到程式運作結束,
#而一般的tensor張量在tensorflow運作過程中隻是在計算圖中流過,并不會儲存下來
# varibale主要用來儲存tensorflow建構的一些結構中的參數,
# 這些參數才不會随着運算的消失而消失,才能最終得到一個模型
# 建立初始随機噪聲的權重和偏置變量
# 權重服從正态分布,784*128(因為下一層有128個神經元?);偏置初始化為0
D_W1 = tf.Variable(xavier_init([784, 128]), name='D_W1')
D_b1 = tf.Variable(tf.zeros(shape=[128]), name='D_b1')
# 繼續建立下一層權重和偏置的變量
# 權重依然為正态分布,128*1(判别器網絡隻有兩層?);偏置依然為0
D_W2 = tf.Variable(xavier_init([128, 1]), name='D_W2')
D_b2 = tf.Variable(tf.zeros(shape=[1]), name='D_b2')
#整合所有判别器參數
theta_D = [D_W1, D_W2, D_b1, D_b2]
# Discriminator Network
# 定義判别器函數:x是輸入的圖像batch矩陣
#判别器discriminator(x)以MNIST圖像作為輸入并傳回一個代表真實圖像機率的标量。即輸入的是圖像,輸出的是機率(标量)
def discriminator(x):
# 網絡也為全連接配接,最後sigmoid輸出,可以了解為二分類器
D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1)
D_logit = tf.matmul(D_h1, D_W2) + D_b2
D_prob = tf.nn.sigmoid(D_logit)
# 判别器sigmoid前的結果D_logit也輸出了
return D_prob, D_logit
# 把随機噪聲batch矩陣輸入生成器中,産生生成圖像batch矩陣,取得生成器的生成結果
G_sample = generator(Z)
# 把真實圖像batch矩陣輸入判别器得到結果,取得判别器判别的真實手寫數字的結果
# D_logit_real是sigmoid之前的結果,用于logistic回歸
D_real, D_logit_real = discriminator(X)
# 把生成圖像batch矩陣輸入判别器得到結果,取得判别器判别的生成的手寫數字的結果
D_fake, D_logit_fake = discriminator(G_sample)
# GAN論文中loss定義:reduce_mean因為求均值降維,得到的loss是标量
# Loss functions from the paper
#D_loss = -tf.reduce_mean(tf.log(D_real) + tf.log(1. - D_fake))
#G_loss = -tf.reduce_mean(tf.log(D_fake))
#此處是github下載下傳的源代碼,總是疊代一會之後就會顯示loss為nan
#Tensorflow的優化器隻能做最小化,是以為了最大化損失函數,我們在上面的代碼中給損失加上了一個負号。與此同時,根據論文的僞代碼算法,
#我們最好最大化tf.reduce_mean(tf.log(D_fake)),而不是最小化tf.reduce_mean(tf.log(1. - D_fake))。
# Alternative losses:
# -------------------
# 分别計算正負樣本與标簽1和0之間的交叉熵,再合并
# 和原始方法比,相當于給判别器打分一個基準:0和1
#對判别器對真實樣本的判别結果計算誤差(将結果與1比較)
D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, labels=tf.ones_like(D_logit_real)))
#對判别器對虛假樣本(即生成器生成的手寫數字)的判别結果計算誤差(将結果與0比較)
D_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.zeros_like(D_logit_fake)))
D_loss = D_loss_real + D_loss_fake #判别器的誤差
#生成器的誤差(将判别器傳回的對虛假樣本的判别結果與1比較)
G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.ones_like(D_logit_fake)))
# 優化算法為Adam,兩者都是minimize
# 原本的判别器loss形式導緻其值越大,判别器效果越好;現在的形式表示loss值越小,性能越好,是以是minimize
#接下來我們通過下面的過程一步步訓練損失函數:
# Update D(X)'s parameters only,var_list=theta_D
D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)
# Update G(Z)'s parameters only,var_list=theta_G
G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)
# 定義采樣函數:随機生成-1到1的值,m*n
def sample_Z(m, n):
return np.random.uniform(-1., 1., size=[m, n])
batch_size = 128
# 随機噪聲向量長度
Z_dim = 100
#會話對象封裝了執行操作對象和計算張量對象的環境
sess = tf.Session()
# global_variables_initializer:Returns an Op that initializes global variables
# 就是初始化參數的操作
sess.run(tf.global_variables_initializer())
# 載入資料
mnist = input_data.read_data_sets('MNIST/', one_hot=True)
# 如果路徑中沒有輸出圖像的檔案夾,建立一個
if not os.path.exists('output/'):
os.makedirs('output/')
i = 0
# 一共訓練1000000次
for itr in range(10000):
# 每1000次疊代輸出一次生成結果并儲存
if itr % 1000 == 0:
# 一次結果中包含16個小圖檔
samples = sess.run(G_sample, feed_dict={Z: sample_Z(16, Z_dim)})
fig = plot(samples)
plt.savefig('output/{}.png'.format(str(i).zfill(3)), bbox_inches='tight')
i += 1
plt.close(fig)
# 按照設定的batch_size從訓練集中取圖檔
X_mb, _ = mnist.train.next_batch(batch_size)
# 訓練過程:判别器和生成器訓練同樣次數?是因為用了Adam的優化算法嗎?
_, D_loss_curr = sess.run([D_solver, D_loss], feed_dict={X: X_mb, Z: sample_Z(batch_size, Z_dim)})
_, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(batch_size, Z_dim)})
# 每1000次輸出目前疊代的次數,判别器和生成器優化前的loss
if itr % 1000 == 0:
print('Iter: {}'.format(itr))
print('D loss: {:.4}'. format(D_loss_curr))
print('G_loss: {:.4}'.format(G_loss_curr))
print()
#訓練過程中會出現loss為nan,具體原因有可能是梯度爆炸,詳情見https://blog.csdn.net/qq_33485434/article/details/80733251