經典論文復現 | LSGAN:最小二乘生成對抗網絡

過去幾年發表於各大 AI 頂會論文提出的 400 多種算法中,公開算法代碼的僅佔 6%,其中三分之一的論文做者分享了測試數據,約 54% 的分享包含「僞代碼」。這是今年 AAAI 會議上一個嚴峻的報告。 人工智能這個蓬勃發展的領域正面臨着實驗重現的危機,就像實驗重現問題過去十年來一直困擾着心理學、醫學以及其餘領域同樣。最根本的問題是研究人員一般不共享他們的源代碼。 node

可驗證的知識是科學的基礎,它事關理解。隨着人工智能領域的發展,打破不可復現性將是必要的。爲此,PaperWeekly 聯手百度 PaddlePaddle 共同發起了本次論文有獎復現,咱們但願和來自學界、工業界的研究者一塊兒接力,爲 AI 行業帶來良性循環。算法

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

做者丨文永亮網絡

學校丨華南理工大學多線程

研究方向丨目標檢測、圖像生成架構

筆者此次選擇復現的是 Least Squares Generative Adversarial Networks,也就是 LSGANs機器學習

640?

近幾年來 GAN 是十分火熱的,由 Goodfellow 在 14 年發表論文 Generative Adversarial Nets [1] 開山之做以來,生成式對抗網絡一直都備受機器學習領域的關注,這種兩人零和博弈的思想十分有趣,充分體現了數學的美感。從 GAN 到 WGAN[2] 的優化,再到本文介紹的 LSGANs,再到最近很火的 BigGAN [3],能夠說生成式對抗網絡的魅力無窮,並且它的用處也是很是奇妙,現在還被用在例如無負樣本的狀況下如何訓練分類器,例如 AnoGAN [4]。 ide

LSGANs 這篇經典的論文主要工做是把交叉熵損失函數換作了最小二乘損失函數,這樣作做者認爲改善了傳統 GAN 的兩個問題,即傳統 GAN 生成的圖片質量不高,並且訓練過程十分不穩定。函數

LSGANs 試圖使用不一樣的距離度量來構建一個更加穩定並且收斂更快的,生成質量高的對抗網絡。可是我看過 WGAN 的論文以後分析這一損失函數,其實並不符合 WGAN 做者的分析。在下面我會詳細分析一下爲何 LSGANs 其實並無那麼好用。學習

論文復現代碼: 測試

http://aistudio.baidu.com/aistudio/#/projectdetail/25767

LSGANs的優勢

咱們知道傳統 GAN 生成的圖片質量不高,傳統的 GANs 使用的是交叉熵損失(sigmoid cross entropy)做爲判別器的損失函數。 

在這裏說一下我對交叉熵的理解,有兩個分佈,分別是真實分佈 p 和非真實分佈 q。

信息熵是640,就是按照真實分佈 p 這樣的樣本空間表達能力強度的相反值,信息熵越大,不肯定性越大,表達能力越弱,咱們記做 H(p)。 交叉熵就是640,能夠理解爲按照不真實分佈 q 這樣的樣本空間表達能力強度的相反值,記做 H(p,q)。 

KL 散度就是 D(p||q) = H(p,q) - H(p),它表示的是兩個分佈的差別,由於真實分佈 p 的信息熵固定,因此通常由交叉熵來決定,因此這就是爲何傳統 GAN 會採用交叉熵的緣故,論文也證實了 GAN 損失函數與 KL 散度的關係。 

咱們知道交叉熵通常都是拿來作邏輯分類的,而像最小二乘這種通常會用在線性迴歸中,這裏爲何會用最小二乘做爲損失函數的評判呢? 

使用交叉熵雖然會讓咱們分類正確,可是這樣會致使那些在決策邊界被分類爲真的、可是仍然遠離真實數據的假樣本(即生成器生成的樣本)不會繼續迭代,由於它已經成功欺騙了判別器,更新生成器的時候就會發生梯度彌散的問題。 

論文指出最小二乘損失函數會對處於判別成真的那些遠離決策邊界的樣本進行懲罰,把遠離決策邊界的假樣本拖進決策邊界,從而提升生成圖片的質量。做者用下圖詳細表達了這一說法:

640

咱們知道傳統 GAN 的訓練過程十分不穩定,這很大程度上是由於它的目標函數,尤爲是在最小化目標函數時可能發生梯度彌散,使其很難再去更新生成器。而論文指出 LSGANs 能夠解決這個問題,由於 LSGANs 會懲罰那些遠離決策邊界的樣本,這些樣本的梯度是梯度降低的決定方向。

