卷积神经网络——基本架构

卷积神经网络

1. 整体结构

相邻层的所有神经元之间都有连接,这称为全连接(fully-connected)

在之前使用的全连接神经网络中,Affine层后面跟着激活函数ReLU层(或者Sigmoid 层)。这里堆叠了4 层“Affine-ReLU”组合,然后第5 层是Affine层,最后由Softmax层输出最终结果(概率)。

卷积神经网络——基本架构

在CNN中出现了新的卷积层和池化层
卷积神经网络——基本架构

CNN 的层的连接顺序是“Convolution - ReLU -(Pooling)”(Pooling 层有时会被省略)。这可以理解为之前的“Affi ne - ReLU”连接被替换成了“Convolution -ReLU -(Pooling)”连接。

在CNN中,靠近输出的层中使用了之前的Affine-ReLU组合。此外最后的输出层还是使用了Affine-softmax组合。这些都是CNN中比较常见的结构

2. 卷积层

2.1 全连接层中存在的问题

全连接层忽视了数据的形状,在全连接层中输入数据是图像的时候,需要将图像的高,长,通道方向上的3维形状拉平为1维数据。前面提到的使用了MNIST数据集的例子中,输入图像就是1 通道、高28 像素、长28 像素的(1, 28, 28)形状,但却被排成1 列,以784 个数据的形式输入到最开始的Affine层。

图像是3 维形状,这个形状中应该含有重要的空间信息。比如,空间上邻近的像素为相似的值、RBG的各个通道之间分别有密切的关联性、相距较远的像素之间没有什么关联等,3 维形状中可能隐藏有值得提取的本质模式。但是,因为全连接层会忽视形状,将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。

而卷积层可以保持形状不变。当输入数据是图像时,卷积层会以3 维数据的形式接收输入数据,并同样以3 维数据的形式输出至下一层。因此,在CNN中,可以(有可能)正确理解图像等具有形状的数据。

CNN中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征(output feature map)。

2.2 卷积运算

卷积层进行的处理就是卷积运算。卷积运算相当于图像处理中的“滤波器运算”。

卷积神经网络——基本架构

如图所示,卷积运算对输入数据应用滤波器。在这个例子中,输入数据是有高长方向的形状的数据,滤波器也一样,有高长方向上的维度。假设用(height, width)表示数据和滤波器的形状,则在本例中,输入大小是(4, 4),滤波器大小是(3, 3),输出大小是(2, 2)。另外,有的文献中也会用“核”这个词来表示这里所说的“滤波器”。

卷积运算以一定的间隔华东滤波器窗口并应用。将各个位置上滤波器元素和输入的对应元素相乘,再求和,有时将这个计算称之为乘积累加运算。然后将这个结果保存带输出的对应位置。将这个过程在所有位置都进行一遍,就可以得到卷积运算的输出。

卷积神经网络——基本架构

在全连接的神经网络中,除了权重参数,还存在偏置。CNN中,滤波器的参数就对应之前的权重。并且,CNN中也存在偏置。

卷积神经网络——基本架构

2.3 填充

在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比
如0 等),这称为填(padding),是卷积运算中经常会用到的处理。比如,在图的例子中,对大小为(4, 4) 的输入数据应用了幅度为1 的填充。“幅度为1 的填充”是指用幅度为1 像素的0 填充周围。

卷积神经网络——基本架构

通过填充,大小为(4, 4) 的输入数据变成了(6, 6) 的形状。然后,应用大小为(3, 3) 的滤波器,生成了大小为(4, 4) 的输出数据。这个例子中将填充设成了1,不过填充的值也可以设置成2、3 等任意的整数。在图7-5的例子中,如果将填充设为2,则输入数据的大小变为(8, 8);如果将填充设为3,则大小变为(10, 10)。

2.4 步幅

应用滤波器的位置间隔称为步幅(stride)

增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。
这里,假设输入大小为(H,W),滤波器大小为(FH, FW),输出大小为(OH,OW),填充为P,步幅为S。此时,输出大小可通过式子进行计算。

\[\begin{aligned} &OH = \frac{H+2P-FH}{S} +1 \\ &OW = \frac{W+2P-FW}{S}+1 \end{aligned} \]

这里需要注意的是,虽然只要代入值就可以计算输出大小,但是所设定的值必须使式中的\(\frac{H+2P-FH}{S}\)和\(\frac{W+2P-FW}{S}\)分别可以除尽。当输出大小无法除尽时(结果是小数时),需要采取报错等对策。但是,根据深度学习的框架的不同,当值无法除尽时,有时会向最接近的整数四舍五入,不进行报错而继续运行。

2.5 3维数据的卷积运算

这里以3 通道的数据为例,展示了卷积运算的结果。和2 维数据时相比,可以发现纵深方向(通道方向)上特征图增加了。通道方向上有多个特征图时,会按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。

卷积神经网络——基本架构

需要注意的是,在3维数据的卷积运算中,输入数据和滤波器的通道数
要设为相同的值。在这个例子中,输入数据和滤波器的通道数一致,均为3。滤波器大小可以设定为任意值(不过,每个通道的滤波器大小要全部相同)。这个例子中滤波器大小为(3, 3),但也可以设定为(2, 2)、(1, 1)、(5, 5) 等任意值。再强调一下,通道数只能设定为和输入数据的通道数相同的值.

2.6 结合方块思考

将数据和滤波器结合长方体的方块来考虑,3 维数据的卷积运算会很容易理解。

卷积神经网络——基本架构

方块是如图所示的3 维长方体。把3 维数据表示为多维数组时,书写顺序为(channel, height, width)。比如,通道数为C、高度为H、长度为W的数据的形状可以写成(C,H,W)。滤波器也一样,要按(channel,height, width)的顺序书写。比如,通道数为C、滤波器高度为FH(FilterHeight)、长度为FW(Filter Width)时,可以写成(C, FH, FW)。

如果要在通道方向上也拥有多个卷积运算的输出,就需要用到多个滤波器(权重)

卷积神经网络——基本架构

通过应用FN个滤波器,输出特征图也生成了FN个。如果将这FN个特征图汇集在一起,就得到了形状为(FN, OH,OW) 的方块。将这个方块传给下一层,就是CNN的处理流。

关于卷积运算的滤波器,也必须考虑滤波器的数量。因此,作为4 维数据,滤波器的权重数据要按(output_channel, input_channel, height, width) 的顺序书写。比如,通道数为3、大小为5 × 5 的滤波器有20 个时,可以写成(20, 3, 5, 5)。

2.7 批处理

我们希望卷积运算也同样对应批处理。为此,需要将在各层间传递的数据保存为4 维数据。具体地讲,就是按(batch_num, channel, height, width)的顺序保存数据。比如,将处理改成对N个数据进行批处理时,数据的形状如图所示。

图的批处理版的数据流中,在各个数据的开头添加了批用的维度。像这样,数据作为4 维的形状在各层间传递。这里需要注意的是,网络间传递的是4 维数据,对这N个数据进行了卷积运算。也就是说,批处理将N次的处理汇总成了1 次进行。

卷积神经网络——基本架构

3. 池化层

池化是缩小高、长方向上的空间的运算。比如,如图所示,进行将2 × 2的区域集约成1个元素的处理,缩小空间大小。

卷积神经网络——基本架构

例子是按步幅2 进行2 × 2 的Max池化时的处理顺序。“Max池化”是获取最大值的运算,“2 × 2”表示目标区域的大小。如图所示,从2 × 2 的区域中取出最大的元素。此外,这个例子中将步幅设为了2,所以2 × 2 的窗口的移动间隔为2 个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如,3 × 3 的窗口的步幅会设为3,4 × 4 的窗口的步幅会设为4 等。

3.1 池化层的特征

没有要学习的参数.

池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。

通道数不发生变化.
经过池化运算,输入数据和输出数据的通道数不会发生变化。如图所示,计算是按通道独立进行的

卷积神经网络——基本架构

对微小位置变化具有鲁棒性.

对数据发生微小偏差时,池化人会返回相同的结果。因此,池化对输入数据的微小偏差具有鲁棒性。

4. 卷积层和池化层的实现

CNN中各层间传递的数据是4维数据

4.1 基于im2col的展开

如果老老实实地实现卷积运算,估计要重复好几层的for语句。这样的实现有点麻烦,而且,NumPy中存在使用for语句后处理变慢的缺点(NumPy中,访问元素时最好不要用for语句)。

im2col是一个函数,将输入数据展开以适合滤波器(权重)

im2col会把输入数据展开以适合滤波器(权重)。具体地说,如图所示,对于输入数据,将应用滤波器的区域(3 维方块)横向展开为1列。im2col会在所有应用滤波器的地方进行这个展开处理。

卷积神经网络——基本架构

使用im2col展开输入数据后,之后就只需将卷积层的滤波器(权重)纵向展开为1 列,并计算2 个矩阵的乘积即可(参照图7-19)。这和全连接层的Affine层进行的处理基本相同。

基于im2col方式的输出结果是2 维矩阵。因为CNN中数据会保存为4 维数组,所以要将2 维输出数据转换为合适的形状。以上就是卷积层的实现流程。

卷积神经网络——基本架构

4.2 卷积层的实现

im2col是一个已经实现的函数,不必关心其内部结构,许多深度学习框架都提供了im2col的实现

im2col (input_data, filter_h, filter_w, stride=1, pad=0)

  • input_data―由(数据量,通道,高,长)的4维数组构成的输入数据
  • filter_h―滤波器的高
  • filter_w―滤波器的长
  • stride―步幅
  • pad―填充

im2col会考虑滤波器大小、步幅、填充,将输入数据展开为2 维数组。

来实际使用一下这个im2col

import sys, os
sys.path.append(os.pardir)
from common.util import im2col
x1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape) # (9, 75)
x2 = np.random.rand(10, 3, 7, 7) # 10个数据
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape) # (90, 75)

这里举了两个例子。第一个是批大小为1、通道为3 的7 × 7 的数据,第二个的批大小为10,数据形状和第一个相同。分别对其应用im2col函数,在这两种情形下,第2 维的元素个数均为75。这是滤波器(通道为3、大小为5 × 5)的元素个数的总和。批大小为1 时,im2col的结果是(9, 75)。而第2个例子中批大小为10,所以保存了10 倍的数据,即(90, 75)。

现在使用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
        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

卷积层的初始化方法将滤波器(权重)、偏置、步幅、填充作为参数接收。滤波器是(FN, C, FH, FW) 的4 维形状。另外,FN、C、FH、FW 分别是Filter Number(滤波器数量)、Channel、Filter Height、Filter Width 的缩写。

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

用im2col展开输入数据,并用reshape将滤波器展开为2 维数组。然后,计算展开后的矩阵的乘积。

展开滤波器的部分,将各个滤波器
的方块纵向展开为1 列。这里通过reshape(FN,-1) 将参数指定为-1,这是reshape的一个便利的功能。通过在reshape时指定为-1,reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致。 比如,(10, 3, 5, 5) 形状的数组的元素个数共有750 个,指定reshape(10,-1) 后,就会转换成(10, 75) 形状的数组。

forward的实现中,最后会将输出大小转换为合适的形状。转换时使用了NumPy的transpose函数。transpose会更改多维数组的轴的顺序。如图所示,通过指定从0 开始的索引(编号)序列,就可以更改轴的顺序。

卷积神经网络——基本架构

以上就是卷积层的forward处理的实现。通过使用im2col进行展开,基本上可以像实现全连接层的Affine层一样来实现。接下来是卷积层的反向传播的实现,因为和Affine层的实现有很多共通的地方,所以就不再介绍。但有一点需要注意,在进行卷积层的反向传播时,必须进行im2col的逆处理。

除了使用col2im这一点,卷积层的反向传播和Affine 层的实现方式都一样。卷积层的反向传播的实现如下。

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]

4.3池化层的实现

池化层的实现和卷积层相同,都使用im2col展开输入数据。不过,池化情的况下,在通道方向上是独立的,这一点和卷积层不同。具体地讲,如图所示,池化的应用区域按通道单独展开。

卷积神经网络——基本架构

像这样展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可

卷积神经网络——基本架构

上面就是池化层的forward处理的实现流程。下面来看一下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

最大池化的反向传播没有需要学习的参数,因此,在卷积神经网络的训练中,Pooling层需要做的仅仅是将误差项传递到上一层,而没有梯度的计算。
对于max pooling,下一层的误差项的值会原封不动的传递到上一层对应区块中的最大值所对应的神经元,而其他神经元的误差项的值都是0;
所以只是使用np.zeros生成形状相同的全0数组。在forward阶段就记录下了Max值的位置,最后在反向传播时,将最大值填入对应位置,其余为0

卷积神经网络——基本架构

上一篇:Python实现FLV视频拼接


下一篇:如何使用PHP将AVI文件转换为FLV格式?