GAN是如何工作的?在MNIST數據集上如何演示GAN的一個簡單實現?

從偽造活動門票的故事中,可以非常直觀地看出GAN的思想。為了清楚地理解GAN是如何工作的以及如何實現它們,本節將會在MNIST數據集上演示GAN的一個簡單實現。

首先,需要構建GAN網絡的核心,它由兩個主要部分組成:生成器和判別器。生成器將會嘗試從某個特定的概率分佈中想象或者偽造數據樣本;而可以訪問和查看實際數據樣本的判別器將會判斷生成器的輸出是在設計中存在缺陷還是它與原始數據樣本非常接近。與前面的活動場景相似,生成器的整個目的就是使得判別器相信生成的圖像是來自真實數據集的,以此來試圖欺騙判別器。

訓練過程和前面的故事有著相似的結尾,生成器最終將會設法生成與原始數據樣本看起來非常相似的圖像。

圖14.2顯示了GAN的典型結構,將在MNIST數據集上訓練GAN。圖14.2中的隱藏樣本部分是一個隨機想法或者向量,生成器將會使用它來從真實圖像中複製出虛假圖像。

GAN是如何工作的?在MNIST數據集上如何演示GAN的一個簡單實現?

圖14.2 針對MNIST數據集的通用GAN架構

正如前文提到的,作為一個判斷者,判別器將嘗試從生成器設計的虛假圖像中分辨出真實圖像。所以這個網絡將產生一個二值輸出,二值輸出可以使用sigmoid函數來表示(0表示輸入的是虛假圖像,1表示輸入的是真實圖像)。

現在繼續實現這個架構,看它在MNIST數據集上的表現如何。

從導入此實現所需的庫開始。

%matplotlib inline
import matplotlib.pyplot as plt
import pickle as pkl
import numpy as np
import tensorflow as tf

因為這裡使用了MNIST數據集,所以將會使用TensorFlow輔助函數來獲取數據集,並將它存儲在某處。

from tensorflow.examples.tutorials.mnist import input_data
mnist_dataset = input_data.read_data_sets('MNIST_data')

輸出如下。

Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz

14.2.1 模型輸入

在深入構建由生成器和判別器表示的GAN的核心之前,先定義計算圖的輸入。如圖14.3所示,需要兩個輸入:一個輸入是真實圖像,會把它提供給判別器;另一個輸入稱為隱空間,會將它提供給生成器,並用於生成虛假圖像。

# Defining the model input for the generator and discrimator
def inputs_placeholders(discrimator_real_dim, gen_z_dim):
real_discrminator_input = tf.placeholder(tf.float32, (None,
discrimator_real_dim), name="real_discrminator_input")
generator_inputs_z = tf.placeholder(tf.float32, (None, gen_z_dim),
name="generator_input_z")
return real_discrminator_input, generator_inputs_z
GAN是如何工作的?在MNIST數據集上如何演示GAN的一個簡單實現?

圖14.3 在MNIST數據集上實現的GAN架構

下面開始深入構建GAN架構的兩個核心組件。首先從構建生成器部分開始。如圖14.3所示,生成器將包含至少一個隱藏層,它將作為一個近似器。此外,將會採用一種稱為Leaky ReLU的激活函數,而不是通用的ReLU激活函數。這將允許梯度值在層與層之間隨意流動(關於Leaky ReLU的更多信息將會在14.2.3節中介紹)。

14.2.2 變量作用域

變量作用域是TensorFlow中的一個特性,作用域有助於執行如下操作。

  • 確保有一些命名約定,以便後續檢索變量。例如,通過使變量以單詞generator或discriminator開頭,這在網絡訓練期間將有所幫助。其實也可以使用名字作用域特性,但是這個特性不能幫助我們實現第二個目的。
  • 能夠重複使用或重複訓練有不同輸入的相同網絡。例如,我們將從生成器中對虛假圖像進行採樣,來查看生成器複製原始圖像的性能如何。此外,判別器可以訪問真實圖像和虛假圖像,這使得在構建計算圖時可以輕鬆地重用變量而不是創建新變量。

