天天看點

GAN的實作

本篇是《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
           

繼續閱讀