卷積神經(jīng)網(wǎng)絡(luò)(CNN)
1.神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)示意圖如下

相比于普通的神經(jīng)網(wǎng)絡(luò),卷積神經(jīng)網(wǎng)絡(luò)多了卷積層以及池化層,還增加了許多特有名詞,例如:填充、步幅、通道等。此外,各層中傳輸?shù)臄?shù)據(jù)是有形狀的數(shù)據(jù)(比如三維數(shù)據(jù))。下面開始介紹卷積層和池化層。
2.卷積層
首先,我們知道,全連接層存在一些問題,在處理三維數(shù)據(jù)時,向全連接層輸入數(shù)據(jù)時,需要將三維數(shù)據(jù)拉成一維數(shù)據(jù)。這就導(dǎo)致了數(shù)據(jù)的形狀被“忽視”了。全連接層會將全部的輸入數(shù)據(jù)作為相同的神經(jīng)元(同一維度的神經(jīng)元)處理,所以無法利用與形狀相關(guān)的信息。
而卷積層可以保持形狀不變。當(dāng)輸入數(shù)據(jù)是圖像時,卷積層會以3維 數(shù)據(jù)的形式接收輸入數(shù)據(jù),并同樣以3維數(shù)據(jù)的形式輸出至下一層。因此, 在CNN中,可以(有可能)正確理解圖像等具有形狀的數(shù)據(jù)。另外,CNN中,有時將卷積層的輸入輸出數(shù)據(jù)稱為特征圖(feature map)。其中,卷積層的輸入數(shù)據(jù)稱為輸入特征圖(input feature map),輸出數(shù)據(jù)稱為輸出特征圖(output feature map)。
2.1卷積運算
卷積層進行的處理就是卷積運算。下面舉個例子:

本例中,輸入大小是 (4, 4),濾波器大小是(3, 3),輸出大小是(2, 2),濾波器在有的例子中也被成為卷積核。
對于輸入數(shù)據(jù),卷積運算以一定間隔滑動濾波器的窗口并應(yīng)用。這里所 說的窗口是指圖中灰色的3×3的部分。




在上述圖中,濾波器中的參數(shù)對應(yīng)于全連接層的參數(shù),CNN中也有偏置,但偏置通常只有一個。
2.2填充
在進行卷積層的處理之前,有時要向輸入數(shù)據(jù)的周圍填入固定的數(shù)據(jù)(比 如0等),這稱為填充(padding),是卷積運算中經(jīng)常會用到的處理。

為什么要使用填充呢?使用填充主要是為了調(diào)整輸出的大小。我們在每次進行卷積運算都會縮小空間,那么在某個時刻輸出大小就有可能變?yōu)?,導(dǎo)致無法再應(yīng)用卷積運算。為了避免出現(xiàn)這樣的情況,就要使用填充。在剛才的例子中,將填充的幅度設(shè)為1,那么相對于輸入大小(4, 4),輸出大小也保持為原來的(4,4)。因此,卷積運算就可以在保持空間大小不變的情況下將數(shù)據(jù)傳給下一層。
2.3步幅
應(yīng)用濾波器的位置間隔稱為步幅(stride)。下面是步幅為1和步幅為2的情況。


由2.2節(jié)和2.3節(jié)可知,增大步幅輸出大小會變小,添加填充輸出大小會變大。我們可以根據(jù)步幅和填充來計算輸出大小。
假設(shè)輸入大小為(H,W),濾波器大小為(FH,FW),輸出大小為(OH,OW),填充為P,步幅為S。那么我們可以得到下面這個式子:

2.4三維數(shù)據(jù)的卷積運算
對于圖像來說,圖像是三維數(shù)據(jù),除了高、長方向之外,還需要處理通道方向。在進行卷積運算時候,當(dāng)通道方向上有多個特征圖時,會按通道 進行輸入數(shù)據(jù)和濾波器的卷積運算,并將結(jié)果相加,從而得到輸出。




我們需要注意的是,在三維的卷積運算中,輸入數(shù)據(jù)和過濾器的通道數(shù)要保持一致。
我們可以把上述例子中的輸入數(shù)據(jù)和過濾器當(dāng)成長方體方塊來考慮。把3維數(shù)據(jù)表示為多維數(shù)組時,書寫順序為(channel,height,width)。比如,通道數(shù)為C、高度為H、長度為W的數(shù)據(jù)的形狀可以寫成(C, H,W)。濾波器也一樣,要按(channel,height,width)的順序書寫。比如,通道數(shù)為C、濾波器高度為FH(Filter Height)、長度為FW(Filter Width)時,可以寫成(C,FH,FW)。

如果我們要在通道方向上有多個卷積運算的輸出,我們應(yīng)該這樣做,即用到多個濾波器。用圖片表示如下:

當(dāng)考慮到濾波器的數(shù)量時,那么數(shù)據(jù)就變成了4維數(shù)據(jù),濾波器的權(quán)重數(shù)據(jù)要按(output_channel,input_channel,height,width)的順序書寫。
在上圖中,每個通道只有一個偏置。這里,偏置的形狀是(FN,1,1), 濾波器的輸出結(jié)果的形狀是(FN,OH,OW)。這兩個方塊相加時,要對濾波 器的輸出結(jié)果(FN,OH,OW)按通道加上相同的偏置值。另外,不同形狀的 方塊相加時,可以基于NumPy的廣播功能輕松實現(xiàn)。
2.5卷積運算中的批處理
我們在卷積層進行批處理時,需要將在各層間傳遞的數(shù)據(jù)保存為4維數(shù)據(jù)。具體地講,就是按(batch_num,channel,height,width)的順序保存數(shù)據(jù)。

