男女速變 —— GAN

本篇還是按上一篇的規矩,先上圖:
首先看漢子變女神:
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
再來看美女變猛男:

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

最後,向泰坦尼克CP致敬:
在這裏插入圖片描述在這裏插入圖片描述
讓我穿越命運之門
尋找另一個你的眼神
如夢幻中的星辰
飄落那冰冷紅塵
璀璨色彩
照耀飄飄長髮
暮色陰沉
雕刻時光裏的皺紋

我要是再寫得飄逸一點,也能進梨花詩派吧。。。

這個效果明顯比上一篇的VAE要好得多,全部代碼在github/face_gan(Starred by AttGAN作者),和VAE一樣使用tensorflow和tensorflow內置的Keras,項目的pictures目錄中有更多的樣例圖片。

那些不是GAN的GAN說的,其實face_gan也和StarGAN、AttGAN一樣不能算一個嚴格意義上的GAN,但因爲一說GAN大家就明白怎麼回事,所以這個GitHub項目和本文都是用GAN的名字。

毋庸諱言,face_gan嚴重的參考了StarGANAttGAN,畢竟任務目標是差不多的。下面我會介紹這個項目一些具體的技術細節,希望讀者能通過本文更深入的瞭解GAN、卷積網絡和深度學習訓練。

網絡結構

face_gan的生成器基本使用了StarGAN的生成器網絡結構(而StarGAN是從CycleGAN那裏學來的,呵呵~):
在這裏插入圖片描述
兩次Down Sampling後直接接殘差塊,然後再兩次Up Sampling恢復原始尺寸。分辨率不降太多,保持足夠多的空間位置信息,對提高圖像質量很有幫助,網絡也更容易訓練。不過face_gan和StarGAN有三個區別:

  • StarGAN使用了6個Residual Block,face_gan使用9個。之所以增加3個是因爲:StarGAN輸入128x128的圖像,而face_gan輸入160x160(最早想用facenet做Perceptual Loss作爲重構損失,facenet輸入160x160,不想resize圖像所以face_gan的輸入就也用160x160了。後來決定直接用pixel的L1做重構損失,但輸入尺寸就沒再改。),是StarGAN的四分之五倍,增加3層Residual Block相應的增大網絡的感受野。從結果看,增加的殘差塊確實對圖像質量有一定的提高。
  • StarGAN和AttGAN的上採樣均採用轉置卷積,實測感覺Upsampling2D+Conv2D的效果更好,所以採用Upsampling2D+Conv2D。
  • StarGAN的Residual Block的實現其實並不標準,分支最後沒加ReLU激活,應該是爲了避免輸出只有大於0的部分。face_gan使用標準的pre-activation方式來保證更廣的輸出範圍。

StarGAN和AttGAN的discriminator網絡結構採用的都是最通用形式,對抗損失/訓練方式都使用WGAN-GP(所以不能用Batch Normalization),face_gan也一樣。只不過StarGAN的discriminator沒用任何Normalization,face_gan的discriminator用的是Instance Normalization(新版的Keras不能直接支持Layer Normalization了,所以我也就沒試),實測對訓練的穩定和收斂還是有幫助的。

另外,face_gan所有的初始化都用的是glorot_normal,Keras缺省glorot_uniform,感覺glorot_normal初始化的網絡更不容易陷入病態。

損失和訓練方法

StarGANAttGANface_gan本質上都是使用相同的組成部分構成損失,但訓練方式略有不同。

StarGAN如下圖:在這裏插入圖片描述
AttGAN如下圖:
在這裏插入圖片描述
它們都使用原圖XaX^a訓練分類,使用轉換屬性後的Xb^X^{\hat b}訓練生成器轉換屬性的能力,都使用像素的L1距離作爲重構損失。face_gan最開始想使用facenet做Perceptual Loss,但感覺並不好用,有可能Perceptual Loss的約束太強,不利於屬性轉換。而直接使用像素損失的效果也不錯,還更加節省運算和內存,何樂而不爲呢?

StarGAN和AttGAN主要的區別是StarGAN使用G(G(Xa,b),a)G(G(X^a, b), a)訓練重構損失,而AttGAN使用G(Xa,a)G(X^a, a)訓練重構損失。另外StarGAN把目標屬性b和XaX^a一起輸入G,而AttGAN在G的瓶頸處輸入b。

face_gan看上去是上面兩者的混合:
在這裏插入圖片描述
然而face_gan並不是爲了和這兩者有所區別而故意把它們的訓練流程混合在一起。

