动手实现深度神经网络7 实现CNN
经过了之前的学习,我们已经掌握了如何构造、优化和使用一个深度神经网络了。然而,单纯的深度神经网络无法考虑到图像中相邻像素中可能存在的联系。于是,这里我们将实现一种专门针对图像处理的卷积神将网络CNN。
关于CNN的理论知识我之前的文章有详细的讲述:Python深度学习入门笔记 4 CNN。这里我们依据不再赘述,直接展示代码中可能会遇到的问题。
卷积层的实现
CNN中处理的是4维数据(batch_num, channel, height, width),因此卷积运算的实现看上去会很复杂,但是通过im2col,问题就会变得很简单。
im2col是一个函数,将输入数据展开以适合滤波器(权重)。对每个3维的输入数据应用im2col后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。
使用im2col展开后,展开后的元素个数会多于原方块的元素个数。因此,使用im2col的实现存在比普通的实现消耗更多内存的缺点。但是,汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。比如,在矩阵计算的库(线性代数库)等中,矩阵计算的实现已被高度最优化,可以高速地进行大矩阵的乘法运算。因此,通过归结到矩阵计算上,可以有效地利用线性代数库。
im2col和col2im的实现
col2im是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
# np.pad用于填充
# 第一个参数是填充的数组
# 第二个参数是一个列表,表示各个轴填充的个数:
# 例如[(1,2),(0,0)]表示 第一个轴上,前面填充1个,后面填充2个 第二个轴上 前面填充0个,后面填充0个
# 第三个参数是填充的模式,默认是'constant'常数填充,默认是0,可以通过constant_values = (0,0)这样设置。
#具体到这里,因为第一个轴是数据量,第二个周是通道数,所以不填充,第三第四个轴是高和长,才根据输入进行填充
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
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
"""
Parameters
----------
col :
input_shape : 输入数据的形状(例:(10, 1, 28, 28))
filter_h :
filter_w
stride
pad
Returns
-------
"""
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]
一!!!!!!!!!
这里生成一个六维0矩阵,我们来说明一下。首先把“数据量N”和“通道数C”摘出来不考虑,因为要把二维矩阵转换为一个适合与卷积相乘的形状,所以一个(H*W)的矩阵,要暂时看成out_h*out_w个(ilter_h,filter_w)形状的矩阵,也就是一个(filter_h, filter_w, out_h, out_w)的四维矩阵。
二!!!!!!!!!
这里是将img中的数据对应复制到六维矩阵col中。实际上是每次将每个(ilter_h,filter_w)矩阵中的1一个像素复制到col中。
三!!!!!!!!!
这里将col转换为最终需要的(N*out_h*out_w, C*filter_h*filter_w)的矩阵。
卷积层代码
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