Octave Convolution原理与Caffe实现

2019-08-01 15:08:47 浏览数 (1)

前言

OctaveNet网络paper是《Drop an Octave: Reducing Spatial Redundancy in Convolutional Neural Networks with Octave Convolution》,是CVPR2019中的一篇论文。 OctaveNet是一个用于ImageNet Classfication任务的backbone结构。这篇论文提出了一种新型的卷积结构,或者叫做卷积模块,叫做 Octave Convolution。 Octave Convolution号称是一种可以无缝嵌入到任何已有backbone中的模块,简单好用,能有效降低已有模型的计算量并带来小幅的性能提升,听起来还是让人非常兴奋的。

从频域的角度理解图像

我们都知道,一副图像从空间域的角度看,它一般情况下是一个3×W×H3 times W times H3×W×H的矩阵,矩阵中每一个位置都有一个[0,255]的值,而从频域的角度出发的话,一副图像都可以被分解为描述平稳变化结构的低空间频率分量(低频域、low-frequency)和描述快速变化的精细细节的高空间频率分量(高频域、high-frequency),就像下面这幅图:

最左侧为原始图像,中间为低频的部分,它比较多的反应的是图像的整体信息,最右侧为高频部分,它更多的反应图像的细节信息,比如边缘。这就好比空间域下的梯度,图像中存在边缘的地方,往往就是梯度大的地方。

特征图的高频与低频表示

既然对于图像来说可以区分高频与低频,那么对于特征图也是这样,特征图无非就是一个channel更多的矩阵而已,但是对于一个端对端的CNN模型,总不能在网络中引入一种频域计算,所以Octave Convolution显示的定义了“下采样”操作后的特征图叫做“低频域”,而不做下采样的原始尺寸叫做“高频域”。这样一来由于下采样带来的特征图尺寸减小,从而使得Octave Convolution计算量降低,此外网络有了不同尺度的信息(两个频域),并且两个频域的信息会在卷积完成后聚合,这个特性使得Octave Convolution具有比之前更好的性能。 “下采样”的scale,采用的是2的幂次,而目前文章只讨论了212^{1}21次幂的情况,说白了就是特征图的长宽都缩小了2,就像下面这张图:

图(b)是一个原始的特征图,并人为的切分特征图为Low Frequency和High Frequency,切分的标准是0.25,0.5,0.75三个系数,比如一个channel=64的特征图,系数为0.5的情况下,那么32个通道为低频,另外32个为高频。图©是用下采样操作实现低频域,就是上面说到缩小2倍。图(d)想要说明这个低频和高频要通过卷积做update,然后还有聚合交换的部分,反正只看(d)是看不出来,后面再具体介绍。

在这里不得不吐槽一点,论文由图像引出了高频和低频,但是到了卷积的地方直接过渡到了“下采样”,此后low-frequency和high-frequency还一直贯穿全文,这给人一种写论文写的过劲的感觉,毕竟Low Frequency、High Frequency和Octave 要比upsample和subsample好听,但是其实就是下采样完了上采样,尤其是我们要去实现它的时候。 ≡(▔﹏▔)≡

Octave Convolution

Octave Convolution原理

既然我们知道了Octave Convolution是一种下采样和上采样的组合,那么它的实现也就好理解了:

一个特征图的通道数cinc_{in}cin​根据预设系数aina_{in}ain​切分为高频(1−ain)cinleft ( 1- a_{in}right )c_{in}(1−ain​)cin​与低频aincina_{in}c_{in}ain​cin​的部分,低频部分的宽高都缩小为原来的一半。然后Octave Convolution会做下面四个部分: (1)高频部分直接卷积:f(XH)fleft ( X^{H}right )f(XH),即高频到高频的卷积,输出通道数(1−aout)coutleft ( 1- a_{out}right )c_{out}(1−aout​)cout​; (2)高频部分先做下采样再卷积,这里的下采样是pool(XH,2)poolleft ( X^{H},2right )pool(XH,2),然后f(pool(XH,2))fleft ( poolleft ( X^{H},2right )right)f(pool(XH,2)),即高频到低频的卷积,输出通道数aoutcouta_{out}c_{out}aout​cout​; (3)低频部分直接卷积后做上采样:f(XL)fleft ( X^{L}right )f(XL),这里的upsample(f(XL))upsampleleft ( fleft ( X^{L}right )right )upsample(f(XL))所用的上采样方法我们后面再说,即低频到高频的卷积,输出通道数(1−aout)coutleft ( 1- a_{out}right )c_{out}(1−aout​)cout​; (4)低频部分直接卷积:f(XL)fleft ( X^{L}right )f(XL),即低频到低频的卷积,输出通道数aoutcouta_{out}c_{out}aout​cout​。 这四个部分完成之后,接下来就要做信息的聚合,也就是(1)和(3)的结果做一个对应位置的按位加操作,(2)和(4)的结果做一个对应位置的按位加操作。 这样Octave Convolution就完成了,它其实在做的就是把原来的一个卷积操作,拆成了4个,而这4个中有三个处理的输入都是原来特征图w,h的一半,所以计算量就下来了。 所以一个one-stream的网络,在使用Octave Convolution之后,其实会变成two-stream结果,也就是高频流和低频流,在每一次的卷积结束之后,两个stream的信息会聚合一次。既然是中间是two-stream结构,那原有的网络怎么开始和收尾呢? 假如最开始时的输入只有一个,通道数还用cinc_{in}cin​表示,那么要分出高频和低频两个流,就是只做(1)和(2),但是区别在于,输入特征图的通道数就是cinc_{in}cin​。而最后就是只做(1)和(3),区别是输出特征图的通道数就是coutc_{out}cout​。

上采样和下采样

下采样: Octave Convolution的低频域输出可以用下面的公式表示: Yp,qL=Yp,qL→L Yp,qH→L Y_{p,q}^{L}=Y_{p,q}^{Lrightarrow L} Y_{p,q}^{Hrightarrow L} Yp,qL​=Yp,qL→L​ Yp,qH→L​ Yp,qL→L=∑i,j∈NkWi k−12,j k−12L→LXp i,q jL Y_{p,q}^{Lrightarrow L}=sum_{i,jin N{_{k}}}W_{i frac{k-1}{2},j frac{k-1}{2}}^{Lrightarrow L}X_{p i,q j}^{L} Yp,qL→L​=i,j∈Nk​∑​Wi 2k−1​,j 2k−1​L→L​Xp i,q jL​ Yp,qH→L=∑i,j∈NkWi k−12,j k−12H→LX2∗p i,2∗q jH Y_{p,q}^{Hrightarrow L}=sum_{i,jin N{_{k}}}W_{i frac{k-1}{2},j frac{k-1}{2}}^{Hrightarrow L}X_{2*p i, 2*q j}^{H} Yp,qH→L​=i,j∈Nk​∑​Wi 2k−1​,j 2k−1​H→L​X2∗p i,2∗q jH​ 其中Yp,qL→LY_{p,q}^{Lrightarrow L}Yp,qL→L​就是(4)的结果,Yp,qH→LY_{p,q}^{Hrightarrow L}Yp,qH→L​就是(2)的结果,Yp,qH→LY_{p,q}^{Hrightarrow L}Yp,qH→L​的公式就解释了下采样的过程,Wi k−12,j k−12H→LW_{i frac{k-1}{2},j frac{k-1}{2}}^{Hrightarrow L}Wi 2k−1​,j 2k−1​H→L​是一个对卷积核的遍历操作,当遍历到某一个点(i,j)left ( i,j right )(i,j)之后,去对应特征图上X2∗p i,2∗q jHX_{2*p i, 2*q j}^{H}X2∗p i,2∗q jH​的点,由于特征图上(p,q)left ( p,q right )(p,q)的遍历是以2倍的系数走的,所以这可以理解为,每一次都选择一个四方格的左上角的点,从而跨过其余三个点,开始卷积。这样一来,就下采样了,这也其实相当于一个跨步的卷积。 此外,这个公式还可以改写成X2∗p 0.5 i,2∗q 0.5 jHX_{2*p 0.5 i, 2*q 0.5 j}^{H}X2∗p 0.5 i,2∗q 0.5 jH​,但是0.5在特征图上是没有值的,所以它想表达的意思就是要聚合那个四方格,那就是平均池化喽。 于是,Octave Convolution的下采样策略就清楚了,要么做跨步卷积,要么先平均池化然后做步长为1的卷积。 当然论文中推荐了后者,因为如果原来网络中就做步长为2的卷积,为了下采样,难道要做步长为4吗?显然这不合理。

