華文網

GAN入門教程|從0開始,手把手教你學會最火的神經網路

安妮 編譯自 O’Reilly

生成式對抗網路是20年來機器學習領域最酷的想法。 ——Yann LeCun

自從兩年前蒙特利爾大學的Ian Goodfellow等人提出生成式對抗網路(Generative Adversarial Networks,GAN)的概念以來,GAN呈現出井噴式發展。

這篇發佈在O’Reilly上的文章中,

作者向初學者進行了GAN基礎知識答疑,並手把手教給大家如何用GAN創建可以生成手寫數位的程式。

本教程由兩人完成:Jon Bruner是O’Reilly編輯組的一員,負責管理硬體、互聯網、製造和電子學等方面的出版物;Adit Deshpande是加州大學洛杉磯分校電腦科學專業的大二學生。

量子位提示:本篇文章:

https://github.com/jonbruner/generative-adversarial-networks

不多說了,開啟你的GAN之旅吧——

序言

GAN是一種神經網路,它會學習創建一些類似已知輸入資料的合成資料。目前,

研究人員已經可以用GAN合成出從臥室到專輯封面等一系列照片,GAN也顯示出反映高階語義邏輯的非凡能力。

這些例子非常複雜,但構建可以生成簡單圖像的GAN真的不難。在這個教程裡,我們將學習構建分析手寫數位圖像的GAN,並且從零開始學如何讓它學會生成新圖像。其實說白了,就是教會神經網路如何寫字。

上面這張圖就是我們在本教程中構建的用GAN生成的示例圖像。

GAN的架構

GAN中包含兩個模型:生成模型(Generative Model)和判別模型(Discriminative Model)。

判別模型是一個分類器,它判斷給定的圖片到底是來自資料集的真實圖像,還是人工創建的假圖像。這基本上就是一個表現為卷積神經網路(CNN)形式的二元分類器。

生成模型通過反卷積神經網路將隨機輸入值轉化為圖像。

在數次訓練反覆運算的歷程中,判別器和生成器的的權重和偏差都是通過反向傳播訓練的。判別器學習從一堆生成器生成的假數位圖像中,

找出真正的數位圖像。與此同時,生成器通過判別器的回饋學習如何生成具有欺騙性的圖片,防止被判別器識別出來。

準備工作

我們將創造一個可以生成手寫數字的GAN,希望可以騙過最好的分類器(當然也包括人類)。我們將使用穀歌開源的TensorFlow使在GPU上訓練神經網路更容易。

https://www.tensorflow.org/

在學習這個教程之前,希望你可以瞭解一些TensorFlow的知識。如果之前沒有接觸過它,

建議你先看看相關的文章和教程。

載入MNIST資料

首先,我們需要給判別器輸入一系列真實的手寫數位圖像,算是給判別器的一個參考。這裡使用的是深度學習基準資料集MNIST,這是一個手寫數位圖片資料庫,每一張都是0-9的單個數字,且每一張都是抗鋸齒(Anti-aliasing)的灰度圖。資料庫中包含美國國家標準與技術研究所收集的人口調查局員工和高中生寫的70000張數位圖像。

MNIST資料集連結(英文):

http://yann.lecun.com/exdb/mnist/

我們從導入TensorFlow和其他有用的資料庫開始講起。首先我們需要用TensorFlow的便捷函數導入MNIST的圖像,不妨把這個函數稱為read_data_sets。

我們創造的MNIST的變數包含圖像和標籤,並將資料集分為訓練集和驗證集(不過在本教程裡我們並不需要考慮標籤這個事情)。我們可以通過調用mnist上的next_batch進行檢索,現在我們先載入一張圖片看看。

這張圖像一開始被格式化為一列784圖元,我們可以將它們改造成28×28圖元的圖像並且用PyPlot查看。

如果你再次運行上面的cell,你會發現和MNIST訓練集不同的圖像。

判別網路

判別器是一個卷積神經網路,接收圖片大小為28×28×1的輸入圖像,之後返還一個單一標量值來描述輸入圖像的真偽——判斷到底是來自MNIST圖像集還是生成器。

判別器的結構與TensorFlow的樣例CNN分類模型密切相關。它有兩層特徵為5×5圖元特徵的卷積層,還有兩個全連接層按圖像中每個圖元計算增加權重的層。

創建了神經網路後,通常需要將權重和偏差初始化,這項任務可以在tf.get_variable中完成。權重在截斷正態分佈中被初始化,偏差在0處被初始化。