上圖批處理版的數(shù)據(jù)流中,在各個數(shù)據(jù)的開頭添加了批用的維度。數(shù)據(jù)作為4維的形狀在各層間傳遞。批處理中網(wǎng)絡(luò)間傳遞的是4維數(shù)據(jù),將N個數(shù)據(jù)進行打包處理,將N卷積運算匯成了一次卷積運算。
3.池化層
池化層是縮小高、長方向上的運算。下面是Max池化運算的示例:

除了,Max池化外,還有Average池化。相對于Max池化是從目標(biāo)區(qū)域中取出最大值,Average池化則是計算目標(biāo)區(qū)域的平均值。 在圖像識別領(lǐng)域,主要使用Max池化。
3.1池化層的特征
1.沒有要學(xué)習(xí)的參數(shù),池化只是從目標(biāo)區(qū)域中取最大值(或者平均值),所以不存在要學(xué)習(xí)的參數(shù)。
2.通道數(shù)不發(fā)生變化,計算是按通道獨立進行的。
3.對微小的數(shù)值變化具有魯棒性(健壯),輸入數(shù)據(jù)發(fā)生微小偏差時,池化仍會返回相同的結(jié)果
4.卷積層和池化層的實現(xiàn)
在這里我們使用了一個im2col函數(shù)。

它能將一個包含批處理的四維數(shù)據(jù)轉(zhuǎn)換成二維數(shù)據(jù)。
在濾波器的應(yīng)用區(qū)域重疊的情況下,使用im2col展開后,展開后的元素個數(shù)會多于原方塊的元素個數(shù)。因此,使用im2col的實現(xiàn)存在比普通的實現(xiàn)消耗更多內(nèi)存的缺點。但是,匯總成一個大的矩陣進行計算,對計算機的計算頗有益處。

上圖是卷積運算的濾波器處理的細(xì)節(jié):將濾波器縱向展開為1列,并計算和im2col展開 的數(shù)據(jù)的矩陣乘積,最后轉(zhuǎn)換(reshape)為輸出數(shù)據(jù)的大小。
4.1卷積層的實現(xiàn)
下面我們使用im2col來實現(xiàn)卷積層
class Convolution:
# 初始化
def __init__(self, W, b, stride = 1, pad = 0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
(FN, C, FH, FW) = self.W.shape
(N, C, H, W) = x.shape
out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1)
#reshape函數(shù)會自動計算-1維度上的元素個數(shù),以使多維數(shù)組的元素個數(shù)前后一致
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h,out_w,-1).transpose(0, 3, 1, 2)
# transpose會更改多維數(shù)組的軸的順序。
self.x = x
self.col = col
self.col_W = col_W
return out
def backword(self,dout):
(FN,C,FH,FW) = self.W.shape
dout = dout.transpose(0,2,3,1).reshape(-1,FN)
# 改變損失中數(shù)組的軸的順序,再用reshape轉(zhuǎn)為二維數(shù)組
self.db = np.sum(dout, axis=0)
self.dW = np.dot(self.col.T, dout)
self.dW = self.dW.transpose(1,0).reshape(FN,C,FH,FW)
# 用reshape轉(zhuǎn)為二維數(shù)組,再改變損失中數(shù)組的軸的順序
dcol = np.dot(dout,self.col_W.T)
dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
return dx
4.2池化層的實現(xiàn)
class Pooling:
def __init__(self, pool_h, pool_w,stride=1,pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
(N, C, H, W) = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
arg_max = np.argmax(col, axis = 1)
out = np.max(col, axis = 1)
out = out.reshape(N, out_h, out_w, C).reshape(0, 3, 2, 1)
self.x = x
self.arg_max = arg_max
return out
def backward(self, dout):
dout = dout.transpose(0,2,3,1)
pool_size = self.pool_h * self.pool_w
dmax = np.zeros(dout_size, pool_size)
dmax[np.arange(self.arg_max.size),self.arg_max.flatten()] = dout.flatten()
dmax = dmax.reshape(dout.shape + (pool_size,))
dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
dx = col2im(dcol, self.x.shape,self.pool_h, self.pool_w,self.stride,self.pad)
return dx
5.參數(shù)減少的原因
卷積神經(jīng)網(wǎng)絡(luò)參數(shù)映射參數(shù)比全連接層少的原因有兩個:
5.1權(quán)重共享

觀察發(fā)現(xiàn),特征檢測如垂直邊緣檢測如果適用于圖片的某個區(qū)域,那么它也可能適用于圖片的其他區(qū)域。也就是說,如果你用一個 3×3 的過濾器檢測垂直邊緣,那么圖片的左上角區(qū)域,以及旁邊的各個區(qū)域(左邊矩陣中藍色方框標(biāo)記的部分)都可以使用這個3×3的過濾器。每個特征檢測器以及輸出都可以在輸入圖片的不同區(qū)域中使用同樣的
參數(shù),以便提取垂直邊緣或其它特征。它不僅適用于邊緣特征這樣的低階特征,同樣適用于高階特征,例如提取臉上的眼睛,貓或者其他特征對象。
5.2稀疏連接

結(jié)合圖解釋下這個概念,圖中標(biāo)記的0是通過3×3的卷積計算得到的,它只依賴于這個3×3的輸入的單元格,右邊這個輸出單元(元素 0)僅與36個輸入特征中9個相連接。而且其它像素值都不會對輸出產(chǎn)生任影響,這就是稀疏連接的概念。