卷积
为什么卷积神经网络的能力更强?以 mnist 数据集为例,在普通的神经网络中,输入的数据是长度为 784 784 784 的像素。但实际上图像本身 28 × 28 28 \times 28 28×28 的的二维结构已经被破坏了,每个像素与之上下更多像素之间的位置关系都消失了。神经网络所看见的世界是由一维向量构成的,自然无法与现实形成更好的拟合。
神经网络中的卷积是通过一个个滤波器对原始图像提取特征来实现的。不妨以二维单通道的图像数据为例,看看卷积的作用方式。(左侧为原始图像数据,中间为滤波器,右侧为特征图像)
原始图像的每个灰色部分都被提取出来,并与滤波器进行按位置的乘法(不是矩阵乘法),将其结果的和作为特征图像的一部分。这样就以滤波器为参照获取到了原始图像的特征,如果图像的某一部分和滤波器越相似,则获得的特征值就越大。
填充
值得注意的是,特征图像与原始图像相比小了一圈,如果经过多次卷积运算,边缘的像素可能会被忽略,同时图像的面积也在不断减小,这时可以通过一种名为**填充(padding)**的处理扩大图像,也就是在图像的四周填充 0
值。如下:
可以得到填充后的特征图像
步幅
应用滤波器的间隔称为步幅(stride),上述的例子步幅都是 1
,也就是说灰色的区域移动的长度是 1
,不过也可以选取其他值。
输出大小
如果取原始图像大小为 ( H , W ) (H,W) (H,W),滤波器大小为 ( F H , F W ) (FH,FW) (FH,FW),填充为 P P P,步幅为 S S S,输出的特征图像大小为 ( O H , O W ) (OH,OW) (OH,OW),则有
O H = H + 2 P − F H S + 1 , O W = W + 2 P − F W S + 1. OH = \frac{H + 2P - FH}{S} + 1, \\ OW = \frac{W + 2P - FW}{S} + 1. OH=SH+2P−FH+1,OW=SW+2P−FW+1.
池化
池化是一种缩小特征图像大小的运算。
池化的好处主要有两个:一是在不大幅改变数据特征的情况下减少数据量,可以提高运算速度;二是将大的范围映射到小的范围可以减少扰动引起的误差。
image_to_column()
的实现
取 N N N 为批处理数量, C C C 为图像通道数量。
实现卷积和池化有一个相当重要的函数需要实现,这个函数可以将 ( N , C , H , W ) (N, C, H, W) (N,C,H,W) 形状的四维数据转化为 ( C × F H × F W , N × O H × O W ) (C \times FH \times FW, N \times OH \times OW) (C×FH×FW,N×OH×OW) 形状的数据,第一个轴与滤波器大小相同,第二个轴与输出的特征图像大小相同。
之所以要使用 image_to_column()
是为了减少 for
循环的使用,提高计算速度。
def image_to_column(data: np.ndarray, filter_h: int, filter_w: int,
stride: int = 1, padding: int = 0) -> np.ndarray:
"""
assert that data.shape is (batch_num, channel, height, width)
@params data: 4D input array
@params filter_h, filter_w: the height and width of the filter
@returns: the column of input array
"""
N, C, H, W = data.shape
assert (H + 2 * padding - filter_h) % stride == 0
assert (W + 2 * padding - filter_w) % stride == 0
out_h = (H + 2 * padding - filter_h) // stride + 1
out_w = (W + 2 * padding - filter_w) // stride + 1
im = np.pad(data, ((0, 0), (0, 0), (padding, padding), (padding, padding)),
mode="constant")
i0 = np.tile(np.repeat(np.arange(filter_h), filter_w), C)
i1 = np.repeat(np.arange(out_h), out_w) * stride
j0 = np.tile(np.arange(filter_w), filter_h * C)
j1 = np.tile(np.arange(out_w), out_h) * stride
i = i0.reshape(-1, 1) + i1.reshape(1, -1)
j = j0.reshape(-1, 1) + j1.reshape(1, -1)
k = np.repeat(np.arange(C), filter_h * filter_w).reshape(-1, 1)
col = im[:, k, i, j]
col = col.transpose(1, 2, 0).reshape(filter_h * filter_w * C, -1)
return col
卷积的过程
我们通过矩阵的维数和轴来观察卷积的过程:
( N , C , H , W ) ⏞ 原始数据 i m a g e _ t o _ c o l → ( C × F H × F W , N × O H × O W ) ⏞ col , ( F N , C , F H , F W ) ⏞ 滤波器 r e s h a p e → ( F N , C × F H × F W ) ⏞ col_f , ( F N , C × F H × F W ) ⏟ col_f ⋅ ( C × F H × F W , N × O H × O W ) ⏟ col ⏞ 矩阵乘法 + b → ( F N , N × O H × O W ) ⏞ 特征图像(2维) r e s h a p e → ( F N , N , O H ,