tf.nn.conv2d()是TensorFlow中的標準卷積函數,它包含四個參數:首個參數就是輸入圖像(input volume),也就是本示例中的28×28圖元的圖片;第二個參數是濾波器/權矩陣,最終你也可以改變卷積的“步幅”和“填充”。這兩個參數控制著輸出圖像的尺寸大小。

其實上面這些就是一個普通簡單的二進位分類器,如果你不是初次接觸CNN,應該對此並不陌生。

定義了判別器之後,我們需要回頭看看生成模型。我們將以Tim O’Shea編寫的簡單生成器代碼為基礎構建模型的整體結構。

Code連結:

https://github.com/osh/KerasGAN

其實你可以把生成器想像成反向卷積神經網路的一種。判別器就是一個典型的CNN,它能將二維或三維的圖元值矩陣(matrix of pixel values)轉化成一個概率。然而作為一個生成器,需要d-維度向量 d-dimensional vector ,並需要將其變為28*28的圖像。ReLU和批量標準化(batch normalization)也經常用於穩定每一層的輸出。

在這個神經網路中,我們用了三個卷積層和插值,直到形成28*28圖元的圖像。

我們在輸出層添加了一個tf.sigmoid() 啟動函數,它將擠壓灰色呈現白色或黑色相,從而產生一個更清晰的圖像。

生成樣本圖像

定義完生成器和判別函數,我們現在看看沒有訓練過的生成器會生成怎樣的樣例。

首先打開TensorFlow,為我們的生成器創建一個預留位置。預留位置的形式是None x z_dimensions,關鍵字None意味著可以在運行會話時確定它的值。我們通常用None作為我們第一個維度,所以我們的批次處理大小是可變的。有了關鍵字None,所以不需要指定batch_size。

現在,我們創建能夠保存生成器輸出的變數(generated_image_output),還要將輸入的隨機雜訊向量初始化。np.random.normal()函數具備了3個參數,前兩個定義了正態分佈的平均偏差和標準差,最後一個定義了向量(1 x 100)的形狀。

接下來需要將所有變數初始化,將z_batch 放到預留位置中,並運行這部分代碼。

sess.run()函數有兩個參數。第一個叫做“獲取”參數,定義你在計算中感興趣的值。在這個案例中,我們想看到生成器會輸出什麼。如果看看最後的代碼片段,你將看到生成函數的輸出被存儲在generated_image_output裡,我們將使用generated_image_output作為第一個參數。

第二個參數相當於一個輸入字典,在運行時可以取代計算圖,也就是我們要填到預留位置裡的。在我們的例子中,我們需要將z_batch變數輸入到之前定義的z_placeholder中,之後在PyPlot中將圖片重新調整為28*28圖元。

它看起來像噪音對吧。現在我們需要訓練生成網路中的權重和偏差,將亂數轉變為可識別的數位。我們再看看損失函數和優化。

訓練GAN

構建和調試GAN就複雜在它有兩個損失函數:一個鼓勵生成器創造出更好的圖像,另一個鼓勵判別器區分哪個是真圖像,哪個是生成器生成的。

我們同時訓練生成器和判別器,當判別器能夠很好區分圖像來自哪裡時,生成器也能更好地調整它的權重和偏差來生成更以假亂真的圖像。

這個網路的輸入和輸出如下:

所以,讓我們首先考慮一下我們需要在網路中得到什麼。判別器的目標是正確地將MNIST圖像標記為真,而判別器生成的標記為假。我們將計算判別器的兩種損失:Dx和1(代表MNIST中的真實圖像)的損失,以及Dg與0(代表生成圖像)的損失。我們將這個函數在TensorFlow中的tf.nn.sigmoid_cross_entropy_with_logits()函數上運行,計算Dx和0與Dg和1之間的交叉熵損失。

sigmoid_cross_entropy_with_logits 是在未縮放的值下運行的,而不是在0到1之間的概率值。看一下判別器的最後一行:這裡並沒有softmax或sigmoid函數層。如果判別器“飽和”了,或者有足夠的信心可以在給出生成圖像後返回0,那麼就會使判別器的梯度下降失去作用。

tf.reduce_mean()函數選取的是交叉熵函數返回的矩陣中所有分量的平均值。這是一種將損失減小到單個標量值的方法,而不是向量或矩陣。

現在我們來設置生成器的損失函數。我們想讓生成網路的圖像騙過判別器:當輸入生成圖像時,判別器可以輸出接近1的值,來計算Dg與1之間的損失。

現在我們已經得到損失函數,需要定義優化程式了。生成網路的優化程式只需要升級生成器的權重,而不是判別器的。同樣的,當訓練判別器的時候,我們需要固定生成器的權重。

