天天看點

卷積神經網絡:卷積層和池化層代碼實作,im2col代碼實作-python(convolution,pooling,im2col)

目錄

    • 1. im2col
    • 2.卷積層
    • 3.池化層

講前注意:

了解卷積層和池化層的代碼主要有兩個稍困難的點,一個是關于transpose軸變換與reshape的使用,一個是反向傳播的思想。關于反向傳播的思想大家可以看這。Affine層和RuLe層的實作思想會用在卷積層和池化層的實作上。

本文不講卷積層和池化層的原理,是代碼實踐,請了解原理後看代碼。代碼中有巨量注釋,不多闡述

注意前向傳播和反向傳播代碼在相反處理資料,軸變換不改變數組的元素,隻改變元素的結構,隻要對應索引值不變,那麼元素是不會變的。大家可以先把im2col當作黑盒,看完卷積層和池化層的實作之後再看會很清晰。大家看代碼一個要注意每個矩陣的transpose和shape。

1. im2col

im2col是講多元資料放在二維矩陣中友善計算。

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 = input_data.shape#N 是資料個數 C 是通道數 H 是高 W 是長
    out_h = (H + 2*pad - filter_h)//stride + 1 #計算輸出資料 即輸出特征圖 out_h 輸出特征圖高
    out_w = (W + 2*pad - filter_w)//stride + 1#計算輸入資料 即輸入特征特 out_w 輸出特征圖長
    # // 整除

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    #np.pad 填充數組input_data
    #constant_values沒有指派,預設填充0
    #第一次元(shape[0])前面填充0個後面填充0個
    #第二次元同理(0,0),第三第四次元同理(pad,pad)
    #資料量和通道預設(0,0),  咩咩咩咩咩咩咩咩,,長和高根據pad參數填充
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
    # col 6維數組
    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]
            # 一次操作取了N批C個通道上
            # col 的第三維y,第四維x指派 img第三次元y導y_max隔stride是步長,隔stride取一位,第四次元同理
            # 資料批量和通道數不變的
            #  filter_h也會簡寫為FH或fh

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    # transpose軸變換 軸變換的目的是改變索引順序友善reshape成想要的模樣
    #  filter_h也會簡寫為FH或fh
    # col.shape = (N*out_h*out_w,C*FH*FW)

    return col
           

這裡有一張圖,可以友善你了解im2col的循環到底做了什麼

卷積神經網絡:卷積層和池化層代碼實作,im2col代碼實作-python(convolution,pooling,im2col)

2.卷積層

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 濾波器長 C 通道數(和濾波器通道數一樣才能進行通道方向的卷積)
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = int((H + 2 * self.pad - FH) / self.stride + 1)
        out_w = int((W + 2 * self.pad - FW) / self.stride + 1)

        #im2col四維變二維,實際上把N放在了一次元,把C放在了二次元
        #im2col作為黑盒,詳細見im2col講解
        #實際上im2col是把元素相乘變為矩陣相乘
        col = im2col(x, FH, FW, self.stride, self.pad)
        #col.shape =(N*out_h*out_w,FH*FW*C)
        col_W = self.W.reshape(FN,-1).T
        #col_W.shape=(FN,FH*FW*C) 轉置後(FH*FW*C,FN)
        #其實col_W.shape=(FN,C*FH*FW),這裡提醒大家reshape是按索引順序來的是以不能直接col_W=reshape(-1,FN)
        #不過值相同是以我沒有按順序寫
        #矩陣乘法的規定了 濾波器通道數和輸入資料通道數必須相同,否則無法相乘

        out = np.dot(col,col_W) + self.b
        #out.shape = (N*out_h*out_w,FN)
        #out的一個元素每個通道上 一個濾波器與輸入資料對應元素乘積和 的和
        #行就是輸出資料的一個通道上每批資料的每個元素,列就是輸出資料的每個通道上的一批資料的一個元素 (一批指批處理)

        out = out.reshape(N, out_h, out_w, -1).transpose(0,3,1,2)
        #二維變成思維,然後再進行軸變換,把通道索引變到輸出資料長索引和輸出資料寬索引前面
        #其實就是變成了下一層輸入資料的模樣,要知道本層輸入資料也是(批,通道,高,長)
        #此時 out.shape = (N,FN,out_h,out_w)
        #本層濾波器的個數就是下一層輸入資料的通道數
        self.x = x
        self.col = col
        self.col_W = col_W

        return out


    def backward(self, dout):
        #根據誤差反向傳播思想,詳細見誤差反向傳播章節
        #與Affine層不同的此處會注釋一下

        FN, C, FH, FW = self.W.shape

        # 與forward相反 把通道索引變在最後
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)
        #transpose後 dout.shape=(批,out_h,out_w,通道)
        #reshape後dout.shape=(批*out_h*out_w,濾波器個數(輸出通道數))
        self.db = np.sum(dout, axis=0)
        #在forward中加了N*out_h*out_w個b,反向傳播就要加這些個dout

        self.dW = np.dot(self.col.T, dout)
        #反向傳播矩陣求導操作 dW.shape = (FH*FW*C,FN) 這四個參數熟悉吧,就是濾波器的參數也就是W該有的參數
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)
        #dw換成标準形式 也就是w的輸入形式

        dcol = np.dot(dout, self.col_W.T)
        #反向傳播矩陣求導操作 dcol.shape=(批*out_h*out_w,C*FH*FW)

        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
        #反向操作im2col
        return dx
  
           

3.池化層

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)
        #一行即是一個池化的應用區域内的值
        # col.shape=(N*C*out_h*out_w,pool_h*pool_w)

        arg_max = np.argmax(col, axis=1)
        #最大值索引數組
        out = np.max(col,axis=1)
        #每行隻留下最大值 max池化
        out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2)
        #重新reshape為四維後做軸變換調整為輸入資料标準形式

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)
        #forward的反向操作 此時dout.shape=(批,out_h,out_w,通道數)
        pool_size = self.pool_h * self.pool_w
        #pool_size 為im2col展開的列數
        dmax = np.zeros((dout.size, pool_size))
        #池化層輸出是所有im2col展開行的最大值,元素的個數即為原展開行的行數
        #dout.size = arg_max.size
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        #在最大值索引處賦予最大值

        dmax = dmax.reshape(dout.shape + (pool_size,))
        #dout.shape+pool_size是在dout的次元上又加了一個次元pool_size
        #dmax.shape = (批,out_h,out_w,C,pool_size)


        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        #dcol.shape = (N*out_h*out_w,C*pool_size)而pool_size = pool_h*pool_w 正式im2col的輸出
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)

        return dx
           

繼續閱讀