論文指出由於傳統 GAN 辨別器 D 使用的是 sigmoid 函數,而且因爲 sigmoid 函數飽和得十分迅速,因此即便是十分小的數據點 x,該函數也會迅速忽略樣本 x 到決策邊界 w 的距離。這就意味着 sigmoid 函數本質上不會懲罰遠離決策邊界的樣本,而且也說明咱們知足於將 x 標註正確,所以辨別器 D 的梯度就會很快地降低到 0。

咱們能夠認爲,交叉熵並不關心距離,而是僅僅關注因而否正確分類。正如論文做者在下圖中所指出的那樣,(a)圖看到交叉熵損失很容易就達到飽和狀態,而(b)圖最小二乘損失只在一點達到飽和,做者認爲這樣訓練會更加穩定。

640

LSGANs的損失函數

傳統 GAN 的損失函數:

640

LSGANs 的損失函數:

640

其中 G 爲生成器(Generator),D 爲判別器(Discriminator),z 爲噪音,它能夠服從歸一化或者高斯分佈,640爲真實數據 x 服從的機率分佈,640爲 z 服從的機率分佈。640爲指望值,640同爲指望值。

def generator(z, name="G"):    with fluid.unique_name.guard(name+'_'):        fc1 = fluid.layers.fc(input = z, size = 1024)        fc1 = fluid.layers.fc(fc1, size = 128 * 7 * 7)        fc1 = fluid.layers.batch_norm(fc1,act = 'tanh')        fc1 = fluid.layers.reshape(fc1, shape=(-1, 128, 7, 7))        conv1 = fluid.layers.conv2d(fc1, num_filters = 4*64,                                    filter_size=5, stride=1,                                     padding=2, act='tanh')        conv1 = fluid.layers.reshape(conv1, shape=(-1,64,14,14))        conv2 = fluid.layers.conv2d(conv1, num_filters = 4*32,                                     filter_size=5, stride=1,                                    padding=2, act='tanh')        conv2 = fluid.layers.reshape(conv2, shape=(-1,32,28,28))        conv3 = fluid.layers.conv2d(conv2, num_filters = 1,                                     filter_size=5, stride=1,                                    padding=2,act='tanh')#         conv3 = fluid.layers.reshape(conv3, shape=(-1,1,28,28))        print("conv3",conv3)        return conv3
    with fluid.unique_name.guard(name+'_'):
        fc1 = fluid.layers.fc(input = z, size = 1024)
        fc1 = fluid.layers.fc(fc1, size = 128 * 7 * 7)
        fc1 = fluid.layers.batch_norm(fc1,act = 'tanh')
        fc1 = fluid.layers.reshape(fc1, shape=(-112877))


        conv1 = fluid.layers.conv2d(fc1, num_filters = 4*64,
                                    filter_size=5, stride=1, 
                                    padding=2, act='tanh')
        conv1 = fluid.layers.reshape(conv1, shape=(-1,64,14,14))

        conv2 = fluid.layers.conv2d(conv1, num_filters = 4*32, 
                                    filter_size=5, stride=1,
                                    padding=2, act='tanh')
        conv2 = fluid.layers.reshape(conv2, shape=(-1,32,28,28))

        conv3 = fluid.layers.conv2d(conv2, num_filters = 1, 
                                    filter_size=5, stride=1,
                                    padding=2,act='tanh')
#         conv3 = fluid.layers.reshape(conv3, shape=(-1,1,28,28))
        print("conv3",conv3)
        return conv3

 生成器代碼展現

def discriminator(image, name="D"):    with fluid.unique_name.guard(name+'_'):        conv1 = fluid.layers.conv2d(input=image, num_filters=32,                                    filter_size=6, stride=2,                                    padding=2)        conv1_act = fluid.layers.leaky_relu(conv1)        conv2 = fluid.layers.conv2d(conv1_act, num_filters=64,                                     filter_size=6, stride=2,                                    padding=2)        conv2 = fluid.layers.batch_norm(conv2)        conv2_act = fluid.layers.leaky_relu(conv2)        fc1 = fluid.layers.reshape(conv2_act, shape=(-1,64*7*7))        fc1 = fluid.layers.fc(fc1, size=512)        fc1_bn = fluid.layers.batch_norm(fc1)        fc1_act = fluid.layers.leaky_relu(fc1_bn)        fc2 = fluid.layers.fc(fc1_act, size=1)        print("fc2",fc2)        return fc2
    with fluid.unique_name.guard(name+'_'):
        conv1 = fluid.layers.conv2d(input=image, num_filters=32,
                                    filter_size=6, stride=2,
                                    padding=2)
        conv1_act = fluid.layers.leaky_relu(conv1)

        conv2 = fluid.layers.conv2d(conv1_act, num_filters=64, 
                                    filter_size=6, stride=2,
                                    padding=2)
        conv2 = fluid.layers.batch_norm(conv2)
        conv2_act = fluid.layers.leaky_relu(conv2)

        fc1 = fluid.layers.reshape(conv2_act, shape=(-1,64*7*7))
        fc1 = fluid.layers.fc(fc1, size=512)
        fc1_bn = fluid.layers.batch_norm(fc1)
        fc1_act = fluid.layers.leaky_relu(fc1_bn)

        fc2 = fluid.layers.fc(fc1_act, size=1)
        print("fc2",fc2)
        return fc2

 判別器代碼展現