為了使這些看起來不同,我們需要創建兩個變數清單,一個是判別器的權重和偏差,另一個是生成器的權重和偏差。這就是當給TensorFlow變數取名字需要深思熟慮的原因。

下一步,你需要制定兩個優化器,我們一般選擇Adam優化演算法,它利用了自我調整學習速率和動量。我們調用Adam最小函數並且指定我們想更新的變數——也就是我們訓練生成器時的生成器權重和偏差,和我們訓練判別器時的判別器權重和偏差。

我們為判別器設置了兩套不同的訓練方案:一種是用真實圖像訓練判別器,另一種是用生成的“假圖像”訓練它。有時使用不同的學習速率很有必要,或者單獨使用它們來規範學習的其他方面。

https://github.com/jonbruner/ezgan

收斂GAN是一件棘手的事情,經常需要訓練很長時間。可以用TensorBoard追蹤訓練過程:它可以用圖表描繪標量屬性(如損失),展示訓練中的樣本圖像,並展示神經網路中的拓撲結構。

想瞭解更多TensorBoard資訊?連結(英文):

https://www.tensorflow.org/get_started/summaries_and_tensorboard

如果在自己的機器上運行此腳本,要記得包含下面的cell。之後在終端視窗中運行 tensorboard —logdir=tensorboard/ ,再在流覽器中輸入http://localhost:6006,打開TensorBoard。

現在先給判別器幾個簡單的原始訓練進行反覆運算,這種方法有助於形成對生成器有用的梯度。

之後我們繼續進行主要的訓練迴圈。當訓練生成器的時候,我們需要將隨機的z向量輸入到生成器中,並將其輸出傳遞給判別器(這就是我們早先定義的Dg變數)。生成器的權重和偏差將被改變,主要是為了生成能騙過判別器的圖像。

為了訓練判別器,我們將給它提供一組來自MNIST資料集中的正面例子,並且再次用生成的圖像訓練判別器,用它們作為反面例子。

因為訓練GAN通常需要很長時間,所以我們建議如果您是第一次使用這個教程,建議先不要運行這個代碼塊。但你可以先執行下面的代碼塊,讓它生成出一個預先訓練模型。

如果你想自己運行這個代碼塊,請做好長時間等待的準備:在速度相對較快的GPU上運行大概需要3小時,在桌上型電腦的CPU上可能耗費10倍時間。

所以,建議你跳過上面直接執行下面的cell。它載入了一個我們在高速GPU機器上訓練了10小時的模型,你可以試驗下訓練過的GAN。

訓練不易

眾所周知訓練GAN很艱難。在沒有正確的超參數、網路架構和培訓流程的情況下,判別器會壓制生成器。

一個常見的故障模式是,判別器壓制了生成器,肯定地把生成圖像定義為假的。當判別器以絕對肯定時,會使生成器無梯度可降。這就是為什麼我們建立判別器來產生未縮放的輸出,而不是通過一個sigmoid函數將其輸出推到0或1。

在另一種常見的故障模式(模式崩潰)中,生成器發現並利用了判別器中的一些弱點,如果它不顧生成器輸入z.變數,生成了很多相似圖像,你是可以識別出這種模式崩潰的。模式崩潰有時可以通過“強化”鑒別器來修正,例如通過調整其訓練速率或重新配置它的層。

研究人員已經確定了一些幫助建立穩定的GAN的小方法。

你也想讓GANs穩定一下?Code連結:

https://github.com/soumith/ganhacks

結論

GANs有巨大的潛力重塑我們每天與之互動的數字世界。這個領域還很年輕,下一個GAN新發現可能就來自你。

其他資料(英文)

1.2014年Ian Goodfellow和他的夥伴合作發表的GAN論文

Paper連結:

https://arxiv.org/abs/1406.2661

2.Goodfellow最近的一篇教程,通俗易懂地解釋了GAN

Tutorial連結:

https://arxiv.org/abs/1701.00160

3.Alec Radford、Luke Metz和Soumith Chintala等人的論文,介紹了本教程中我們在生成器上使用的複雜GANs的基本結構

Paper連結:

https://arxiv.org/abs/1511.06434

GitHub上的DCGAN代碼:

https://github.com/Newmu/dcgan_code

【完】

一則通知

招聘

如果你再次運行上面的cell,你會發現和MNIST訓練集不同的圖像。

判別網路

判別器是一個卷積神經網路,接收圖片大小為28×28×1的輸入圖像,之後返還一個單一標量值來描述輸入圖像的真偽——判斷到底是來自MNIST圖像集還是生成器。

判別器的結構與TensorFlow的樣例CNN分類模型密切相關。它有兩層特徵為5×5圖元特徵的卷積層,還有兩個全連接層按圖像中每個圖元計算增加權重的層。