實際上,StarGAN使用G(G(Xa,b),a)G(G(X^a, b), a)訓練重構損失存在問題。在這個流程裏,把XaX^a轉換成Xb^X^{\hat b}時,無法保證XaX^a的原始信息完全包含在Xb^X^{\hat b}的像素裏,當Xb^X^{\hat b}再次通過生成器輸出Xa^X^{\hat a}時,卻要求Xa^X^{\hat a}儘可能還原XaX^a,這本身就是不可能完成的任務(或者說貝葉斯誤差不可能爲零)。舉例來說,假設第一步把一個金髮美女的照片和屬性“黑髮”輸入生成器,這時生成器輸出的是一張黑髮美女照片,第二步則是把這幅黑髮美女的照片和屬性“金髮”輸入生成器,輸出金髮美女照片,並且希望這張照片和原始照片儘量相同,可是在第二步中,生成器的輸入只有一張黑髮美女的照片,現在告訴它要轉換成金髮,它怎麼能知道有多金?!爲了達到重構損失最小,唯一合理的就是輸出一個金髮的平均顏色。StarGAN論文中Figure 8的樣例圖片(應該是使用實際生成的樣圖)就展現了這種情況,棕發美女的還原圖髮色和原來是不一樣的!StarGAN這樣做是因爲CycleGAN就是這樣做的,而它學習了CycleGAN的方法。所以實際上CycleGAN也一樣有這個貝葉斯誤差,但是StarGAN的任務場景和CycleGAN不一樣,它並不一定要使用CycleGAN的這種cycle consistency loss,而只需要使用AttGAN的方式就可以了。

歸根結底,屬性是一個非常抽象的信息,實際圖像中包含的複雜的細節信息可以歸納成某種屬性,但從一個屬性還原出原始的細節是不可能的。StarGAN這個問題的結果是導致/強迫它做屬性轉換時儘可能的保留原始圖像的信息,儘可能少的修改原圖。它在做男女轉換時都非常微妙,頭髮絕對不動,比如下面這兩張圖:
在這裏插入圖片描述在這裏插入圖片描述
這是face_gan把G(Xa,a)G(X^a, a)改成G(G(Xa,b),a)G(G(X^a, b), a)用於重構損失,訓練8個epochs(從第5個epoch開始做學習率衰減)的結果,你似乎能看出生成器想動頭髮又不敢動,生怕還原不回去的掙扎的樣子。。。

所以face_gan採用AttGAN的方式訓練重構損失。這種方式下,重構損失可以最大程度的幫助保留原圖的信息,屬性轉換時又可以無拘無束的放飛自我~~

但是face_gan並沒有像AttGAN一樣在生成器的瓶頸處輸入目標屬性,主要是覺得這樣似乎沒有必要,屬性在網絡的開始就加進去,暗示着網絡從一開始就朝着屬性轉換這個目標努力。實際上,這個決定對最終的效果應該沒有什麼影響。AttGAN強調編碼器解碼器的概念,所以它把屬性插入到網絡瓶頸處輸入,這種結構可以做到不同人臉先融合再編輯屬性,不過在它的論文裏沒有提。

face_gan和StarGAN實現/AttGAN實現在訓練方法上另一個不同的地方是,StarGAN和AttGAN都是用類似下面的方式(參見StarGAN/solver.py)隨機生成目標屬性:

# Generate target domain labels randomly.
rand_idx = torch.randperm(label_org.size(0))
label_trg = label_org[rand_idx]

我猜測之所以使用這種方式,一個很可能的原因是:它們都是訓練網絡進行多屬性轉換,而很多屬性在數據集裏是數據不平衡的,比如Bald這個標籤,大部分人臉應該都不是禿子。上面的代碼保證了屬性轉換任務訓練的平衡性,雖然訓練量會縮小到相對少的標籤數量。
另一個原因也許是它們想降低訓練的難度,在一次訓練中,只有一部分屬性被要求轉換。

但因爲face_gan只專注於男女轉換這一項任務,所以這兩個原因都不適合,CelebA裏男女基本各半,不存在不平衡問題。因此face_gan每次訓練都做相反的屬性轉換:

target_label = 1 - label

需要說明的是做多屬性轉換其實並沒有什麼技術上的障礙,目前只做性別轉換主要是爲了控制項目範圍,並且男女轉換在所有屬性轉換裏無疑是最有趣的。

GAN訓練中的其他超參數

要實現人臉屬性轉換這個目標,引入對抗訓練是必需的。單靠分類約束,根本無法生成滿意的效果。這一點AttGAN的論文也提到過,沒有對抗,生成圖像基本就相當於對抗攻擊樣本,可以騙過分類器,但人眼基本看不出圖像產生了什麼變化。

