《深度學習入門》讀書筆記5:卷積神經網絡

一丶一般的神經網絡與CNN

在這裏插入圖片描述
在這裏插入圖片描述

CNN 中新增了 Convolution 層 和 Pooling 層。連接順序是“Convolution - ReLU -(Pooling)”(Pooling層有時會被省略),之前的“Affine - ReLU”連接被替換成了“Convolution -ReLU -(Pooling)”連接

全連接層存在的問題:
圖像通常是高、長、通道方向上的3維形狀但是,向全連接層輸入時,需要將3維數據拉平爲1維數據,輸入圖像就是1通道、高28像素、長28像素的(1, 28, 28)形狀,但卻被排成1列,以784個數據的形式輸入到最開始的Affine層無法利用與形狀相關的信息比如,空間上鄰近的像素爲相似的值

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

有一點需要注意
神經網絡的矩陣的乘積運算在幾何學領域被稱爲“仿射變換”。因此,這裏將進行仿射變換的處理實現爲“Affine層”,所以在Affine層的網絡中權重矩陣的大小爲(前一層神經元數量,後一次神經元數量),但使用卷積層的時候使用的是卷積運算,權重矩陣的大小不需要由前一層和當前神經元數量來決定(不過當然過濾器的通道數需要由前一層決定),是由你需要的濾波器的大小決定的,這也就是兩種初始化對網絡的區別
# 普通網絡
def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
  self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
  self.params['b1'] = np.zeros(hidden_size)
  self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
# CNN
def __init__(self, input_dim=(1, 28, 28), 
                 conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
                 hidden_size=100, output_size=10, weight_init_std=0.01):
  self.params['W1'] = weight_init_std *   np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
  self.params['b1'] = np.zeros(filter_num)
  self.layers['Conv1'] = Convolution(self.params['W1'],   self.params['b1'],conv_param['stride'], conv_param['pad'])

二丶卷積神經網絡實現

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

① 卷積層實現

這裏就不說卷積運算了:將各個位置上濾波器的元素和輸入的對應元素相乘,然後再求和
填充也不說了:用填充主要是爲了調整輸出的大小,比如,對大小爲(4, 4)的輸入數據應用(3, 3)的濾波器時,輸出大小變爲(2, 2),相當於輸出大小比輸入大小縮小了 2個元素。這在反覆進行多次卷積運算的深度網絡中會成爲問題。

如果老老實實地實現卷積運算,估計要重複好幾層的for語句。這樣的實現有點麻煩,而且,NumPy中存在使用for語句後處理變慢的缺點(NumPy中,訪問元素時最好不要用for語句)。這裏,我們不使用for語句,而是使用im2col這個便利的函數進行簡單的實現,im2col會把輸入數據展開以適合濾波器(權重),把包含批數量的4維數據轉換成了2維數據

  • 比如下面 10個 通道 爲 3的7 × 7的數據 數據 然後濾波器的大小爲5 × 5,步幅爲1,填充爲0
x2 = np.random.rand(10, 3, 7, 7) # 10個數據
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape)
>> (90,75)

在這裏插入圖片描述
上圖爲了便於觀察,將步幅設置得很大,以使濾波器的應用區
而在實際的卷積運算中,濾波器的應用區域幾乎都是重疊的。在濾波器的應用區域重疊的情況下,使用im2col展開後,展開後的元素個數會多於原方塊的元素個數。因此,使用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)
		# 用im2col展開輸入數據
        col = im2col(x, FH, FW, self.stride, self.pad)
        # 用reshape將濾波器展開爲2維數組
        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
  1. 卷積層的初始化方法將濾波器(權重)、偏置、步幅、填充作爲參數接收
  2. 濾波器是 (FN, C, FH, FW)的 4 維形狀。另外,FN、C、FH、FW分別是 Filter Number(濾波器數量)、Channel、Filter Height、Filter Width的縮寫,濾波器的這幾個參數是由權重W的shape獲取
  3. N, C, H, W 從輸入參數X的shape獲取

② 池化層實現

池化是縮小高、長方向上的空間的運算,除了Max池化之外,還有Average池化等
在這裏插入圖片描述
池化層的實現和卷積層相同,都使用im2col展開輸入數據,不過,池化
的情況下,在通道方向上是獨立的,這一點和卷積層不同。池化的應用區域按通道單獨展開

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

卷積運算

在這裏插入圖片描述

填充

數據應用(3, 3)的濾波器時,輸出大小變爲(2, 2),相當於輸出大小比輸入大小縮小了 2個元素。這在反覆進行多次卷積運算的深度網
絡中會成爲問題。爲什麼呢?因爲如果每次進行卷積運算都會縮小空間,那麼在使用填充主要是爲了調整輸出的大小。比如,對大小爲(4, 4)的輸入某個時刻輸出大小就有可能變爲 1,導致無法再應用卷積運算。
在這裏插入圖片描述

步幅

應用濾波器的位置間隔稱爲步幅(stride)
在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章