卷积神经网络——im2col函数

讲解之前,先确定下卷积运算中矩阵的维度都是多少。
N, C, H, W分别代表输入数据的批数据量、每个数据的通道数、每个通道的高、每个通道的宽。所以输入数据的维度是(N, C, H, W)
FN代表滤波器的数量
FH代表滤波器的高度
FW代表滤波器的宽度
所以,滤波器的维度是(FN, C, FH, FW)
先不考虑偏置,那么输出数据的维度就应该为(N, FN, OH, OW)
其中OH, OW代表滤波器分别在输入数据的高和宽这两个维度上面能够滑动的次数(这里是有一个计算公式的,直接使用就好)
这里写图片描述
以上图为例,考虑输入数据是一个(1,1,3,4)的数据,也就是数据数量为1,而且是单通道的。因为前面都是1,所以这里是一个二维矩阵。
卷积核是(1,1,2,2)的卷积核,也就是卷积核数量为1,第二个1和上面的第二个1是对应的(都是指通道数),核是2乘2的。
可以发现,输出数据的维度是(1,1,2,3),第一个1是数据数量,第二个1是卷积核数量。

在计算输出数据的第一个元素时,可以发现,是用的numpy的对应元素的乘法(element-wise),但你仔细观察就能发现,这和正常的矩阵相乘其实是一样的。
这里写图片描述
如上图所示,把abef展开成一行,把wxyz展开成一列,再按照矩阵乘法相乘,就得到了一个1*1的矩阵,也就是输出数据的第一个元素。按照这个逻辑分析,卷积核在输入数据上总共能滑动6次,每次都会将一部分输入数据展开成一维数据(维度大小为(FH,FW),所以应该是2乘2得4),所以输入数据会变成(6,4)维度的数据,以方便进行矩阵乘法。卷积核则会转为一维数据(大小为4)。所以最后是(6,4)维度的输入数据与(4,)一维卷积核列矩阵相乘,结果将为(6,),刚好是输出数据的6个元素。

以上只是分析最简单的情况,下面将给出代码(来自深度学习入门):

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]#
            #确定下来了后4个纬度,col的前两个冒号和img的前两个冒号对应,
            #y:y_max:stride, x:x_max:stride是赋值给y, x, :, :后面的两个冒号
            #这里可以理解为卷积核的每个元素(filter_h, filter_w确定一个卷积核元素)能在图中滑动的范围,
            #y_max-y/stride就是out_h,也就是输出图的高,也就是滑动范围

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    #-1表示第二个维度需要程序进行推理,即总个数除以N*out_h*out_w
    return col

代码讲解:
首先注意到,此代码以卷积核的个数为1。即FN为1.
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))col变量存储将输入数据“列转换”后的数据(原理和上图一样)。但是注意这里是6个维度,C, filter_h, filter_w这三个维度确定下来了,某个通道的根据某行某列确定下来的卷积核元素。
之所以后两个维度是out_h, out_w,是因为每个卷积核元素在高度和宽度这两个维度能滑动的次数分别就是out_h, out_w。看第一张图,每个卷积核元素在横轴方向都只能滑动3次,在纵轴方向都只能滑动2次。
之后看双层for循环,这句col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]看起来很难理解。首先看y:y_max:stride,这是指在第三个维度上的分片操作,道理和range一样,所以这里你可以想成是y_max减去y再除以stride,这么一运算结果刚好就为out_h,也就是之前说的,每个卷积核元素在高度维度能滑动的次数。
最难理解的是,总体的赋值是什么意思,首先请看如下例子:
这里写图片描述
发现提示的是,could not broadcast input array from shape (2,3) into shape (3,5),shape (2,3)指的是等号右边的数据是(2,3)形状,但为什么要into shape (3,5)呢,因为col[:,2,:]已经把第二个维度确定了,需要赋值过去的是,第一个维度和第三个维度,而第一个维度和第三个维度刚好就是3和5,所以要把输入数据broadcast成(3,5)的形状。

看完例子再回来,col和img的前两个维度都是N和C,所以不用broadcast就能匹配上,而col的后两个冒号则是通过y:y_max:stride, x:x_max:stride来确定的。

transpose(0, 4, 5, 1, 2, 3)最后再变换轴,变换后前三个维度刚好就是N*out_h*out_w这三个数。
reshape(N*out_h*out_w, -1)这里是指第二个维度靠程度自己推理出来。
推理过程如下,总共6个元素在矩阵,第一个维度为3,所以第二个维度自然为2:
这里写图片描述

原文链接:加载失败,请重新获取