創建了神經網路後,通常需要將權重和偏差初始化,這項任務可以在tf.get_variable中完成。權重在截斷正態分佈中被初始化,偏差在0處被初始化。

tf.nn.conv2d()是TensorFlow中的標準卷積函數,它包含四個參數:首個參數就是輸入圖像(input volume),也就是本示例中的28×28圖元的圖片;第二個參數是濾波器/權矩陣,最終你也可以改變卷積的“步幅”和“填充”。這兩個參數控制著輸出圖像的尺寸大小。

其實上面這些就是一個普通簡單的二進位分類器,如果你不是初次接觸CNN,應該對此並不陌生。

定義了判別器之後,我們需要回頭看看生成模型。我們將以Tim O’Shea編寫的簡單生成器代碼為基礎構建模型的整體結構。

Code連結:

https://github.com/osh/KerasGAN

其實你可以把生成器想像成反向卷積神經網路的一種。判別器就是一個典型的CNN,它能將二維或三維的圖元值矩陣(matrix of pixel values)轉化成一個概率。然而作為一個生成器,需要d-維度向量 d-dimensional vector ,並需要將其變為28*28的圖像。ReLU和批量標準化(batch normalization)也經常用於穩定每一層的輸出。

在這個神經網路中,我們用了三個卷積層和插值,直到形成28*28圖元的圖像。

我們在輸出層添加了一個tf.sigmoid() 啟動函數,它將擠壓灰色呈現白色或黑色相,從而產生一個更清晰的圖像。

生成樣本圖像

定義完生成器和判別函數,我們現在看看沒有訓練過的生成器會生成怎樣的樣例。

首先打開TensorFlow,為我們的生成器創建一個預留位置。預留位置的形式是None x z_dimensions,關鍵字None意味著可以在運行會話時確定它的值。我們通常用None作為我們第一個維度,所以我們的批次處理大小是可變的。有了關鍵字None,所以不需要指定batch_size。

現在,我們創建能夠保存生成器輸出的變數(generated_image_output),還要將輸入的隨機雜訊向量初始化。np.random.normal()函數具備了3個參數,前兩個定義了正態分佈的平均偏差和標準差,最後一個定義了向量(1 x 100)的形狀。

接下來需要將所有變數初始化,將z_batch 放到預留位置中,並運行這部分代碼。

sess.run()函數有兩個參數。第一個叫做“獲取”參數,定義你在計算中感興趣的值。在這個案例中,我們想看到生成器會輸出什麼。如果看看最後的代碼片段,你將看到生成函數的輸出被存儲在generated_image_output裡,我們將使用generated_image_output作為第一個參數。

第二個參數相當於一個輸入字典,在運行時可以取代計算圖,也就是我們要填到預留位置裡的。在我們的例子中,我們需要將z_batch變數輸入到之前定義的z_placeholder中,之後在PyPlot中將圖片重新調整為28*28圖元。

它看起來像噪音對吧。現在我們需要訓練生成網路中的權重和偏差,將亂數轉變為可識別的數位。我們再看看損失函數和優化。

訓練GAN

構建和調試GAN就複雜在它有兩個損失函數:一個鼓勵生成器創造出更好的圖像,另一個鼓勵判別器區分哪個是真圖像,哪個是生成器生成的。

我們同時訓練生成器和判別器,當判別器能夠很好區分圖像來自哪裡時,生成器也能更好地調整它的權重和偏差來生成更以假亂真的圖像。

這個網路的輸入和輸出如下:

所以,讓我們首先考慮一下我們需要在網路中得到什麼。判別器的目標是正確地將MNIST圖像標記為真,而判別器生成的標記為假。我們將計算判別器的兩種損失:Dx和1(代表MNIST中的真實圖像)的損失,以及Dg與0(代表生成圖像)的損失。我們將這個函數在TensorFlow中的tf.nn.sigmoid_cross_entropy_with_logits()函數上運行,計算Dx和0與Dg和1之間的交叉熵損失。

sigmoid_cross_entropy_with_logits 是在未縮放的值下運行的,而不是在0到1之間的概率值。看一下判別器的最後一行:這裡並沒有softmax或sigmoid函數層。如果判別器“飽和”了,或者有足夠的信心可以在給出生成圖像後返回0,那麼就會使判別器的梯度下降失去作用。

tf.reduce_mean()函數選取的是交叉熵函數返回的矩陣中所有分量的平均值。這是一種將損失減小到單個標量值的方法,而不是向量或矩陣。

