tensorflow如何實(shí)現(xiàn)基于深度學(xué)習(xí)的圖像補(bǔ)全
作者 | Brandon Amos
編譯 | Molly 寒小陽
目錄
1. 簡介
2. 第一步:將圖像理解為一個(gè)概率分布的樣本
- 你是怎樣補(bǔ)全缺失信息的呢?
- 但是怎樣著手統(tǒng)計(jì)呢?這些都是圖像啊。
- 那么我們?cè)鯓友a(bǔ)全圖像?
3. 第二步:快速生成假圖像
- 在未知概率分布情況下,學(xué)習(xí)生成新樣本
- [ML-Heavy] 生成對(duì)抗網(wǎng)絡(luò)(Generative Adversarial Net, GAN) 的架構(gòu)
- 使用G(z)生成偽圖像
- [ML-Heavy] 訓(xùn)練DCGAN
- 現(xiàn)有的GAN和DCGAN實(shí)現(xiàn)
- [ML-Heavy] 在Tensorflow上構(gòu)建DCGANs
- 在圖片集上跑DCGAN
4. 第三步:找到用于圖像補(bǔ)全最好的偽圖像
- 使用 DCGAN 進(jìn)行圖像補(bǔ)全
- [ML-Heavy] 到 pgpg 的投影的損失函數(shù)
- [ML-Heavy] 使用tensorflow來進(jìn)行DCGAN圖像補(bǔ)全
- 補(bǔ)全圖像
5. 結(jié)論
一、簡介
內(nèi)容識(shí)別填充(譯注: Content-aware fill ,是 photoshop 的一個(gè)功能)是一個(gè)強(qiáng)大的工具,設(shè)計(jì)師和攝影師可以用它來填充圖片中不想要的部分或者缺失的部分。在填充圖片的缺失或損壞的部分時(shí),圖像補(bǔ)全和修復(fù)是兩種密切相關(guān)的技術(shù)。有很多方法可以實(shí)現(xiàn)內(nèi)容識(shí)別填充,圖像補(bǔ)全和修復(fù)。在這篇博客中,我會(huì)介紹 RaymondYeh 和 Chen Chen 等人的一篇論文,“基于感知和語境損失的圖像語義修補(bǔ)(Semantic Image Inpainting with Perceptual and ContextualLosses)”。論文在2016年7月26號(hào)發(fā)布于 arXiv 上,介紹了如何使用 DCGAN 網(wǎng)絡(luò)來進(jìn)行圖像補(bǔ)全。博文面向一般技術(shù)背景的讀者,部分內(nèi)容需要有機(jī)器學(xué)習(xí)的背景。我在相關(guān)章節(jié)標(biāo)注了[ML-Heavy]標(biāo)簽,如果你不想了解太多細(xì)節(jié),可以跳過這些章節(jié)。我們只會(huì)涉及到填充人臉圖像缺失部分的情況。博文相關(guān) Tensorflow 代碼已經(jīng)發(fā)布到 GitHub 上:bamos/dcgan-completion.tensorflow 。
圖像補(bǔ)全分為三個(gè)步驟。
- 首先,我們將圖像理解為一個(gè)概率分布的樣本。
- 基于這種理解,學(xué)習(xí)如何生成偽圖片。
- 然后我們找到最適合填充回去的偽圖片。
使用photoshop來對(duì)圖像缺失部分補(bǔ)全,并使用photoshop自動(dòng)刪除不要的部分。
下文將要介紹到的圖像補(bǔ)全。圖像的中心是自動(dòng)生成的。
這些圖像是我從 LFW 數(shù)據(jù)集中取得的一個(gè)隨機(jī)樣本。
二、第一步:將圖像理解為一個(gè)概率分布的樣本
1. 你是怎樣補(bǔ)全缺失信息的呢?
在上面的例子中,想象你正在構(gòu)造一個(gè)可以填充缺失部分的系統(tǒng)。你會(huì)怎么做呢?你覺得人類大腦是怎么做的呢?你使用了什么樣的信息呢?
在博文中,我們會(huì)關(guān)注兩種信息:
- 語境信息:你可以通過周圍的像素來推測缺失像素的信息。
- 感知信息:你會(huì)用“正常”的部分來填充,比如你在現(xiàn)實(shí)生活中或其它圖片上看到的樣子。
兩者都很重要。沒有語境信息,你怎么知道填充哪一個(gè)進(jìn)去?沒有感知信息,通過同樣的上下文可以生成無數(shù)種可能。有些機(jī)器學(xué)習(xí)系統(tǒng)看起來“正常”的圖片,人類看起來可能不太正常。
如果有一種確切的、直觀的算法,可以捕獲前文圖像補(bǔ)全步驟介紹中提到的兩種屬性,那就再好不過了。對(duì)于特定的情況,構(gòu)造這樣的算法是可行的。但是沒有一般的方法。目前最好的解決方案是通過統(tǒng)計(jì)和機(jī)器學(xué)習(xí)來得到一個(gè)近似的技術(shù)。
2. 但是怎樣著手統(tǒng)計(jì)呢?這些都是圖像啊。
為了激發(fā)大家的思考,我們從一個(gè)很好理解、可以寫成簡潔形式的概率分布開始:一個(gè)正態(tài)分布。這是正態(tài)分布的概率密度函數(shù)(PDF)。你可以將PDF理解成在輸入空間橫向移動(dòng),縱軸表示某個(gè)值出現(xiàn)的概率。(如果你感興趣,繪制這幅圖的代碼可以從 bamos/dcgan-completion.tensorflow:simple-distributions.py 下載。)
從這個(gè)分布中采樣,就可以得到一些數(shù)據(jù)。需要搞清楚的是PDF和樣本之間的聯(lián)系。
從正態(tài)分布中的采樣:
2維圖像的PDF和采樣。 PDF 用等高線圖表示,樣本點(diǎn)畫在上面。
這是1維分布,因?yàn)檩斎胫荒苎刂粋€(gè)維度。在兩個(gè)維度上也可以這么做。
在圖像和統(tǒng)計(jì)學(xué)之間,最關(guān)鍵的聯(lián)系就是,我們可以將圖像看作是從一個(gè)高維概率分布中得到的采樣。概率分布對(duì)應(yīng)的是圖像的像素。想象你在用相機(jī)拍照。得到的圖像是由有限個(gè)數(shù)的像素組成。當(dāng)你通過相機(jī)拍照的時(shí)候,你就在從這個(gè)復(fù)雜的概率分布中進(jìn)行采樣。這個(gè)概率分布就決定了我們判斷一張圖片是正常的,還是不正常的。對(duì)于圖片而言,與正態(tài)分布不同的是,我們無法得知真實(shí)的概率分布,我們只能去收集樣本。
在這篇文章中,我們會(huì)使用彩色圖像,它用 RGB顏色 表示。我們的圖像寬64像素,高64像素,所以我們的概率分布是 64⋅64⋅3≈12k 維的。
3. 那么我們?cè)鯓友a(bǔ)全圖像?
首先考慮多變量正態(tài)分布,以求得到一些啟發(fā)。給定 x=1 , 那么 y 最可能的值是什么?我們可以固定x的值,然后找到使PDF最大的 y。
在多維正態(tài)分布中,給定x,得到最大可能的y
這個(gè)概念可以很自然地推廣到圖像概率分布。我們已知一些值,希望補(bǔ)全缺失值。這可以簡單理解成一個(gè)最大化問題。我們搜索所有可能的缺失值,用于補(bǔ)全的圖像就是可能性最大的值。
從正態(tài)分布的樣本來看,只通過樣本,我們就可以得出PDF。只需挑選你喜歡的 統(tǒng)計(jì)模型,然后擬合數(shù)據(jù)即可。
然而,我們實(shí)際上并沒有使用這種方法。對(duì)于簡單分布來說,PDF很容易得出來。但是對(duì)于更復(fù)雜的圖像分布來說,就十分困難,難以處理。之所以復(fù)雜,一部分原因是復(fù)雜的條件依賴:一個(gè)像素的值依賴于圖像中其它像素的值。另外,最大化一個(gè)一般的PDF是一個(gè)非常困難和棘手的非凸優(yōu)化問題。
三、第二步:快速生成假圖像
1. 在未知概率分布情況下,學(xué)習(xí)生成新樣本
除了學(xué)習(xí)如何計(jì)算PDF之外,統(tǒng)計(jì)學(xué)中另一個(gè)成熟的想法是學(xué)習(xí)怎樣用生成模型生成新的(隨機(jī))樣本。生成模型一般很難訓(xùn)練和處理,但是后來深度學(xué)習(xí)社區(qū)在這個(gè)領(lǐng)域有了一個(gè)驚人的突破。Yann LeCun 在這篇 Quora 回答中對(duì)如何進(jìn)行生成模型的訓(xùn)練進(jìn)行了一番精彩的論述,并將它稱為機(jī)器學(xué)習(xí)領(lǐng)域近10年來最有意思的想法。
Yann LeCun 對(duì)生成對(duì)抗網(wǎng)絡(luò)的介紹
將生成對(duì)抗網(wǎng)絡(luò)類比為街機(jī)游戲。兩個(gè)網(wǎng)絡(luò)相互對(duì)抗,共同進(jìn)步。就像兩個(gè)人類在游戲中對(duì)抗一樣。
其它的深度學(xué)習(xí)方法,比如 VariationalAutoencoders(VAEs),也可以用來訓(xùn)練生成模型。在這篇博文中,我們用的是生成對(duì)抗網(wǎng)絡(luò)(GenerativeAdversarial Nets,GANs)。
2. [ML-Heavy] 生成對(duì)抗網(wǎng)絡(luò)(GenerativeAdversarial Net, GAN) 的架構(gòu)
這個(gè)想法是 IanGoodfellow 等人在2014年NeuralInformation Processing Systems (NIPS) 研討會(huì)上發(fā)表的里程碑式論文“生成對(duì)抗網(wǎng)絡(luò)”(GenerativeAdversarial Nets,GANs)中提出的。主要思想是,我們定義一個(gè)簡單、常用的分布,用pzpz表示。在下文中,我們使用pzpz來表示在-1到1閉區(qū)間上的均勻分布。我們將從分布中的一個(gè)采樣記作 z∼pzz∼pz 。若 pzpz 是五維的,我們可以通過一行python的 numpy代碼來進(jìn)行采樣:
- z =np.random.uniform(-1, 1, 5)
- array([0.77356483, 0.95258473,-0.18345086, 0.69224724, -0.34718733])
現(xiàn)在有了一個(gè)用于采樣的簡單分布,我們定義一個(gè)函數(shù) G(z) 來從我們的原始概率分布中采樣。
- def G(z):
- ...
- return imageSample
- z = np.random.uniform(-1,1, 5)
- imageSample =G(z)
那么,我們?cè)鯓佣xG(z),可以使它輸入一個(gè)向量,輸出一張圖像?我們將使用深度神經(jīng)網(wǎng)絡(luò)。神經(jīng)網(wǎng)絡(luò)基礎(chǔ)有很多教程,所以我不會(huì)在此介紹。推薦一些不錯(cuò)的參考,斯坦福CS231n課程,Ian Goodfellow 等人的 deeplearning book 、Image Kernels Explained Visually,以及 convolution arithmetic guide。
構(gòu)造一個(gè)基于深度學(xué)習(xí)的 G(z)有很多種方式。原始的 GAN 論文提出了一個(gè)想法,一個(gè)訓(xùn)練過程,以及一個(gè)初步的實(shí)驗(yàn)結(jié)果。這個(gè)想法已經(jīng)被極大地發(fā)揚(yáng)了,其中一個(gè)想法在論文“基于深度卷積生成對(duì)抗網(wǎng)絡(luò)的無監(jiān)督表征學(xué)習(xí)(Unsupervised Representation Learning withDeep Convolutional Generative Adversarial Networks)”中提出,作者是 AlecRadford, Luke Metz, 和 SoumithChintala,發(fā)表在 2016International Conference on Learning Representations (ICLR, 讀作“eye-clear”)上。這篇論文提出了深度卷積GANS(叫做DCGANs),使用微步長卷積來對(duì)圖像進(jìn)行上采樣。
那么什么是微步長卷積,以及它是怎樣對(duì)圖像進(jìn)行上采樣的呢? VincentDumoulin 和 Francesco Visin 的論文“深度學(xué)習(xí)卷積運(yùn)算指南(A guide to convolution arithmetic for deeplearning)”和卷積運(yùn)算項(xiàng)目是對(duì)深度學(xué)習(xí)中的卷積運(yùn)算的一個(gè)非常好的介紹。圖例非常棒,可以讓我們對(duì)微步長卷積的工作方式有一個(gè)直觀的理解。首先,確保你搞懂了一般卷積如何將內(nèi)核滑過輸入空間(藍(lán)色),得到輸出空間(綠色)。此處,輸出比輸入要小。(如果不理解,參閱 CS231n CNN section 或 theconvolution arithmetic guide)
卷積運(yùn)算圖示,藍(lán)色是輸入,綠色是輸出。
接下來,假設(shè)你有一個(gè)3X3的輸入。我們的目標(biāo)是進(jìn)行上采樣(upsample),這樣,得到一個(gè)更大的輸出。你可以將微步長卷積理解為將輸入圖像放大,然后在像素間插入0。然后在這個(gè)放大后的圖像上進(jìn)行卷積操作,得到一個(gè)較大的輸出。此處,輸出為5X5。
微步長卷積運(yùn)算圖示,藍(lán)色是輸入,綠色是輸出。
插一段邊注:進(jìn)行上采樣的卷積層有很多名字: 全卷積( fullconvolution), 網(wǎng)內(nèi)上采樣(in-networkupsampling), 微步長卷積(fractionally-stridedconvolution),反向卷積(backwardsconvolution),反卷積(deconvolution),上卷積(upconvolution),或者轉(zhuǎn)置卷積(transposedconvolution)。非常不推薦使用術(shù)語“反卷積”,因?yàn)檫@個(gè)術(shù)語已經(jīng)有其他含義了:在某種數(shù)學(xué)運(yùn)算,以及計(jì)算機(jī)視覺的其它應(yīng)用中,這個(gè)術(shù)語有完全不同的含義。
現(xiàn)在我們有了微步長卷積結(jié)構(gòu),可以得到G(z)的表達(dá),以一個(gè)向量z∼pzz∼pz 作為輸入,輸出一張 64x64x3 的RGB圖像。
使用 DCGAN 構(gòu)造生成器的一種方法。圖像來自DCGAN論文。
DCGAN 論文也提出了其他的在訓(xùn)練 DCGANs 時(shí)的技巧和調(diào)整,比如批量正則化(batchnormalization)以及 leaky RELUs。
3. 使用G(z)生成偽圖像
讓我們先停下來欣賞一下 G(z) 多么強(qiáng)大吧!DCGAN 論文給出了DCGAN在臥室數(shù)據(jù)集訓(xùn)練出來的樣子。然后 G(z) 可以給出下面的偽圖像,生成器認(rèn)為的臥室是什么樣子的。下面的圖片都不在原始數(shù)據(jù)集里哦!
另外,你也可以在輸入空間z進(jìn)行代數(shù)運(yùn)算。下面是一個(gè)生成人臉的網(wǎng)絡(luò)。
基于DCGAN的人臉代數(shù)運(yùn)算 DCGAN論文。
4. [ML-Heavy] 訓(xùn)練DCGAN
現(xiàn)在我們已經(jīng)定義了G(z),并見識(shí)了它多么強(qiáng)大。那么我們?cè)趺从?xùn)練它呢?我們有很多未知的變量(參數(shù)),需要找到它們。此時(shí),我們就要用到對(duì)抗網(wǎng)絡(luò)了。
首先,我們要定義一些符號(hào)。數(shù)據(jù)的概率分布(未知的)記作pdatapdata。那么G(z),(其中z∼pzz∼pz )可以理解為從一個(gè)概率分布中的采樣。讓我們把這個(gè)概率分布記作pgpg。
符號(hào)pzpdatapg含義z的概率分布,簡單、已知圖像的概率分布(未知),是圖像數(shù)據(jù)樣本的來源生成器G用來采樣的概率分布,我們希望pg==pdata符號(hào)含義pzz的概率分布,簡單、已知pdata圖像的概率分布(未知),是圖像數(shù)據(jù)樣本的來源pg生成器G用來采樣的概率分布,我們希望pg==pdata判別器網(wǎng)絡(luò)D(x)輸入圖像x,返回圖像x是從pdatapdata的分布中采樣的概率。理論上,當(dāng)輸入圖像是從pdatapdata中采樣得到時(shí),判別器輸出一個(gè)接近1的值,當(dāng)輸入圖像是偽圖像,比如pgpg采樣得到的圖像時(shí),判別器輸出一個(gè)接近0的值。在DCGANs中,D(x)是一個(gè)傳統(tǒng)的卷積神經(jīng)網(wǎng)絡(luò)。
判別器卷積神經(jīng)網(wǎng)絡(luò),圖片來自 圖像恢復(fù)論文
訓(xùn)練判別器的目標(biāo)是:
- 對(duì)于真實(shí)數(shù)據(jù)分布x∼pdatax∼pdata的每一張圖片,最大化D(x)。
- 對(duì)于不是真實(shí)數(shù)據(jù)分布x≁pdatax≁pdata的每一張圖片,最小化D(x)。
生成器G(z)的訓(xùn)練目標(biāo)是生成可以迷惑D的樣本。輸出是一張圖像,可以作為判別器的輸入。因此,生成器希望最大化D(G(z)),也就是最小化(1-D(G(z))),因?yàn)镈是一個(gè)概率,取值在0和1之間。
論文中提出,對(duì)抗網(wǎng)絡(luò)是通過下面的最小最大策略實(shí)現(xiàn)的。第一項(xiàng)中的數(shù)學(xué)期望遍歷了真實(shí)數(shù)據(jù)分布,第二項(xiàng)的數(shù)學(xué)期望遍歷了pzpz中的樣本,也就是遍歷了G(z)∼pgG(z)∼pg。
- minGmaxDEx∼pdatalog(D(x)+Ez∼pz[log(1−D(G(z)))]minGmaxDEx∼pdatalog(D(x)+Ez∼pz[log(1−D(G(z)))]
通過這個(gè)表達(dá)式關(guān)于D和G的參數(shù)的梯度,可以訓(xùn)練它們。我們知道如何快速計(jì)算這個(gè)表達(dá)式的每一個(gè)部分。數(shù)學(xué)期望可以通過大小為m的小批數(shù)據(jù)來估計(jì),內(nèi)側(cè)的最大化可以通過k步梯度來估計(jì)。已經(jīng)證明,k=1是比較適合訓(xùn)練的值。
我們用θdθd來表示判別器的參數(shù),用θgθg來表示生成器的參數(shù)。關(guān)于用θdθd和θgθg的損失的梯度可以通過反向傳播來計(jì)算,因?yàn)镈和G都是由成熟的神經(jīng)網(wǎng)絡(luò)模塊組成的。下面是GAN論文中的訓(xùn)練策略。理論上,訓(xùn)練結(jié)束后,pg==pdatapg==pdata。所以G(z)可以生成服從pdatapdata分布的樣本。
GAN 論文中的訓(xùn)練算法。
5. 現(xiàn)有的GAN和DCGAN實(shí)現(xiàn)
在 Github 上,你可以看到很多極棒的 GAN 和 DCGAN 實(shí)現(xiàn)。
- goodfeli/adversarial: GAN論文作者寫的 Theano GAN 實(shí)現(xiàn)。
- tqchen/mxnet-gan: 非官方 MXNet GAN 實(shí)現(xiàn)。
- Newmu/dcgan_code: DCGAN論文作者寫的 Theano GAN 實(shí)現(xiàn)。
- soumith/dcgan.torch: DCGAN論文作者之一 (Soumith Chintala) 的 Torch DCGAN 實(shí)現(xiàn)。
- carpedm20/DCGAN-tensorflow: 非官方 TensorFlow DCGAN 實(shí)現(xiàn)。
- openai/improved-gan: OpenAI 第一篇論文背后的代碼。在 carpedm20/DCGAN-tensorflow 基礎(chǔ)上進(jìn)行了大量的修改。
- mattya/chainer-DCGAN: 非官方 Chainer DCGAN 實(shí)現(xiàn)。
- jacobgil/keras-dcgan: 非官方 (未完成) KerasDCGAN 實(shí)現(xiàn)。
我們會(huì)在 carpedm20/DCGAN-tensorflow 的基礎(chǔ)上構(gòu)造模型。
6. [ML-Heavy] 在Tensorflow上構(gòu)建DCGANs
這部分的實(shí)現(xiàn)在我的 bamos/dcgan-completion.tensorflow Github庫中。我需要強(qiáng)調(diào)的是,這部分的代碼來自Taehoon Kim的 carpedm20/DCGAN-tensorflow 。在我自己的庫中使用它,方便我們?cè)谙乱徊糠謭D像補(bǔ)全中使用。
大部分實(shí)現(xiàn)代碼在model.py中的一個(gè)python類,DCGAN中。把所有東西放進(jìn)一個(gè)類中是有很多好處的,這樣我們可以在訓(xùn)練結(jié)束后保留住中間過程,并在之后的使用中加載。
首先我們定義生成器和判別器結(jié)構(gòu)。linear,conv2d_transpose, conv2d, 和 lrelu 函數(shù)在 ops.py 中定義。
- defgenerator(self, z):
- self.z_, self.h0_w, self.h0_b = linear(z,self.gf_dim*8*4*4, 'g_h0_lin', with_w=True)
- self.h0 = tf.reshape(self.z_, [-1, 4, 4,self.gf_dim * 8])
- h0 = tf.nn.relu(self.g_bn0(self.h0))
- self.h1, self.h1_w, self.h1_b =conv2d_transpose(h0,
- [self.batch_size, 8, 8, self.gf_dim*4],name='g_h1', with_w=True)
- h1 =tf.nn.relu(self.g_bn1(self.h1))
- h2, self.h2_w, self.h2_b =conv2d_transpose(h1,
- [self.batch_size, 16, 16,self.gf_dim*2], name='g_h2', with_w=True)
- h2 = tf.nn.relu(self.g_bn2(h2))
- h3, self.h3_w, self.h3_b =conv2d_transpose(h2,
- [self.batch_size, 32, 32,self.gf_dim*1], name='g_h3', with_w=True)
- h3 = tf.nn.relu(self.g_bn3(h3))
- h4, self.h4_w, self.h4_b =conv2d_transpose(h3,
- [self.batch_size, 64, 64, 3],name='g_h4', with_w=True)
- return tf.nn.tanh(h4)
- defdiscriminator(self, image, reuse=False):
- if reuse:
- tf.get_variable_scope().reuse_variables()
- h0 = lrelu(conv2d(image, self.df_dim,name='d_h0_conv'))
- h1 = lrelu(self.d_bn1(conv2d(h0,self.df_dim*2, name='d_h1_conv')))
- h2 = lrelu(self.d_bn2(conv2d(h1,self.df_dim*4, name='d_h2_conv')))
- h3 = lrelu(self.d_bn3(conv2d(h2,self.df_dim*8, name='d_h3_conv')))
- h4 = linear(tf.reshape(h3, [-1, 8192]), 1,'d_h3_lin')
- return tf.nn.sigmoid(h4), h4
當(dāng)我們初始化這個(gè)類的時(shí)候,將要用到這兩個(gè)函數(shù)來構(gòu)建模型。我們需要兩個(gè)判別器,它們共享(復(fù)用)參數(shù)。一個(gè)用于來自數(shù)據(jù)分布的小批圖像,另一個(gè)用于生成器生成的小批圖像。
- selfself.G =self.generator(self.z)
- self.D,selfself.D_logits = self.discriminator(self.images)
- self.D_,selfself.D_logits_ = self.discriminator(self.G, reuse=True)
接下來,我們定義損失函數(shù)。這里我們不用求和,而是用D的預(yù)測值和真實(shí)值之間的交叉熵(cross entropy),因?yàn)樗糜谩E袆e器希望對(duì)所有“真”數(shù)據(jù)的預(yù)測都是1,對(duì)所有生成器生成的“偽”數(shù)據(jù)的預(yù)測都是0。生成器希望判別器對(duì)兩者的預(yù)測都是1 。
- self.d_loss_real= tf.reduce_mean(
- tf.nn.sigmoid_cross_entropy_with_logits(self.D_logits,
- tf.ones_like(self.D)))
- self.d_loss_fake= tf.reduce_mean(
- tf.nn.sigmoid_cross_entropy_with_logits(self.D_logits_,
- tf.zeros_like(self.D_)))
- self.g_loss =tf.reduce_mean(
- tf.nn.sigmoid_cross_entropy_with_logits(self.D_logits_,
- tf.ones_like(self.D_)))
- selfself.d_loss =self.d_loss_real + self.d_loss_fake
將每個(gè)模型的變量匯總到一起,這樣,它們可以分別訓(xùn)練。
- t_vars = tf.trainable_variables()
- self.d_vars =[var for var in t_vars if 'd_' in var.name]
- self.g_vars =[var for var in t_vars if 'g_' in var.name]
現(xiàn)在我們開始優(yōu)化參數(shù),使用 ADAM 優(yōu)化。它是一種自適應(yīng)非凸優(yōu)化方法,在SGD面前很有競爭力,一般不需要手動(dòng)調(diào)整學(xué)習(xí)率 (learning rate), 動(dòng)量(momentum),以及其他超參數(shù)。
- d_optim =tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) \
- .minimize(self.d_loss,var_list=self.d_vars)
- g_optim =tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) \
- .minimize(self.g_loss,var_list=self.g_vars)
下面我們遍歷數(shù)據(jù)。每一次迭代,我們采樣一個(gè)小批數(shù)據(jù),然后使用優(yōu)化器來更新網(wǎng)絡(luò)。有趣的是,如果G只更新一次,鑒別器的損失不會(huì)變成0。另外,我認(rèn)為最后調(diào)用d_loss_fake 和 d_loss_real 進(jìn)行了一些不必要的計(jì)算,因?yàn)檫@些值在 d_optim 和 g_optim 中已經(jīng)計(jì)算過了。作為Tensorflow 的一個(gè)聯(lián)系,你可以試著優(yōu)化這一部分,并發(fā)送PR到原始的repo。
- for epoch inxrange(config.epoch):
- ...
- for idx in xrange(0, batch_idxs):
- batch_images = ...
- batch_z = np.random.uniform(-1, 1,[config.batch_size, self.z_dim]) \
- .astype(np.float32)
- # Update D network
- _, summary_str =self.sess.run([d_optim, self.d_sum],
- feed_dict={ self.images:batch_images, self.z: batch_z })
- # Update G network
- _, summary_str =self.sess.run([g_optim, self.g_sum],
- feed_dict={ self.z: batch_z })
- # Run g_optim twice to make sure thatd_loss does not go to zero (different from paper)
- _, summary_str =self.sess.run([g_optim, self.g_sum],
- feed_dict={ self.z: batch_z })
- errD_fake =self.d_loss_fake.eval({self.z: batch_z})
- errD_real =self.d_loss_real.eval({self.images: batch_images})
- errG = self.g_loss.eval({self.z:batch_z})
搞定!當(dāng)然,完整的代碼會(huì)有更多的注釋,可以在 model.py 中查看。
7. 在圖片集上跑DCGAN
如果你跳過了上一節(jié),但是想跑跑代碼,這部分代碼在 bamos/dcgan-completion.tensorflow Github 庫中。我要再次強(qiáng)調(diào)這個(gè)代碼來自 Taehoon Kim 的 carpedm20/DCGAN-tensorflow 。在這里我們用我的庫,是因?yàn)檫M(jìn)行下一步比較方便。警告,如果你沒有支持CUDA的GPU,這部分網(wǎng)絡(luò)的訓(xùn)練會(huì)非常慢。
首先,clone 我的 bamos/dcgan-completion.tensorflow Github庫和 OpenFace 到本地。我們要用到 OpenFace 的 Python-Only 部分來進(jìn)行圖像預(yù)處理。別擔(dān)心,你不需要安裝OpenFace 的 Torch 依賴。創(chuàng)建新目錄, clone 下面的資源庫。
- git clonehttps://github.com/cmusatyalab/openface.git
- git clonehttps://github.com/bamos/dcgan-completion.tensorflow.git
接下來,安裝 OpenCV 和支持python2 的 dlib。如果你感興趣,可以嘗試實(shí)現(xiàn) dlib 對(duì) python3 的支持。安裝時(shí)候有一些小技巧,我寫了一些筆記,在 OpenFace setup guide ,包括我安裝的是那個(gè)版本、如何安裝。接下來,安裝 OpenFace 的python 庫,這樣我們可以對(duì)圖像進(jìn)行預(yù)處理。如果你不是用虛擬環(huán)境,在運(yùn)行 setup.py 時(shí)你需要用 sudo 來進(jìn)行全局安裝。(如果對(duì)你來說這部分比較困難,也可以使用 OpenFace 的 Docker 安裝。)
下面下載一個(gè)人臉圖像數(shù)據(jù)集。數(shù)據(jù)集中有沒有標(biāo)注不重要,我們會(huì)刪掉它。不完全列表如下:MS-Celeb-1M,CelebA, CASIA-WebFace, FaceScrub, LFW, 和 MegaFace。將圖片放在目錄dcgan-completion.tensorflow/data/your-dataset/raw 下,表明它是數(shù)據(jù)集的原始數(shù)據(jù)。
現(xiàn)在,我們用 OpenFace 的 alignment 工具將圖像預(yù)處理為 64X64 的數(shù)據(jù)。
- ./openface/util/align-dlib.pydata/dcgan-completion.tensorflow/data/your-dataset/raw aligninnerEyesAndBottomLipdata/dcgan-completion.tensorflow/data/your-dataset/aligned --size 64
最后,我們將處理好圖像的目錄展平,這樣目錄下只有圖像,沒有子文件夾。
- cddcgan-completion.tensorflow/data/your-dataset/aligned
- find . -name'*.png' -exec mv {} . \;
- find . -type d-empty -delete
- cd ../../..
現(xiàn)在,我們可以訓(xùn)練 DCGAN 了。安裝 Tensorflow ,開始訓(xùn)練。
- ./train-dcgan.py--dataset ./data/your-dataset/aligned --epoch 20
你可以在 sample 文件夾中查看從生成器中隨機(jī)抽樣出來的樣本發(fā)圖像是什么樣子。我在 CASIA-WebFace 數(shù)據(jù)集和 FaceScrub 數(shù)據(jù)集上訓(xùn)練,因?yàn)槲沂诸^就有這兩個(gè)數(shù)據(jù)集。 14輪訓(xùn)練之后,我的樣本是這樣的。
在 CASIA-WebFace 和 FaceScrub 上訓(xùn)練14輪后的 DCGAN 的樣本
你也可以在 TensorBoard 上查看 Tensorflow 圖像,以及損失函數(shù)。
- tensorboard--logdir ./logs
TensorBoard 損失可視化圖像。在訓(xùn)練過程中實(shí)時(shí)更新。
DCGAN 網(wǎng)絡(luò)的TensorBoard可視化
四、第三步:找到用于圖像補(bǔ)全最好的偽圖像
1. 使用 DCGAN 進(jìn)行圖像補(bǔ)全
既然我們已經(jīng)有了鑒別器 D(x) 和生成器 G(z),我們?cè)趺窗阉迷趫D像補(bǔ)全上呢?在這章,我要介紹的是 RaymondYeh 和 Chen Chen 等人的一篇論文,“基于感知和語境損失的圖像語義修補(bǔ)(Semantic Image Inpainting with Perceptual andContextual Losses)”。論文在2016年7月26號(hào)發(fā)布于 arXiv 上。
對(duì)于某個(gè)圖片y進(jìn)行圖像補(bǔ)全,一個(gè)有道理但是不可行的方案是,對(duì)于缺失的像素,最大化D(y)。結(jié)果既不是數(shù)據(jù)分布(pdatapdata),也不是生成分布(pgpg)。我們期望的是,將y投影到生成分布上。
(a)生成分布的 y 的理想重建(藍(lán)色曲面)。(b)嘗試通過對(duì) D(y) 最大化來重建 y 的一個(gè)失敗的例子。圖像來自圖像修復(fù)論文。
2. [ML-Heavy] 到 pgpg 的投影的損失函數(shù)
為了給投影一個(gè)合理的定義,我們先為圖像補(bǔ)全定義一些符號(hào)。我們使用一個(gè)二值掩碼 M(mask), 也就是只有0、1兩個(gè)值。值為1表示圖像這部分我們想要保留,值為0表示這部分我們需要補(bǔ)全?,F(xiàn)在我們可以定義,在給定了二值掩碼M之后如何對(duì)y進(jìn)行補(bǔ)全。將y中的元素和M中的元素相乘。兩個(gè)矩陣對(duì)應(yīng)位置元素相乘也叫做 Hadamard 積,用 M⊙yM⊙y 表示。M⊙yM⊙y 表示圖像的原始部分。
二值掩碼圖例
接下來,假設(shè)我們已經(jīng)找到了一個(gè) z^z^, 可以生成一個(gè)對(duì)缺失值進(jìn)行重構(gòu)的合理的G(z^)G(z^)。補(bǔ)全的像素 (1−M)⊙G(z^)(1−M)⊙G(z^) 可以加到原始像素上,得到重構(gòu)的圖像:
- xreconstructed=M⊙y+(1−M)⊙G(z^)xreconstructed=M⊙y+(1−M)⊙G(z^)
現(xiàn)在我們要做的事情,就是找到一個(gè)適于補(bǔ)全圖像的 G(z^)G(z^)。為了找到 z^z^ ,我們回顧一下文章開頭提到的 語境 和 感知,將它們作為DCGANs的上下文。為此,我們定義了對(duì)于任意z∼pzz∼pz的損失函數(shù)。損失函數(shù)越小,說明 z^z^ 越合適。
語境損失:為了得到和輸入圖像相同的上下文,需要確保y已知像素對(duì)應(yīng)位置的G(z)G(z)盡可能相似。所以,當(dāng) G(z) 的輸出和 y 已知位置圖像不相似的時(shí)候,需要對(duì) G(z) 進(jìn)行懲罰。為此,我們用 G(z) 減去 y 中對(duì)應(yīng)位置的像素,然后得到它們不相似的程度:
- Lcontextual(z)=||M⊙G(z)−M⊙y||Lcontextual(z)=||M⊙G(z)−M⊙y||
其中||x||1=∑i|xi|||x||1=∑i|xi|是某個(gè)向量x的l1l1 范數(shù)。l2l2 范數(shù)也是可取的,但是論文指出,實(shí)踐表明l1l1 范數(shù)效果更好。
理想情況下,已知部分的 y 和 G(z) 的像素是相等的。也就是對(duì)于已知位置的像素i,
- ||M⊙G(z)i−M⊙yi||=0||M⊙G(z)i−M⊙yi||=0 , Lcontextual(z)=0Lcontextual(z)=0
感知損失:為了重建一個(gè)看起來真實(shí)的圖像,需要確保判別器判定圖像看起來是真實(shí)的。為此,我們進(jìn)行和訓(xùn)練 DCGAN 中相同的步驟。
- Lperceptual(z)=log(1−D(G(z)))Lperceptual(z)=log(1−D(G(z)))
最后,將語境損失和感知損失組合起來,就可以找到 z^z^ 了;
- L(z)=Lcontextual(z)+λLperceptual(z)z^=argminzL(z)L(z)=Lcontextual(z)+λLperceptual(z)z^=argminzL(z)
其中 λλ 是超參數(shù),用來控制相比于感知損失,語境損失重要的程度。(我用的是默認(rèn)的λ=0.1λ=0.1,并沒有對(duì)這個(gè)值進(jìn)行深入研究。)然后如前所述,使用 G(z) 來重建y中缺失的部分。
- Lcontextual(z)=||M⊙G(z)−M⊙y||Lcontextual(z)=||M⊙G(z)−M⊙y||
圖像也使用了 poisson blending 來使圖像變得平滑。
3. [ML-Heavy] 使用tensorflow來進(jìn)行DCGAN圖像補(bǔ)全
這一章給出了我對(duì)于 Taehoon Kim 的 carpedm20/DCGAN-tensorflow 代碼的修改,用于圖像補(bǔ)全。
- self.mask =tf.placeholder(tf.float32, [None] + self.image_shape, name='mask')
我們通過對(duì)梯度∇zL(z)∇zL(z)進(jìn)行梯度下降,可以迭代地求出 argminzL(z)argminzL(z) 。我們定義了損失函數(shù)之后,Tensorflow 的 automatic differentiation 可以自動(dòng)地為我們計(jì)算出這個(gè)值!所以,完整的基于DCGANs的實(shí)現(xiàn)可以通過在現(xiàn)有的DCGAN實(shí)現(xiàn)上添加4行Tensorflow代碼來完成。(當(dāng)然,實(shí)現(xiàn)它還需要一些非 Tensorflow代碼。)
- self.contextual_loss= tf.reduce_sum(
- tf.contrib.layers.flatten(
- tf.abs(tf.mul(self.mask, self.G) -tf.mul(self.mask, self.images))), 1)
- selfself.perceptual_loss= self.g_loss
- selfself.complete_loss= self.contextual_loss + self.lam*self.perceptual_loss
- self.grad_complete_loss= tf.gradients(self.complete_loss, self.z)
接下來,我們定義掩碼。我只是在圖像的中央?yún)^(qū)域加了一個(gè),你可以加一些別的,比如隨機(jī)掩碼,然后發(fā)一個(gè)pull請(qǐng)求。
- ifconfig.maskType == 'center':
- scale = 0.25
- assert(scale <= 0.5)
- mask = np.ones(self.image_shape)
- l = int(self.image_size*scale)
- u = int(self.image_size*(1.0-scale))
- mask[l:u, l:u, :] = 0.0
梯度下降方面,我們對(duì)于z在[-1, 1]上的投影,使用小批量、含動(dòng)量的投影梯度下降。
- for idx inxrange(0, batch_idxs):
- batch_images = ...
- batch_mask = np.resize(mask,[self.batch_size] + self.image_shape)
- zhats = np.random.uniform(-1, 1,size=(self.batch_size, self.z_dim))
- v = 0
- for i in xrange(config.nIter):
- fd = {
- self.z: zhats,
- self.mask: batch_mask,
- self.images: batch_images,
- }
- run = [self.complete_loss,self.grad_complete_loss, self.G]
- loss, g, G_imgs = self.sess.run(run,feed_dict=fd)
- v_prev = np.copy(v)
- v = config.momentum*v - config.lr*g[0]
- zhats += -config.momentum * v_prev +(1+config.momentum)*v
- zhats = np.clip(zhats, -1, 1)
4. 補(bǔ)全圖像
選擇一些用于圖像補(bǔ)全的圖片,將它們放到dcgan-completion.tensorflow/your-test-data/raw 。然后,像之前dcgan-completion.tensorflow/your-test-data/aligned 那樣排列整齊。這里我從LFW中隨機(jī)抽出一些圖像。我的DCGAN沒有使用LFW的圖像來訓(xùn)練。
你可以這樣補(bǔ)全圖像:
- ./complete.py./data/your-test-data/aligned/* --outDir outputImages
這段代碼會(huì)生成圖像,并周期性地將圖像輸出在 —outDir 文件夾中。你可以使用ImageMagick來生成一個(gè)gif:
- cd outputImages
- convert -delay10 -loop 0 completed/*.png completion.gif
最后的圖像補(bǔ)全。圖像的中心是自動(dòng)生成的。源代碼從此處下載。這是我隨機(jī)從 LFW 中挑出的樣本。
五、結(jié)論
感謝閱讀,現(xiàn)在我們成功了!在文章中,我們涉及了圖像補(bǔ)全的一種方法:
1. 將圖像理解為概率的分布。
2. 生成偽圖像。
3. 找到用于補(bǔ)全最好的偽圖像。
我的例子是人臉,但是DCGANs也可以在其他類型的圖像上使用??傮w而言,GANs 訓(xùn)練比較困難,我們尚不清楚如何在一個(gè)特定種類的物體上進(jìn)行訓(xùn)練,也不清楚如何在大圖像上訓(xùn)練。然而,這是一個(gè)很有潛力的模型,我很期待GAN將為我們創(chuàng)造什么樣的未來!
原文:http://bamos.github.io/2016/08/09/deep-completion/?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more
【本文是51CTO專欄機(jī)構(gòu)大數(shù)據(jù)文摘的原創(chuàng)譯文,微信公眾號(hào)“大數(shù)據(jù)文摘( id: BigDataDigest)”】