卷積神經網絡——基本架構


卷積神經網絡

1. 整體結構

相鄰層的所有神經元之間都有連接,這稱為全連接(fully-connected)

在之前使用的全連接神經網絡中,Affine層后面跟着激活函數ReLU層(或者Sigmoid 層)。這里堆疊了4 層“Affine-ReLU”組合,然后第5 層是Affine層,最后由Softmax層輸出最終結果(概率)。

AffineReLU

在CNN中出現了新的卷積層和池化層
CNN

CNN 的層的連接順序是“Convolution - ReLU -(Pooling)”(Pooling 層有時會被省略)。這可以理解為之前的“Affi ne - ReLU”連接被替換成了“Convolution -ReLU -(Pooling)”連接。

在CNN中,靠近輸出的層中使用了之前的Affine-ReLU組合。此外最后的輸出層還是使用了Affine-softmax組合。這些都是CNN中比較常見的結構

2. 卷積層

2.1 全連接層中存在的問題

全連接層忽視了數據的形狀,在全連接層中輸入數據是圖像的時候,需要將圖像的高,長,通道方向上的3維形狀拉平為1維數據。前面提到的使用了MNIST數據集的例子中,輸入圖像就是1 通道、高28 像素、長28 像素的(1, 28, 28)形狀,但卻被排成1 列,以784 個數據的形式輸入到最開始的Affine層。

圖像是3 維形狀,這個形狀中應該含有重要的空間信息。比如,空間上鄰近的像素為相似的值、RBG的各個通道之間分別有密切的關聯性、相距較遠的像素之間沒有什么關聯等,3 維形狀中可能隱藏有值得提取的本質模式。但是,因為全連接層會忽視形狀,將全部的輸入數據作為相同的神經元(同一維度的神經元)處理,所以無法利用與形狀相關的信息。

而卷積層可以保持形狀不變。當輸入數據是圖像時,卷積層會以3 維數據的形式接收輸入數據,並同樣以3 維數據的形式輸出至下一層。因此,在CNN中,可以(有可能)正確理解圖像等具有形狀的數據。

CNN中,有時將卷積層的輸入輸出數據稱為特征圖(feature map)。其中,卷積層的輸入數據稱為輸入特征圖(input feature map),輸出數據稱為輸出特征(output feature map)。

2.2 卷積運算

卷積層進行的處理就是卷積運算。卷積運算相當於圖像處理中的“濾波器運算”。

Conv

如圖所示,卷積運算對輸入數據應用濾波器。在這個例子中,輸入數據是有高長方向的形狀的數據,濾波器也一樣,有高長方向上的維度。假設用(height, width)表示數據和濾波器的形狀,則在本例中,輸入大小是(4, 4),濾波器大小是(3, 3),輸出大小是(2, 2)。另外,有的文獻中也會用“核”這個詞來表示這里所說的“濾波器”。

卷積運算以一定的間隔華東濾波器窗口並應用。將各個位置上濾波器元素和輸入的對應元素相乘,再求和,有時將這個計算稱之為乘積累加運算。然后將這個結果保存帶輸出的對應位置。將這個過程在所有位置都進行一遍,就可以得到卷積運算的輸出。

howToConv

在全連接的神經網絡中,除了權重參數,還存在偏置。CNN中,濾波器的參數就對應之前的權重。並且,CNN中也存在偏置。

bias

2.3 填充

在進行卷積層的處理之前,有時要向輸入數據的周圍填入固定的數據(比
如0 等),這稱為填(padding),是卷積運算中經常會用到的處理。比如,在圖的例子中,對大小為(4, 4) 的輸入數據應用了幅度為1 的填充。“幅度為1 的填充”是指用幅度為1 像素的0 填充周圍。

padding

通過填充,大小為(4, 4) 的輸入數據變成了(6, 6) 的形狀。然后,應用大小為(3, 3) 的濾波器,生成了大小為(4, 4) 的輸出數據。這個例子中將填充設成了1,不過填充的值也可以設置成2、3 等任意的整數。在圖7-5的例子中,如果將填充設為2,則輸入數據的大小變為(8, 8);如果將填充設為3,則大小變為(10, 10)。

2.4 步幅

應用濾波器的位置間隔稱為步幅(stride)

增大步幅后,輸出大小會變小。而增大填充后,輸出大小會變大。
這里,假設輸入大小為(H,W),濾波器大小為(FH, FW),輸出大小為(OH,OW),填充為P,步幅為S。此時,輸出大小可通過式子進行計算。

\[\begin{aligned} &OH = \frac{H+2P-FH}{S} +1 \\ &OW = \frac{W+2P-FW}{S}+1 \end{aligned} \]

這里需要注意的是,雖然只要代入值就可以計算輸出大小,但是所設定的值必須使式中的\(\frac{H+2P-FH}{S}\)\(\frac{W+2P-FW}{S}\)分別可以除盡。當輸出大小無法除盡時(結果是小數時),需要采取報錯等對策。但是,根據深度學習的框架的不同,當值無法除盡時,有時會向最接近的整數四舍五入,不進行報錯而繼續運行。