以下語句將說明如何使用TensorFlow中的變量作用域特性。

with tf.variable_scope('scopeName', reuse=False):
# Write your code here

讀者可以在TensorFlow官網中搜索“variable scope”來了解關於使用變量作用域特性的更多好處。

14.2.3 Leaky ReLU

前文提到,使用與ReLU激活函數不同版本的激活函數——Leaky ReLU。傳統版本的ReLU激活函數通過其他方式將負值截斷為零,只會取輸入值和零值中的最大值。而這裡使用的Leaky ReLU版本允許存在一些負值,因此得名Leaky ReLU

有時使用傳統的ReLU激活函數,網絡會陷入一種常態——死亡狀態,這是因為網絡所有的輸出全為零。

Leaky ReLU的思想是通過允許一些負值傳遞來阻止這種死亡狀態。

使生成器工作的整個思想就是從判別器接收梯度值,並且如果網絡陷入死亡狀態,學習過程就不會出現。

圖14.4和圖14.5顯示了傳統ReLU與Leaky ReLU激活函數之間的不同。

GAN是如何工作的?在MNIST數據集上如何演示GAN的一個簡單實現?

圖14.4 ReLU激活函數

GAN是如何工作的?在MNIST數據集上如何演示GAN的一個簡單實現?

圖14.5 Leaky ReLU激活函數

因為Leaky ReLU激活函數並沒有在TensorFlow中實現,所以需要我們自己去實現它。如果輸入為正數,此激活函數的輸出也為正數;如果輸入為負數,則此激活函數的輸出將是一個受控制的負值。這裡將使用一個稱為alpha的參數來控制負值,通過允許傳遞一些負值來引入網絡的容錯性。

下面的等式表示需要實現的Leaky ReLU函數。

f(x) = max(ax,x)

14.2.4 生成器

把MNIST圖像歸一化到0~1,從而使得sigmoid激活函數充分發揮作用。但實際上,我們發現tanh激活函數比其他任何函數都具有更好的性能。因此為了使用tanh激活函數,需要將這些圖像的像素值範圍重新縮放到−1~1。

def generator(gen_z, gen_out_dim, num_hiddern_units=128, reuse_vars=False,
leaky_relu_alpha=0.01):
''' Building the generator part of the network

Function arguments
---------
gen_z : the generator input tensor
gen_out_dim : the output shape of the generator
num_hiddern_units : Number of neurons/units in the hidden layer
reuse_vars : Reuse variables with tf.variable_scope
leaky_relu_alpha : leaky ReLU parameter
Function Returns
-------
tanh_output, logits_layer:
'''
with tf.variable_scope('generator', reuse=reuse_vars):
# Defining the generator hidden layer
hidden_layer_1 = tf.layers.dense(gen_z, num_hiddern_units,
activation=None)
# Feeding the output of hidden_layer_1 to leaky relu
hidden_layer_1 = tf.maximum(hidden_layer_1,
leaky_relu_alpha*hidden_layer_1)
# Getting the logits and tanh layer output
logits_layer = tf.layers.dense(hidden_layer_1, gen_out_dim,
activation=None)
tanh_output = tf.nn.tanh(logits_layer)
return tanh_output, logits_layer

現在我們已經準備好了生成器部分,下面繼續定義GAN的第二個組件。

14.2.5 判別器

接下來,構建生成對抗網絡中的第二個主要組件,即判別器。判別器與生成器基本相同,但不使用tanh激活函數,而使用sigmoid激活函數;它將產生一個二值輸出,代表判別器對輸入圖像的判斷。