做者提出了兩種 abc 的取值方法: 

1. 使 b - c = 1,b - a = 2,例如 a = -1,b = 1,c = 0:

640

2. 使 c = b,用 0-1 二元標籤,咱們能夠獲得:

640

做者在文獻中有詳細推倒過程,詳細說明了 LSGAN 與 f 散度之間的關係,這裏簡述一下。

經過對下式求一階導可獲得 D 的最優解:

640

代入:

640

其中另加項640並不影響640的值,由於它不包含參數 G。

最後咱們設 b - c = 1,b - a =2 就能夠獲得:

640

其中640就是皮爾森卡方散度。

LSGANs未能解決的地方

下面我會指出 LSGANs 給出的損失函數到底符不符合 WGAN 前做的理論。關於 WGAN 前做及 WGAN 論文的分析能夠參考本文 [5]

上面咱們指出了 D 的最優解爲公式(5),咱們最經常使用的設 a=-1,b=1,c=0 能夠得出:

640

把最優判別器帶入上面加附加項的生成器損失函數能夠表示爲:

640

也就是優化上面說的皮爾森卡方散度,其實皮爾森卡方散度和 KL 散度、JS 散度有同樣的問題,根據 WGAN 給出的理論,下面用 P1,P2 分別表示640640

當 P1 與 P2 的支撐集(support)是高維空間中的低維流形(manifold)時,P1 與 P2 重疊部分測度(measure)爲 0 的機率爲 1。也就是 P1 和 P2 不重疊或重疊部分可忽略的可能性很是大。

對於數據點 x,只可能發生以下四種狀況:

1. P1(x)=0,P2(x)=0

2. P1(x)!=0,P2(x)!=0

3. P1(x)=0,P2(x)!=0

4. P1(x)!=0,P2(x)=0

能夠想象成下面這幅圖,假設 P1(x) 分佈就是 AB 線段,P2(x) 分佈就是 CD 線段,數據點要麼在兩條線段的其中一條,要麼都不在,同時在兩條線段上的可能性忽略不計。

640

狀況 1 是沒有意義的,而狀況 2 因爲重疊部分可忽略的可能性很是大因此對計算損失貢獻爲 0,狀況 3 能夠算出 D*=-1,損失是個定值 1,狀況 4 相似。

因此咱們能夠得出結論,當 P1 和 P2 不重疊或重疊部分可忽略的可能性很是大時,當判別器達到最優時,生成器仍然是不迭代的,由於此時損失是定值,提供的梯度仍然爲 0。同時咱們也能夠從另外一個角度出發,WGAN 的 Wasserstein 距離能夠變換以下:

640

它要求函數 f 要符合 Lipschitz 連續,但是最小二乘損失函數是不符合的,他的導數是沒有上界的。因此結論就是 LSGANs 其實仍是未能解決判別器足夠優秀的時候,生成器仍是會發生梯度彌散的問題。

兩種模型架構和訓練

模型的結構

做者也提出了兩類架構:

第一種處理類別少的狀況,例如 MNIST、LSUN。網絡設計以下:

640

第二類處理類別特別多的情形,其實是個條件版本的 LSGAN。針對手寫漢字數據集,有 3740 類,提出的網絡結構以下:

640

訓練數據

論文中使用了不少場景的數據集,而後比較了傳統 GANs 和 LSGANs 的穩定性,最後還經過訓練 3740 個類別的手寫漢字數據集來評價 LSGANs。

640

 本文使用的數據集列表

在 LSUN 和 HWDB1.0 的這兩個數據集上使用 LSGANs 的效果圖以下,其中 LSUN 使用了裏面的 bedroom, kitchen, church, dining room 和 conference room 五個場景,bedroom 場景還對比了 DCGANs 和 EBGANs 的效果在圖 5 中,能夠觀察到 LSGANs 生成的效果要比那兩種的效果好。

640

640

圖 7 則體現了 LSGANs 和傳統 GANs 生成的圖片對比。

640

經過實驗觀察,做者發現 4 點技巧: 

1. 生成器 G 帶有 batch normalization 批處理標準化(如下簡稱 BN)而且使用 Adam 優化器的話,LSGANs 生成的圖片質量好,可是傳統 GANs 歷來沒有成功學習到,會出現 mode collapse 現象;