2.5 3維數據的卷積運算

這里以3 通道的數據為例,展示了卷積運算的結果。和2 維數據時相比,可以發現縱深方向(通道方向)上特征圖增加了。通道方向上有多個特征圖時,會按通道進行輸入數據和濾波器的卷積運算,並將結果相加,從而得到輸出。

3DConv

需要注意的是,在3維數據的卷積運算中,輸入數據和濾波器的通道數
要設為相同的值。在這個例子中,輸入數據和濾波器的通道數一致,均為3。濾波器大小可以設定為任意值(不過,每個通道的濾波器大小要全部相同)。這個例子中濾波器大小為(3, 3),但也可以設定為(2, 2)、(1, 1)、(5, 5) 等任意值。再強調一下,通道數只能設定為和輸入數據的通道數相同的值.

2.6 結合方塊思考

將數據和濾波器結合長方體的方塊來考慮,3 維數據的卷積運算會很容易理解。

thiking

方塊是如圖所示的3 維長方體。把3 維數據表示為多維數組時,書寫順序為(channel, height, width)。比如,通道數為C、高度為H、長度為W的數據的形狀可以寫成(C,H,W)。濾波器也一樣,要按(channel,height, width)的順序書寫。比如,通道數為C、濾波器高度為FH(FilterHeight)、長度為FW(Filter Width)時,可以寫成(C, FH, FW)。

如果要在通道方向上也擁有多個卷積運算的輸出,就需要用到多個濾波器(權重)

thinking2

通過應用FN個濾波器,輸出特征圖也生成了FN個。如果將這FN個特征圖匯集在一起,就得到了形狀為(FN, OH,OW) 的方塊。將這個方塊傳給下一層,就是CNN的處理流。

關於卷積運算的濾波器,也必須考慮濾波器的數量。因此,作為4 維數據,濾波器的權重數據要按(output_channel, input_channel, height, width) 的順序書寫。比如,通道數為3、大小為5 × 5 的濾波器有20 個時,可以寫成(20, 3, 5, 5)。

2.7 批處理

我們希望卷積運算也同樣對應批處理。為此,需要將在各層間傳遞的數據保存為4 維數據。具體地講,就是按(batch_num, channel, height, width)的順序保存數據。比如,將處理改成對N個數據進行批處理時,數據的形狀如圖所示。

圖的批處理版的數據流中,在各個數據的開頭添加了批用的維度。像這樣,數據作為4 維的形狀在各層間傳遞。這里需要注意的是,網絡間傳遞的是4 維數據,對這N個數據進行了卷積運算。也就是說,批處理將N次的處理匯總成了1 次進行。

CNNBatch

3. 池化層

池化是縮小高、長方向上的空間的運算。比如,如圖所示,進行將2 × 2的區域集約成1個元素的處理,縮小空間大小。

MaxPooling

例子是按步幅2 進行2 × 2 的Max池化時的處理順序。“Max池化”是獲取最大值的運算,“2 × 2”表示目標區域的大小。如圖所示,從2 × 2 的區域中取出最大的元素。此外,這個例子中將步幅設為了2,所以2 × 2 的窗口的移動間隔為2 個元素。另外,一般來說,池化的窗口大小會和步幅設定成相同的值。比如,3 × 3 的窗口的步幅會設為3,4 × 4 的窗口的步幅會設為4 等。

3.1 池化層的特征

沒有要學習的參數.

池化層和卷積層不同,沒有要學習的參數。池化只是從目標區域中取最大值(或者平均值),所以不存在要學習的參數。

通道數不發生變化.
經過池化運算,輸入數據和輸出數據的通道數不會發生變化。如圖所示,計算是按通道獨立進行的

PoolingFeature

對微小位置變化具有魯棒性.

對數據發生微小偏差時,池化人會返回相同的結果。因此,池化對輸入數據的微小偏差具有魯棒性。

4. 卷積層和池化層的實現

CNN中各層間傳遞的數據是4維數據

4.1 基於im2col的展開

如果老老實實地實現卷積運算,估計要重復好幾層的for語句。這樣的實現有點麻煩,而且,NumPy中存在使用for語句后處理變慢的缺點(NumPy中,訪問元素時最好不要用for語句)。

im2col是一個函數,將輸入數據展開以適合濾波器(權重)

im2col會把輸入數據展開以適合濾波器(權重)。具體地說,如圖所示,對於輸入數據,將應用濾波器的區域(3 維方塊)橫向展開為1列。im2col會在所有應用濾波器的地方進行這個展開處理。

im2col

使用im2col展開輸入數據后,之后就只需將卷積層的濾波器(權重)縱向展開為1 列,並計算2 個矩陣的乘積即可(參照圖7-19)。這和全連接層的Affine層進行的處理基本相同。

