歡迎光臨
每天分享高質量文章

變分自編碼器VAE:原來是這麼一回事 | 附開原始碼

作者丨蘇劍林

單位丨廣州火焰資訊科技有限公司

研究方向丨NLP,神經網路

個人主頁丨kexue.fm

過去雖然沒有細看,但印象裡一直覺得變分自編碼器(Variational Auto-Encoder,VAE)是個好東西。趁著最近看機率圖模型的三分鐘熱度,我決定也爭取把 VAE 搞懂。


於是乎照樣翻了網上很多資料,無一例外發現都很含糊,主要的感覺是公式寫了一大通,還是迷迷糊糊的,最後好不容易覺得看懂了,再去看看實現的程式碼,又感覺實現程式碼跟理論完全不是一回事啊。 


終於,東拼西湊再加上我這段時間對機率模型的一些積累,並反覆對比原論文 Auto-Encoding Variational Bayes,最後我覺得我應該是想明白了。


其實真正的 VAE,跟很多教程說的的還真不大一樣,很多教程寫了一大通,都沒有把模型的要點寫出來。於是寫了這篇東西,希望透過下麵的文字,能把 VAE 初步講清楚。

分佈變換


通常我們會拿 VAE 跟 GAN 比較,的確,它們兩個的標的基本是一致的——希望構建一個從隱變數 Z 生成標的資料 X 的模型,但是實現上有所不同。


更準確地講,它們是假設了服從某些常見的分佈(比如正態分佈或均勻分佈),然後希望訓練一個模型 X=g(Z),這個模型能夠將原來的機率分佈對映到訓練集的機率分佈,也就是說,它們的目的都是進行分佈之間的變換



生成模型的難題就是判斷生成分佈與真實分佈的相似度,因為我們只知道兩者的取樣結果,不知道它們的分佈運算式。

那現在假設服從標準的正態分佈,那麼我就可以從中取樣得到若干個 Z1,Z2,…,Zn,然後對它做變換得到 X̂1=g(Z1),X̂2=g(Z2),…,X̂n=g(Zn),我們怎麼判斷這個透過 f 構造出來的資料集,它的分佈跟我們標的的資料集分佈是不是一樣的呢?


有讀者說不是有 KL 散度嗎?當然不行,因為 KL 散度是根據兩個機率分佈的運算式來算它們的相似度的,然而目前我們並不知道它們的機率分佈的運算式。


我們只有一批從構造的分佈取樣而來的資料 {X̂1,X̂2,…,X̂n},還有一批從真實的分佈取樣而來的資料 {X1,X2,…,Xn}(也就是我們希望生成的訓練集)。我們只有樣本本身,沒有分佈運算式,當然也就沒有方法算 KL 散度。


雖然遇到困難,但還是要想辦法解決的。GAN 的思路很直接粗獷:既然沒有合適的度量,那我乾脆把這個度量也用神經網路訓練出來吧


就這樣,WGAN 就誕生了,詳細過程請參考互懟的藝術:從零直達 WGAN-GP。而 VAE 則使用了一個精緻迂迴的技巧。

VAE慢談


這一部分我們先回顧一般教程是怎麼介紹 VAE 的,然後再探究有什麼問題,接著就自然地發現了 VAE 真正的面目。


經典回顧


首先我們有一批資料樣本 {X1,…,Xn},其整體用 X 來描述,我們本想根據 {X1,…,Xn} 得到 X 的分佈 p(X),如果能得到的話,那我直接根據 p(X) 來取樣,就可以得到所有可能的 X 了(包括 {X1,…,Xn} 以外的),這是一個終極理想的生成模型了。


當然,這個理想很難實現,於是我們將分佈改一改:


這裡我們就不區分求和還是求積分了,意思對了就行。此時 p(X|Z) 就描述了一個由 Z 來生成 X 的模型,而我們假設 Z 服從標準正態分佈,也就是 p(Z)=N(0,I)。如果這個理想能實現,那麼我們就可以先從標準正態分佈中取樣一個 Z,然後根據 Z 來算一個 X,也是一個很棒的生成模型