2. 生成器 G 和判別器 D 都帶有 BN 層,而且使用 RMSProp 優化器處理,LSGANs 會生成質量比 GANs 高的圖片,而且 GANs 會出現輕微的 mode collapse 現象;

3. 生成器 G 帶有 BN 層而且使用 RMSProp 優化器,生成器 G 判別器 D 都帶有 BN 層而且使用 Adam 優化器時,LSGANs 與傳統 GANs 有着類似的表現;

4. RMSProp 的表現比 Adam 要穩定,由於傳統 GANs 在 G 帶有 BN 層時,使用 RMSProp 優化能夠成功學習,可是使用 Adam 優化卻不行。

下面是使用 LSGANs 和 GANs 學習混合高斯分佈的數據集,下圖展示了生成數據分佈的動態結果,能夠看到傳統 GAN 在 Step 15k 時就會發生 mode collapse 現象,但 LSGANs 很是成功地學習到了混合高斯分佈。

640

論文具體實現

筆者使用了 MNIST 數據集進行實驗,具體實現效果以下:

LSGANs:

640

GAN:

640

從本次用 MNIST 數據訓練的效果來看,LSGANs 生成的效果彷佛是比 GAN 的要清晰高質量一些。

總結

LSGANs 是對 GAN 的一次優化,從實驗的狀況中,筆者也發現了一些奇怪的現象。我原本是參考論文把判別器 D 的損失值,按真假兩種 loss 加起來一併放入 Adam 中優化,可是不管如何都學習不成功,梯度仍是彌散了,最後把 D_fake_loss 和 D_real_loss 分爲兩個 program,放入不一樣的 Adam 中優化判別器D 的參數才達到預期效果。

這篇論文中的思想是很是值得借鑑的,從最小二乘的距離的角度考量,並非判別器分類以後就完事了,可是 LSGANs 其實仍是未能解決判別器足夠優秀的時候,生成器梯度彌散的問題。

關於PaddlePaddle

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

筆者反饋:幫助文檔有點少,並且我原本就直接寫好了想改爲使用 GPU 運算,沒找到怎麼改;

PaddlePaddle團隊:關於如何使用 GPU 運行,能夠看下執行器 Executor(單 GPU 或單線程 CPU 執行器)或 ParallelExecutor(多 GPU 或多線程 CPU 執行器,也能夠單 GPU/線程 CPU 執行)的文檔,前者指定 place 爲 CUDAPlace,後者接口有個 use_cuda,具體請參考文檔。也能夠看 models repo 例子,好比 image_classification 或 text_classification 的例子。 

筆者反饋:Program 這個概念有點新穎,一個模型能夠有多個 Program,可是我實現的 GAN 能夠只用一個,也能夠分別放進三個 Program,沒有太瞭解到 Program 這個概念的優越之處,我仍是像計算圖那樣使用了,官方也沒給出與 TensorFlow 的對比。

PaddlePaddle團隊:關於 Program 設計能夠參考官方文檔。這裏提一點,在用戶使用的直觀感覺中和 TensorFlow graph 不一樣的是,凡是放在一個 Program 裏 op,只要運行該 Program,這些 op 就都會執行;而 TensorFlow,指定一個 variable,只運行以該 variable 爲葉子節點的 graph,其餘多餘 node 不執行,這是最大的用戶感覺到的區別。 

至於一個 Program 仍是多個 Program,看用戶使用需求而定,多個 Program 時要注意的東西就比較多,例如是否要參數共享等,固然運行屢次的時間代價也稍多。 若是是 GAN 也能夠參考 models repo 的例子。

小道消息:據說全新版本的 PaddlePaddle 已於今日發佈哦。

參考文獻

[1]. I. Goodfellow, J. Pouget-Abadie, M. Mirza, B. Xu, D. Warde-Farley, S. Ozair, A. Courville, and Y. Bengio, 「Generative adversarial nets,」 in Advances in Neural Information Processing Systems (NIPS), pp. 2672–2680, 2014.

[2]. M. Arjovsky, S. Chintala, and L. Bottou. Wasserstein GAN. arXiv preprint arXiv:1701.07875, 2017.

[3]. Andrew Brock, Jeff Donahue and Karen Simonyan. Large Scale GAN Training for High Fidelity Natural Image Synthesis. arXiv:1809.11096, 2018.

[4]. Schlegl, Thomas, et al. "Unsupervised Anomaly Detection with Generative Adversarial Networks to Guide Marker Discovery." arXiv preprint arXiv:1703.05921 (2017).

[5]. https://zhuanlan.zhihu.com/p/25071913?from_voters_page=true

640?

文章來源:PaperWeekly

640?wx_fmt=jpeg

本文同步分享在 博客「飛槳PaddlePaddle」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索