def discriminator(disc_input, num_hiddern_units=128, reuse_vars=False,
leaky_relu_alpha=0.01):
''' Building the discriminator part of the network
Function Arguments
---------
disc_input : discrminator input tensor

num_hiddern_units : Number of neurons/units in the hidden layer
reuse_vars : Reuse variables with tf.variable_scope
leaky_relu_alpha : leaky ReLU parameter
Function Returns
-------
sigmoid_out, logits_layer:
'''
with tf.variable_scope('discriminator', reuse=reuse_vars):
# Defining the generator hidden layer
hidden_layer_1 = tf.layers.dense(disc_input, num_hiddern_units,
activation=None)
# Feeding the output of hidden_layer_1 to leaky relu
hidden_layer_1 = tf.maximum(hidden_layer_1,
leaky_relu_alpha*hidden_layer_1)
logits_layer = tf.layers.dense(hidden_layer_1, 1, activation=None)
sigmoid_out = tf.nn.sigmoid(logits_layer)
return sigmoid_out, logits_layer

14.2.6 構建GAN網絡

在定義了構建生成器和判別器組件的主要函數之後,下面將它們堆疊起來,然後為此實現定義模型損失和優化器。

1.模型超參數

可以通過改變下面一組超參數來微調GAN。

# size of discriminator input image
#28 by 28 will flattened to be 784
input_img_size = 784
# size of the generator latent vector
gen_z_size = 100
# number of hidden units for the generator and discriminator hidden layers
gen_hidden_size = 128
disc_hidden_size = 128
#leaky ReLU alpha parameter which controls the leak of the function
leaky_relu_alpha = 0.01
# smoothness of the label
label_smooth = 0.1

2.定義生成器和判別器

在定義了用於生成虛假MNIST圖像(看起來和真實圖像基本相同)的GAN架構的兩個主要組件之後,下面使用目前已經定義的函數來構建網絡。構建網絡將遵循以下步驟。

(1)定義模型輸入,輸入包含兩個變量。其中一個變量是真實圖像,把它輸入判別器,另一個變量是生成器用於複製原始圖像的隱空間。

(2)調用前面定義的生成器函數來構建網絡的生成器部分。

(3)調用前面定義的判別器函數來構建網絡的判別器部分,但是這裡會調用該函數兩次。第一次調用針對真實數據,第二次調用針對生成器生成的虛假數據。

(4)通過重用變量來保持真實圖像和虛假圖像的權重是一樣的。

tf.reset_default_graph()
# creating the input placeholders for the discrminator and
generator
real_discrminator_input, generator_input_z =
inputs_placeholders(input_img_size, gen_z_size)
# Create the generator network
gen_model, gen_logits = generator(generator_input_z,
input_img_size, gen_hidden_size, reuse_vars=False,

leaky_relu_alpha=leaky_relu_alpha)
# gen_model is the output of the generator
# Create the generator network
disc_model_real, disc_logits_real =
discriminator(real_discrminator_input, disc_hidden_size,
reuse_vars=False, leaky_relu_alpha=leaky_relu_alpha)
disc_model_fake, disc_logits_fake = discriminator(gen_model,
disc_hidden_size, reuse_vars=True,
leaky_relu_alpha=leaky_relu_alpha)

3.判別器與生成器損失

這一部分需要定義判別器和生成器損失,可以認為這是此實現中最富有技巧的部分。

我們知道生成器試圖偽造原始圖像,並且判別器作為判斷者,同時接收來自生成器和原始輸入的圖像。因此在為每一部分設計損失時,需要關注兩件事。

首先,網絡的判別器部分要能夠區分由生成器生成的虛假圖像和來自原始訓練樣本的真實圖像。在訓練時,將給判別器部分提供一批分為兩類的數據。第一類是來自原始輸入的圖像,第二類是生成器生成的虛假圖像。

因此,判別器最終的總損失將是它接受真實圖像為真實圖像並且檢測假圖像為虛假圖像的能力之和。最終的總損失如下。

disc_loss = disc_loss_real + disc_loss_fake

tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_layer,
labels=labels))

然後,需要計算兩個損失才能得到最終的判別器損失。