接下來就是結合自編碼器來實現重構,保證有效資訊沒有丟失,再加上一系列的推導,最後把模型實現。框架的示意圖如下:

VAE的傳統理解


看出了什麼問題了嗎?如果像這個圖的話,我們其實完全不清楚:究竟經過重新取樣出來的 Zk,是不是還對應著原來的 Xk,所以我們如果直接最小化 D(X̂ k,Xk)^2(這裡 代表某種距離函式)是很不科學的,而事實上你看程式碼也會發現根本不是這樣實現的


也就是說,很多教程說了一大通頭頭是道的話,然後寫程式碼時卻不是按照所寫的文字來寫,可是他們也不覺得這樣會有矛盾。

VAE初現


其實,在整個 VAE 模型中,我們並沒有去使用 p(Z)(先驗分佈)是正態分佈的假設,我們用的是假設 p(Z|X)(後驗分佈)是正態分佈


具體來說,給定一個真實樣本 Xk,我們假設存在一個專屬於 Xk 的分佈 p(Z|Xk)(學名叫後驗分佈),併進一步假設這個分佈是(獨立的、多元的)正態分佈。


為什麼要強調“專屬”呢?因為我們後面要訓練一個生成器 X=g(Z),希望能夠把從分佈 p(Z|Xk) 取樣出來的一個 Zk 還原為 Xk


如果假設 p(Z) 是正態分佈,然後從 p(Z) 中取樣一個 Z,那麼我們怎麼知道這個 Z 對應於哪個真實的 X 呢?現在 p(Z|Xk) 專屬於 Xk,我們有理由說從這個分佈取樣出來的 Z 應該要還原到 Xk 中去


事實上,在論文 Auto-Encoding Variational Bayes 的應用部分,也特別強調了這一點:


In this case, we can let the variational approximate posterior be a multivariate Gaussian with a diagonal covariance structure:

論文中的式 (9) 是實現整個模型的關鍵,不知道為什麼很多教程在介紹 VAE 時都沒有把它凸顯出來。儘管論文也提到 p(Z) 是標準正態分佈,然而那其實並不是本質重要的。


再次強調,這時候每一個 Xk 都配上了一個專屬的正態分佈,才方便後面的生成器做還原。但這樣有多少個 X 就有多少個正態分佈了。我們知道正態分佈有兩組引數:均值 μ 和方差 σ^2(多元的話,它們都是向量)。


那我怎麼找出專屬於 Xk 的正態分佈 p(Z|Xk) 的均值和方差呢?好像並沒有什麼直接的思路。


那好吧,我就用神經網路來擬合出來。這就是神經網路時代的哲學:難算的我們都用神經網路來擬合,在 WGAN 那裡我們已經體驗過一次了,現在再次體驗到了。


於是我們構建兩個神經網路 μk=f1(Xk),logσ^2=f2(Xk) 來算它們了。我們選擇擬合 logσ^2 而不是直接擬合 σ^2,是因為 σ^2 總是非負的,需要加啟用函式處理,而擬合 logσ^2 不需要加啟用函式,因為它可正可負。


到這裡,我能知道專屬於 Xk 的均值和方差了,也就知道它的正態分佈長什麼樣了,然後從這個專屬分佈中取樣一個 Zk 出來,然後經過一個生成器得到 X̂k=g(Zk)。


現在我們可以放心地最小化 D(X̂k,Xk)^2,因為 Zk 是從專屬 Xk 的分佈中取樣出來的,這個生成器應該要把開始的 Xk 還原回來。於是可以畫出 VAE 的示意圖:

事實上,VAE 是為每個樣本構造專屬的正態分佈,然後取樣來重構。

分佈標準化


讓我們來思考一下,根據上圖的訓練過程,最終會得到什麼結果。 