現在我們來設置生成器的損失函數。我們想讓生成網路的圖像騙過判別器:當輸入生成圖像時,判別器可以輸出接近1的值,來計算Dg與1之間的損失。

現在我們已經得到損失函數,需要定義優化程式了。生成網路的優化程式只需要升級生成器的權重,而不是判別器的。同樣的,當訓練判別器的時候,我們需要固定生成器的權重。

為了使這些看起來不同,我們需要創建兩個變數清單,一個是判別器的權重和偏差,另一個是生成器的權重和偏差。這就是當給TensorFlow變數取名字需要深思熟慮的原因。

下一步,你需要制定兩個優化器,我們一般選擇Adam優化演算法,它利用了自我調整學習速率和動量。我們調用Adam最小函數並且指定我們想更新的變數——也就是我們訓練生成器時的生成器權重和偏差,和我們訓練判別器時的判別器權重和偏差。

我們為判別器設置了兩套不同的訓練方案:一種是用真實圖像訓練判別器,另一種是用生成的“假圖像”訓練它。有時使用不同的學習速率很有必要,或者單獨使用它們來規範學習的其他方面。

https://github.com/jonbruner/ezgan

收斂GAN是一件棘手的事情,經常需要訓練很長時間。可以用TensorBoard追蹤訓練過程:它可以用圖表描繪標量屬性(如損失),展示訓練中的樣本圖像,並展示神經網路中的拓撲結構。

想瞭解更多TensorBoard資訊?連結(英文):

https://www.tensorflow.org/get_started/summaries_and_tensorboard

如果在自己的機器上運行此腳本,要記得包含下面的cell。之後在終端視窗中運行 tensorboard —logdir=tensorboard/ ,再在流覽器中輸入http://localhost:6006,打開TensorBoard。

現在先給判別器幾個簡單的原始訓練進行反覆運算,這種方法有助於形成對生成器有用的梯度。

之後我們繼續進行主要的訓練迴圈。當訓練生成器的時候,我們需要將隨機的z向量輸入到生成器中,並將其輸出傳遞給判別器(這就是我們早先定義的Dg變數)。生成器的權重和偏差將被改變,主要是為了生成能騙過判別器的圖像。

為了訓練判別器,我們將給它提供一組來自MNIST資料集中的正面例子,並且再次用生成的圖像訓練判別器,用它們作為反面例子。

因為訓練GAN通常需要很長時間,所以我們建議如果您是第一次使用這個教程,建議先不要運行這個代碼塊。但你可以先執行下面的代碼塊,讓它生成出一個預先訓練模型。

如果你想自己運行這個代碼塊,請做好長時間等待的準備:在速度相對較快的GPU上運行大概需要3小時,在桌上型電腦的CPU上可能耗費10倍時間。

所以,建議你跳過上面直接執行下面的cell。它載入了一個我們在高速GPU機器上訓練了10小時的模型,你可以試驗下訓練過的GAN。

訓練不易

眾所周知訓練GAN很艱難。在沒有正確的超參數、網路架構和培訓流程的情況下,判別器會壓制生成器。

一個常見的故障模式是,判別器壓制了生成器,肯定地把生成圖像定義為假的。當判別器以絕對肯定時,會使生成器無梯度可降。這就是為什麼我們建立判別器來產生未縮放的輸出,而不是通過一個sigmoid函數將其輸出推到0或1。

在另一種常見的故障模式(模式崩潰)中,生成器發現並利用了判別器中的一些弱點,如果它不顧生成器輸入z.變數,生成了很多相似圖像,你是可以識別出這種模式崩潰的。模式崩潰有時可以通過“強化”鑒別器來修正,例如通過調整其訓練速率或重新配置它的層。

研究人員已經確定了一些幫助建立穩定的GAN的小方法。

你也想讓GANs穩定一下?Code連結:

https://github.com/soumith/ganhacks

結論

GANs有巨大的潛力重塑我們每天與之互動的數字世界。這個領域還很年輕,下一個GAN新發現可能就來自你。

其他資料(英文)

1.2014年Ian Goodfellow和他的夥伴合作發表的GAN論文

Paper連結:

https://arxiv.org/abs/1406.2661

2.Goodfellow最近的一篇教程,通俗易懂地解釋了GAN

Tutorial連結:

https://arxiv.org/abs/1701.00160

3.Alec Radford、Luke Metz和Soumith Chintala等人的論文,介紹了本教程中我們在生成器上使用的複雜GANs的基本結構

Paper連結:

https://arxiv.org/abs/1511.06434

GitHub上的DCGAN代碼:

https://github.com/Newmu/dcgan_code

【完】

一則通知

招聘