face_gan/face_adversarial_translator.py把一些可調的超參數或者權重都放在文件開頭,方便調整,但其實缺省的數值基本就都是最優的。下面對一些超參數進行一下說明:

  • 優化器:WGAN的論文建議GAN不使用帶動量的優化器,可能是GAN的訓練過程中優化的方向要經常改變,不像一般的網絡會向一個固定的方向收斂。所以動量反而對GAN的訓練有損害。加入梯度懲罰後可以用Adam了,不過用的時候基本都把β1\beta1設成0.5,也就是隻保留很近的幾次動量。StarGAN和AttGAN這樣做,我在face_gan裏也毫不猶豫的這樣做 ?
  • batch_size:很多任務都需要適當的引入方差,face_gan裏這個最優值是16,32或更大反而不好。
  • 學習率衰減:因爲face_gan的網絡結構是模仿StarGAN的,所以學習率衰減也使用StarGAN式的線性衰減。
  • 重構損失權重 lambda_rec:這項非常重要,前面提過StarGAN重構損失的訓練方式會使屬性轉換後的圖像只有一些微妙/微小的變化,如果重構損失權重設成100,即使不使用它的訓練方式,也會有同樣的效果。我希望屬性轉換能有一些生動的變化,所以儘管設成100會提高生成圖像的清晰度,仍舊把這項權重設成10。
  • 其它權重:lambda_cls_gen和lambda_g_w可以一起調整,單調lambda_cls_gen可能會導致訓練不穩。因爲生成圖像的屬性類別和它的真實度有競爭/對抗的關係。兩者一起提高會生成更加誇張的屬性轉換效果。

訓練經驗

深度學習項目開發中有許多重要的經驗,本文只想說非常重要的一點,那就是在訓練中提供/打印出足夠的信息。比如face_gan的損失由很多部分組成:

discriminator_loss = wasserstein_loss + lambda_gp * gradient_penalty_loss() + lambda_cls_real * real_cls_loss
generator_loss = generator_wasserstein_loss + lambda_rec * generator_rec_loss + lambda_cls_gen * gen_class_loss

各項損失不能失衡,如果只是籠統的顯示discriminator_loss和generator_loss,調整權重時就無從下手,如果訓練過程出現問題,也無法知道具體是哪一項造成的。face_gan/face_adversarial_translator.py除了會打印這些loss,還打印出了圖像準確度,分類準確度這些metrics作爲參考。

在訓練過程中進行監控尤其對GAN非常必要,一旦發現位面崩潰,就要立即撤出。。。本文的圖樣是由最後一次正式訓練得到的模型生成的,實際上在那次訓練中,epoch 4的末尾到epoch 5開始階段位面險些崩潰,我都要中止那次訓練了,它又從懸崖邊溜達了回來。因此大概浪費了一個epoch的訓練effort,不過我實在不願花時間再來一次了,所以就用這最後一次的結果,訓練的log上傳在github上,供大家參考。如果訓練真的崩潰了,不要灰心,有可能只是一次恰好陷入病態的初始化,重新來過就行 ? 當然,如果是系統性的問題,就需要根據前述的各項損失數值狀況進行分析解決了。

評價GAN圖像生成任務的效果,不僅要靠loss和metrics的數值,更需要根據生成圖像的主觀感受。因此在訓練過程中,測試一下圖像生成也非常重要。
比如前面提到的訓練崩潰,在face_gan中生成的圖像會有黑洞或白洞:
在這裏插入圖片描述
看着這些洞洞,彷彿又有了位面穿越的感覺。。。。
另外,更重要的是根據訓練中生成的圖像效果進行調整,不要等到訓練完畢再進行。

問題和改進

現在face_gan生成的圖像主要存在3個問題:

  1. 和原圖比略有模糊(如果原圖比較高清的話)
  2. 屬性轉換後,和原圖不同的頭髮的生成質量不高
  3. 女變男長髮變短髮時,消去的區域生成器不知道如何補完

可能的改進方法包括:

  • 生成高質量的頭髮需要空間上大範圍的關聯信息,使用SAGAN的技術應該可以解決,並且實現簡單,增加的運算負擔小。很有可能對提高生成圖像的整體質量都有幫助。
  • WGAN-GP使用梯度懲罰,對當前的生成數據分佈具有高度的依賴性。隨着訓練過程進行,生成的數據分佈空間會逐漸變化,就會導致這種方法對李氏連續約束的不穩定。譜歸一化直接作用於網絡參數,穩定性更高。但是譜歸一化在Keras裏實現起來比較麻煩,暫時不會考慮。

至於生成真實的圖像去填補消去的區域,主要依賴GAN能學到多少真實分佈的信息,暫時沒有有把握的思路。

人臉屬性轉換的這個系列到此就結束了,讓人工智能進行創造這個夢想纔剛剛開始實現!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章