最近幾天陸續補充了一些“線性回歸”部分內容,這節繼續機器學習基礎部分,這節主要對CNN的基礎進行整理,僅限於基礎原理的了解,更復雜的內容和實踐放在以后再進行總結。
卷積神經網絡的基本原理
前面對全連接神經網絡和深度學習進行了簡要的介紹,這一節主要對卷積神經網絡的基本原理進行學習和總結。
所謂卷積,就是通過一種數學變換的方式來對特征進行提取,通常用於圖片識別中。
既然全連接的神經網絡可以用於圖片識別,那么為什么還要用卷積神經網絡呢?
0.使用卷積神經網絡的理由
(1)首先來看下面一張圖片:
在這個圖片當中,鳥嘴是一個很明顯的特征,當我們做圖像識別時,當識別到有“鳥嘴”這樣的特征時,可以具有很高的確定性認為圖片是一個鳥類。
那么,在提取特征的過程中,有時就沒有必要去看完整張圖片,只需要一小部分就能識別出一定具有代表的特征。
因此,使用卷積就可以使某一個特定的神經元(在這里,這個神經元可能就是用來識別“鳥嘴”的)僅僅處理帶有該特征的部分圖片就可以了,而不必去看整張圖片。
那么這樣就會使得這個神經元具有更少的參數(因為不用再跟圖片的每一維輸入都連接起來)。
(2)再來看下面一組圖片:
上面兩張圖片都是鳥類,而不同的是,兩只鳥的“鳥嘴”的位置不同,但在普通的神經網絡中,需要有兩個神經元,一個去識別左上角的“鳥嘴”,另一個去識別中間的“鳥嘴”:
但其實這兩個“鳥嘴”的形狀是一樣的,這樣相當於上面兩個神經元是在做同一件事情。
而在卷積神經網絡中,這兩個神經元可以共用一套參數,用來做同一件事情。
(3)對樣本進行子采樣,往往不會影響圖片的識別。
如下面一張圖:
假設把一張圖片當做一個矩陣的話,取矩陣的奇數行和奇數列,可看做是對圖片的一種縮放,而這種縮放往往不會影響識別效果。
卷積神經網絡中就可以對圖片進行縮放,是圖片變小,從而減少模型的參數。
1.卷積神經網絡的基本結構
卷積神經網絡的基本結構如圖所示:
從右到左,輸入一張圖片→卷積層→max pooling(池化層)→卷積層→max pooling(池化層)→......→展開→全連接神經網絡→輸出。
中間的卷積層和池化層可以重復多次。后面會一一介紹每一層是如何工作的。
對於第0部分的三個功能:
(1)某個神經元只需偵測某一部分的圖片,來識別某種特征這個工作是在卷積層內完成的。
(2)具有相同功能的神經元共用一套參數,這個工作也是在卷積層內完成的。
(3)通過縮小圖片,來減少模型的參數,這個工作是在池化層中完成的。
稍后會解釋上面三個部分是如何進行的。
1.1.卷積層的工作原理
假設有一張6*6的黑白圖片,如圖所示:
首先圖片經過卷積層,卷積層有一組filter,每個filter用來抓取圖片中的某一種特征,如圖所示:
假設filter是3*3的矩陣,每個filter有9個參數,而這些參數就是通過訓練學習得到的,這里假設我們已經學習得到了上面一組參數的值。
因此也就說明了問題(1)中,每一個filter只偵測圖片中的很小一部分數據。
那么每個filter是如何去抓取特征的呢?也就是(1)中使某一個神經元只偵測一部分圖片就能提取某一種特征的問題。
首先看filter1,filter大小為3*3,那么相當於這個filter1依次從左到右去覆蓋整張圖片,然后與覆蓋區域做內積,如圖所示:
首先從左上角開始,覆蓋圖片左上角3*3的區域,計算結果得到3,然后向右移動,這里移動的步長稱之為stride,當stride為1時,即一次移動一格,為2時,一次移動兩格,如圖所示:
移動之后再次用圖片被覆蓋的區域與filter做內積,得到第二個值:
依次進行移動和計算,當移到最右邊盡頭時,則從下一行開始繼續移動,最終得到如圖所示矩陣:
通過觀察這個filter1可以看出,filter1的對角矩陣全為1,其他為-1,那么對於圖片中對角為1的部分,與filter1做內積后的值就會很大(例子中等於3),其他的則會很小。
因此filter1是一種用於偵測對角都為1的圖片這種屬性,在圖片中可以看到,坐上角和右下角都具有這種特征。
所以這也就說明了(2)中的問題,一個filter的一組參數,可以偵測到圖片中兩個位置的相同屬性。
接下來是第二個filter,filter2,那么filter2與圖片的計算方式一模一樣,如圖所示:
其他的filter也是如此,依次計算,然后把每個filter處理結果放在一起,如圖所示:
那么相當於一個紅色方框現在是由2個值來描述的,最終得到的是2個4*4的圖片,稱之為“feature map”。
上面是對於一張黑白的圖片進行一次卷積(convolution)的過程,那么對於彩色的圖片是怎樣處理的呢?
彩色圖片通常是由“RGB”組成,分別表示紅色、綠色和藍色,那么就相當於有三個部分的組成,如圖所示:
這三個層稱之為“通道”(channel),那么利用卷積處理這種圖片時,filter也應該是三層,即3*3*3的,是帶有深度的,長下面這個樣子:
相當於每個filter具有27個參數。
卷積與全連接網絡的關系
其實卷積就是一種特殊形式的全連接網絡,還是假設是上面那張6*6的圖片,如下是全連接的網絡結構:
把圖片進行拉直展開,但在卷積中,這個網絡有些連接的地方被切斷了,只有一部分輸入與神經元相連接。下面進行解釋:
正如上面的卷積過程,如下是其中的一步,如圖所示:
那么這個步驟我們可以想象成是這樣子的:
左邊把圖片的36個數值拉直,然后,對於filter1所覆蓋的區域為左上角,那么元素對應的位置為1、2、3、7、8、9、13、14、15。
然后這9個數值,依次經過各自的weight相乘再相加,得到第一個值3,這里的weight就是對應的filter中各個位置的值,圖中weight線的顏色與filter中圓圈顏色一一對應。
到這里就很清楚的看出卷積與全連接之間的關系,相當於簡化了全連接神經網絡,從而使得參數量更少。
然后filter開始向右移動一格(stride=1,上面計算過程的第二個方框,元素依次為2、3、4、8、9、10、14、15、16),與filter做inner product,得到結果-1,對應到全連接網絡中如圖所示:
依舊是每個元素的weight與輸入輸出連接,顏色對應的filter圓圈與weight的顏色一致。
從上面的圖可以看出,兩個神經元(3和-1)都是通過filter1作為weight與輸入進行連接的,也就是說,這兩個神經元是共用同一組參數,這樣,在上面減少參數的基礎上,使得模型的參數更少了。
上述過程就是卷積層的工作過程,主要用於圖片中特征的抓取,完成第0節內容的前兩個。
1.2池化層的工作原理
池化層的作用上面說了,就是為了圖片的縮放,那么池化層是如何進行縮放的呢?
其實池化層的原理很簡單,這里以max-pooling為例,經過卷積層之后的圖片變為4*4大小的圖片,有2個filter,也就是兩張4*4的圖片:
池化就是將上面得到的數據進行分組,如圖所示:
如圖示中的分組方法,這種分組方法可以是任意的,也可以三個一組等等,然后取每個組中的最大值:
那么原來4*4的圖片經過maxpooling之后就縮小為2*2的圖片了,當然這里也不一定非要取最大值,也可以取平均,或者又取平均又取最大值等。
那么上面的過程總結一下就可以用如下圖進行概括:
這就是池化層的工作原理,比較簡單。
1.3Flatten和全連接層
接下來就來到了flatten和全連接層了,flatten的作用就是把上面得到的兩個2*2的image拉直展開,然后再丟進全連接網絡中計算就可以了,這個跟前面全連接神經網絡的方法一樣,如圖所示:
這里就不再詳細贅述了。那么整個網絡就是利用梯度下降的方法進行訓練的,所有的參數被一起學習得到。
2.使用Keras對CNN的簡單實現
這里還是使用keras對CNN的建模過程進行一個簡單的實現,首先先來看一下keras分別添加卷積層和池化層的過程:
如圖中所示,首先要導入Convolution2D(后來這個接口會改成Conv2D)和Maxpooling2D。
然后與之前神經網絡中一樣,不過是把“Dense”換成了上面兩個方法,其中Convolution2D中,25表示filter的數量,3,3表示filter的尺寸,input_shape為輸入圖片的大小,28*28為圖片大小,1表示黑白圖片,3則表示彩圖“RGB”;
Maxpooling2D中只有分組的形狀,即2*2大小的。
接下來就是根據設計的網絡的形狀,重復上面兩個步驟,完成卷積和池化。如圖所示:
圖中黑色的方框為代碼的實現內容,在keras1.0和2.0中可能有些差異;
黃色的框中是輸入和輸出的尺寸大小,可以看到,輸入為28*28的圖片,經過25個3*3的filter組成的第一個卷積層之后,變成了25*26*26的尺寸;
然后經過第一個2*2的池化層,縮小后的輸出尺寸大小為25*13*13;
然后經過第二個由50個3*3的filter之后,輸出變為50*11*11;
之后經過第二個2*2的池化層后,縮小后的輸出尺寸變為50*5*5(這里由於輸入為基數,最后一格就忽略掉了)。
而在卷積層中,filter的參數數量是在變化的,在第一個卷積層的參數為3*3=9個,這個比較好理解;
而在第二個卷積層中,參數就變成了225個,這是因為上一層的輸出為25*13*13大小的圖片,這時是帶有channel的(就相當於一開始輸入為RGB時,通道數為3)當使用3*3的filter進行處理時,需要帶有深度25,因此每個filter的參數數量為25*3*3=225。
然后就是經過Faltten和fully-connection了,這個與之前的神經網絡一致:
然后就可以對模型進行訓練了。這里要注意的是,這里的輸入跟全連接有所不同,要把輸入圖片數據轉化為(28,28,1)的格式。完整代碼如下:
from sklearn.datasets import fetch_openml
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
import numpy as np
from keras import Sequential
from keras.layers import Dense
from keras.layers import Activation
from keras.layers import Convolution2D, MaxPooling2D, Flatten
data_x, data_y = fetch_openml('mnist_784', version=1, return_X_y=True)
x = []
for i in range(len(data_x)):
tmp = data_x.iloc[i, :].tolist()
tmp = np.array(tmp).reshape((28, 28, 1))
x.append(tmp)
x = np.array(x)
one_hot = OneHotEncoder()
data_y = one_hot.fit_transform(np.array(data_y).reshape(data_y.shape[0], 1)).toarray()
train_x, test_x, train_y, test_y = train_test_split(x, data_y)
model2 = Sequential()
model2.add(Convolution2D(25, 3, 3, input_shape=(28, 28, 1)))
model2.add(MaxPooling2D((2, 2)))
model2.add(Convolution2D(50, 3, 3))
model2.add(MaxPooling2D((2, 2)))
model2.add(Flatten())
model2.add(Dense(units=100))
model2.add(Activation('relu'))
model2.add(Dense(units=10))
model2.add(Activation('softmax'))
model2.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model2.fit(train_x, train_y, batch_size=300, epochs=20)
最后的輸出如下:
3.CNN都學到了啥?
通過CNN的原理我們大致知道,CNN的卷積是不斷提取特征的過程,池化是對圖片的縮放,那么究竟CNN學習到了什么呢?如下面一張圖:
給機器一張圖片,那么對人來說,這張圖片是一雙鞋,而對機器來說,它可能認為是是美洲獅,因為圖中的鞋標有一只美洲獅。
因此,我們想要知道CNN究竟學習到了啥,下面介紹幾種方法來查看CNN都在偵測什么特征的方法。
3.1 直接查看filter
前面我們說到,filter就是為了抓取某一種特征的,那么我們是不是可以看一下這些filter分別都是偵測什么樣的特征的。
根據CNN的原理,隨着網絡越來越靠近輸出層,那么這一層所學習到的東西就越來越抽象,不太容易觀察。因此,我們選取第一個卷積層的filters來看一下。
對於一個訓練好的模型,將第一個卷積層的filters單獨拿出來,並畫出來,如圖所示:
上面一共96個filter,每個filter的大小為11*11,從filter我們可以看出,上面幾排的filter主要是偵測形狀特征的,下面幾排的filter主要是偵測色彩特征的。
我們也可以通過單獨看一個filter,然后把圖片依次輸入的CNN中,看哪些圖片經過這個filter后的輸出(Activate)最大,如下圖所示:
白色的框表示filter,這里白色的框看着很大,主要是因為這個filter比較靠后,也就是它所看到的是前面經過縮小后的輸入,因此框就會變得很大。
第一排的圖片中的框是一個filter1,可以看到,這個filter對於臉部的偵測比較強;
第二排的框是另一個filter2,可以看到filter2偵測的主要是洞狀排列的特征;
第三排的filter3則是偵測紅色的特征;
以此類推...
3.2 特征的可視化
這種方法是查看特征的,也就是說使用反卷積、反池化的方法,來可視化輸入圖像的激活特征。
還用原理部分那個CNN結構的例子,如下圖左邊的結構:
在第二個卷積層中,假設第k個filter,它與輸入作用以后得到的結果如上圖中右側,每一個元素aij,然后把aij相加,即為ak,即:
那么現在我們想要通過輸入x使得ak最大,也就是找一張圖片x,使得ak的輸出最大,即:
這里就要利用gradient ascent的方法去找這個最大值了,即求:
就相當於原先經過卷積、池化過程,現在要將x當做未知量,反過來求反池化、反卷積去求解出一個使得ak最大的x(注意這里x並不是真正的圖片了,相當於是通過反池化和反卷積所得到的的帶有一些特征的圖像);
具體原因就在於反池化和反卷積的求解過程,無法還原為原先的圖片,這里暫不過多介紹如何反池化和反卷積的原理,后續再補充。
那么經過上面的步驟,還原得到的x的結果如圖所示:
一共50個filter,這里取前12個,每個圖片的意思代表1個filter所還原得到的圖片x,也就是說,比如第一張圖片,將其作為input丟進上面那個網絡后,其輸出與第一個filter作用后得到的ak最大。
仔細觀察一下上面的圖,還是有一定的規律可循的。
同時我們也可以去查看fully-connection部分的神經元,原理跟上面一樣,不過再求解釋時又多了一層反向傳播的過程,如圖:
當然也可以去拿輸出層的神經元,比如識別結果為0,輸出層在第0維值就很大,然后一樣根據上面的原理得到的結果如圖:
會發現得到的結果什么也看不到,但是當把某張圖片再次輸入進網絡之后,得到的確實是對應的結果。
這也就說明了深度學習很容易被欺騙,但從側面也反映出了,深度學習確實學習到了某些特征,並非僅僅簡單“記住”數據,也說明深度學習的“玄學”性,難以解釋。
當然,在上面的求解過程中,我們可以適當加一些限制,比如正則化:
那么得到的結果如圖:
發現結果稍微清晰了一些,有些能夠看到一些筆畫。
那么正則化的目的是在保證y越大越高的同時,也要使得xij(圖片中的每一個pixel)越小越好,也就是說對於圖片顯示(白色的是筆畫,黑色的是空白)時,盡可能使空白減少,能連的連起來。所以效果會好些。
3.3 微分
對於一張圖片的識別,我們可以計算類別y對圖像像素pixel的微分的值:
通過改變像素xij,來看這個像素對識別的影響是否重要,也就是說,當xij稍微做一下改變,對識別的結果影響有多大,用這樣的方法可以得到如下的結果:
白色區域表示對識別影響較為重要的pixel,可以大概看到一些形狀。那這樣做有什么意義呢?
有時當我們從網上爬取圖片進行建模后,發現對馬的識別率很高,但對其他的識別一般,這是因為可能關於“馬”的圖片都包含“horse”字樣的標簽,機器真正識別到的是“horse”的標簽,而並非知道馬長什么樣子。
同樣的道理,我們可以通過蓋住圖片的一部分,去看看蓋住的部分結果的影響有多大,比如:
圖中灰色的區域是被蓋住的,不斷移動灰色的區域,看對結果的影響有多大,那么可以獲取到下面一樣的熱力圖:
圖中顏色越淺表示對辨識的結果影響越大,越不能辨別出是什么,可以看到,第一張當蓋住狗的臉的時候,很難分辨出狗;
第二張圖片當蓋住輪胎部分的時候,就很難辨別出輪胎;
第三張圖片當蓋住狗的身子的部分,就很難辨別出狗了。
更多有關CNN可視化的內容,可搜索一些有關博客進行學習:https://blog.csdn.net/xys430381_1/article/details/90413169
4.關於CNN一些有趣的應用
根據上面的理論,我們可以通過修改某個filter的參數,去還原出一張另類的圖片,比較有趣的應用有deep dream和風格遷移,如下圖(圖片來源於網絡):
這里就暫時不具體介紹原理了,后面會找一些開源的項目去玩。
參考資料:台大李宏毅《機器學習-卷積神經網絡》
上面是對CNN進行的一個初步的介紹,后面會對深度學習部分進行系統的學習和整理,這里主要是對原理有個初步的認識,因此很多都是概念性的東西,后續會進一步添加補充。