上采样: Octave Convolution的高频域输出可以用下面的公式表示: Yp,qH=Yp,qH→H Yp,qL→H Y_{p,q}^{H}=Y_{p,q}^{Hrightarrow H} Y_{p,q}^{Lrightarrow H} Yp,qH​=Yp,qH→H​ Yp,qL→H​ Yp,qH→H=∑i,j∈NkWi k−12,j k−12H→HXp i,q jH Y_{p,q}^{Hrightarrow H}=sum_{i,jin N{_{k}}}W_{i frac{k-1}{2},j frac{k-1}{2}}^{Hrightarrow H}X_{p i,q j}^{H} Yp,qH→H​=i,j∈Nk​∑​Wi 2k−1​,j 2k−1​H→H​Xp i,q jH​ Yp,qL→H=∑i,j∈NkWi k−12,j k−12H→LXp2 i,q2 jL Y_{p,q}^{Lrightarrow H}=sum_{i,jin N{_{k}}}W_{i frac{k-1}{2},j frac{k-1}{2}}^{Hrightarrow L}X_{frac{p}{2} i, frac{q}{2} j}^{L} Yp,qL→H​=i,j∈Nk​∑​Wi 2k−1​,j 2k−1​H→L​X2p​ i,2q​ jL​ 有了上面的介绍,这个就可以简单点说了,应特征图上而Xp2 i,q2 jLX_{frac{p}{2} i, frac{q}{2} j}^{L}X2p​ i,2q​ jL​的点其实是一个点复制成了一个四方格,或者说,这分明是一个最邻近插值。

OctaveConv如何减低计算量

假如我们有一个这样的卷积操作,那么它的计算量应该是: 其中一次卷积的计算量为: Componce=(3×3 8)×c c−1 Comp_{once} = left ( 3times3 8right )times c c-1 Componce​=(3×3 8)×c c−1 那么完成所有的运算的计算量就应该是: Compb=Compo×c×w×h Comp_{b} =Comp_{o}times c times w times h Compb​=Compo​×c×w×h 为了让后续的约分方便,我们把这个计算近似一下,忽略单次卷积里面的逐通道相加操作: Compb=(3×3 8)×c×c×w×h Comp_{b} = left ( 3times3 8right )times c times c times w times h Compb​=(3×3 8)×c×c×w×h

那么这样的一个操作按照上面提到的Octave Convolution实现,应该怎么计算呢? 假设系数ain=aout=0.5a_{in}=a_{out}=0.5ain​=aout​=0.5 步骤(1): Compo1=(3×3 8)×12c×12c×w×h=14Compa Comp_{o1} = left ( 3times3 8right )times frac{1}{2}c times frac{1}{2}c times w times h = frac{1}{4}Comp_{a} Compo1​=(3×3 8)×21​c×21​c×w×h=41​Compa​ 步骤(2)忽略下采样: Compo2=(3×3 8)×14c×14c×14w×14h=116Compa Comp_{o2} = left ( 3times3 8right )times frac{1}{4}c times frac{1}{4}c times frac{1}{4}w times frac{1}{4}h = frac{1}{16}Comp_{a} Compo2​=(3×3 8)×41​c×41​c×41​w×41​h=161​Compa​ 步骤(3): Compo3=(3×3 8)×14c×14c×14w×14h=116Compa Comp_{o3} = left ( 3times3 8right )times frac{1}{4}c times frac{1}{4}c times frac{1}{4}w times frac{1}{4}h = frac{1}{16}Comp_{a} Compo3​=(3×3 8)×41​c×41​c×41​w×41​h=161​Compa​ 步骤(4)忽略上采样: Compo4=(3×3 8)×14c×14c×14w×14h=116Compa Comp_{o4} = left ( 3times3 8right )times frac{1}{4}c times frac{1}{4}c times frac{1}{4}w times frac{1}{4}h = frac{1}{16}Comp_{a} Compo4​=(3×3 8)×41​c×41​c×41​w×41​h=161​Compa​ 最后加起来: Compo==716Compa Comp_{o} = = frac{7}{16}Comp_{a} Compo​==167​Compa​

