說明:這篇文章須要有一些相關的基礎知識,不然看起來可能比較吃力。後端
1.卷積與神經元數組
1.1 什麼是卷積?網絡
簡單來講,卷積(或內積)就是一種先把對應位置相乘而後再把結果相加的運算。(具體含義或者數學公式能夠查閱相關資料)ide
以下圖就表示卷積的運算過程:函數
(圖1)學習
卷積運算一個重要的特色就是,經過卷積運算,可使原信號特徵加強,而且下降噪音.優化
1.2 激活函數spa
這裏以經常使用的激活函數sigmoid爲例:設計
把上述的計算結果269帶入此公式,得出f(x)=13d
1.3 神經元
如圖是一我的工神經元的模型:
(圖2)
對於每個神經元,都包含如下幾部分:
x:表示輸入
w:表示權重
θ:表示偏置
∑wx:表示卷積(內積)
f :表示激活函數
o:表示輸出
1.4 圖像的濾波操做
對於一個灰度圖片(圖3) 用sobel算子(圖4)進行過濾,將獲得如圖5所示的圖片。
1.5小結
上面的內容主要是爲了統一一下概念上的認識:
圖1的藍色部分、圖2中的xn、圖3的圖像都是神經元的輸入部分;圖1的紅色部分數值值、圖2的wn值、圖4的矩陣值均可以叫作權重(或者濾波器或者卷積核,下文統稱權重)。而權重(或卷積核)的大小(如圖4的3×3)叫作接受域(也叫感知野或者數據窗口,下文統稱接受域)
2.卷積神經網絡
在介紹卷積神經網絡定義以前,先說幾種比較流行的卷積神經網絡的結構圖。
2.1 常見的幾種卷積神經網絡結構圖
(圖6)
(圖7)
(圖8)
(圖9)
圖8中的C-層、S-層是6中的Convolutions層和subsampling層的簡寫,C-層是卷積層,S-層是子抽樣和局部平均層。在圖6和圖7中C-層、S-層不是指具體的某 一個層,而是指輸入層和特徵映射層、特徵映射層和特徵映射層之間的計算過程,而特徵映射層則保持的是卷積、子抽樣(或下采樣)和局部平均的輸出結果。而圖 6和圖7的區別在於最終結果輸出以前是否有全鏈接層,而有沒有全鏈接層會影響到是否還須要一個扁平層(扁平層在卷積層和全鏈接層之間,做用是多維數據一維化)。
圖9中CONV層是卷積層(即C-層),可是新出現的RELU層和POOL層是什麼呢?RELU層實際上是激活層(relu只是激活函數的一種,sigmoid/tanh比較常見於全鏈接層,relu常見於卷積層),爲何會多出來一個激活層呢?請看下圖:
(圖10) (圖2,方便對比複製了過來)
神經元的完整的數學建模應該是圖10所示,與圖2相比,把原來在一塊兒的操做拆成了兩個獨立的操做:∑wx(卷積)和f(激活),所以多出了一個激活層。因此,在Keras中組建卷積神經網絡的話,便可以採用如圖2的方式(激活函數做爲卷積函數的一個參數)也可採用圖10所示的方式(卷積層和激活層分開)。激活層不須要參數。
Pool層,即池化層,其做用和S-層同樣:進行子抽樣而後再進行局部平均。它沒有參數,起到降維的做用。將輸入切分紅不重疊的一些 n×n 區域。每個區域就包含個值。從這個值計算出一個值。計算方法能夠是求平均、取最大 max 等等。假設 n=2,那麼4個輸入變成一個輸出。輸出圖像就是輸入圖像的1/4大小。若把2維的層展平成一維向量,後面可再鏈接一個全鏈接前向神經網絡。
從圖7能夠看出,不管是卷積層仍是池化層均可以叫作特徵映射層,而兩層之間的計算過程叫作卷積或者池化,可是這麼表述容易在概念上產生混淆,因此本文不採用這種表述方式,只是拿來做爲對比理解使用。
下面對上面的內容作一下總結:
卷積層(C-層或Convolutions層或CONV層或特徵提取層,下文統稱卷積層):主要做用就是進行特徵提取。
池化層(S-層或子抽樣局部平均層或下采樣局部平均層或POOL層,下文統稱池化層):主要做用是減少特徵圖,起到降維的做用。經常使用的方法是選取局部區域的最大值或者平均值。以下圖所示:
(圖11)
對於第一個卷積層來講(圖6的C1-層),一個特徵對應一個通道(或叫feature map或特徵映射或者叫濾波器,下文統稱特徵映射),例如三原色(RGB)的圖像就須要三個特徵映射層。可是通過第一個池化層(圖6的S2-層,PS:圖中錯標成了S1-層)以後,下一個特徵提取層 (圖6的C3-層)的特徵映射 (feature map)個數並不必定與開始的相同了(圖6中從8特徵變成了20特徵),通常狀況下會比初始的特徵映射個數多,由於根據視覺系統原理----底層的結構構成上層更抽象的結構,因此當前層的特徵映射是上一層的特徵映射的組合,也就是一個特徵映射會對應上一層的一個或多個特徵映射。
2.2 接受域和步長
2.2.1 接受域
(圖12)
如圖所示,中間的正方形都表示接受域,其大小爲5*5。這裏再重複一下:權重(卷積核)指的是數字,接受域指的是權重(卷積核)的大小。
2.2.2 步長
(圖13)
接受域的對應範圍從輸入的區域1移到區域2的過程,或者從區域3移動到區域4都涉及到一個參數:步長,即每次移動的幅度。在此例中的步長能夠表示成3或 者(3,3),單個3表示橫縱座標方向都移動3個座標點,若是(3,2)則表示橫向移動3個座標點,縱向移動2個座標點。每次移動是按一個方向移動,不是兩個方向都移 動(圖13中,從區域1移動到區域二、區域3,而後才移動到區域4,若是兩個方向都移動三個座標點則從區域1到了區域5,是不對的)。
2.3卷積神經網絡
2.3.1卷積神經網絡定義
卷積神經網絡是一個多層的神經網絡,每層由多個二維平面(特徵映射)組成,而每一個平面由多個獨立神經元組成。
2.3.2卷積神經網絡特色
卷積神經網絡是爲識別二維形狀而特殊設計的一個多層感知器,這種網絡結構對二維形狀的平移、比例縮放、傾斜或者共他形式的變形具備高度不變性。
卷積神經網絡是前饋型網絡。
2.3.3 卷積神經網絡的形式的約束
2.3.3.1 特徵提取
每個神經元從上一層的局部接受域獲得突觸輸人,於是迫使它提取局部特徵。一旦一個特徵被提取出來,只要它相對於其餘特徵的位置被近似地保留下來,它的精確位置就變得沒有那麼重要了。
下圖兩個X雖然有點稍微變形,可是仍是能夠識別出來都是X。
(圖14)
2.3.3.2 特徵映射
網絡的每個計算層都是由多個特徵映射組成的,每一個特徵映射都是平面形式的。平面中單獨的神經元在約束下共享相同的突觸權值集(權重),這種結構形式具備以下的有益效果:
a.平移不變性(圖14的兩個X)
b.自由參數數量的縮減(經過權值共享實現)
這裏重點說下共享權值,以及卷積層神經單元個數的肯定問題。
以圖12的結構爲基礎,作如下假設:
假設一:輸入範圍在橫縱方向與接受域正好是倍數關係
(圖15)
先說沒有接受域的狀況,若是特徵映射有9個神經元,這9個神經元與9*9的輸入作全鏈接,那麼須要的權重個數爲9*9*9=729個。添加了接受域後,9個神經元 分別與接受域作連接,這種狀況下若是一個神經元對應一組權重,則有9*9=81個權重值,再假如這一組權重的值是固定的(可參考圖12的接受域值,這組值不變,而不是每一個值相等),那麼就只剩下了9個權重值。從729個權重值減小到9個權重值,這個過程就是權值共享的過程。也許從729減小到9個,差異不是特別大,若是輸入是1000*1000,神經元個數是1000萬呢?這樣的話由權值共享致使的減小的計算量就很客觀了。
假設二:輸入範圍在橫縱方向與接受域不是是倍數關係
(圖16 ) (圖17)
以圖15爲參考,只是把輸入域變成圖16所示的8*8的狀況,若步長仍是(3,3),那麼橫縱向各移動一次後就沒法移動了,即接受域的可視範圍爲粉色邊框內的6*6的區域(圖16的粉紅框A區域和B區域),外側的2行2列的數據是讀不到的。這種狀況有兩種處理方式,一是直接放棄,可是這種方式幾乎不用,另一種方式在周圍補0(圖17所示),使輸入域變成(3,3)的倍數。
經過上述內容,能夠知道每一個特徵映射的神經元的個數是由輸入域大小、接受域、步長共同決定的。如圖15,9*9的輸入域、3*3的接受域、(3,3)的步長,可 以計算出特徵映射的神經元個數爲9個。
2.3.3.3 子抽樣
每一個卷積層後面跟着一個實現局部平均和子抽樣的計算層(池化層),由此特徵映射的分辨率下降。這種操做具備使特徵映射的輸出對平移和其餘形式的變形 的敏感度降低的做用。
3.用Keras構建一個卷積神經網絡
3.1 卷積神經網絡結構圖
(圖18)
3.2 Keras中的輸入及權重
3.2.1 示例代碼(小數字方便打印驗證)
PS:Convolution2D 前最好加ZeroPadding2D,不然須要本身計算輸入、kernel_size、strides三者之間的關係,若是不是倍數關係,會直接報錯。
model = Sequential() model.add(ZeroPadding2D((1, 1), batch_input_shape=(1, 4, 4, 1))) model.add(Convolution2D(filters=1,kernel_size=(3,3),strides=(3,3), activation='relu', name='conv1_1')) model.layers[1].get_weights() model.add(ZeroPadding2D((1, 1))) model.add(Convolution2D(filters=1,kernel_size=(2,3),strides=(2,3), activation='relu', name='conv1_2')) model.layers[3].get_weights()
3.2.2 代碼解釋
1) batch_input_shape=(1, 4, 4, 1)
表示:輸入1張1通道(或特徵映射)的4*4的數據.由於我採用的是用Tensorflow作後端,因此採用「channels_last」數據格式。
2) filters = 1
表示:有1個通道(或特徵映射)
3) kernel_size = (2,3)
表示:權重是2*3的矩陣
4) striders = (2,3)
表示:步長是(2,3)
3.2.3 權重(默認會初始化權重)
3.2.3.1 第一個model.layers[1].get_weights()輸出(格式整理後)
(圖19)
3.2.3.2 第二個model.layers[1].get_weights()輸出
(圖20)
3.2.3.3 另外兩個輸出(沒有寫的參數值同上)
1)batch_input_shape=(1, 4, 4, 3),filters = 1, kernel_size = (3,3)
(圖21)
2) batch_input_shape=(1, 4, 4, 3),filters = 3, kernel_size = (2,3)
(圖22)
3.2.4 小結
圖19到圖22的內容是爲了說明在Keras中權重的表示方式,爲下面的實驗作準備。
經過以上參數得出的權重對比,當權重是二維矩陣(n*n)時:
1) [[[…]]]:表示橫軸數目
2) [[…]]:表示縱軸數目
3) […]:表示通道的個數。圖21和圖22的區別:由於圖22是3通道3filter,因此每一個通道對應一個filter(圖20),而圖22至關於把3個圖20合一塊兒了。
4) […]內逗號隔開的數:filter的個數
5) [[[[…]]]]:這個可能表明層數(前面幾個參數是平面的,這個參數是立體的。在下面的實驗中沒有獲得具體驗證,只是根據最外層是5個方括號推測的)
3.3 sobel算子轉化成權重
由3.2.4的結論,能夠把圖4的sobel算子轉換成Keras中的權重:
weights = [[[[[-1]],[[0]],[[1]]],[[[-2]],[[0]],[[2]]],[[[-1]],[[0]],[[1]]]]]
weights =np.array(weights)
PS:注意最外圍是5個方括號
3.4 實驗代碼
from PIL import Image import numpy as np from keras.models import Sequential from keras.layers import Convolution2D, ZeroPadding2D, MaxPooling2D from keras.optimizers import SGD ''' 第一步:讀取圖片數據 說明:這個過程須要安裝pillow模塊:pip install pillow ''' ##1張1通道的256*256的灰度圖片 ##data[0,0,0,0]:表示第一張圖片的第一個通道的座標爲(0,0)的像素值 img_width, img_height = 256, 256 data = np.empty((1,1,img_width,img_height),dtype="float32") ##打開圖片 img = Image.open("D:\\keras\\lena.jpg") ##把圖片轉換成數組形式 arr = np.asarray(img,dtype="float32") data[0,:,:,:] = arr ''' 第二步:設置權重 說明:注意最外圍是5個方括號 ''' weights = [[[[[-1]],[[0]],[[1]]],[[[-2]],[[0]],[[2]]],[[[-1]],[[0]],[[1]]]]] weights =np.array(weights) ''' 第三步:組織卷積神經網絡 說明: 1.由於實驗採用的是默認的tensorflow後端,而輸入圖像是channels_first模式,因此注意data_format參數的設置 2.爲了實驗的效果,因此strides、pool_size等參數設置成了1 ''' ##第一次卷積 model = Sequential() model.add(ZeroPadding2D(padding=(2, 2), data_format='channels_first', batch_input_shape=(1, 1,img_width, img_height))) model.add(Convolution2D(filters=1,kernel_size=(3,3),strides=(1,1), activation='relu', name='conv1_1', data_format='channels_first')) model.set_weights(weights) ##第二次卷積 model.add(ZeroPadding2D((1, 1))) model.add(Convolution2D(filters=1,kernel_size=(3,3),strides=(1,1), activation='relu', name='conv1_2',data_format='channels_first')) model.set_weights(weights) ##池化操做 model.add(ZeroPadding2D((0, 0))) model.add(MaxPooling2D(pool_size=1, strides=None,data_format='channels_first')) ''' 第四步: 設置優化參數並編譯網絡 ''' # 優化函數,設定學習率(lr)等參數 sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) # 使用mse做爲loss函數 model.compile(loss='mse', optimizer=sgd, class_mode='categorical') ''' 第五步:預測結果 ''' result = model.predict(data,batch_size=1,verbose=0) ''' 第六步:保存結果到圖片 ''' img_new=Image.fromarray(result[0][0]).convert('L') img_new.save("D:\\keras\\tt123.jpg")
3.5 實驗效果
(圖23) (圖24)
圖24是池化層參數改成pool_size=2時的效果。