# 1.卷積層
這裡為了叙述簡便,僅考慮一個資料(三維)。
卷積層前向函數實作原理:将輸入三維資料的每個應用濾波器的部分展平為一行得到輸入資料矩陣,将每個濾波器展平為一列得到權重矩陣,再将輸入資料矩陣與權重矩陣相乘得到二維的中間資料,最後将二維中間資料reshape為三維輸出資料。(其中展平三維資料至二維資料用到了im2col函數。這種降維的方法可以有效利用線性代數庫,以實作高效的運算)
im2col函數的詳細圖解,參考連結https://blog.csdn.net/mrhiuser/article/details/52672824
卷積層反向函數實作原理:将上遊傳來的四維導數轉換為二維矩陣,利用誤差反向傳播法的計算公式求得權重、偏置的二維梯度,以及将向下遊傳遞的導數的二維形式,利用reshape函數将權重梯度矩陣轉成四維形狀,利用col2im函數将求得的二維形式導數轉換為四維并輸出。(其中的col2im函數與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 # 将四維權重資料,各維值依次賦給FN(濾波器個數)、C(通道數)、FH(濾波器高度)、FW(濾波器寬度)
N,C,H,W = x.shape # 将四維輸入資料,各維值依次賦給N(輸入資料個數)、C(通道數)、H(輸入資料的高度)、W(輸入資料的寬度)
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) # 通過im2col函數将輸入四維資料轉換為二維矩陣
col_W = self.W.reshape(FN,-1).T # 通過reshape函數将權重四維資料轉換為二維矩陣(運用-1表示得到的資料形狀為FN*(總元素個數/FN))
out = np.dot(col,col_W)+self.b # 類似Affine層的做法,将輸入二維矩陣乘以權重二維矩陣加上偏置,得到二維矩陣版輸出結果out
out = out.reshape(N,out_h,out_w,-1).transpose(0,3,1,2) # 将二維矩陣版輸出結果轉換為計算出的四維輸出形狀,transpose(0,3,1,2)表示将out資料格式(N,out_h,out_w,C)轉換為(N,C,out_h,out_w)
# 為反向傳播保留中間結果
self.x = x # 保留輸入四維資料為執行個體變量x
self.col = col # 保留輸入二維矩陣為執行個體變量col
self.col_W = col_W # 保留二維權重矩陣為執行個體變量col_W
return out
def backward(self,dout): # 後向函數,用于進行反向傳播
FN, C, FH, FW = self.W.shape # 将四維權重資料,各維值依次賦給FN(濾波器個數)、C(通道數)、FH(濾波器高度)、FW(濾波器寬度)
dout = dout.transpose(0,2,3,1).reshape(-1, FN) # 将上遊傳來的導數dout換軸并轉換為二維矩陣格式
self.db = np.sum(dout, axis=0) # 類Affine,求得偏置的梯度
self.dW = np.dot(self.col.T, dout) # 類Affine,求得權重的梯度(二維形狀)
self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW) # 将求得的權重梯度轉換為四維形狀
dcol = np.dot(dout, self.col_W.T) # 類Affine,求得損失函數關于輸入資料的導數(二維矩陣形狀)
dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad) # 利用col2im函數,将損失函數關于輸入資料的導數轉換為四維形狀
return dx
# 2.池化層的實作
池化層的實作原理:利用im2col函數将三維輸入資料每個通道的每個池化核應用區域展平為一行,形成二維輸入資料。然後對二維輸入資料的每一行取最大值,得到輸出資料的一維數組形式。最後将輸出資料reshape為對應的三維形狀後輸出。
池化層的實作:
class Pooling:
def __init__(self,pool_h,pool_w,stride=1,pad=0): # 初始化函數,依次為執行個體變量pool_h(池化視窗高度)、pool_w(池化視窗寬度)、stride(池化步幅)、pad(池化填充)
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 # 将四維輸入資料,各維值依次賦給N(輸入資料個數)、C(通道數)、H(輸入資料的高度)、W(輸入資料的寬度)
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) # 将四維輸入資料x轉換為二維矩陣
col = col.reshape(-1,self.pool_h*self.pool_w) # 将輸入二維矩陣的形狀修改為((元素總數/pool_h*pool_w)*(pool_h*pool_w))
# 第二步求最大值
out = np.max(col,axis=1) # 沿輸入二維矩陣水準方向,依次取最大值得到一維數組形式的輸出資料
# 第三步轉換
out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2) # 将輸出資料轉換為計算出的形狀
# 為反向傳播保留中間值
self.x = x
arg_max = np.argmax(col, axis=1)
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
# 本部落格參考了《深度學習入門——基于Python的理論與實作》(齋藤康毅著,陸宇傑譯),特在此聲明。