首先,我們希望重構 X,也就是最小化 D(X̂k,Xk)^2,但是這個重構過程受到噪聲的影響,因為 Zk 是透過重新取樣過的,不是直接由 encoder 算出來的。


顯然噪聲會增加重構的難度,不過好在這個噪聲強度(也就是方差)透過一個神經網路算出來的,所以最終模型為了重構得更好,肯定會想盡辦法讓方差為0。


而方差為 0 的話,也就沒有隨機性了,所以不管怎麼取樣其實都只是得到確定的結果(也就是均值),只擬合一個當然比擬合多個要容易,而均值是透過另外一個神經網路算出來的。 


說白了,模型會慢慢退化成普通的 AutoEncoder,噪聲不再起作用。 


這樣不就白費力氣了嗎?說好的生成模型呢? 


別急別急,其實 VAE 還讓所有的 p(Z|X) 都向標準正態分佈看齊,這樣就防止了噪聲為零,同時保證了模型具有生成能力。


怎麼理解“保證了生成能力”呢?如果所有的 p(Z|X) 都很接近標準正態分佈 N(0,I),那麼根據定義:

這樣我們就能達到我們的先驗假設:p(Z) 是標準正態分佈。然後我們就可以放心地從 N(0,I中取樣來生成影象了。

為了使模型具有生成能力,VAE 要求每個 p(Z_X) 都向正態分佈看齊。


那怎麼讓所有的 p(Z|X) 都向 N(0,I看齊呢?如果沒有外部知識的話,其實最直接的方法應該是在重構誤差的基礎上中加入額外的 loss:

因為它們分別代表了均值 μk 和方差的對數 logσ^2,達到 N(0,I就是希望二者儘量接近於 0 了。不過,這又會面臨著這兩個損失的比例要怎麼選取的問題,選取得不好,生成的影象會比較模糊。


所以,原論文直接算了一般(各分量獨立的)正態分佈與標準正態分佈的 KL 散度 KL(N(μ,σ^2)‖N(0,I))作為這個額外的 loss,計算結果為:

這裡的 d 是隱變數 Z 的維度,而 μ(i) 和 σ_{(i)}^{2} 分別代表一般正態分佈的均值向量和方差向量的第 i 個分量。直接用這個式子做補充 loss,就不用考慮均值損失和方差損失的相對比例問題了。


顯然,這個 loss 也可以分兩部分理解:

推導


由於我們考慮的是各分量獨立的多元正態分佈,因此只需要推導一元正態分佈的情形即可,根據定義我們可以寫出:

整個結果分為三項積分,第一項實際上就是 −logσ^2 乘以機率密度的積分(也就是 1),所以結果是 −logσ^2;第二項實際是正態分佈的二階矩,熟悉正態分佈的朋友應該都清楚正態分佈的二階矩為 μ^2+σ^2;而根據定義,第三項實際上就是“-方差除以方差=-1”。所以總結果就是:

重引數技巧


最後是實現模型的一個技巧,英文名是 Reparameterization Trick,我這裡叫它做重引數吧。

▲ 重引數技巧


其實很簡單,就是我們要從 p(Z|Xk) 中取樣一個 Zk 出來,儘管我們知道了 p(Z|Xk是正態分佈,但是均值方差都是靠模型算出來的,我們要靠這個過程反過來最佳化均值方差的模型,但是“取樣”這個操作是不可導的,而取樣的結果是可導的,於是我們利用了一個事實:

所以,我們將從 N(μ,σ^2) 取樣變成了從 N(μ,σ^2) 中取樣,然後透過引數變換得到從 N(μ,σ^2) 中取樣的結果。這樣一來,“取樣”這個操作就不用參與梯度下降了,改為取樣的結果參與,使得整個模型可訓練了。


具體怎麼實現,大家把上述文字對照著程式碼看一下,一下子就明白了。

後續分析


即便把上面的所有內容都搞清楚了,面對 VAE,我們可能還存有很多疑問。


本質是什麼


VAE 的本質是什麼?VAE 雖然也稱是 AE(AutoEncoder)的一種,但它的做法(或者說它對網路的詮釋)是別具一格的。


在 VAE 中,它的 Encoder 有兩個,一個用來計算均值,一個用來計算方差,這已經讓人意外了:Encoder 不是用來 Encode 的,是用來算均值和方差的,這真是大新聞了,還有均值和方差不都是統計量嗎,怎麼是用神經網路來算的? 


事實上,我覺得 VAE 從讓普通人望而生畏的變分和貝葉斯理論出發,最後落地到一個具體的模型中,雖然走了比較長的一段路,但最終的模型其實是很接地氣的。


它本質上就是在我們常規的自編碼器的基礎上,對 encoder 的結果(在VAE中對應著計算均值的網路)加上了“高斯噪聲”,使得結果 decoder 能夠對噪聲有魯棒性;而那個額外的 KL loss(目的是讓均值為 0,方差為 1),事實上就是相當於對 encoder 的一個正則項,希望 encoder 出來的東西均有零均值。 


那另外一個 encoder(對應著計算方差的網路)的作用呢?它是用來動態調節噪聲的強度的。


直覺上來想,當 decoder 還沒有訓練好時(重構誤差遠大於 KL loss),就會適當降低噪聲(KL loss 增加),使得擬合起來容易一些(重構誤差開始下降)


反之,如果 decoder 訓練得還不錯時(重構誤差小於 KL loss),這時候噪聲就會增加(KL loss 減少),使得擬合更加困難了(重構誤差又開始增加),這時候 decoder 就要想辦法提高它的生成能力了

▲ VAE的本質結構

說白了,重構的過程是希望沒噪聲的,而 KL loss 則希望有高斯噪聲的,兩者是對立的。所以,VAE 跟 GAN 一樣,內部其實是包含了一個對抗的過程,只不過它們兩者是混合起來,共同進化的


從這個角度看,VAE 的思想似乎還高明一些,因為在 GAN 中,造假者在進化時,鑒別者是安然不動的,反之亦然。當然,這隻是一個側面,不能說明 VAE 就比 GAN 好。


GAN 真正高明的地方是:它連度量都直接訓練出來了,而且這個度量往往比我們人工想的要好(然而 GAN 本身也有各種問題,這就不展開了)。

正態分佈?


對於 p(Z|X) 的分佈,讀者可能會有疑惑:是不是必須選擇正態分佈?可以選擇均勻分佈嗎?


首先,這個本身是一個實驗問題,兩種分佈都試一下就知道了。但是從直覺上來講,正態分佈要比均勻分佈更加合理,因為正態分佈有兩組獨立的引數:均值和方差,而均勻分佈只有一組。


前面我們說,在 VAE 中,重構跟噪聲是相互對抗的,重構誤差跟噪聲強度是兩個相互對抗的指標,而在改變噪聲強度時原則上需要有保持均值不變的能力,不然我們很難確定重構誤差增大了,究竟是均值變化了(encoder的鍋)還是方差變大了(噪聲的鍋)


而均勻分佈不能做到保持均值不變的情況下改變方差,所以正態分佈應該更加合理。 


變分在哪裡


還有一個有意思(但不大重要)的問題是:VAE 叫做“變分自編碼器”,它跟變分法有什麼聯絡?在VAE 的論文和相關解讀中,好像也沒看到變分法的存在? 


其實如果讀者已經承認了 KL 散度的話,那 VAE 好像真的跟變分沒多大關係了,因為 KL 散度的定義是:

如果是離散機率分佈就要寫成求和,我們要證明:已機率分佈 p(x)(或固定q(x))的情況下,對於任意的機率分佈 q(x)(或 p(x)),都有 KLp(x)q(x))≥0,而且只有當p(x)=q(x)時才等於零


因為 KL(p(x)‖q(x))實際上是一個泛函,要對泛函求極值就要用到變分法,當然,這裡的變分法只是普通微積分的平行推廣,還沒涉及到真正複雜的變分法。而 VAE 的變分下界,是直接基於 KL 散度就得到的。所以直接承認了 KL 散度的話,就沒有變分的什麼事了。


一句話,VAE 的名字中“變分”,是因為它的推導過程用到了 KL 散度及其性質。

條件VAE


最後,因為目前的 VAE 是無監督訓練的,因此很自然想到:如果有標簽資料,那麼能不能把標簽資訊加進去輔助生成樣本呢?


這個問題的意圖,往往是希望能夠實現控制某個變數來實現生成某一類影象。當然,這是肯定可以的,我們把這種情況叫做 Conditional VAE,或者叫 CVAE(相應地,在 GAN 中我們也有個 CGAN)。


但是,CVAE 不是一個特定的模型,而是一類模型,總之就是把標簽資訊融入到 VAE 中的方式有很多,目的也不一樣。這裡基於前面的討論,給出一種非常簡單的 VAE。

▲ 一個簡單的CVAE結構


在前面的討論中,我們希望 X 經過編碼後,Z 的分佈都具有零均值和單位方差,這個“希望”是透過加入了 KL loss 來實現的。


如果現在多了類別資訊 Y我們可以希望同一個類的樣本都有一個專屬的均值 μ^Y(方差不變,還是單位方差),這個 μ^Y 讓模型自己訓練出來


這樣的話,有多少個類就有多少個正態分佈,而在生成的時候,我們就可以透過控制均值來控制生成影象的類別


事實上,這樣可能也是在 VAE 的基礎上加入最少的程式碼來實現 CVAE 的方案了,因為這個“新希望”也只需透過修改 KL loss 實現:

下圖顯示這個簡單的 CVAE 是有一定的效果的,不過因為 encoder 和 decoder 都比較簡單(純 MLP),所以控制生成的效果不盡完美。


用這個 CVAE 控制生成數字 9,可以發現生成了多種樣式的 9,並且慢慢向 7 過渡,所以初步觀察這種 CVAE 是有效的。


更完備的 CVAE 請讀者自行學習了,最近還出來了 CVAE 與 GAN 結合的工作 CVAE-GAN: Fine-Grained Image Generation through Asymmetric Training,模型套路千變萬化。

程式碼


我把 Keras 官方的 VAE 程式碼複製了一份,然後微調並根據前文內容添加了中文註釋,也把最後說到的簡單的 CVAE 實現了一下,供讀者參考。


程式碼:https://github.com/bojone/vae

終點站


磕磕碰碰,又到了文章的終點了。不知道講清楚了沒,希望大家多提點意見。


總的來說,VAE 的思路還是很漂亮的。倒不是說它提供了一個多麼好的生成模型(因為事實上它生成的影象並不算好,偏模糊),而是它提供了一個將機率圖跟深度學習結合起來的一個非常棒的案例,這個案例有諸多值得思考回味的地方。

點選以下標題檢視相關內容: 

#榜 單 公 布 #


2017年度最值得讀的AI論文 | NLP篇 · 評選結果公佈

2017年度最值得讀的AI論文 | CV篇 · 評選結果公佈


          

 我是彩蛋

 解鎖新功能:熱門職位推薦!

PaperWeekly小程式升級啦

今日arXiv√猜你喜歡√熱門職位

找全職找實習都不是問題

 

 解鎖方式 

1. 識別下方二維碼開啟小程式

2. 用PaperWeekly社群賬號進行登陸

3. 登陸後即可解鎖所有功能

 職位釋出 

請新增小助手微信(pwbot02)進行諮詢

 

長按識別二維碼,使用小程式

*點選閱讀原文即可註冊

關於PaperWeekly


PaperWeekly 是一個推薦、解讀、討論、報道人工智慧前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號後臺點選「交流群」,小助手將把你帶入 PaperWeekly 的交流群裡。

▽ 點選 | 閱讀原文 | 進入作者部落格

贊(0)

分享創造快樂