這次上手一個比較有意思的項目:圖像的風格遷移(論文的連接配接在底部,以及論文作者的實作)
風格遷移,簡單的說就是提取一個圖檔的風格(該圖檔稱為風格圖像),如下面這張圖檔:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SMzgjNkhTYiJTZ3ImM0IjNwI2NxUmYzMTY0E2M0YjN18CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
風格圖檔
然後在另外一個圖像(稱為内容圖像)為主要内容的基礎上:
内容圖檔
得到一個基于内容圖像并融合風格圖像的生成圖像:
生成圖檔
一般的神經網絡都基本流程都是:
- 正向傳播
- 計算損失
- 反向傳播
- 更新參數
随着訓練的進行,不斷改變的是網絡的參數,輸入資料(即訓練樣本)是不變的;而風格遷移的實作中則相反,網絡是已經訓練好的,在訓練中,
網絡的參數不會改變,改變的是輸入資料。
先介紹一下大緻流程:
- 定好要生成的圖檔的像素規模,比如256x256
- 找好相同大小的内容圖像和風格圖像
- 找一個已經訓練好的網絡(論文中使用的是VGG19,我的小筆記本改用VGG16)
- 随機建立一個256x256x3的數組(RGG三通道),取值範圍在[0-255],作為生成圖像
- 将生成圖像( G )、内容圖像( C )、風格圖像( S )丢到VGG16中,計算風格損失和内容損失
上圖VGG16中一個色塊表示
一個卷積層和
一個ReLU層,不同色塊之間有一個池化層(論文中提到使用
平均池化層替代
最大池化層會得到“更有吸引力”的結果),後面的全連接配接層沒有畫出來,因為也不用它。一般為了産生更好的效果,會計算多個層的風格損失
- 反向傳播(隻傳播梯度,不更新參數),得到損失關于生成圖像的梯度
- 使用優化算法(論文裡是LBFGS)更新生成圖像的資料,然後不斷重複最後這三步
如果把輸入當作一個層(Layer G),該層的輸出就是它自身的參數,再将VGG16整個看作是一個損失函數,這就變成了一個最簡單的梯度下降的過程了:
如前面所讨論的,整個損失分成兩部分,内容損失和風格損失:
是兩個超參數,分别是這兩個損失的權重,需要自己設定,下面來分别讨論這兩個損失的具體計算
内容損失(content loss)
關于VGG16的結構就不贅述了,不太記得的可以百度,内容損失的計算方法方式是:
- 在VGG16中標明一個層,例如上面的圖中第7個ReLU層
- 計算得到該層關于内容圖像輸出 和生成圖像的輸出
pytorch自帶網絡_Pytorch實作風格遷移 pytorch自帶網絡_Pytorch實作風格遷移 - 基于上述兩個輸出值計算損失
因為是在卷積網絡中,ReLU的輸出是一個四維張量,是以
,這四個次元分别是樣本批數,特征值的高度、寬度和通道數。因為确定内容圖像和風格圖像之後,一次隻優化一個生成圖檔,是以
為1,在後面的讨論中,都會把這一次元省略掉
然後,論文給出計算兩個圖檔的内容差異的公式為:
這個公式計算梯度也很簡單,就不寫過程了,直接貼出來:
不過論文作者自己的
實作中使用的是均方誤差(MSE)損失函數計算的内容損失,公式為:
這樣的話就比論文給出的公式數量上低了好幾個量級,不過這都沒什麼關系,因為還有一個
參數可以調節
風格損失(style loss)
風格損失的計算沒有内容損失這麼直接,稍微委婉一點;雖然可能有多個層計算風格損失,但是對于任意一層的處理步驟是一樣的:
- 計算得到該層關于風格圖像的輸出 和生成圖像的輸出
pytorch自帶網絡_Pytorch實作風格遷移 pytorch自帶網絡_Pytorch實作風格遷移 - 計算兩個輸出值的格拉姆矩陣
- 對兩個格拉姆矩陣計算均方誤差損失
其中,格拉姆矩陣計算公式為:
上式是關于生成圖像輸出的格拉姆矩陣的計算,風格圖像也是一樣的(而且可以提前計算),雖然這個式子看起來有點繞,但是用einsum函數可以直接搞定
M_g
這樣,将兩個圖像的風格使用格拉姆矩陣表示出來後,就可以計算它們之間的風格差異了:
可以很容易得到風格損失關于格拉姆矩陣的梯度:
為了得到風格損失關于生成圖像輸出的梯度
,還需要知道格拉姆矩陣中各元素關于輸出的梯度,回顧格拉姆矩陣的計算公式:
可以知道
的任意一個元素
與它的格拉姆矩陣的第
行和第
列上的元素都有關:
上面的兩個式子
代表除
以外的任何值,另外:
是以,根據使用全導數求生成圖像輸出的梯度為:
整理一下,最終得到:
上面這個公式與論文裡的形式不一樣,是因為論文裡的特征值隻有兩個次元,他将特征值的寬和高用一個次元表示了,而且通道是放在了第一個次元,是以其實本質是一回事
以上是論文中給的公式,還有一個需要注意的是在作者的自己的代碼裡,他并沒有按照自己的公式來,其計算格拉姆矩陣的代碼變成公式是下面這樣:
然後使用均方誤差計算風格損失:
如果将
提出來的話,和前面的公式也是一樣的。最後,因為風格損失設計多個層的計算,是以總的風格損失為:
以上,就是損失計算的最主要的兩個部分,其他的什麼基本上也沒什麼了
最後的最後,再把作者的Github貼一下,并說一下代碼與論文不同的一些點:
https://github.com/leongatys/PytorchNeuralStyleTransfer
- 代碼雖然使用的是Pytorch的實作,但不是直接下載下傳Pytorch的預訓練模型,而是使用https://bethgelab.org/media/uploads/pytorch_models/vgg_conv.pth這個連結的模型(可以使用迅雷下載下傳)
- 在我自己的實驗中,使用Pytorch預訓練的模型需要将裡圖像資料除上255是其調整到(0,1)的範圍,作者自帶的這個模型不用
- 作者的代碼裡對圖像做了些預處理,先是将圖像調整到(0,1)然後使用均值為[0.40760392, 0.45795686, 0.48501961]和方差為[1,1,1]進行歸一化,然後又乘上255(此時圖像的資料範圍是[-128~128]),我沒有在論文中找到相關說明
- 論文中說明生成圖像初始化的方式并不影響最終結果,無論你是完全随機的初始化,還是直接複制内容圖像作為初始化,最後都看不多出來有什麼差別(論文裡有圖檔),然而我找了張顯示卡使用随機初始化疊代了800次後的圖像是這樣的
随機初始化的結果
如果是複制内容圖像,結果就正常了:
參考文獻:
[1] Image Style Transfer Using Convolutional Neural Networks