圖像修復.python實現

下面是我當時在復現這篇論文是時的過程,不想看的可以直接去我的Github上看代碼。

https://github.com/CoderAnn/GLCI

有問題可以留言,一定會盡全力解答。

實驗過程簡介:

      首先,我是基於Context Encoders:Feature Learning by Impainting(2016)復現SATOSHI IIZUKA等人在2017年發表的Globally and Locally Consistent Image Completion。2016年的這篇論文我沒咋細看,大概是說把上下文編碼器的思想和GAN的思想結合去做圖像補全,上下文編碼器就是先把圖像進行編碼之後再進行解碼,從而學的圖像的特徵並生成圖像待修補區域對應的預測圖。GAN用來判別圖像是否是虛假的。

     現在先複習一下GAN的訓練過程:首先對z和x採樣一個批次,獲得他們的數據分佈,然後通過隨機梯度下降的方法先對D做k次更新,之後對G做一次更新,這樣做的主要目的是保證D一直有足夠的能力去分辨真假。實際在代碼中我們可能會多更新幾次G只更新一次D,不然D學習的太好,會導致訓練前期發生梯度消失的問題。

      而我要復現的論文其實就是在這篇論文的基礎上,修改了鑑別網絡,把之前的單個鑑別器擴充到兩個鑑別器,一個用來查看全局內容的一致性,另一個用來看局部內容的一致性。最後鑑別器的輸出是將二者全連接後再Sigmoid成概率。另外這篇論文還將重構損失修改爲MSE+GAN損失,這一點是爲了防止GAN的不穩定性。

      具體的訓練過程是先用MSE損失更新Tc次補全網絡,之後再進行Td次鑑別器的更新,最後兩個一起更新。先要更新補全網絡是因爲最開始的時候,補全網絡很差,補全的效果非常明顯,鑑別器很容易就能看出這是虛假圖片,此時鑑別器的損失很小,幾乎爲0,也就是此時的鑑別器並沒有發揮作用。

     網絡架構如下圖所示:

訓練過程:

實驗經過:

     整個的訓練過程基本實現:首先讀入數據x,之後通過生成隨機點來確定mask,將(x*mask)作爲補全網絡的輸入,該輸入是缺失部分區域的圖片,之後通過降低MSE損失來使補全網絡生成圖片,待MSE損失降低後,開始訓練鑑別器(這裏單獨訓練鑑別器可能是爲了讓鑑別器明白什麼樣的圖片是真實的,什麼樣的圖片是虛假的??先單獨訓練生成器的原因是由於最開始補全網絡效果很差,如果直接讓鑑別器去判斷會一眼看出是假的,必須訓練到讓鑑別器不太清楚真假的時候讓兩者進行對抗),最後一起訓練生成器和鑑別器。但是在具體的數據集當中發現鑑別器的損失非常小,這一點還得繼續訓練完善。

    目前訓練出現的問題:鑑別器的損失不下降爲定值(學習率:2e-3,數據集:500張人臉圖片),下次訓練學習率設置爲0.5(無效,非學習率問題!)

   考慮鑑別器代碼本身有誤。鑑別器代碼確實有誤,在全連接之前少了一步reshape。

   另外,發現鑑別器最後的輸出按理說應該是一個打分,也就是需要用sigmoid激活,但是參考的代碼統統沒有激活,直接將全連接的輸出作爲鑑別器的輸出。這一點還需查資料。(訓練GAN的技巧中說明如果最後一層不用sigmoid會讓網絡更加穩定)

   仔細看過代碼後發現,其實最後用的全連接層是將數據全連接成一個數字了,也就是numsout=1,如果最後全連接的輸出是一個數字的話,就可以不用sigmoid函數去激活成(0,1)的數字了。重點不是因爲這個,而是因爲sigmoid會忽視掉負值的含義,容易讓梯度消失,所以最後一層用了batch_norm先歸一化然後再用leaky_rule去激活。

爲什麼在全連接之前卷積之後需要對圖像進行reshape呢?

       因爲卷積之後的圖像仍是一個矩陣,但是全連接層的輸入是一個向量,因此需要一步reshape.

鑑別器是如何判斷圖像是虛假的呢?

       在GAN中,鑑別器是通過構建多層感知機來進行分類的。在Encoder-Decoder當中,鑑別器是通過卷積神經網絡進行分類,具體的說是先卷積降低分辨率最後交給全連接層,這裏的全連接層充當一個分類器的作用。why???原因還不太明白。。。

目前實驗進展:生成器沒問題,訓練過程應該也沒問題,重要的是鑑別器的結構有誤,之前沒有在全連接之前reshape圖像,師兄的代碼中最後的全連接層加了一個tf.squeeze(x,-1)函數,這個函數目前我不理解,刪除-1維度的所有值爲1的維度???爲什麼要這樣呢。。。

 