第一個損失disc_loss_real將會根據從判別器和labels獲得的logits值計算出來。在這種情況下,labels的值全都為1,因為此時最小批次中所有圖像都來自MNIST數據集中的真實輸入圖像。為了增強模型在測試集上的泛化能力並給出更好的結果,我們發現其實將labels的值從1改為0.9會更好。標籤的這種改變稱為標籤平滑

labels = tf.ones_like(tensor) * (1 - smooth)

判別器損失的第二部分是判別器能夠檢測虛假圖像的能力,損失介於從判別器獲得的logits值和labels值之間。此時,所有的labels值都是零,因為已知這個最小批次中的所有圖像都來自生成器,而不是來自原始輸入。

既然已經討論了判別器損失,那麼同樣也需要計算生成器損失。生成器損失稱為gen_loss,它介於disc_logits_fake(判別器對於虛假圖像的輸出)和標籤(全都為1,因為生成器試圖使判別器相信它生成的虛假圖像)之間。

# calculating the losses of the discrimnator and generator 

disc_labels_real = tf.ones_like(disc_logits_real) * (1 - label_smooth)
disc_labels_fake = tf.zeros_like(disc_logits_fake)
disc_loss_real =
tf.nn.sigmoid_cross_entropy_with_logits(labels=disc_labels_real,
logits=disc_logits_real)
disc_loss_fake =
tf.nn.sigmoid_cross_entropy_with_logits(labels=disc_labels_fake,
logits=disc_logits_fake)
#averaging the disc loss
disc_loss = tf.reduce_mean(disc_loss_real + disc_loss_fake)
#averaging the gen loss
gen_loss = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
labels=tf.ones_like(disc_logits_fake),
logits=disc_logits_fake))

4.優化器

最後是優化器部分。在本節中,將會定義訓練過程中使用的優化標準。首先,將分別更新生成器和判別器的變量,因此需要能夠檢索每一部分的變量。

對於第一個優化器(即生成器1),將從計算圖中的可訓練變量中檢索以generator名稱開頭的所有變量,然後通過參考其名稱來檢查每個變量屬於哪一模塊。

同樣也要對判別器的變量做同樣的操作,方法是令其所有變量都以discriminator開頭。在這之後,就可以將想要優化的變量列表傳遞給優化器。

TensorFlow的變量作用域特性使得我們能夠檢索以某個字符串開頭的變量,然後會有兩個不同的變量列表,一個用於生成器,另一個用於判別器。

# building the model optimizer
learning_rate = 0.002
# Getting the trainable_variables of the computational graph, split into
Generator and Discrimnator parts
trainable_vars = tf.trainable_variables()
gen_vars = [var for var in trainable_vars if
var.name.startswith("generator")]
disc_vars = [var for var in trainable_vars if
var.name.startswith("discriminator")]
disc_train_optimizer = tf.train.AdamOptimizer().minimize(disc_loss,
var_list=disc_vars)
gen_train_optimizer = tf.train.AdamOptimizer().minimize(gen_loss,
var_list=gen_vars)

本文截選自《深度學習案例精粹》第14章,[愛爾蘭] 艾哈邁德·曼肖伊(Ahmed Menshawy) 著,洪志偉,曹檑,廖釗坡 譯。

GAN是如何工作的?在MNIST數據集上如何演示GAN的一個簡單實現?

本書使用目前廣泛應用的深度學習框架之一—TensorFlow以及非常流行的Python語言進行代碼示例,想要進一步學習的讀者將會有極多的社區資源。

本書主要講述了深度學習中的重要概念和技術,並展示瞭如何使用TensorFlow實現高級機器學習算法和神經網絡。本書首先介紹了數據科學和機器學習中的基本概念,然後講述如何使用TensorFlow訓練深度學習模型,以及如何通過訓練深度前饋神經網絡對數字進行分類,如何通過深度學習架構解決計算機視覺、語言處理、語義分析等方面的實際問題,最後討論了高級的深度學習模型,如生成對抗網絡及其應用。


分享到:


相關文章: