換臉原理,使用GAN網路再造ZAO應用:使用卷積網路提升影像識別率

  • 2019 年 10 月 11 日
  • 筆記

上一節我們使用全連接網路來對圖片內容進行識別,正確率大概在50%左右,準確率不高的原因在於,網路沒有對像素點在二維平面上的分布規律加以考量。如果對神經網路引入卷積運算,網路就能具備識別像素點在空間上分布的規律,我們先看看什麼是卷積操作。

卷積操作簡單來說是對兩個矩陣進行點和點的乘機後求和,例如下面運算:

運算將兩個矩陣中的元素依次相乘後求和,也就是0.6*1+0.2*1+0.6*1+0.1*0+-0.2*2+-0.3*0+-0.5*-1+-0.1*-1+-0.3*-1=2.3 左邊矩陣其實可以對應於圖片像素點組成的二維數組,因此左邊矩陣的維度完全可以比右邊矩陣大,假設左邊矩陣是10行10列,右邊矩陣保持3行3列不變,那麼右邊矩陣就可以通過「掃描」的方式進行卷積運算,右邊矩陣也稱為卷積運算的kernel。

例如從第0行第0個元素開始從它的右邊選擇3列,從它下面選擇3行所形成的矩陣與右邊矩陣做卷積得一個結果,然後從第0行第1列元素開始從右邊選擇3列,從下標選擇3行然後進行卷積運算,如此依次進行,如下面動圖所示:

注意看每次卷積運算時,每次挪動一個元素,這種挪動的幅度叫做stride,如果stride == 2,那麼每次橫線或縱向移動兩個元素後才進行卷積操作。我們也注意到卷積運算後所得結果相比於原來矩陣有所「縮水」,為了保證卷積後的結果與卷積前一樣大,我們可以先在運算前對左邊矩陣進行「填充」,也就是使用數值0增加左邊矩陣的行數和列數,使得增加後矩陣在卷積運算後大小與原來一樣,如下圖所示:

上圖中下面藍色部分對應卷積運算的左邊矩陣,陰影部分對應kernel的大小,從圖中看kernel的維度是[3,3],我們先用0填充藍色部分對應的句子,也就是上圖中的白色方塊,綠色部分是卷積運算後所得結果,我們看到綠色部分的大小與白色部分相同。

卷積操作是常有的影像處理演算法,不同的kernel經過卷積操作後能將圖片中的某些資訊抽取出來,例如下圖所示的兩個kernel能分別將圖片中物體的水平邊緣和豎直邊緣抽取出來:

卷積神經網路的目的,其實就是找出一系列kernel,這些kernel能夠從圖片中抽取特定資訊從而能幫助網路識別圖片中的物體。我們看keras給我們提供的卷積網路層構造介面:

