TensorFlow實現基于深度學習的圖像補全
目錄
■ 簡介
■ 第一步:將圖像理解為一個概率分布的樣本
你是怎樣補全缺失信息的呢?
但是怎樣著手統計呢?這些都是圖像啊。
那么我們怎樣補全圖像?
■ 第二步:快速生成假圖像
在未知概率分布情況下,學習生成新樣本
[ML-Heavy] 生成對抗網絡(Generative Adversarial Net, GAN) 的架構
使用G(z)生成偽圖像
[ML-Heavy] 訓練DCGAN
現有的GAN和DCGAN實現
[ML-Heavy] 在Tensorflow上構建DCGANs
在圖片集上跑DCGAN
■ 第三步:找到用于圖像補全最好的偽圖像
使用 DCGAN 進行圖像補全
[ML-Heavy] 到 pgpg 的投影的損失函數
[ML-Heavy] 使用tensorflow來進行DCGAN圖像補全
補全圖像
■ 結論
簡介
內容識別填充(譯注: Content-aware fill ,是 photoshop 的一個功能)是一個強大的工具,設計師和攝影師可以用它來填充圖片中不想要的部分或者缺失的部分。在填充圖片的缺失或損壞的部分時,圖像補全和修復是兩種密切相關的技術。有很多方法可以實現內容識別填充,圖像補全和修復。在這篇博客中,我會介紹 RaymondYeh 和 Chen Chen 等人的一篇論文,“基于感知和語境損失的圖像語義修補(Semantic Image Inpainting with Perceptual and ContextualLosses)”。論文在2016年7月26號發布于 arXiv 上,介紹了如何使用 DCGAN 網絡來進行圖像補全。博文面向一般技術背景的讀者,部分內容需要有機器學習的背景。我在相關章節標注了[ML-Heavy]標簽,如果你不想了解太多細節,可以跳過這些章節。我們只會涉及到填充人臉圖像缺失部分的情況。博文相關 Tensorflow 代碼已經發布到 GitHub 上:bamos/dcgan-completion.tensorflow 。
圖像補全分為三個步驟。
-
首先我們將圖像理解為一個概率分布的樣本。
-
基于這種理解,學習如何生成偽圖片。
-
然后我們找到最適合填充回去的偽圖片。
使用photoshop來對圖像缺失部分補全,并使用photoshop自動刪除不要的部分。
下文將要介紹到的圖像補全。圖像的中心是自動生成的。源碼可以從此處下載。
這些圖像是我從 LFW 數據集中取得的一個隨機樣本。
第一步:將圖像理解為一個概率分布的樣本
1.你是怎樣補全缺失信息的呢?
在上面的例子中,想象你正在構造一個可以填充缺失部分的系統。你會怎么做呢?你覺得人類大腦是怎么做的呢?你使用了什么樣的信息呢?
在博文中,我們會關注兩種信息:
語境信息:你可以通過周圍的像素來推測缺失像素的信息。
感知信息:你會用“正常”的部分來填充,比如你在現實生活中或其它圖片上看到的樣子。
兩者都很重要。沒有語境信息,你怎么知道填充哪一個進去?沒有感知信息,通過同樣的上下文可以生成無數種可能。有些機器學習系統看起來“正常”的圖片,人類看起來可能不太正常。
如果有一種確切的、直觀的算法,可以捕獲前文圖像補全步驟介紹中提到的兩種屬性,那就再好不過了。對于特定的情況,構造這樣的算法是可行的。但是沒有一般的方法。目前最好的解決方案是通過統計和機器學習來得到一個近似的技術。
2.但是怎樣著手統計呢?這些都是圖像啊。
為了激發大家的思考,我們從一個很好理解、可以寫成簡潔形式的概率分布開始:一個正態分布。這是正態分布的概率密度函數(PDF)。你可以將PDF理解成在輸入空間橫向移動,縱軸表示某個值出現的概率。(如果你感興趣,繪制這幅圖的代碼可以從 bamos/dcgan-completion.tensorflow:simple-distributions.py 下載。)
從這個分布中采樣,就可以得到一些數據。需要搞清楚的是PDF和樣本之間的聯系。
從正態分布中的采樣
2維圖像的PDF和采樣。 PDF 用等高線圖表示,樣本點畫在上面。
這是1維分布,因為輸入只能沿著一個維度。在兩個維度上也可以這么做。
在圖像和統計學之間,最關鍵的聯系就是,我們可以將圖像看作是從一個高維概率分布中得到的采樣。概率分布對應的是圖像的像素。想象你在用相機拍照。得到的圖像是由有限個數的像素組成。當你通過相機拍照的時候,你就在從這個復雜的概率分布中進行采樣。這個概率分布就決定了我們判斷一張圖片是正常的,還是不正常的。對于圖片而言,與正態分布不同的是,我們無法得知真實的概率分布,我們只能去收集樣本。
在這篇文章中,我們會使用彩色圖像,它用 RGB顏色 表示。我們的圖像寬64像素,高64像素,所以我們的概率分布是 64⋅64⋅3≈12k 維的。
3.那么我們怎樣補全圖像?
首先考慮多變量正態分布,以求得到一些啟發。給定 x=1 , 那么 y 最可能的值是什么?我們可以固定x的值,然后找到使PDF最大的 y。
在多維正態分布中,給定x,得到最大可能的y
這個概念可以很自然地推廣到圖像概率分布。我們已知一些值,希望補全缺失值。這可以簡單理解成一個最大化問題。我們搜索所有可能的缺失值,用于補全的圖像就是可能性最大的值。
從正態分布的樣本來看,只通過樣本,我們就可以得出PDF。只需挑選你喜歡的 統計模型,然后擬合數據即可。
然而,我們實際上并沒有使用這種方法。對于簡單分布來說,PDF很容易得出來。但是對于更復雜的圖像分布來說,就十分困難,難以處理。之所以復雜,一部分原因是復雜的條件依賴:一個像素的值依賴于圖像中其它像素的值。另外,最大化一個一般的PDF是一個非常困難和棘手的非凸優化問題。
第二步:快速生成假圖像
1.在未知概率分布情況下,學習生成新樣本
除了學習如何計算PDF之外,統計學中另一個成熟的想法是學習怎樣用 生成模型 生成新的(隨機)樣本。生成模型一般很難訓練和處理,但是后來深度學習社區在這個領域有了一個驚人的突破。Yann LeCun 在這篇 Quora 回答中對如何進行生成模型的訓練進行了一番精彩的論述,并將它稱為機器學習領域近10年來最有意思的想法。
Yann LeCun 對生成對抗網絡的介紹
將生成對抗網絡類比為街機游戲。兩個網絡相互對抗,共同進步。就像兩個人類在游戲中對抗一樣
其它的深度學習方法,比如 VariationalAutoencoders(VAEs),也可以用來訓練生成模型。在這篇博文中,我們用的是生成對抗網絡(GenerativeAdversarial Nets,GANs)。
2.[ML-Heavy] 生成對抗網絡(GenerativeAdversarial Net, GAN) 的架構
這個想法是 IanGoodfellow 等人在2014年NeuralInformation Processing Systems (NIPS) 研討會上發表的里程碑式論文“生成對抗網絡”(GenerativeAdversarial Nets,GANs)中提出的。主要思想是,我們定義一個簡單、常用的分布,用pzpz表示。在下文中,我們使用pzpz來表示在-1到1閉區間上的均勻分布。我們將從分布中的一個采樣記作 z∼pzz∼pz 。若 pzpz 是五維的,我們可以通過一行python的 numpy代碼來進行采樣:
- z =np.random.uniform(-1, 1, 5)
- array([0.77356483, 0.95258473,-0.18345086, 0.69224724, -0.34718733])
現在有了一個用于采樣的簡單分布,我們定義一個函數 G(z) 來從我們的原始概率分布中采樣。
- def G(z):
- ...
- return imageSample
- z = np.random.uniform(-1,1, 5)
- imageSample =G(z)
那么我們怎樣定義G(z),可以使它輸入一個向量,輸出一張圖像?我們將使用深度神經網絡。神經網絡基礎有很多教程,所以我不會在此介紹。推薦一些不錯的參考,斯坦福CS231n課程,Ian Goodfellow 等人的 deeplearning book 、Image Kernels Explained Visually,以及 convolution arithmetic guide。
構造一個基于深度學習的 G(z)有很多種方式。原始的 GAN 論文提出了一個想法,一個訓練過程,以及一個初步的實驗結果。這個想法已經被極大地發揚了,其中一個想法在論文“基于深度卷積生成對抗網絡的無監督表征學習(Unsupervised Representation Learning withDeep Convolutional Generative Adversarial Networks)”中提出,作者是 AlecRadford, Luke Metz, 和 SoumithChintala,發表在 2016International Conference on Learning Representations (ICLR, 讀作“eye-clear”)上。這篇論文提出了深度卷積GANS(叫做DCGANs),使用微步長卷積來對圖像進行上采樣。
那么什么是微步長卷積,以及它是怎樣對圖像進行上采樣的呢? VincentDumoulin 和 Francesco Visin 的論文“深度學習卷積運算指南(A guide to convolution arithmetic for deeplearning)”和卷積運算項目是對深度學習中的卷積運算的一個非常好的介紹。圖例非常棒,可以讓我們對微步長卷積的工作方式有一個直觀的理解。首先,確保你搞懂了一般卷積如何將內核滑過輸入空間(藍色),得到輸出空間(綠色)。此處,輸出比輸入要小。(如果不理解,參閱 CS231n CNN section 或 theconvolution arithmetic guide)
卷積運算圖示,藍色是輸入,綠色是輸出。
接下來,假設你有一個3X3的輸入。我們的目標是進行上采樣(upsample),這樣,得到一個更大的輸出。你可以將微步長卷積理解為將輸入圖像放大,然后在像素間插入0。然后在這個放大后的圖像上進行卷積操作,得到一個較大的輸出。此處,輸出為5X5。
微步長卷積運算圖示,藍色是輸入,綠色是輸出。
插一段邊注:進行上采樣的卷積層有很多名字: 全卷積( fullconvolution), 網內上采樣(in-networkupsampling), 微步長卷積(fractionally-stridedconvolution),反向卷積(backwardsconvolution),反卷積(deconvolution),上卷積(upconvolution),或者轉置卷積(transposedconvolution)。非常不推薦使用術語“反卷積”,因為這個術語已經有其他含義了:在某種數學運算,以及計算機視覺的其它應用中,這個術語有完全不同的含義。
現在我們有了微步長卷積結構,可以得到G(z)的表達,以一個向量z∼pzz∼pz 作為輸入,輸出一張 64x64x3 的RGB圖像。
使用 DCGAN 構造生成器的一種方法。圖像來自DCGAN論文
DCGAN 論文也提出了其他的在訓練 DCGANs 時的技巧和調整,比如批量正則化(batchnormalization)以及 leaky RELUs。
3.使用G(z)生成偽圖像
讓我們先停下來欣賞一下 G(z) 多么強大吧!DCGAN 論文給出了DCGAN在臥室數據集訓練出來的樣子。然后 G(z) 可以給出下面的偽圖像,生成器認為的臥室是什么樣子的。下面的圖片都不在原始數據集里哦!
另外,你也可以在輸入空間z進行代數運算。下面是一個生成人臉的網絡
基于DCGAN的人臉代數運算 DCGAN論文
4.[ML-Heavy] 訓練DCGAN
現在我們已經定義了G(z),并見識了它多么強大。那么我們怎么訓練它呢?我們有很多未知的變量(參數),需要找到它們。此時,我們就要用到對抗網絡了。
首先我們要定義一些符號。數據的概率分布(未知的)記作pdatapdata。那么G(z),(其中z∼pzz∼pz )可以理解為從一個概率分布中的采樣。讓我們把這個概率分布記作pgpg。
符號pzpdatapg含義z的概率分布,簡單、已知圖像的概率分布(未知),是圖像數據樣本的來源生成器G用來采樣的概率分布,我們希望pg==pdata符號含義pzz的概率分布,簡單、已知pdata圖像的概率分布(未知),是圖像數據樣本的來源pg生成器G用來采樣的概率分布,我們希望pg==pdata
判別器網絡D(x)輸入圖像x,返回圖像x是從pdatapdata的分布中采樣的概率。理論上,當輸入圖像是從pdatapdata中采樣得到時,判別器輸出一個接近1的值,當輸入圖像是偽圖像,比如pgpg采樣得到的圖像時,判別器輸出一個接近0的值。在DCGANs中,D(x)是一個傳統的卷積神經網絡。
判別器卷積神經網絡,圖片來自 圖像恢復論文
訓練判別器的目標是:
1、對于真實數據分布x∼pdatax∼pdata的每一張圖片,最大化D(x)。
2、對于不是真實數據分布x≁pdatax≁pdata的每一張圖片,最小化D(x)。
生成器G(z)的訓練目標是生成可以迷惑D的樣本。輸出是一張圖像,可以作為判別器的輸入。因此,生成器希望最大化D(G(z)),也就是最小化(1-D(G(z))),因為D是一個概率,取值在0和1之間。
論文中提出,對抗網絡是通過下面的最小最大策略實現的。第一項中的數學期望遍歷了真實數據分布,第二項的數學期望遍歷了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)))]
通過這個表達式關于D和G的參數的梯度,可以訓練它們。我們知道如何快速計算這個表達式的每一個部分。數學期望可以通過大小為m的小批數據來估計,內側的最大化可以通過k步梯度來估計。已經證明,k=1是比較適合訓練的值。
我們用θdθd來表示判別器的參數,用θgθg來表示生成器的參數。關于用θdθd和θgθg的損失的梯度可以通過反向傳播來計算,因為D和G都是由成熟的神經網絡模塊組成的。下面是GAN論文中的訓練策略。理論上,訓練結束后,pg==pdatapg==pdata。所以G(z)可以生成服從pdatapdata分布的樣本。
GAN 論文中的訓練算法。
5.現有的GAN和DCGAN實現
- 在 Github 上,你可以看到很多極棒的 GAN 和 DCGAN 實現。
- goodfeli/adversarial: GAN論文作者寫的 Theano GAN 實現。
- tqchen/mxnet-gan: 非官方 MXNet GAN 實現。
- Newmu/dcgan_code: DCGAN論文作者寫的 Theano GAN 實現。
- soumith/dcgan.torch: DCGAN論文作者之一 (Soumith Chintala) 的 Torch DCGAN 實現。
- carpedm20/DCGAN-tensorflow: 非官方 TensorFlow DCGAN 實現。
- openai/improved-gan: OpenAI 第一篇論文背后的代碼。在 carpedm20/DCGAN-tensorflow 基礎上進行了大量的修改。
- mattya/chainer-DCGAN: 非官方 Chainer DCGAN 實現。
- jacobgil/keras-dcgan: 非官方 (未完成) KerasDCGAN 實現。
我們會在 carpedm20/DCGAN-tensorflow 的基礎上構造模型。
6.[ML-Heavy] 在Tensorflow上構建DCGANs
這部分的實現在我的 bamos/dcgan-completion.tensorflow Github庫中。我需要強調的是,這部分的代碼來自Taehoon Kim的 carpedm20/DCGAN-tensorflow 。在我自己的庫中使用它,方便我們在下一部分圖像補全中使用。
大部分實現代碼在model.py中的一個python類,DCGAN中。把所有東西放進一個類中是有很多好處的,這樣我們可以在訓練結束后保留住中間過程,并在之后的使用中加載。
首先我們定義生成器和判別器結構。linear,conv2d_transpose, conv2d, 和 lrelu 函數在 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
當我們初始化這個類的時候,將要用到這兩個函數來構建模型。我們需要兩個判別器,它們共享(復用)參數。一個用于來自數據分布的小批圖像,另一個用于生成器生成的小批圖像。
- self.G =self.generator(self.z)
- self.D,self.D_logits = self.discriminator(self.images)
- self.D_,self.D_logits_ = self.discriminator(self.G, reuse=True)
接下來,我們定義損失函數。這里我們不用求和,而是用D的預測值和真實值之間的交叉熵(cross entropy),因為它更好用。判別器希望對所有“真”數據的預測都是1,對所有生成器生成的“偽”數據的預測都是0。生成器希望判別器對兩者的預測都是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_)))
- self.d_loss =self.d_loss_real + self.d_loss_fake
將每個模型的變量匯總到一起,這樣,它們可以分別訓練。
- 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]
現在我們開始優化參數,使用 ADAM 優化。它是一種自適應非凸優化方法,在SGD面前很有競爭力,一般不需要手動調整學習率 (learning rate), 動量(momentum),以及其他超參數。
- 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)
下面我們遍歷數據。每一次迭代,我們采樣一個小批數據,然后使用優化器來更新網絡。有趣的是,如果G只更新一次,鑒別器的損失不會變成0。另外,我認為最后調用d_loss_fake 和 d_loss_real 進行了一些不必要的計算,因為這些值在 d_optim 和 g_optim 中已經計算過了。作為Tensorflow 的一個聯系,你可以試著優化這一部分,并發送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})
搞定!當然,完整的代碼會有更多的注釋,可以在 model.py 中查看。
7.在圖片集上跑DCGAN
如果你跳過了上一節,但是想跑跑代碼,這部分代碼在 bamos/dcgan-completion.tensorflow Github 庫中。我要再次強調這個代碼來自 Taehoon Kim 的 carpedm20/DCGAN-tensorflow 。在這里我們用我的庫,是因為進行下一步比較方便。警告,如果你沒有支持CUDA的GPU,這部分網絡的訓練會非常慢。
首先,clone 我的 bamos/dcgan-completion.tensorflow Github庫和 OpenFace 到本地。我們要用到 OpenFace 的 Python-Only 部分來進行圖像預處理。別擔心,你不需要安裝OpenFace 的 Torch 依賴。創建新目錄, clone 下面的資源庫。
- git clonehttps://github.com/cmusatyalab/openface.git
- git clonehttps://github.com/bamos/dcgan-completion.tensorflow.git
接下來,安裝 OpenCV 和支持python2 的 dlib。如果你感興趣,可以嘗試實現 dlib 對 python3 的支持。安裝時候有一些小技巧,我寫了一些筆記,在 OpenFace setup guide ,包括我安裝的是那個版本、如何安裝。接下來,安裝 OpenFace 的python 庫,這樣我們可以對圖像進行預處理。如果你不是用虛擬環境,在運行 setup.py 時你需要用 sudo 來進行全局安裝。(如果對你來說這部分比較困難,也可以使用 OpenFace 的 Docker 安裝。)
下面下載一個人臉圖像數據集。數據集中有沒有標注不重要,我們會刪掉它。不完全列表如下:MS-Celeb-1M,CelebA, CASIA-WebFace, FaceScrub, LFW, 和 MegaFace。將圖片放在目錄dcgan-completion.tensorflow/data/your-dataset/raw 下,表明它是數據集的原始數據。
現在我們用 OpenFace 的 alignment 工具將圖像預處理為 64X64 的數據。
- ./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 ../../..
現在我們可以訓練 DCGAN 了。安裝 Tensorflow ,開始訓練。
- ./train-dcgan.py--dataset ./data/your-dataset/aligned --epoch 20
你可以在 sample 文件夾中查看從生成器中隨機抽樣出來的樣本發圖像是什么樣子。我在 CASIA-WebFace 數據集和 FaceScrub 數據集上訓練,因為我手頭就有這兩個數據集。 14輪訓練之后,我的樣本是這樣的。
在 CASIA-WebFace 和 FaceScrub 上訓練14輪后的 DCGAN 的樣本
你也可以在 TensorBoard 上查看 Tensorflow 圖像,以及損失函數。
- tensorboard--logdir ./logs
TensorBoard 損失可視化圖像。在訓練過程中實時更新。
DCGAN 網絡的TensorBoard可視化
第三步:找到用于圖像補全最好的偽圖像
1.使用 DCGAN 進行圖像補全
既然我們已經有了鑒別器 D(x) 和生成器 G(z),我們怎么把它用在圖像補全上呢?在這章我要介紹的是 RaymondYeh 和 Chen Chen 等人的一篇論文,“基于感知和語境損失的圖像語義修補(Semantic Image Inpainting with Perceptual andContextual Losses)”。論文在2016年7月26號發布于 arXiv 上。
對于某個圖片y進行圖像補全,一個有道理但是不可行的方案是,對于缺失的像素,最大化D(y)。結果既不是數據分布(pdatapdata),也不是生成分布(pgpg)。我們期望的是,將y投影到生成分布上。
(a): 生成分布的 y 的理想重建(藍色曲面)。(b):嘗試通過對 D(y) 最大化來重建 y 的一個失敗的例子。圖像來自圖像修復論文
2.[ML-Heavy] 到 pgpg 的投影的損失函數
為了給投影一個合理的定義,我們先為圖像補全定義一些符號。我們使用一個二值掩碼 M(mask), 也就是只有0、1兩個值。值為1表示圖像這部分我們想要保留,值為0表示這部分我們需要補全?,F在我們可以定義,在給定了二值掩碼M之后如何對y進行補全。將y中的元素和M中的元素相乘。兩個矩陣對應位置元素相乘也叫做 Hadamard 積,用 M⊙yM⊙y 表示。M⊙yM⊙y 表示圖像的原始部分。
二值掩碼圖例
接下來,假設我們已經找到了一個 z^z^, 可以生成一個對缺失值進行重構的合理的G(z^)G(z^)。補全的像素 (1−M)⊙G(z^)(1−M)⊙G(z^) 可以加到原始像素上,得到重構的圖像:
- xreconstructed=M⊙y+(1−M)⊙G(z^)xreconstructed=M⊙y+(1−M)⊙G(z^)
現在我們要做的事情,就是找到一個適于補全圖像的 G(z^)G(z^)。為了找到 z^z^ ,我們回顧一下文章開頭提到的 語境 和 感知,將它們作為DCGANs的上下文。為此,我們定義了對于任意z∼pzz∼pz的損失函數。損失函數越小,說明 z^z^ 越合適。
語境損失:為了得到和輸入圖像相同的上下文,需要確保y已知像素對應位置的G(z)G(z)盡可能相似。所以,當 G(z) 的輸出和 y 已知位置圖像不相似的時候,需要對 G(z) 進行懲罰。為此,我們用 G(z) 減去 y 中對應位置的像素,然后得到它們不相似的程度:
- Lcontextual(z)=||M⊙G(z)−M⊙y||Lcontextual(z)=||M⊙G(z)−M⊙y||
其中||x||1=∑i|xi|||x||1=∑i|xi|是某個向量x的l1l1 范數。l2l2 范數也是可取的,但是論文指出,實踐表明l1l1 范數效果更好。
理想情況下,已知部分的 y 和 G(z) 的像素是相等的。也就是對于已知位置的像素i, ||M⊙G(z)i−M⊙yi||=0||M⊙G(z)i−M⊙yi||=0 , Lcontextual(z)=0Lcontextual(z)=0 。
感知損失:為了重建一個看起來真實的圖像,需要確保判別器判定圖像看起來是真實的。為此,我們進行和訓練 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)
其中 λλ 是超參數,用來控制相比于感知損失,語境損失重要的程度。(我用的是默認的λ=0.1λ=0.1,并沒有對這個值進行深入研究。)然后如前所述,使用 G(z) 來重建y中缺失的部分。
- Lcontextual(z)=||M⊙G(z)−M⊙y||Lcontextual(z)=||M⊙G(z)−M⊙y||
圖像也使用了 poisson blending 來使圖像變得平滑。
3.[ML-Heavy] 使用tensorflow來進行DCGAN圖像補全
這一章給出了我對于 Taehoon Kim 的 carpedm20/DCGAN-tensorflow 代碼的修改,用于圖像補全。
- self.mask =tf.placeholder(tf.float32, [None] + self.image_shape, name='mask')
我們通過對梯度∇zL(z)∇zL(z)進行梯度下降,可以迭代地求出 argminzL(z)argminzL(z) 。我們定義了損失函數之后,Tensorflow 的 automatic differentiation 可以自動地為我們計算出這個值!所以,完整的基于DCGANs的實現可以通過在現有的DCGAN實現上添加4行Tensorflow代碼來完成。(當然,實現它還需要一些非 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)
- self.perceptual_loss= self.g_loss
- self.complete_loss= self.contextual_loss + self.lam*self.perceptual_loss
- self.grad_complete_loss= tf.gradients(self.complete_loss, self.z)
接下來,我們定義掩碼。我只是在圖像的中央區域加了一個,你可以加一些別的,比如隨機掩碼,然后發一個pull請求。
- 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
梯度下降方面,我們對于z在[-1, 1]上的投影,使用小批量、含動量的投影梯度下降。
- 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.補全圖像
選擇一些用于圖像補全的圖片,將它們放到dcgan-completion.tensorflow/your-test-data/raw 。然后像之前dcgan-completion.tensorflow/your-test-data/aligned 那樣排列整齊。這里我從LFW中隨機抽出一些圖像。我的DCGAN沒有使用LFW的圖像來訓練。
你可以這樣補全圖像:
- ./complete.py./data/your-test-data/aligned/* --outDir outputImages
這段代碼會生成圖像,并周期性地將圖像輸出在 —outDir 文件夾中。你可以使用ImageMagick來生成一個gif:
- cd outputImages
- convert -delay10 -loop 0 completed/*.png completion.gif
最后的圖像補全。圖像的中心是自動生成的。源代碼從此處下載。這是我隨機從 LFW 中挑出的樣本
結論
感謝閱讀,現在我們成功了!在文章中,我們涉及了圖像補全的一種方法:
1、將圖像理解為概率的分布。
2、生成偽圖像。
3、找到用于補全最好的偽圖像。
我的例子是人臉,但是DCGANs也可以在其他類型的圖像上使用??傮w而言,GANs 訓練比較困難,我們尚不清楚如何在一個特定種類的物體上進行訓練,也不清楚如何在大圖像上訓練。然而,這是一個很有潛力的模型,我很期待GAN將為我們創造什么樣的未來!