基於im2col方式的輸出結果是2 維矩陣。因為CNN中數據會保存為4 維數組,所以要將2 維輸出數據轉換為合適的形狀。以上就是卷積層的實現流程。

im2colCa

4.2 卷積層的實現

im2col是一個已經實現的函數,不必關心其內部結構,許多深度學習框架都提供了im2col的實現

im2col (input_data, filter_h, filter_w, stride=1, pad=0)

  • input_data―由(數據量,通道,高,長)的4維數組構成的輸入數據
  • filter_h―濾波器的高
  • filter_w―濾波器的長
  • stride―步幅
  • pad―填充

im2col會考慮濾波器大小、步幅、填充,將輸入數據展開為2 維數組。

來實際使用一下這個im2col

import sys, os
sys.path.append(os.pardir)
from common.util import im2col
x1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape) # (9, 75)
x2 = np.random.rand(10, 3, 7, 7) # 10個數據
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape) # (90, 75)

這里舉了兩個例子。第一個是批大小為1、通道為3 的7 × 7 的數據,第二個的批大小為10,數據形狀和第一個相同。分別對其應用im2col函數,在這兩種情形下,第2 維的元素個數均為75。這是濾波器(通道為3、大小為5 × 5)的元素個數的總和。批大小為1 時,im2col的結果是(9, 75)。而第2個例子中批大小為10,所以保存了10 倍的數據,即(90, 75)。

現在使用im2col實現卷積層

class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 中間數據(backward時使用)
        self.x = None   
        self.col = None
        self.col_W = None
        
        # 權重和偏置參數的梯度
        self.dW = None
        self.db = None

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        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)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx

卷積層的初始化方法將濾波器(權重)、偏置、步幅、填充作為參數接收。濾波器是(FN, C, FH, FW) 的4 維形狀。另外,FN、C、FH、FW 分別是Filter Number(濾波器數量)、Channel、Filter Height、Filter Width 的縮寫。

col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T # 濾波器的展開
out = np.dot(col, col_W) + self.b

用im2col展開輸入數據,並用reshape將濾波器展開為2 維數組。然后,計算展開后的矩陣的乘積。

展開濾波器的部分,將各個濾波器
的方塊縱向展開為1 列。這里通過reshape(FN,-1) 將參數指定為-1,這是reshape的一個便利的功能。通過在reshape時指定為-1,reshape函數會自動計算-1維度上的元素個數,以使多維數組的元素個數前后一致。 比如,(10, 3, 5, 5) 形狀的數組的元素個數共有750 個,指定reshape(10,-1) 后,就會轉換成(10, 75) 形狀的數組。

forward的實現中,最后會將輸出大小轉換為合適的形狀。轉換時使用了NumPy的transpose函數。transpose會更改多維數組的軸的順序。如圖所示,通過指定從0 開始的索引(編號)序列,就可以更改軸的順序。

numpyTranspose

以上就是卷積層的forward處理的實現。通過使用im2col進行展開,基本上可以像實現全連接層的Affine層一樣來實現。接下來是卷積層的反向傳播的實現,因為和Affine層的實現有很多共通的地方,所以就不再介紹。但有一點需要注意,在進行卷積層的反向傳播時,必須進行im2col的逆處理。

除了使用col2im這一點,卷積層的反向傳播和Affine 層的實現方式都一樣。卷積層的反向傳播的實現如下。

def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    """

    Parameters
    ----------
    col :
    input_shape : 輸入數據的形狀(例:(10, 1, 28, 28))
    filter_h :
    filter_w
    stride
    pad

    Returns
    -------

    """
    N, C, H, W = input_shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1
    col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)

    img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]

    return img[:, :, pad:H + pad, pad:W + pad]

4.3池化層的實現

池化層的實現和卷積層相同,都使用im2col展開輸入數據。不過,池化情的況下,在通道方向上是獨立的,這一點和卷積層不同。具體地講,如圖所示,池化的應用區域按通道單獨展開。

Pooing

像這樣展開之后,只需對展開的矩陣求各行的最大值,並轉換為合適的形狀即可

HowPoolingWork

上面就是池化層的forward處理的實現流程。下面來看一下Python的實現示例

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
        
        self.x = None
        self.arg_max = None

    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).transpose(0, 3, 1, 2)

        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

最大池化的反向傳播沒有需要學習的參數,因此,在卷積神經網絡的訓練中,Pooling層需要做的僅僅是將誤差項傳遞到上一層,而沒有梯度的計算。
對於max pooling,下一層的誤差項的值會原封不動的傳遞到上一層對應區塊中的最大值所對應的神經元,而其他神經元的誤差項的值都是0;
所以只是使用np.zeros生成形狀相同的全0數組。在forward階段就記錄下了Max值的位置,最后在反向傳播時,將最大值填入對應位置,其余為0

池化的反向傳播


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM