卷积的实现原理

筅森魡賤 提交于 2020-03-09 13:48:02

  我前面的一篇文章中,卷积操作的计算量分析中,引用了矩阵相乘来概括卷积的计算量,这样做确实是有道理的,因为在卷积的实现中的确是通过矩阵相乘来加速卷积的计算。但是我在那篇文章只是简单的通过矩阵相乘来简单的分析了一下维度信息,事实上,各种框架的实现也并不一定符合我的分析,但是殊途同归。
  本文将为大家耐心整理了一下具体的实现(吐槽一下干这件事确实花了我相当多的功夫,因为网上写的东西太乱了,很多人都搞错了框架和实现的对应。再或者是自己的分析和配图不符,或者是自己实现后一运行,单通道对了,多通道就错了。)
  首先也是从最容易错的地方,通道这个概念说起,大体目前的框架实现卷积的时候,特征图格式分为两种,一种是NCHWN*C*H*W,即N表示batchsize中特征图的数量,C表示通道数,H,W表示每个特征图的长宽。一种是NHWCN*H*W*C,与第一种的区别在于通道的位置不一样。首先通道的位置决定了一个通道是否连续的问题,连续的利于访问局部性,可以提高速度。另一个差异决定了决定了矩阵乘法的结果形状转化有区别。
通道顺序差异图
  另外一处区别就在于,那就是矩阵乘法操作的特征图作为左元和右元的区别。这句话怎么理解,一个454*5的矩阵,和一个434*3的矩阵要想做矩阵乘法,显然要对其中一个矩阵做转置,谁转置了,谁就要放到乘号的右边这样才可以相乘,所以将其作为右元。右元做完乘法,保留了列数,左元保留了行数。如果我们的特征图是NHWCN*H*W*C,我们需要保留特征图的数量N,直观上来想,特征图的数量N我们适合保留在最左边,所以把特征图放在乘法的左元比较合适。但实际上这里的区别不大,我们先讲解放在右边的情况。

特征图作为矩阵乘法的右元

  如果我们把特征图作为右元,特征图的维度就是(N,Cin,H,WN,C_{in},H,W),卷积之后的特征图维度是(N,Cout,HN,WNN,C_{out},H_N,W_N),显然这里CoutC_{out}是从卷积那里来的。卷积的时候这里会做一个im2col的操作,这个算法的主要功能是把卷积操作通过矩阵乘法来实现。这个算法的讲解我也专门写了一篇文章卷积运算中的im2col算法讲解。显然如果卷积核的尺寸是(Cout,Cin,KH,KWC_{out},C_{in},KH,KW)的,我们把后三个维度拉成一个,得到(Cout,CinKHKWC_{out},C_{in}*KH*KW)的卷积核矩阵。这里有必要强调一点,卷积核的形状不同框架会有差异,但是都是C_{in},KH,KW这三个放在一起,然后拉成一个一维的。
  但是现在的尺寸的两个矩阵,明显是无法乘到一起的,这就是im2col算法的妙处,对特征图进行转换,只需要将特征图给转换成(N,CinKHKW,XN,C_{in}*KH*KW,X),X表示卷积之后的特征图大小。这样就出来一个公共维度CinKHKWC_{in}*KH*KW,而我们的矩阵乘法也就是想要把这个维度给消解掉,直接对这个维度进行相乘就好
  如果我们使用底层实现,这里可以选择对N个特征图逐个的去乘,然后就把N保留在最前面了。而这里如果你使用numpy来实现的话,就可以直接相乘,然后最后对结果特征图做一个转置就行了。
  我们还需要注意的一个点,如果我们把特征图作为右元,显然矩阵乘法的时候,我们就不能按列对特征图的感受野进行展开了,这个是im2col算法的内容,我们那篇文章的讲解会提到。

特征图作为矩阵乘法的左元

  这种情况也就是说卷积核要作为右元,我们仍然把卷积核的尺寸变为(Cout,Cin,KHKWC_{out},C_{in},*KH*KW),我们对卷积核做转置,现在的维度是(CinKHKW,CoutC_{in}*KH*KW,C_{out}),可以看到CoutC_{out}正好是右元的列,而它实际上是输出特征图的通道数,可以保留在最后,其实这个时候就很适合特征图为(N,H,W,CinN,H,W,C_{in})的情况,因为我们乘完之后需要做的形状转换很少,乘完之后直接就得到了(N,X,CoutN,X,C_{out})的新特征图,这样就可以改变形状的的比较少。
  但是还是那个问题,不能直接乘,还是需要通过im2col算法对特征图进行处理,把特征图变为(N,X,HWCinN,X,H*W*C_{in})的情况,但是这个时候其实转换就是比较容易了。同样我们看到特征图作为左元,也就是消解的部分应该是矩阵的行,这个时候我们就需要把感受野转为行了。这又是一点差异,这差异的细节我会在im2col算法中讲解。

总结

  两种方式因为都用了Im2col方式把输出的特征图尺寸拉成了一维(HNWN)(H_N*W_N),在卷积结束之后都是要复原为二维的。
  事实上通道顺序并不是特征图作为左元或者右元的果,甚至可能是因。但是我为了便于理解,将两种情况都说清楚,而不至于大家为此困惑,所以才这样讲解。因为我是看了几种框架之后这样理解的,并没有大量样本支撑,也有可能左元和(N,Cin,H,WN,C_{in},H,W)组合,或者其他,但是显然这样实现较为简单也较为容易分析,最后还是比较适合实现。
  不同的通道顺序被不同的框架采用着,但是目前一个比较公认的事实是如果使用CPU来计算,(N,H,W,CinN,H,W,C_{in})更普遍一些,因为很多情况都是单个像素的处理。因为cuda使用的是(N,Cin,H,WN,C_{in},H,W),所以GPU基本全是这种通道方式,这样利于整个通道的并行。
  以上只是从宏观上分析了卷积的算法,考虑因素还有微观的在对矩阵进行变换的时候能否很好的利用访问局部性来提高速度,这些都过于细节了,不作为重点讲解,后面的im2col算法也会对此做一定的分析,并贴出我自己实现的代码。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!