1. 程式人生 > >【深度學習】基於im2col的展開Python實現卷積層和池化層

【深度學習】基於im2col的展開Python實現卷積層和池化層

一、回顧

上一篇 我們介紹了,卷積神經網的卷積計算和池化計算,計算過程中視窗一直在移動,那麼我們如何準確的取到視窗內的元素,並進行正確的計算呢?

另外,以上我們只考慮的單個輸入資料,如果是批量資料呢?

首先,我們先來看看批量資料,是如何計算的

二、批處理

在神經網路的處理中,我們一般將輸入資料進行打包批處理,通過批處理,能夠實現處理的高效化和學習時對mini-batch的對應

自然,我們也希望在卷積神經網路的卷積運算中也使用批處理,為此,需要將在各層間傳遞的資料儲存為四維資料

在這裡插入圖片描述

如上圖

  • 輸入資料:批數目為3,通道為3
  • 卷積核:數目為3,通道為3
  • 輸出資料:數目為3,通道為1

三、四維資料

如上所述,CNN中各層將傳遞的資料是四維資料,例如:

  • 資料形狀為(10,1,28,28),表示10個高為28,長為28,通道為1的資料

在這裡插入圖片描述

CNN中處理四維資料,按照以上的操作會很複雜,但是通過im2col這個技巧,就會變得很簡單

四、im2col

  • 對於輸入資料

在這裡插入圖片描述

  • 對於卷積核
    在這裡插入圖片描述

輸入資料展開以適合卷積核(權重)

  • 輸入資料,將應用卷積核的區域(3維資料)橫向展開為一行
  • 卷積核,縱向展開為1列
  • 計算乘積即可
  • 批量計算
    在這裡插入圖片描述

五、python實現im2col和col2im

  • np.pad()函式
import numpy as np

A = np.arange(1,5).reshape(2,2)  # 將1,2,3,4轉換為2*2的矩陣
print(A)

B = np.pad(A,((1,1),(2,2)),'constant')  
# ((1,1),(2,2))
# 對A矩陣進行擴充,(1,1)表示上下各加一行,(2,2)左右各加兩列
print(B)
輸出為:
[[1 2]
 [3 4]]
 
[[0 0 0 0 0 0]
 [0 0 1 2 0 0]
 [0 0 3 4 0 0]
 [0 0 0 0 0 0]]

1、im2col

import numpy as np

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """
    Parameters
    ----------
    input_data : 由(資料量, 通道, 高, 長)的4維陣列構成的輸入資料
    filter_h : 卷積核的高
    filter_w : 卷積核的長
    stride : 步幅
    pad : 填充

    Returns
    -------
    col : 2維陣列
    """
    # 輸入資料的形狀
    # N:批數目,C:通道數,H:輸入資料高,W:輸入資料長
    N, C, H, W = input_data.shape  
    out_h = (H + 2*pad - filter_h)//stride + 1  # 輸出資料的高
    out_w = (W + 2*pad - filter_w)//stride + 1  # 輸出資料的長
    # 填充 H,W
    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    # (N, C, filter_h, filter_w, out_h, out_w)的0矩陣
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
    
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
    # 按(0, 4, 5, 1, 2, 3)順序,交換col的列,然後改變形狀
    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col

# 測試
x = np.random.rand(3, 3, 4, 4) # 隨機數生成
print(im2col(x,2,2,2,0))
print(im2col(x,2,2,2,0).shape)
輸出為:
[[0.8126537  0.0124403  0.98329453 0.57534957 0.01075175 0.28476833 0.55240652 0.71247792 0.8866451  0.08312604 0.82491841 0.53558742]
 [0.29177981 0.50891739 0.58534285 0.18979016 0.82281101 0.82324137 0.56737161 0.31075336 0.02638588 0.63497472 0.32696265 0.96400363]
 [0.18770887 0.80279488 0.80743415 0.01885739 0.6043541  0.1325915 0.99802281 0.70238769 0.03320778 0.21225932 0.73413182 0.68671415]
 [0.63299868 0.71823646 0.81703541 0.20652069 0.05803092 0.78660436 0.86116481 0.24152935 0.75596431 0.97947061 0.84386563 0.53657106]
 [0.77410888 0.92973798 0.42845759 0.20494453 0.55320755 0.86069213 0.14749488 0.5110566  0.19249778 0.38564893 0.78868462 0.49548582]
 [0.28778559 0.67286705 0.6351968  0.50743453 0.42905218 0.20382354 0.04566382 0.32610886 0.60199126 0.21139752 0.06912991 0.69890244]
 [0.03473951 0.67443498 0.53320896 0.44542062 0.96787968 0.92660522 0.8726162  0.54056736 0.62510367 0.12935292 0.35858458 0.88899527]
 [0.08295843 0.86116853 0.11507337 0.27507467 0.43662151 0.23341227 0.66133038 0.32065362 0.07386012 0.45717299 0.00857706 0.26429706]
 [0.5203922  0.79072121 0.0861702  0.64400793 0.76287695 0.53397396 0.61997931 0.88647105 0.07416818 0.23701745 0.91067976 0.14036269]
 [0.92652241 0.90052592 0.28945134 0.10758509 0.66142524 0.81998174 0.29023353 0.10070337 0.84680753 0.38787205 0.62224245 0.25184519]
 [0.00896273 0.86085848 0.72448385 0.97710942 0.27221302 0.30631167 0.80608232 0.92652234 0.35904511 0.26833127 0.72821478 0.72382557]
 [0.31399633 0.12624691 0.37392867 0.71121627 0.98641763 0.01551433 0.82284883 0.67615753 0.21826556 0.90338993 0.41159445 0.98961608]]
(12, 12)

我們設定如上圖中的形狀

  • 輸入資料:(3,3,4,4)
  • 卷積核:(2,2)
  • 結果為:(12,12)

結果和圖中的結果一樣,12行(3個數據,每個資料4行),每行有12個元素(應用(2,2)卷積核1次,三個通道,322=12)

2、col2im

def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    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]

六、實現卷積層

class Convolution:
	# 初始化權重(卷積核4維)、偏置、步幅、填充
    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)
        # 卷積核轉換為列,展開為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

七、實現池化層

在這裡插入圖片描述

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

總結

  • 本篇的難點在於資料的展開和逆轉換
  • 關於卷積層和池化層的計算也比較好理解,但是程式碼實現有點繞,不過原理和神經網路中的全連線層實現方式一樣