目前修改過鑑別器(沒有采用師兄的代碼,相比之前的代碼就是在鑑別器裏的全連接層之前加入了reshape,學習率1e-3,數據量500張數據集)後對模型進行訓練,發現以下幾點變化:

  •       最開始的優化mse損失的表現沒有之前的穩定(按理來說不應該發生改變)
  •       鑑別器中判斷爲真實圖片的損失很小几乎爲0,也就是G_LOSS_GAN和D_LOSS幾乎相等
  •       拿最後的效果對比來看,鑑別器的損失應該對於圖像的補全是有效果的
  •       最後在迭代8000次的時候鑑別器的損失爲0了,從對應的圖片來看效果不是非常完美,但是確實有了很大的改進。

初次修復效果對比圖(最開始以爲成功了其實並沒有Orz...):

100次
100次
200次
2000次
3500
7000

 

8500

 

注:3000次之前是用MSE更新補全網絡,3000-3500是單獨訓練鑑別器

       3500-9000鑑別器和補全網絡一起訓練

下次實驗更改的地方:將鑑別器中的全連接層歸一化並激活看看會不會有效果。

 這次實驗看起來損失比之前的能好看一點,但是大佬說這次的模型仍舊是一個失敗的模型,雖然圖形的訓練結果雖然最後看起來還行,但是僅僅只是mse損失降低了所以看起來不錯,也就是說鑑別器是沒有參與到模型的訓練中的???原因:明顯看出來g_loss_gan減小時D_loss也會減小,這根本就沒有形成對抗,對抗是一個讓他變大一個讓他變小,因此這確實是一個失敗的模型。現在呢繼續更改代碼,通過閱讀別人的代碼發現,他們呢的鑑別器和補全網絡採取的是不同的卷積函數,同時發現之前的代碼G_loss_gan計算錯誤,而且對於訓練過程理解的也不是很對,最後一次訓練,是隻單獨訓練G_loss_all並步訓練D(師兄不是這麼訓練的)因此這次更改了鑑別器中的卷積函數以及激活函數,調用了tf自帶的函數來進行操作,並且修改了訓練過程,以及修正了g_loss_gan。大佬還說我的代碼運行速度太慢,這一點還有待修改。卷積函數的不同對於結果的影響非常微弱,這並不是主要原因。

修正了錯誤但是鑑別器還是不起作用,按正常道理來說鑑別器應該和生成器產生對抗,也就是優化G_loss_all時D_loss一定會有變化纔對,但是目前我優化G_loss_all對於對抗損失無任何影響。。。所以兩個沒有產生對抗。

之後修改了模型訓練中鑑別器數據讀取模式(之前是先把圖片取出來然後再進行切割即切出蒙版區域),利用tf.image.crop_to_bounding_box(self.inputs[k],y1,x1,64,64)這個函數直接在模型中獲取蒙版區域。並且發現之前的優化參數有問題,修改過後發現確實生成器和鑑別器存在對抗,但是!可能是由於參數設置有誤或者鑑別器訓練太好導致兩者的對抗屬於力量嚴重失衡狀態,即生成器處於被吊打階段。。。這可能也是由於初次訓練GAN(GAN本身就很不穩定)沒啥經驗導致的。發現一片好文戳這裏鴨!按其中說的優化了GAN,坐等訓練結果~!

原始GAN不穩定的原因:判別器訓練得太好,生成器梯度消失,生成器loss降不下去;判別器訓練得不好,生成器梯度不準,四處亂跑。只有判別器訓練得不好不壞纔行,但是這個火候又很難把握,甚至在同一輪訓練的前後不同階段這個火候都可能不一樣,所以GAN才那麼難訓練。

                      超參數a=1,g_learn=d_learn=0.002:失敗!之後還嘗試了超參數a=0.05 g_learn=0.001,d_learn=0.0001失敗!

正確的損失變化趨勢:

正確的損失變化趨勢。發現:我寫的鑑別器在最開始的時候下降的就非常快,收斂速度太快???,導致在和生成器對抗時損失以及很低了。

a=0.0004,d_learn=0.0001失敗!

a=0.0004,d_learn=0.00001失敗!

a=0.0004,d_learn=0.000001 失敗!

但是學習率這麼低其實沒有毛用,鑑別器還是不行。

經過向師兄請教後發現原來生成器一直都有問題,生成的結果一直都很差導致了鑑別器很快就收斂了,從輸入到輸出全部替換一邊比對之後發現問題出在生成器生成的圖像上,下圖中左圖是正常的生成器經過1000次的生成圖像,但是我的就是連眼睛嘴巴都完全糊掉的樣子,這裏應該就是模型一直以來的問題。

