三大深度學習生成模型:VAE、GAN及其變種
編者按:本書節選自圖書《深度學習輕松學》第十章部分內容,書中以輕松直白的語言,生動詳細地介紹了深層模型相關的基礎知識,並深入剖析了算法的原理與本質。同時還配有大量案例與源碼,幫助讀者切實體會深度學習的核心思想和精妙之處。
本章將為讀者介紹基於深度學習的生成模型。前面幾章主要介紹了機器學習中的判別式模型,這種模型的形式主要是根據原始圖像推測圖像具備的一些性質,例如根據數字圖像推測數字的名稱,根據自然場景圖像推測物體的邊界;而生成模型恰恰相反,通常給出的輸入是圖像具備的性質,而輸出是性質對應的圖像。這種生成模型相當於構建了圖像的分布,因此利用這類模型,我們可以完成圖像自動生成(采樣)、圖像信息補全等工作。
在深度學習之前已經有很多生成模型,但苦於生成模型難以描述難以建模,科研人員遇到了很多挑戰,而深度學習的出現幫助他們解決了不少問題。本章就介紹基於深度學習思想的生成模型——VAE和GAN,以及GAN的變種模型。
VAE
本節將為讀者介紹基於變分思想的深度學習的生成模型——Variational autoencoder,簡稱VAE。
生成式模型
前面的章節里讀者已經看過很多判別式模型。這些模型大多有下面的規律:已知觀察變量X,和隱含變量z,判別式模型對p(z|X)進行建模,它根據輸入的觀察變量x得到隱含變量z出現的可能性。生成式模型則是將兩者的順序反過來,它要對p(X|z)進行建模,輸入是隱含變量,輸出是觀察變量的概率。
可以想象,不同的模型結構自然有不同的用途。判別模型在判別工作上更適合,生成模型在分布估計等問題上更有優勢。如果想用生成式模型去解決判別問題,就需要利用貝葉斯公式把這個問題轉換成適合自己處理的樣子:
對於一些簡單的問題,上面的公式還是比較容易解出的,但對於一些復雜的問題,找出從隱含變量到觀察變量之間的關系是一件很困難的事情,生成式模型的建模過程會非常困難,所以對於判別類問題,判別式模型一般更適合。
但對於“隨機生成滿足某些隱含變量特點的數據”這樣的問題來說,判別式模型就會顯得力不從心。如果用判別式模型生成數據,就要通過類似於下面這種方式的方法進行。
第一步,利用簡單隨機一個X。
第二步,用判別式模型計算p(z|X)概率,如果概率滿足,則找到了這個觀察數據,如果不滿足,返回第一步。
這樣用判別式模型生成數據的效率可能會十分低下。而生成式模型解決這個問題就十分簡單,首先確定好z的取值,然后根據p(X|z)的分布進行隨機采樣就行了。
了解了兩種模型的不同,下面就來看看生成式模型的建模方法。
Variational Lower bound
雖然生成模型和判別模型的形式不同,但兩者建模的方法總體來說相近,生成模型一般也通過最大化后驗概率的形式進行建模優化。也就是利用貝葉斯公式:
這個公式在復雜的模型和大規模數據面前極難求解。為了解決這個問題,這里將繼續采用變分的方法用一個變分函數q(z)代替p(z|X)。第9章在介紹Dense CRF時已經詳細介紹了變分推導的過程,而這一次的推導並不需要做完整的變分推導,只需要利用變分方法的下界將問題進行轉換即可。
既然希望用q(z)這個新函數代替后驗概率p(z|X),那么兩個概率分布需要盡可能地相近,這里依然選擇KL散度衡量兩者的相近程度。根據KL公式就有:
根據貝葉斯公式進行變換,就得到了:
由於積分的目標是z,這里再將和z無關的項目從積分符號中拿出來,就得到了:
將等式左右項目交換,就得到了下面的公式:
雖然這個公式還是很復雜,因為KL散度的性質,這個公式中還是令人看到了一絲曙光。
首先看等號左邊,雖然p(X)的概率分布不容易求出,但在訓練過程中當X已經給定,p(X)已經是個固定值不需要考慮。如果訓練的目標是希望KL(q(z)||p(z|X))盡可能小,就相當於讓等號右邊的那部分盡可能變大。等號右邊的第一項實際上是基於q(z)概率的對數似然期望,第二項又是一個負的KL散度,所以我們可以認為,為了找到一個好的q(z),使得它和p(z|X)盡可能相近,實現最終的優化目標,優化的目標將變為:
- 右邊第一項的log似然的期望最大化:
- 右邊第二項的KL散度最小化:
右邊兩個項目的優化難度相對變小了一些,下面就來看看如何基於它們做進一步的計算。
Reparameterization Trick
為了更方便地求解上面的公式,這里需要做一點小小的trick工作。上面提到了q(z)這個變分函數,為了近似后驗概率,它實際上代表了給定某個X的情況下z的分布情況,如果將它的概率形式寫完整,那么它應該是q(z|X)。這個結構實際上對后面的運算產生了一些障礙,那么能不能想辦法把X抽離出來呢?
例如,有一個隨機變量a服從均值為1,方差為1的高斯分布,那么根據高斯分布的性質,隨機變量b=a-1將服從均值為0,方差為1的高斯分布,換句話說,我們可以用一個均值為0,方差為1的隨機變量加上一個常量1來表示現在的隨機變量a。這樣一個隨機變量就被分成了兩部分——一部分是確定的,一部分是隨機的。
實際上,q(z|X)也可以采用上面的方法完成。這個條件概率可以拆分成兩部分,一部分是一個觀察變量g?(X),它代表了條件概率的確定部分,它的值和一個隨機變量的期望值類似;另一部分是隨機變量ε,它負責隨機的部分,基於這樣的表示方法,條件概率中的隨機性將主要來自這里。
這樣做有什么好處呢?經過變換,如果z條件概率值完全取決於ε的概率。也就是說如果z(i)=g?(X+ε(i)),那么q(z(i))=p(ε(i)),那么上面關於變分推導的公式就變成了下面的公式:
這就是替換的一小步,求解的一大步!這個公式已經很接近問題最終的答案了,既然?完全決定了z的分布,那么假設一個?服從某個分布,這個變分函數的建模就完成了。如果?服從某個分布,那么z的條件概率是不是也服從這個分布呢?不一定。z的條件分布會根據訓練數據進行學習,由於經過了函數g?()的計算,z的分布有可能產生了很大的變化。而這個函數,就可以用深度學習模型表示。前面的章節讀者已經了解到深層模型的強大威力,那么從一個簡單常見的隨機變量映射到復雜分布的變量,對深層模型來說是一件很平常的事情,它可以做得很好。
於是這個假設?服從多維且各維度獨立高斯分布。同時,z的先驗和后驗也被假設成一個多維且各維度獨立的高斯分布。下面就來看看兩個優化目標的最終形式。
Encoder和Decoder的計算公式
回顧一下10.1.2的兩個優化目標,下面就來想辦法求解這兩個目標。首先來看看第二個優化目標,也就是讓公式右邊第二項KL(q(z)||p(z))最小化。剛才z的先驗被假設成一個多維且各維度獨立的高斯分布,這里可以給出一個更強的假設,那就是這個高斯分布各維度的均值為0,協方差為單位矩陣,那么前面提到的KL散度公式就從:
瞬間簡化成為:
前面提到了一個用深層網絡實現的模型g?(X,?),它的輸入是一批圖像,輸出是z,因此這里需要它通過X生成z,並將這一個批次的數據匯總計算得到它們的均值和方差。這樣利用上面的公式,KL散度最小化的模型就建立好了。
實際計算過程中不需要將協方差表示成矩陣的形狀,只需要一個向量σ1來表示協方差矩陣的主對角線即可,公式將被進一步簡化:
由於函數g?()實現了從觀測數據到隱含數據的轉變,因此這個模型被稱為Encoder模型。
接下來是第一個優化目標,也就是讓公式左邊第一項的似然期望最大化。這一部分的內容相對簡單,由於前面的Encoder模型已經計算出了一批觀察變量X對應的隱含變量z,那么這里就可以再建立一個深層模型,根據似然進行建模,輸入為隱含變量z,輸出為觀察變量X。如果輸出的圖像和前面生成的圖像相近,那么就可以認為似然得到了最大化。這個模型被稱為Decoder,也就是本章的主題——生成模型。
到這里VAE的核心計算推導就結束了。由於模型推導的過程有些復雜,下面就來看看VAE實現的代碼,同時來看看VAE模型生成的圖像是什么樣子。
實現
本節要介紹VAE模型的一個比較不錯的實現——GitHub - cdoersch/vae_tutorial: Caffe code to accompany my Tutorial on Variational Autoencoders(https://link.zhihu.com/?target=https%3A//github.com/cdoersch/vae_tutorial),這個工程還配有一個介紹VAE的文章[2],感興趣的讀者可以閱讀,讀后會有更多啟發。這個實現使用的目標數據集依然是MNIST,模型的架構如圖10-1所示。為了更好地了解模型的架構,這里將模型中的一些細節隱去,只留下核心的數據流動和Loss計算部分。
圖中粗框表示求解Loss的部分。虛線展現了兩個模塊之間數據共享的情況。可以看出圖的上半部分是優化Encoder的部分,下面是優化Decoder的部分,除了Encoder和Decoder,圖中還有三個主要部分。
- Encoder的Loss計算:KL散度。
- z的重采樣生成。
- Decoder的Loss計算:最大似然。
這其中最復雜的就是第一項,Encoder的Loss計算。由於Caffe在實際計算過程中只能采用向量的計算方式,沒有廣播計算的機制,所以前面的公式需要進行一定的變換:
在完成了前面的向量級別計算后,最后一步就是完成匯總加和的過程。這樣Loss計算就順利完成了。
經過上面對VAE理論和實驗的介紹,相信讀者對VAE模型有了更清晰的認識。經過訓練后VAE的解碼器在MNIST數據庫上生成的字符如圖10-2所示。
MNIST生成模型可視化
除了直接觀察最終生成的數字結果,實際上還有另一種觀察數據的方式,那就是站在隱變量空間的角度觀察分布的生成情況。實現這個效果需要完成以下兩個工作。
- 隱變量的維度為2,相當於把生成的數字圖片投影到2維平面上,這樣更方便可視化觀察分析。
- 由於隱變量的維度為2,就可以從二維平面上等間距地采樣一批隱變量,這樣這批隱變量可以代表整個二維平面上隱變量的分布,然后這批隱變量經過解碼器處理后展示,這樣就可以看到圖像的分布情況了。
上面描述的算法的流程如圖10-3所示。
圖10-4所示的模型很好地完成了隱變量的建模,絕大多數數字出現在了這個平面分布中,數字與數字一些過渡區域,這些過渡區域的圖像擁有多個數字的特征,而這些數字的外形確實存在着相似之處。可以明顯地感受到,圖像隨着隱變量變換產生了變換。
VAE的內容就介紹到這里,下面來看看另一個生成模型。
GAN
前面我們介紹了VAE,下面來看看GAN(Generative Adversarial Network)[3],這個網絡組是站在對抗博弈的角度展現生成模型和判別模型各自的威力的,在效果上比VAE還要好些。
GAN的概念
同VAE模型類似,GAN模型也包含了一對子模型。GAN的名字中包含一個對抗的概念,為了體現對抗這個概念,除了生成模型,其中還有另外一個模型幫助生成模型更好地學習觀測數據的條件分布。這個模型可以稱作判別模型D,它的輸入是數據空間內的任意一張圖像x,輸出是一個概率值,表示這張圖像屬於真實數據的概率。對於生成模型G來說,它的輸入是一個隨機變量z,z服從某種分布,輸出是一張圖像G(z),如果它生成的圖像經過模型D后的概率值很高,就說明生成模型已經比較好地掌握了數據的分布模式,可以產生符合要求的樣本;反之則沒有達到要求,還需要繼續訓練。
兩個模型的目標如下所示。
- 判別模型的目標是最大化這個公式:Ex[D(x)],也就是甄別出哪些圖是真實數據分布中的。
- 生成模型的目標是最大化這個公式:Ez[D(G(z))],也就是讓自己生成的圖被判別模型判斷為來自真實數據分布。
看上去兩個模型目標聯系並不大,下面就要增加兩個模型的聯系,如果生成模型生成的圖像和真實的圖像有區別,判別模型要給它判定比較低的概率。這里可以舉個形象的例子,x好比是一種商品,D是商品的檢驗方,負責檢驗商品是否是正品;G是一家山寨公司,希望根據拿到手的一批產品x研究出生產山寨商品x的方式。對於D來說,不管G生產出來的商品多像正品,都應該被判定為贗品,更何況一開始G的技術水品不高,生產出來的產品必然是漏洞百出,所以被判定為贗品也不算冤枉,只有不斷地提高技術,才有可能迷惑檢驗方。
基於上面的例子,兩個模型的目標就可以統一成一個充滿硝煙味的目標函數。
上面這個公式對應的模型架構如圖10-5所示。
對應的模型學習算法偽代碼如下所示:
def GAN(G,D,X):
# G 表示生成模型
# D 表示判別模型
# X 表示訓練數據
for iter in range(MAX_ITER):
for step in range(K):
x = data_sample(X)
z = noise_sample()
optimize_D(G, D, x, z)
z = noise_sample()
optimize_G(G, D, z)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
上面的代碼只是從宏觀的層面介紹了模型的優化方法,其中K表示了判別模型D的迭代次數,K一般大於等於1。從上面的公式可以看出,兩個模型的目標是對立的。生成模型希望最大化自己生成圖像的似然,判別模型希望最大化原始數據的似然的同時,能夠最小化G生成的圖像的似然。既然是對立的,那么兩個模型經過訓練產生的能力就可能有很多種情況。它們既可能上演“魔高一尺,道高一尺”,“道高一丈,魔高十丈”的競爭戲碼,在競爭中共同成長,最終產生兩個強大的模型;也可能產生一個強大的模型,將另一方完全壓倒。
如果判別模型太過強大,那么生成模型會產生兩種情況:一種情況是發現自己完全被針對,模型參數無法優化;另外一種情況是發現判別模型的一些漏洞后,它的模型將退化,不管輸入是什么樣子,輸出統一變成之前突破了判別模型防線的那幾類結果。這種情況被稱為“Mode Collapse”,有點像一個復雜強大的模型崩塌成一個簡單弱小的模型,這樣的模型即使優化結果很好,也不能拿去使用。
如果判別模型不夠強大,它的判別不夠精准,而生成模型又是按照它的判別結果生產,那么生產出的產品不會很穩定,這同樣不是我們想看到的結果。
總而言之,對抗是GAN這個模型要面對的一個大問題。雖然論文中作者試圖將兩個模型共同優化的問題轉換成類似Coordinate Ascent那樣的優化問題,並證明像Coordinate Ascent這樣的算法可以收斂,那么GAN這個模型也可以。不過作者在完成證明后立刻翻臉,說證明結果和實驗結果不符。所以這個問題在當時也就變成了一個懸案。
GAN的訓練分析
關於GAN訓練求解的過程,作者用了十分數學化的方式進行了推演。我們首先來證明第一步:當生成模型固定時,判別模型的最優形式。
首先將目標函數做變換:
由於組成式子的兩部分積分的區域不同,會對后面的計算造成困難,我們首先將兩個積分區域統一。我們將生成圖像G(z)的分布與真實圖像x的分布做一個投射,只要判別式能夠在真實數據出現的地方保證判別正確最大化即可,於是公式就變成了:
只要讓積分內部的公式最大化,整個公式就可以實現最大化。這樣問題就轉變為最大化下面的公式:
對它進行求導取極值,可以得到:
令上面的式子為0,我們可以得到結果:
這就是理論上判別式的預測結果,如果一張圖像在真實分布中出現的概率大而在生成分布中出現的概率小,那么最優的判別模型會認為它是真實圖像,反之則認為不是真實圖像。如果生成模型已經達到了完美的狀態,也就是說對每一幅圖像都有:
接下來就可以利用上面的結果,計算當生成模型達到完美狀態時,損失函數的值。我們將D*(x)=1/2的結果代入,可以得到:
也就是說生成模型損失函數的理論最小值為-2log2。那么,一般情況下它的損失函數是什么樣子呢?我們假設在某一時刻判別式經過優化已經達到最優,所以
我們將這個公式代入之前的公式,可以得到:
后面的兩個KL散度的計算公式可以轉化為Jenson-Shannon散度,也就是:
這其實是生成模型真正的優化目標函數。在介紹VAE時,讀者已經了解了KL散度,也了解了它的一些基本知識,那么這個JS散度又是什么?它又有什么特性和優勢?從最直觀的角度,讀者可以發現一個KL散度不具備的性質——JS散度是對稱的:
對稱又能帶來什么好處呢?它能讓散度度量更准確。接下來將用一段代碼展示這其中的道理。首先給出兩個離散隨機變量的KL散度和JS散度的計算方法:
import numpy as np
import math
def KL(p, q):
# p,q為兩個list,里面存着對應的取值的概率,整個list相加為1
if 0 in q:
raise ValueError
return sum(_p * math.log(_p/_q) for (_p,_q) in zip(p, q) if _p != 0)
def JS(p, q):
M = [0.5 * (_p + _q) for (_p, _q) in zip(p, q)]
return 0.5 * (KL(p, M) + KL(q, M))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
下面將用3組實驗看看兩個散度的計算結果。首先選定一個簡單的離散分布,然后求出它的KL散度和JS散度。在此基礎上,把兩個分布分別做一定的調整。首先是基礎的分布:
def exp(a, b):
a = np.array(a, dtype=np.float32)
b = np.array(b, dtype=np.float32)
a /= a.sum()
b /= b.sum()
print a
print b
print KL(a,b)
print JS(a,b)
# exp 1
exp([1,2,3,4,5],[5,4,3,2,1])
#以下為運行結果顯示
[ 0.066 0.133 0.2 0.266 0.333]
[ 0.333 0.266 0.2 0.133 0.066]
0.521
0.119
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
接下來把公式中第二個分布做修改,假設這個分布中有某個值的取值非常小,就有可能增加兩個分布的散度值,它的代碼如下所示:
# exp 2
exp([1,2,3,4,5],[1e-12,4,3,2,1])
exp([1,2,3,4,5],[5,4,3,2,1e-12])
#以下為運行結果顯示
[ 0.066 0.133 0.2 0.266 0.333]
[ 9.999e-14 4.000e-01 3.000e-01 2.000e-01 1.000e-01]
2.06550201846
0.0985487692551
[ 0.066 0.133 0.2 0.266 0.333]
[ 3.571e-01 2.857e-01 2.142e-01 1.428e-01 7.142e-14]
9.662
0.193
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
可以看出KL散度的波動比較大,而JS的波動相對小。
最后修改前面的分布,代碼如下所示:
# exp 3
exp([1e-12,2,3,4,5],[5,4,3,2,1])
exp([1,2,3,4,1e-12],[5,4,3,2,1])
- 1
- 2
- 3
- 4
這回得到的結果是這樣的:
[ 7.142e-14 1.428e-01 2.142e-01 2.857e-01 3.571e-01]
[ 0.333 0.266 0.2 0.133 0.0666]
0.742
0.193
[ 1.000e-01 2.000e-01 3.000e-01 4.000e-01 9.999e-14]
[ 0.333 0.266 0.2 0.133 0.066]
0.383
0.098
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果將第二個實驗和第三個實驗做對比,就可以發現KL散度在衡量兩個分布的差異時具有很大的不對稱性。如果后面的分布在某一個值上缺失,就會得到很大的散度值;但是如果前面的分布在某一個值上缺失,最終的KL散度並沒有太大的波動。這個例子可以很清楚地看出KL不對稱性帶來的一些小問題。而JS具有對稱性,所以第二個實驗和第三個實驗的JS散度實際上是距離相等的分布組。
從這個小例子我們可以看出,有時KL散度下降的程度和兩個分布靠近的程度不成比例,而JS散度靠近的程度更令人滿意,這也是GAN模型的一大優勢。
GAN實戰
看完了前面關於GAN的理論分析,下面我們開始實戰。在實戰之前目標函數還要做一點改動。從前面的公式中可以看出這個模型和VAE一樣都是有嵌套關系的模型,那么生成模型G要想完成前向后向的計算,要先將計算結果傳遞到判別模型計算損失函數,然后將梯度反向傳播回來。那么不可避免地我們會遇到一個問題,如果梯度在判別模型那邊消失了,生成模型豈不是沒法更新了?生成模型的目標函數如下所示:
如果判別模型非常厲害,成功地讓D(G(z))等於一個接近0的數字,那么這個損失函數的梯度就消失了。其實從理論上分析這個結果很正常,生成模型的梯度只能從判別模型這邊傳過來,這個結果接近0對於判別模型來說是滿意的,所以它不需要更新,梯度就沒有了,於是生成模型就沒法訓練了。所以作者又設計了新的函數目標:
這樣一來梯度又有了,生成模型也可以繼續訓練。當然,這個目標函數也有不足的地方。
下面來看一個具體的基於深層模型的實現——DC-GAN。全稱是Deep Convolution GAN。也就是用深度卷積網絡進行對抗生成網絡的建模。在此之前,也有一些基於卷積神經網絡的GAN實現,但是相對來說,DC-GAN的最終表現與同期的模型相比更優秀,在介紹它的論文中,作者也詳細介紹了模型的一些改進細節。
- 將Pooling層替換成帶有stride的卷積層
- 使用Batch Normalization
- 放棄使用全連接層
- 將卷積層的非線性部分換成ReLU或者Leaky ReLU
下面將使用DC-GAN的模型進行實驗,這個實驗使用的數據集還是MNIST。由於Caffe並不是十分適合構建GAN這樣的模型,因此這里使用另外一個十分流行且簡單易懂的框架——Keras來展示DC-GAN的一些細節。代碼來自https://github.com/jacobgil/keras-dcgan。由於Keras的代碼十分直觀,這里就直接給出源碼。首先是生成模型:
def generator_model():
model = Sequential()
model.add(Dense(input_dim=100, output_dim=1024))
model.add(Activation('tanh'))
model.add(Dense(out_dim=128*7*7))
model.add(BatchNormalization())
model.add(Activation('tanh'))
model.add(Reshape((128, 7, 7), input_shape=(128*7*7,)))
model.add(UpSampling2D(size=(2, 2)))
model.add(Convolution2D(out_channel=64, kernel_height=5, kernel_width=5, border_mode='same'))
model.add(Activation('tanh'))
model.add(UpSampling2D(size=(2, 2)))
model.add(Convolution2D(out_channel=1, kernel_height=5, kernel_width=5, border_mode='same'))
model.add(Activation('tanh'))
return model
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
這里需要說明的一點是,這個實現和論文中的描述有些不同,不過對於MNIST這樣的小數據集,這樣的模型差異不影響效果。
判別模型的結構如下所示,仔細地讀一遍就可以理解,這里不再贅述。
def discriminator_model():
model = Sequential()
model.add(Convolution2D(64, 5, 5, border_mode='same', input_shape=(1, 28, 28)))
model.add(Activation('tanh'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Convolution2D(128, 5, 5))
model.add(Activation('tanh'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(1024))
model.add(Activation('tanh'))
model.add(Dense(1))
model.add(Activation('sigmoid'))
return model
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
完成訓練后,生成模型生成的手寫數字如圖10-6所示。
除了個別數字外,大多數數字生成得和真實數據很像。將圖10-6和圖10-2進行對比,我們可以發現,GAN模型生成的數字相對而言更為“清晰”,而VAE模型的數字略顯模糊,這和兩個模型的目標函數有很大的關系。另外,兩個模型在訓練過程中的Loss曲線如圖10-7所示。
其中上面的曲線表示生成模型的Loss,下面的曲線是判別模型的Loss,雖然這兩個Loss的絕對數值看上去不能說明什么問題,但是相信讀者還是可以看出兩個模型的Loss存在着強相關的關系,這也算是對抗過程中的此消彼長。
最終生成的數據還算令人滿意,我們還很好奇,在模型優化過程中生成模型生成的圖像都是什么樣的呢?接下來就來觀察生成圖像的演變過程。在優化開始時,隨機生成的圖像如圖10-8所示。
其實就是噪聲圖片,一點都不像數字。經過400輪的迭代,生成模型可以生成的圖像如圖10-9所示。
可以看出數字的大體結構已經形成,但是能夠表征數字細節的特征還沒有出現。
經過10個Epoch后,生成模型的作品如圖10-10所示。
這時有些數字已經成形,但是還有一些數字仍然存在欠缺。
20輪Epoch后的結果如圖10-11所示。
這時的數字已經具有很強的辨識度,但與此同時,我們發現生成的數字中有大量的“1”。
當完成了所有的訓練,取出生成模型在最后一輪生成的圖像,如圖10-12所示。
可以看出這里面的數字質量更高一些,但是里面的“1”更多了。
從模型的訓練過程中可以看出,一開始生成的數字質量都很差,但生成數字的多樣性比較好,后來的數字質量比較高但數字的多樣性逐漸變差,模型的特性在不斷發生變化。這個現象和兩個模型的對抗有關系,也和增強學習中的“探索—利用”困境很類似。
站在生成模型的角度思考,一開始生成模型會盡可能地生成各種各樣形狀的數字,而判別模型會識別出一些形狀較差的模型,而放過一些形狀較好的模型,隨着學習的進程不斷推進,判別模型的能力也在不斷地加強,生成模型慢慢發現有一些固定的模式比較容易通過,而其他的模式不那么容易通過,於是它就會盡可能地增大這些正確模式出現的概率,讓自己的Loss變小。這樣,一個從探索為主的模型變成了一個以利用為主的模型,因此它的數據分布已經不像剛開始那么均勻了。
如果這個模型繼續訓練下去,生成模型有可能進一步地利用這個模式,這和機器學習中的過擬合也有很相近的地方。
Info-GAN
本節將要介紹GAN模型的一個變種——InfoGAN,它要解決隱變量可解釋性的問題。前面提到GAN的隱變量服從某種分布,但是這個分布背后的含義卻不得而知。雖然經過訓練的GAN可以生成新的圖像,但是它卻無法解決一個問題——生成具有某種特征的圖像。例如,對於MNIST的數據,生成某個具體數字的圖像,生成筆畫較粗、方向傾斜的圖像等,這時就會發現經典的GAN已經無法解決這樣的問題,想要解決就需要想點別的辦法。
首先想到的方法就是生成模型建模的方法:挑出幾個隱變量,強制指定它們用來表示這些特性的屬性,例如數字名稱和方向。這樣看上去似乎沒有解決問題,但這種方法需要提前知道可以建模的隱變量內容,還要為這些隱變量設置好獨立的分布假設,實際上有些麻煩又不夠靈活。本節的主角——InfoGAN,將從信息論角度,嘗試解決GAN隱變量可解釋性問題。
互信息
介紹算法前要簡單回顧機器學習中的信息論基本知識。第2章已經介紹了熵和“驚喜度”這些概念,熵衡量了一個隨機變量帶來的“驚喜度”。本節要介紹的概念叫做互信息,它衡量了隨機變量之間的關聯關系。假設隨機事件A的結果已經知道,現在要猜測某個事件B的結果,那么知道A的取值對猜測B有多大幫助?這就是互信息要表達的東西。
我們以擲骰子為例,如果我們知道手中的骰子是不是“韋小寶特制”骰子這件事,那么它會對我們猜測最終投擲的點數有幫助嗎?當然有幫助,因為一旦確定這個骰子是“韋小寶特制”,那么骰子點數是幾這個信息就變得沒有“驚喜”了。同理,“美國第45屆總統是誰”這個消息對我們手中骰子投擲出的點數這個事情就沒那么多幫助了,所以這兩件事情的互信息就低,甚至可以說這兩個事件是相互獨立的。
了解了上面比較直觀的例子,下面就可以給出連續隨機變量X,Y互信息的計算公式:
上面的公式可以做如下變換:
就可以發現互信息的進一步解釋:它可以變為熵和條件熵的差。同樣地,這個公式還可以轉變為:I(X;Y)=H(X)-H(X|Y)
最終表示為熵和條件熵的差距。用通俗的話解釋,兩個隨機變量的互信息就是在知道和不知道一個隨機變量取值的情況下,另一個隨機變量“驚喜度”的變化。互信息的計算方法的代碼如下所示:
import numpy as np
import math
def mutual_info(x_var, y_var):
sum = 0.0
x_set = set(x_var)
y_set = set(y_var)
for x_val in x_set:
px = float(np.sum(x_var == x_val)) / x_var.size
x_idx = np.where(x_var == x_val)[0]
for y_val in y_set:
py = float(np.sum(y_var == y_val)) / y_var.size
y_idx = np.where(y_var == y_val)[0]
pxy = float(np.intersect1d(x_idx, y_idx).size) / x_var.size
if pxy > 0.0:
sum += pxy * math.log((pxy / (px * py)), 10)
return sum
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
下面隨意給出一對隨機變量和它們的概率分布,並用上面的代碼分析這對變量的互信息:
a = np.array([0,0,5,6,0,4,4,3,1,2])
b = np.array([3,4,5,5,3,7,7,6,5,1])
print mutual_info(a,b)
# 0.653
a = np.array([0,0,5,6,0,4,4,3,1,2])
b = np.array([3,3,5,6,3,7,7,9,4,8])
print mutual_info(a,b)
# 0.796
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
很明顯,下面一組數據的相關性更強,知道其中一個隨機變量的取值,就會非常容易猜出同一時刻另外一個隨機變量的采樣值。如果我們進一步觀察第二組數據,會發現任意一組數據的熵都是0.796,也就是說當知道其中一個隨機變量的值后,它們的條件熵就變成了0,另一個隨機變量變得完全“驚喜”了。雖然條件熵為0這個信息並沒有展現在互信息的數值中,但互信息實際上就是在衡量一個相對的信息差距,並不像熵那樣衡量信息絕對量。
其實數學包含了很多人生哲理和智慧。人的一生實際上一直在和熵作斗爭,每個人的人生軌跡的熵意味着什么?一個人未來的不確定性?一個人未來的“驚喜”程度?有的人說自己“一輩子也就這樣了”的時候,是不是表示這個人的未來已經從一個隨機變量變成了常量,它的熵變成了0?為什么人們總是向往青春,是不是因為那些年華充滿了各種不確定性與精彩,可以理解為熵很大?
“身體和靈魂,總有一個在路上”,是不是標榜追求最大熵的一個口號?“公務員這種穩定工作才是好工作”是不是一種追求最小化熵的行為呢?那么對於一個人來說,究竟是熵越大越好,還是熵越小越好?
回到問題,互信息在這個問題中有什么用?如果說隱變量的確定對確定生成圖像的樣子有幫助,那么隱變量和最終的圖像之間的互信息就應該很大:當知道了隱變量這個信息,圖像的信息對變得更確定了。所以InfoGAN這個算法就是要通過約束互信息使隱變量“更有價值”。
InfoGAN模型
那么,InfoGAN模型的具體形式是什么樣的呢?如果把互信息定義為損失函數的一部分,這部分損失函數就是InfoGAN中基於經典GAN修改的部分。前面的小節已經推導出了互信息的公式,那么在具體計算時要使用哪個公式計算呢?
- I(X;Z)=H(X)-H(X|Z)
- I(X;Z)=H(Z)-H(Z|X)
最終的選擇是后者,因為圖像X的分布太難確定,求解它的熵肯定相當困難,所以前者的第一項非常難計算。當然,即使選擇了第二項,這個公式也不是很好優化,因為其中還有一個后驗項P(Z|X)需要求解,這也是個大麻煩,不過這里可以使用本書多次提到的方法——Variational Inference求解這個后驗項。
在介紹VAE時我們曾經運用過Reparameterization Trick這個方法,這里將再次采用類似的方法。在VAE中,Trick公式是z(i)=g?(X+ε(i)),在Encoder的過程中,輸入部分被分解成確定部分和不確定部分,然后利用一個高維非線性模型擬合輸入到輸出的映射。這里要求出的X,和VAE正好相反,需要的是這樣的一個公式:X=g?([c,z]+?)
其中c表示與圖像有相關關系的隱變量,z表示與圖像無關的隱變量。於是互信息計算公式就變成了:
從實踐上講,?項可以忽略,於是公式可以做進一步簡化:
接下來將期望用蒙特卡羅方法代替,訓練時可以通過計算大量樣本求平均來代替期望值,於是公式又變成了:
這個方程變簡單了很多。當然,我們也看出上面的公式中我們有一個Q,這個Q函數可以理解為一個Encoder,這部分模型在經典GAN中並不存在,但是在實際建模過程中,由於Encoder和判別模型的輸入相同,且模型目標比較相近,因此二者部分網絡結構可以共享。論文的作者提供了InfoGAN的源碼,代碼的鏈接在https://github.com/openai/InfoGAN,代碼使用的框架為TensorFlow,感興趣的讀者可以自行閱讀。模型實現的結構如圖10-13所示。
虛線部分表示的就是計算互信息的目標函數,這部分內容看似比較復雜,實則不然。由於InfoGAN模型中定義了兩種類型的隨機變量——服從Categorical分布、用於表示數字內容的離散類型變量,和服從均勻分布用於表示其他連續特征的連續型變量,而兩種類型的變量在計算熵的方法不同,因此上面的計算圖對它們進行分情況處理。
互信息計算起始於如下兩個變量。
- reg_z:表示了模型開始隨機生成的隱變量。
- fake_ref_z_dist_info:表示了經過Encoder計算后的隱變量分布信息。
接下來,根據連續型和離散型的分類,兩個變量分成了以下四個變量。
- cont_reg_z:reg_z的連續變量部分
- cont_reg_dist_info:fake_ref_z_dist_info的連續變量部分
- disc_reg_z:reg_z的離散變量部分
- disc_reg_dist_info:fake_ref_z_dist_info的連續變量部分
接下來,四個變量兩兩組隊完成了后驗公式P(c)logQ(c|g?([c,z])的計算:
cont_log_q_c_given_x:連續變量的后驗
- disc_log_q_c_given_x:離散變量的后驗
同時,輸入的隱變量也各自完成先驗P(c)logP(c)的計算:
- cont_log_q_c:連續變量的先驗
- disc_log_q_c:離散變量的后驗
由於上面的運算全部是元素級的計算,還要把向量求出的內容匯總,得到∑P(c)logQ(c|g?([c,z])和∑P(c)logP(c) 。
- cont_cross_ent:連續變量的交叉熵
- cont_ent:連續變量的熵
- disc_cross_ent:離散變量的交叉熵
- disc_ent:離散變量的熵
接下來,根據互信息公式兩兩相減,得到各自的互信息損失。
- cont_mi_est:連續變量的互信息
- disc_mi_est:離散變量的互信息
最后將兩者相加就得到了最終的互信息損失。
模型在訓練前定義了12個和圖像有強烈互信息的隨機變量,其中10個變量表示顯示的數字,它們組成一個Categorical的離散隨機向量;另外2個是服從范圍為[-1,1]的連續隨機變量。訓練完成后,調整離散隨機變量輸入並生成圖像,得到如圖10-14所示的數字圖像。
可以看出模型很好地識別了這些數字。調整另外兩個連續隨機變量,可以生成如圖10-15所示的數字圖像。
可以看出,這兩個連續隨機變量學到了數字粗細和傾斜的特征,而且這是在完全沒有暗示的情況下完成的。可見InfoGAN模型的能力。
到此InfoGAN的介紹就結束了。從這個模型可以看出,在經典GAN模型基礎上添加更多的內容會產生更多意想不到的效果。
總結
本章主要介紹了基於深度學習的生成模型,它們在生成圖像上有着很強的能力。
- VAE:基於變分下界約束得到的Encoder-Decoder模型對。
- GAN:基於對抗的Generator-Discriminator模型對。
- InfoGAN:挖掘GAN模型隱變量特點的模型。
《深度學習輕松學》訂購鏈接:https://item.jd.com/12106435.html#
點擊參與贈送此書活動