1 卷积神经网络整体结构
卷积神经网络(CNN)相比全连接网络多了卷积层和池化层。对于全连接网络,所有相邻层的神经元都用Affine层进行连接,如图中即为Affine-ReLU的连接组合。
卷积神经网络则包含卷积层和池化层与激活函数相连,其结构如下:
2 全连接层对图象识别的局限性
我们之前一直使用全连接网络识别mnist数据集,但这其实是一个低效的方法。对于28 X 28的图片信息,我们必须将其拉长成1 X 784的数据再传入网络。这一操作不但需要大量神经元作为输入层,而且当我们将二维图片拉伸为一维后,会丢失原图中部分特征信息,如邻近像素值的关系。
CNN可以保留原图二维的形状同时识别图像,从而保留原图中的各类特征。CNN卷积层输入输出数据被称为特征图
3 卷积运算
如图,卷积运算中我们用一个矩阵(被称为卷积核),在图片矩阵中“滑动”,并和图片矩阵重合部分进行内积运算(即将对应位置元素相乘在加和)。最终将各个部分内积结果汇总成一个新的矩阵作为输出特征图
填充(paddling):通过在原图片矩阵周围拓展0以增大原矩阵形状,这一操作用于控制输出特征图的形状和大小。
如图,使用1的填充,可以使得输出特征图大小和原矩阵一样为4 X 4,这样防止了深度学习中由于不断卷积导致矩阵被不断缩小。
步幅(stride):步幅为每一次卷积核在图片上移动的步长,步长会对提取到的特征和输出特征图形状有影响
对于一个形状(w,h)的输入图,步幅为S,填充为P,卷积核(cw, ch),输出特征图形状为((w + 2P - cw) / s + 1, (h + 2P - ch) / 2 + 1)
三维数据卷积运算
对于有多通道的图片(如RGB图片),需要使用和通道数一致的卷积核数量,其得到的特征图通道数恒为1.可以使用多个卷积核分别和图片做卷积运算,将每一个运算得到的特征图累积成一个多通道特征图
如图,对于形状为(C,H,W)的输入图,使用FN个形状为(C,FH,FW)的卷积核,得到形状为(FN,OH,OW)的输出特征图
4 批处理
和全连接网络的批处理类似,卷积神经网络批处理数据为4维数据,按照(batch_num, channel, height, width)的顺序保存数据
池化层
池化层会缩小特征图长宽方向上大小,其作用为将一片区域内的元素缩为一个元素。如Max池化取该区域内最大值,而Average池化取区域所有元素平均值。(在图像识别中我们一般使用max池化)
一般来说,我们池化窗口的大小和卷积核步幅设为相同值,如下图中池化层窗口为2 X 2,同时卷积核步幅也为2
池化层不存在参数,因此不需要进行学习。另外池化层不会改变输入输出的通道数。对输入特征图的每一个通道池化层会单独处理
python实现卷积层和池化层
实现卷积层可以使用im2col函数,该函数将批处理的4维数据转换为2维矩阵以适合滤波器(权重)。如对于通道为3的7X7数据。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
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
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]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
该程序将输入img批处理中每一个特征图展开为一个一维数组col,其中数组长度为通道数 X 图片矩阵长 X 图片矩阵宽。
卷积层的实现
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)
col = im2col(x, FH, FW, self.stride, self.pad)
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
在正向传播中,我们使用im2col将输入数据展开,和经过reshape的卷积核进行点乘得到输出值(如图示)
对于反向传播,我们的实现方法和Affline层完全一致(因为本质上都是矩阵乘法反向传播)。只是这里要在最后将输出的col再转换回图片形状(使用方法col2im)
池化层python实现
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
这里我们还是先使用im2col将批处理数据转化为矩阵。在一个维度上(即batch中每一个特征图)取各个数组中最大值汇总为输出。