转载自;http://blog.csdn.net/jiongnima/article/details/69736844
推荐该博主的解析代码笔记详细易理解。
卷积层输出 = 权值矩阵 * 输入特征图转化得到的矩阵
权值矩阵尺度 = (卷积组输出通道数) * (卷积组输入通道数*卷积核高*卷积核宽)
输入特征图转化得到的矩阵尺度 = (卷积组输入通道数*卷积核高*卷积核宽) * (卷积层输出单通道特征图高 * 卷积层输出单通道特征图宽)
因此,卷积层输出尺度可以表示为
卷积层输出尺度 = (卷积层输出通道数) * (卷积层输出单通道特征图高 * 卷积层输出单通道特征图宽)
到此是不是可以看到,卷积层输出尺度正好是理论上的卷积输出尺度。那么,在这个卷积乘法中,权值矩阵与输入特征图转化得到的矩阵是怎么得来的呢?这就是im2col.cpp中定义的了,也是本篇博客笔者解析的重点,下面笔者将以一张卷积层输入的单通道特征图为例,解析一下是通过怎样的操作生成相应的矩阵的。
首先给出is_a_ge_zero_and_a_lt_b函数的定义及注释:
该函数定义是:若a大于0且严格小于b,则返回真,否则返回假,该函数的作用是判断矩阵上某元的输出是否为pad的0。
然后给出im2col_cpu函数定义及注释:
im2col_cpu函数将卷积层输入转化为矩阵相乘的右元,核心是5个for循环,首先第一个for循环表示按照输入的通道数逐个处理卷积层输入的特征图,下面笔者将用图示表示剩余的四个for循环操作,向读者朋友们展示卷积层输入的单通道特征图是通过怎样的方式转化为一个矩阵。在这里我们假设,卷积层输入单通道特征图原大小为5*5,高和宽方向的pad为1,高和宽方向步长为2,卷积核不进行扩展。
我们先计算一下,卷积层输入单通道特征图转化得到的矩阵的尺度,矩阵的行数应该为卷积核高*卷积核宽,即为9,列数应该为卷积层输出特征图高(output_h)*卷积层输出特征图宽(output_w),也为9,那么,im2col算法起始由下图开始:
首先kernel_row为0,kernel_col也为0。按照input_row = -pad_h + kernel_row * dilation_h计算input_row的值,在这里,pad_h为1,kernel_row为0,dilation_h为1,计算出input_row为-1,此时output_row为3,满足函数中的第一个if条件,那么在输出图像上先置output_w个零,因为output_w为3,因此得到下图:
然后input_row加上步长2,由-1变成1,此时output_rows为2,计算input_col等于-1,此时执行input_col定义下面的for循环,得到3个值:依次往目标矩阵中填入0,data_im[1*5+1]和data_im[1*5+3],即填入0,7和9。得到下图:
再接着执行,此时input_row再加上2变为3,此时output_rows变为1,计算input_col等于-1,执行input_col定义下面的for循环,得到3个值,分别为0,data_im[3*5+1]和data_im[3*5+3],即填入0,17和19。得到下图:
接着,kernel_col变成1,此时kernel_row为0,kernel_col为1。计算input_row又变成-1,第一个if条件成立,那么,再在输出矩阵上输出3个0。然后,input_row变成1,input_col分别为0(-1+1),2(-1+1+2)和4(-1+1+2+2)时,输出矩阵上分别输出data_im[1*5+0],data[1*5+2],data[1*5+4],即分别填入6,8,10。然后,input_row变成3,input_col分别为0,2,4时,输出矩阵上分别输出data_im[3*5+0],data[3*5+2],data[3*5+4],即分别输出16,18,20。
然后,kernel_col变成2,此时kernel_row为0,kernel_col为2。计算input_row又变成-1,第一个if条件成立,那么,再在输出矩阵上输出3个0。然后,input_row变成1,input_col分别为1(-1+2),3(-1+2+2)和5(-1+2+2+2)时,输出矩阵上分别输出data_im[1*5+1],data[1*5+3],0,即分别填入7,9,0。然后,input_row变成3,input_col分别为1,3,5时,输出矩阵上分别输出data_im[3*5+0],data[3*5+2],0,即分别输出17,19,0。见下图:
接着,kernel_row变成1,kernel_col变成0。计算input_row又变成0,input_col分别为-1(-1+0),1(-1+0+2)和3(-1+0+2+2),输出矩阵上分别输出0,data[0*5+1],data[0*5+3],即分别填入0,2,4。然后,input_row变成2,input_col分别为-1,1和3时,输出矩阵上分别输出0,data[2*5+1],data[2*5+3],即分别填入0,12,14。然后,input_row变成4,input_col分别为-1,1,3时,输出矩阵上分别输出0,data[4*5+1],data[4*5+3],即分别输出0,22,24。见下图:
然后,kernel_row为1,kernel_col变成1。计算input_row为0,input_col分别为0(-1+1),2(-1+1+2)和4(-1+1+2+2),输出矩阵上分别输出data[0*5+0],data[0*5+2],data[0*5+4],即分别填入1,3,5。然后,input_row变成2,input_col分别为0,2和4时,输出矩阵上分别输出data[2*5+0],data[2*5+2],data[2*5+4],即分别填入11,13,15。然后,input_row变成4,input_col分别为0,2,4时,输出矩阵上分别输出data[4*5+0],data[4*5+2],data[4*5+4],即分别输出21,23,25。见下图:
然后,kernel_row为1,kernel_col变成2。计算input_row为0,input_col分别为1(-1+2),3(-1+2+2)和5(-1+2+2+2),输出矩阵上分别输出data[0*5+1],data[0*5+3],0,即分别填入2,4,0。然后,input_row变成2,input_col分别为1,3和5时,输出矩阵上分别输出data[2*5+1],data[2*5+3],0,即分别填入12,14,0。然后,input_row变成4,input_col分别为1,3,5时,输出矩阵上分别输出data[4*5+1],data[4*5+3],0,即分别输出22,24,0。见下图:
接着,kernel_row变成2,kernel_col变成0。计算input_row为1,input_col分别为-1(-1+0),1(-1+0+2)和3(-1+0+2+2),输出矩阵上分别输出0,data[1*5+1],data[1*5+3],即分别填入0,7,9。然后,input_row变成3,input_col分别为-1,1和3时,输出矩阵上分别输出0,data[3*5+1],data[3*5+3],即分别填入0,17,19。然后,input_row变成5,满足第一个if条件,直接输出三个0。见下图:
然后,kernel_row为2,kernel_col变成1。计算input_row为1,input_col分别为0(-1+1),2(-1+1+2)和4(-1+1+2+2),输出矩阵上分别输出data[1*5+0],data[1*5+2],data[1*5+4],即分别填入6,8,10。然后,input_row变成3,input_col分别为0,2和4时,输出矩阵上分别输出data[3*5+0],data[3*5+2],data[3*5+4],即分别填入16,18,20。然后,input_row变成5,满足第一个if条件,直接输出三个0。见下图:
最后,kernel_row为2,kernel_col变成2。计算input_row为1,input_col分别为1(-1+2),3(-1+2+2)和5(-1+2+2+2),输出矩阵上分别输出data[1*5+1],data[1*5+3],0,即分别填入7,9,0。然后,input_row变成3,input_col分别为1,3和5时,输出矩阵上分别输出data[3*5+1],data[3*5+3],0,即分别填入17,19,0。然后,input_row变成5,满足第一个if条件,直接输出三个0。见下图:
到此卷积层单通道输入特征图就转化成了一个矩阵,请读者朋友们仔细看看,矩阵的各列是不是卷积核操作的各小窗口呢?
笔者还想提醒大家的是,注意卷积中的zero-pad操作的实现,并不是真正在原始输入特征图周围添加0,而是在特征图转化得到的矩阵上的对应位置添加0。
而im2col_cpu函数功能的相反方向的实现则有由col2im_cpu函数完成,笔者依旧把该函数的代码注释放在下面:
到此,im2col.cpp中的核心函数就已经解析完毕了,笔者在最开始阅读这个源码的时候,也没有弄得太明白,可是经过仔细画图推敲,明白了其中的含义。从这件小事可以看出,光看不练假把式,在阅读源码时,遇到功能实现中比较抽象的部分,应该再仔细思考分析的同时,多动笔杆,切勿偷懒!