conv_layer1 = Conv2D(filters = 2, kernel_size = (3,3), stride = 1,  padding = 'same])(input_layer)

程式碼中input_layer對應前面描述卷積操作中的左邊矩陣,filters指定右邊矩陣的個數,kernel_size指定右邊矩陣的維度,如果有2個kernel,那麼就有兩個計算結果,這兩個結果會「重疊「在一起,例如經過一次卷積後所得結果是維度為[5,5]的矩陣,那麼兩個kernel完成卷積後所得結果就是維度為[2,5,5]的三維矩陣。值得注意的是我們在前面例子中的kernel是一個3*3的矩陣,這是相對於左邊也是二維矩陣而言,通常情況下,由於圖片是RGB格式,因此每個像素點由三個值組成,所以對於輸入規格為32*32的圖片,它對應三維矩陣[32, 32, 3]。

這就相當於3個32*32的二維矩陣疊在一起,此時右邊kernel也會相應變成[3,3,3]形式,也就是kernel也變成3層,因此卷積時就不再是兩個3*3矩陣之間的元素相乘後求和,而是兩個3*3*3的立方體對應元素之間相乘後再求和。這裡kernel的」高度「在上面Conv2D調用中沒有顯示出來,kernel_size只規定了長河寬,但是該調用會根據輸入input_layer的高度來自動設置kernel的高度。

由此我們在構建網路時,可以設置兩個卷積層來識別輸入圖片相關程式碼如下所示:

input_layer = Input(shape=(32, 32, 3)) #輸入圖片規格為32*32*3  '''  第一層卷積層有10個kenerl大小為4*4注意卷積層會根據輸入圖片的高度3自動調整kernel高度也為3,  因此實際上kernel的體積變成4*4*3  '''  conv1 = Conv2D(filters = 10, kernel_size = (3, 3), strides = 1, padding = 'same')(input_layer)  '''  再增添一層卷積層加大圖片識別力度,注意到上層卷積層有10個kernel,因此conv1的規格為[32, 32, 10]  '''  conv2 = Conv2D(filters = 20, kernel_size = (3,3), strides = 1, padding = 'same')(conv1)  '''  第二層卷積層由於有20個kenel,因此conv2的規格為[32, 32, 20],下面程式碼將它』壓扁『為[32*32*20]的一維向量  '''  flatten_layer = Flatten()(conv2)  #對數據進行10種分類  output_layer = Dense(units = 10, activation = 'softmax')(flatten_layer)  model = Model(input_layer, output_layer)  model.summary()

上面程式碼運行後結果為:


Layer (type) Output Shape Param #

input_1 (InputLayer) (None, 32, 32, 3) 0


conv2d_1 (Conv2D) (None, 32, 32, 10) 280


conv2d_2 (Conv2D) (None, 32, 32, 20) 1820


flatten_1 (Flatten) (None, 20480) 0


dense_1 (Dense) (None, 10) 204810

Total params: 206,910 Trainable params: 206,910 Non-trainable params: 0


上面的輸出不是很好理解,我們需要好好分析一下。輸入層後跟著的第一個卷積層,kernel的長和寬是3,注意輸入層輸入的圖片高度為3,因此卷積層自動將kernel高度也拉伸為3,於是一個kernel擁有3*3*3=27個分量,最後在機器學習中,兩個矩陣相乘後往往還喜歡在結果上加上一個稱為bias的參數,卷積運算有點類似於直線方程:a*x+b,a可以看做是前面例子中左邊的矩陣,x相當於右邊kernel,b就是我們這裡所說的bias,由此一個kernel其實對應27+1=28個分量,由於有10個kernel,因此總共有280個分量。

同理第二層卷積層kernel的長和寬都是3,但是它的高度根據輸入數據的高度而調整為10,因此一個kernel具備的分量數為3*3*10=90,再加上1個bias參數就是91個分量,由於有20個kenel,因此輸出結果為91*20=1820個參數。

這裡我們還需要了解經過卷積運算後輸出的數據規格為(input_height/stride,input_width/strides, filters),這是在使用padding=』same』,也就是在卷積運算前使用0對輸入數據進行填充的情況下,運算後所得結果的規格,根據該公式我們可以確認conv1的規格為(32/1,32/1,10),conv2對應規格為(32/1,32/1,20),卷積運算過程容易理解,但運算後所得結果的規格變化很容易讓人陷入迷惑。

深度學習本質上是一種高強度大規模的數值運算,想像在一條高速公路上大量汽車快速前進的情形,很有發生碰撞車禍,同理在大規模的數值計算過程中也會產生某種異常現象,那就是網路中有些參數突然急劇碰撞變得很大,這樣會嚴重影響網路最終結果的準確性。

為了防止這種異常的出現,深度學習引入了兩種手段,一種叫批量正規化,其實就是把卷積層輸出的結果再次進行一些特定的數值計算,這就相當於在車流量大的高速路上放置限速標誌防止車速過快而發生意外,因此我們對卷積層的輸出做如下加工運算:

其中的x就是卷積層輸出結果中的各個分量,r和B是兩個需要在網路中訓練的參數,這類似於對高速公路上的車流進行限速。keras框架提供了一個簡單介面讓我們迅速實現上面功能,那就是BatchNormalization()。

第二種方法叫dropout,其實就是隨機將網路層間的連接參數清零,這種方法雖然簡單但效果非常好,keras提供的介面是Dropout(rate = 0.25),它表示將四分之一的網路層參數隨機清零,所有這些內容結合在一起最終形成的網路程式碼如下:

input_layer = Input((32,32,3))    x = Conv2D(filters = 32, kernel_size = 3, strides = 1, padding = 'same')(input_layer)  x = BatchNormalization()(x)  x = LeakyReLU()(x)    x = Conv2D(filters = 32, kernel_size = 3, strides = 2, padding = 'same')(x)  x = BatchNormalization()(x)  x = LeakyReLU()(x)    x = Conv2D(filters = 64, kernel_size = 3, strides = 1, padding = 'same')(x)  x = BatchNormalization()(x)  x = LeakyReLU()(x)    x = Conv2D(filters = 64, kernel_size = 3, strides = 2, padding = 'same')(x)  x = BatchNormalization()(x)  x = LeakyReLU()(x)  x = Flatten()(x)    x = Dense(128)(x)  x = BatchNormalization()(x)  x = LeakyReLU()(x)  x = Dropout(rate = 0.5)(x)    x = Dense(NUM_CLASSES)(x)  output_layer = Activation('softmax')(x)    model = Model(input_layer, output_layer)

通過上面網路對輸入圖片進行識別,所得結果如下圖:

從中我們可以看到使用了卷積網路層後,網路對圖片的識別率從以前的50%提升到73%,這是一個相當顯著的提升!更多詳細解讀和相關課程請點擊』閱讀原文『。