卷积神经网络(CNN)
1.神经网络结构示意图如下
相比于普通的神经网络,卷积神经网络多了卷积层以及池化层,还增加了许多特有名词,例如:填充、步幅、通道等。此外,各层中传输的数据是有形状的数据(比如三维数据)。下面开始介绍卷积层和池化层。
2.卷积层
首先,我们知道,全连接层存在一些问题,在处理三维数据时,向全连接层输入数据时,需要将三维数据拉成一维数据。这就导致了数据的形状被“忽视”了。全连接层会将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。
而卷积层可以保持形状不变。当输入数据是图像时,卷积层会以3维 数据的形式接收输入数据,并同样以3维数据的形式输出至下一层。因此, 在CNN中,可以(有可能)正确理解图像等具有形状的数据。另外,CNN中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征图(output feature map)。
2.1卷积运算
卷积层进行的处理就是卷积运算。下面举个例子:
本例中,输入大小是 (4, 4),滤波器大小是(3, 3),输出大小是(2, 2),滤波器在有的例子中也被成为卷积核。
对于输入数据,卷积运算以一定间隔滑动滤波器的窗口并应用。这里所 说的窗口是指图中灰色的3×3的部分。
在上述图中,滤波器中的参数对应于全连接层的参数,CNN中也有偏置,但偏置通常只有一个。
2.2填充
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比 如0等),这称为填充(padding),是卷积运算中经常会用到的处理。
为什么要使用填充呢?使用填充主要是为了调整输出的大小。我们在每次进行卷积运算都会缩小空间,那么在某个时刻输出大小就有可能变为1,导致无法再应用卷积运算。为了避免出现这样的情况,就要使用填充。在刚才的例子中,将填充的幅度设为1,那么相对于输入大小(4, 4),输出大小也保持为原来的(4,4)。因此,卷积运算就可以在保持空间大小不变的情况下将数据传给下一层。
2.3步幅
应用滤波器的位置间隔称为步幅(stride)。下面是步幅为1和步幅为2的情况。
由2.2节和2.3节可知,增大步幅输出大小会变小,添加填充输出大小会变大。我们可以根据步幅和填充来计算输出大小。
假设输入大小为(H,W),滤波器大小为(FH,FW),输出大小为(OH,OW),填充为P,步幅为S。那么我们可以得到下面这个式子:
2.4三维数据的卷积运算
对于图像来说,图像是三维数据,除了高、长方向之外,还需要处理通道方向。在进行卷积运算时候,当通道方向上有多个特征图时,会按通道 进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。
我们需要注意的是,在三维的卷积运算中,输入数据和过滤器的通道数要保持一致。
我们可以把上述例子中的输入数据和过滤器当成长方体方块来考虑。把3维数据表示为多维数组时,书写顺序为(channel,height,width)。比如,通道数为C、高度为H、长度为W的数据的形状可以写成(C, H,W)。滤波器也一样,要按(channel,height,width)的顺序书写。比如,通道数为C、滤波器高度为FH(Filter Height)、长度为FW(Filter Width)时,可以写成(C,FH,FW)。
如果我们要在通道方向上有多个卷积运算的输出,我们应该这样做,即用到多个滤波器。用图片表示如下:
当考虑到滤波器的数量时,那么数据就变成了4维数据,滤波器的权重数据要按(output_channel,input_channel,height,width)的顺序书写。
在上图中,每个通道只有一个偏置。这里,偏置的形状是(FN,1,1), 滤波器的输出结果的形状是(FN,OH,OW)。这两个方块相加时,要对滤波 器的输出结果(FN,OH,OW)按通道加上相同的偏置值。另外,不同形状的 方块相加时,可以基于NumPy的广播功能轻松实现。
2.5卷积运算中的批处理
我们在卷积层进行批处理时,需要将在各层间传递的数据保存为4维数据。具体地讲,就是按(batch_num,channel,height,width)的顺序保存数据。
上图批处理版的数据流中,在各个数据的开头添加了批用的维度。数据作为4维的形状在各层间传递。批处理中网络间传递的是4维数据,将N个数据进行打包处理,将N卷积运算汇成了一次卷积运算。
3.池化层
池化层是缩小高、长方向上的运算。下面是Max池化运算的示例:
除了,Max池化外,还有Average池化。相对于Max池化是从目标区域中取出最大值,Average池化则是计算目标区域的平均值。 在图像识别领域,主要使用Max池化。
3.1池化层的特征
1.没有要学习的参数,池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
2.通道数不发生变化,计算是按通道独立进行的。
3.对微小的数值变化具有鲁棒性(健壮),输入数据发生微小偏差时,池化仍会返回相同的结果
4.卷积层和池化层的实现
在这里我们使用了一个im2col函数。
它能将一个包含批处理的四维数据转换成二维数据。
在滤波器的应用区域重叠的情况下,使用im2col展开后,展开后的元素个数会多于原方块的元素个数。因此,使用im2col的实现存在比普通的实现消耗更多内存的缺点。但是,汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。
上图是卷积运算的滤波器处理的细节:将滤波器纵向展开为1列,并计算和im2col展开 的数据的矩阵乘积,最后转换(reshape)为输出数据的大小。
4.1卷积层的实现
下面我们使用im2col来实现卷积层
class Convolution:
# 初始化
def __init__(self, W, b, stride = 1, pad = 0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
(FN, C, FH, FW) = self.W.shape
(N, C, H, W) = x.shape
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)
col_W = self.W.reshape(FN, -1)
#reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h,out_w,-1).transpose(0, 3, 1, 2)
# transpose会更改多维数组的轴的顺序。
self.x = x
self.col = col
self.col_W = col_W
return out
def backword(self,dout):
(FN,C,FH,FW) = self.W.shape
dout = dout.transpose(0,2,3,1).reshape(-1,FN)
# 改变损失中数组的轴的顺序,再用reshape转为二维数组
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)
# 用reshape转为二维数组,再改变损失中数组的轴的顺序
dcol = np.dot(dout,self.col_W.T)
dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
return dx
4.2池化层的实现
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
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).reshape(0, 3, 2, 1)
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
5.参数减少的原因
卷积神经网络参数映射参数比全连接层少的原因有两个:
5.1权重共享
观察发现,特征检测如垂直边缘检测如果适用于图片的某个区域,那么它也可能适用于图片的其他区域。也就是说,如果你用一个 3×3 的过滤器检测垂直边缘,那么图片的左上角区域,以及旁边的各个区域(左边矩阵中蓝色方框标记的部分)都可以使用这个3×3的过滤器。每个特征检测器以及输出都可以在输入图片的不同区域中使用同样的
参数,以便提取垂直边缘或其它特征。它不仅适用于边缘特征这样的低阶特征,同样适用于高阶特征,例如提取脸上的眼睛,猫或者其他特征对象。
5.2稀疏连接
结合图解释下这个概念,图中标记的0是通过3×3的卷积计算得到的,它只依赖于这个3×3的输入的单元格,右边这个输出单元(元素 0)仅与36个输入特征中9个相连接。而且其它像素值都不会对输出产生任影响,这就是稀疏连接的概念。