OctaveConv的Caffe实现

octave_upsample_layer

在此之前,OctaveConv已经有了MXNet和Pytorch版本的实现,分别是OctaveConv和OctaveConv_pytorch,但是还没有Caffe版本的实现,按照OctaveConv的原理,这个网络用Caffe是可以搭的,Caffe的已有上采样方式都不适用。 所以我添加了一个自定义层:octave_upsample_layer,以支持OctaveConv的上采样操作。 在这个层中,forward的部分就是上面提到的,而backward的部分,实现方式是这样:

自定义层注册

要把这个层添加到Caffe中需要

代码语言:javascript复制
message LayerParameter {
optional OctaveUpsampleParameter octaveupsample_param = 最后一个ID;
}

并且它有下面几个参数:

代码语言:javascript复制
message OctaveUpsampleParameter {
  // DEPRECATED. No need to specify upsampling scale factors when
  // exact output shape is given by upsample_h, upsample_w parameters.
  optional uint32 scale = 1 [default = 2];
  // DEPRECATED. No need to specify upsampling scale factors when
  // exact output shape is given by upsample_h, upsample_w parameters.
  optional uint32 scale_h = 2;
  // DEPRECATED. No need to specify upsampling scale factors when
  // exact output shape is given by upsample_h, upsample_w parameters.
  optional uint32 scale_w = 3;
  // DEPRECATED. Specify exact output height using upsample_h. This
  // parameter only works when scale is 2
  optional bool pad_out_h = 4 [default = false];
  // DEPRECATED. Specify exact output width using upsample_w. This
  // parameter only works when scale is 2
  optional bool pad_out_w = 5 [default = false];
  optional uint32 upsample_h = 6;
  optional uint32 upsample_w = 7;
}

重新Build就好了。

Example

已有的主干网络添加OctaveConv的例子已经上传到了OctaveConv_Caffe项目。

OctaveConv一定能让网络变快吗?

这个答案是否定的,因为对于一个模型,在输入图像尺寸固定的情况下,它的计算量就是确定的,但是对于一个模型forward的速度,却和平台有关。所以OctaveConv一定能降低已有模型的计算量,这个是公式可证的。 但是它不一定能让已有模型的速度变得更快,这是因为,OctaveConv把原来一次就能完成的卷积分开了四次完成,这里就会增加额外的数据传输时的消耗,此外还有额外的上采样、下采样、按位加操作。 所以,比如我能在一个高性能的处理器上forward一个模型,它处理卷积操作的速度很快,那么如果Octaveconv节省下来的卷积操作耗时无法弥补这些额外的开销的话,网络就不会变得更快。 相反的,如果卷积操作的耗时很大,利用Octaveconv可以节省下的时间比其余的开销要大,那么网络就会变快。

下面是一个resnet18和resnet18_octave_0.5的网络耗时对比,平台是i7cpu: resnet18的conv19耗时

代码语言:javascript复制
conv19   forward: 6.9 ms.

resnet18_octave_0.5conv19耗时6.9ms

代码语言:javascript复制
 conv19_hf                forward: 3 ms.
 conv19_hf_add        forward: 1.14 ms.
 conv19_lf                 forward: 1.24 ms.
 conv19_lf_add         forward: 1.26 ms.

最后他们相差并不多,下面四个卷积操作并没有按照14frac{1}{4}41​、116frac{1}{16}161​、116frac{1}{16}161​、116frac{1}{16}161​的比例。一方面是因为相比于很多移动平台,i7-7700的性能还是比较强的,此外,resnet18的conv19卷积,本身计算量也不太大。 当然,这并不能说方法不work,paper本身提供的是一个思路,我们需要在合适的结构和平台下做更多的验证。

0 人点赞