正確的生成結果
正確的結果
我的結果
我的結果

 

之前imitation結果不好的原因應該是訓練的次數不到位,訓練次數太少了的原因,後來延長訓練次數後發現圖片除了補全區域很差外其餘地方均和目標圖像差不太多。所以是同樣的網絡爲什麼會導致圖片的補全效果差這麼多呢??

網絡完全一樣輸入的圖片完全一樣訓練次數完全一樣,補全效果千差萬別???(修改了數據讀取的方式後效果非常好)

將數據的讀入方式修改成了隊列的方法,並且生成器和鑑別器的卷積操作均使用slim庫中的函數,修改後果然生成效果非常好。修復效果如下:

2000_22000_15000

所以現在有了一個非常大的疑惑,slim庫中的卷積函數和tensorflow中的卷積函數有什麼不同?隊列讀取數據的方式和之前的一批次讀取隊列的方式爲什麼差這麼大?

slim庫中的卷積函數和tensorflow中的卷積函數有什麼不同?答案請戳這裏~!

下一步實驗計劃:

接下來將會把生成器和鑑別器的網絡換成之前的模型,即只控制數據的輸入方式爲隊列形式,看訓練的結果如何。

Result:更換之後的結果鑑別器再次不起作用,也許之前寫的鑑別器確實有問題。

接下來將把鑑別器更換成師兄的鑑別器網絡,觀察是否能起作用,如果不行繼續就再師兄的鑑別器的基礎上更改,打算先把所有的卷積函數替換成tf.nn.conv2d()看有沒有變化。

Result:換成師兄的鑑別器後補全效果不錯

打算進行一次6000張圖片的訓練,如果訓練效果好就完善一下把保存節點也寫好。並且同時在小的訓練集上進行測試,修改自己的鑑別器,探尋爲什麼師兄的鑑別器就可以。

Result:可能因爲拿6000張圖片去訓練時參數沒設置好導致訓練效果一般。第二個實驗把slim.conv2d()換成了tf.nn.conv2d(),發現結果差別不大

準備把歸一化函數也進行更換,觀察結果。

Results:發現確實是歸一化的函數不同導致了結果不對,正確的模型使用的是tf.contrib.slim.batch_norm()函數進行歸一化的而把該函數替換成其他的tensorflow中封裝的函數則發現不能對抗But why?

tf.contrib.slim.batch_norm()中的scale和center默認爲False,但是slim.batch_norm()中均默認爲True,這可能是原因但具體的還得細看。

不過總之也算是找到根本原因了!基本大功告成!!撒花~~~~

實驗中函數彙總:

tf.transpose(input, [dimension_1, dimenaion_2,..,dimension_n]):

        這個函數主要適用於交換輸入張量的不同維度用的,如果輸入張量是二維,就相當是轉置。dimension_n是整數,如果張量是三維,就是用0,1,2來表示。這個列表裏的每個數對應相應的維度。如果是[2,1,0],就把輸入張量的第三維度和第一維度交換。

tf.reshape(tensor,shape,name=None):

       這個函數是對tensor的shape重新定義爲新的shape,如果參數中有-1則表示我們不用親自去指定這一維的大小,函數會自動進行計算,但是列表中只能存在一個-1。(如果存在多個-1,就是一個存在多解的方程)

tensorflow.squeeze(input, squeeze_dims=None, name=None):

       刪除input中所有大小是squeeze_dims 的維度。squeeze_dims = None  -->默認None是刪除input中所有大小是1的維度,若指定位置則刪除所指定位置大小是1的維度。此處要注意指定的維度必須確保其是1,否則會報錯。

zeros(shape, dtype=float, order='C'):

      返回來一個給定形狀和類型的用0填充的數組;參數:shape:形狀。dtype:數據類型,可選參數,默認numpy.float64

tf.clip_by_value(A, min, max):

      輸入一個張量A,把A中的每一個元素的值都壓縮在min和max之間。小於min的讓它等於min,大於max的元素的值等於max

實驗中命令彙總:

nvidia-smi:用來查看當前顯卡的使用情況

CUDA_VISIBLE_DEVICES=id python XXXX.py: 用id號顯卡來運行代碼

unzip;ls;cd;cd..;rm;rm-r...:  字面理解就好。

mv:移動文件

disown -h %1 :讓程序在後臺運行關掉終端也不影響(前提程序已經在後臺了(bg %1))

df -h:顯示分區的內存佔比

dh -sh:顯示當前文件夾的大小

dh -sh *:顯示當前文件夾下每個文件夾的大小

# ./pso > pso.file 2>&1 & :將程序放入後臺執行,關閉終端也依舊執行的命令

ipconfig/all :查看整個電腦的詳細的IP配置信息

linux命令戳這裏!

 

 

 

 

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