# 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的理论与实现》(斋藤康毅著,陆宇杰译),特在此声明。