Octave Convolution原理与Caffe实现

前言

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 × H 3 \times W \times H 3×W×H的矩阵,矩阵中每一个位置都有一个[0,255]的值,而从频域的角度出发的话,一副图像都可以被分解为描述平稳变化结构的低空间频率分量(低频域、low-frequency)和描述快速变化的精细细节的高空间频率分量(高频域、high-frequency),就像下面这幅图:

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

特征图的高频与低频表示

既然对于图像来说可以区分高频与低频,那么对于特征图也是这样,特征图无非就是一个channel更多的矩阵而已,但是对于一个端对端的CNN模型,总不能在网络中引入一种频域计算,所以Octave Convolution显示的定义了“下采样”操作后的特征图叫做“低频域”,而不做下采样的原始尺寸叫做“高频域”。这样一来由于下采样带来的特征图尺寸减小,从而使得Octave Convolution计算量降低,此外网络有了不同尺度的信息(两个频域),并且两个频域的信息会在卷积完成后聚合,这个特性使得Octave Convolution具有比之前更好的性能。
“下采样”的scale,采用的是2的幂次,而目前文章只讨论了 2 1 2^{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是一种下采样和上采样的组合,那么它的实现也就好理解了:
在这里插入图片描述
一个特征图的通道数 c i n c_{in} cin根据预设系数 a i n a_{in} ain切分为高频 ( 1 − a i n ) c i n \left ( 1- a_{in}\right )c_{in} (1ain)cin与低频 a i n c i n a_{in}c_{in} aincin的部分,低频部分的宽高都缩小为原来的一半。然后Octave Convolution会做下面四个部分:
(1)高频部分直接卷积: f ( X H ) f\left ( X^{H}\right ) f(XH),即高频到高频的卷积,输出通道数 ( 1 − a o u t ) c o u t \left ( 1- a_{out}\right )c_{out} (1aout)cout
(2)高频部分先做下采样再卷积,这里的下采样是 p o o l ( X H , 2 ) pool\left ( X^{H},2\right ) pool(XH,2),然后 f ( p o o l ( X H , 2 ) ) f\left ( pool\left ( X^{H},2\right )\right) f(pool(XH,2)),即高频到低频的卷积,输出通道数 a o u t c o u t a_{out}c_{out} aoutcout
(3)低频部分直接卷积后做上采样: f ( X L ) f\left ( X^{L}\right ) f(XL),这里的 u p s a m p l e ( f ( X L ) ) upsample\left ( f\left ( X^{L}\right )\right ) upsample(f(XL))所用的上采样方法我们后面再说,即低频到高频的卷积,输出通道数 ( 1 − a o u t ) c o u t \left ( 1- a_{out}\right )c_{out} (1aout)cout
(4)低频部分直接卷积: f ( X L ) f\left ( X^{L}\right ) f(XL),即低频到低频的卷积,输出通道数 a o u t c o u t a_{out}c_{out} aoutcout
这四个部分完成之后,接下来就要做信息的聚合,也就是(1)和(3)的结果做一个对应位置的按位加操作,(2)和(4)的结果做一个对应位置的按位加操作。
这样Octave Convolution就完成了,它其实在做的就是把原来的一个卷积操作,拆成了4个,而这4个中有三个处理的输入都是原来特征图w,h的一半,所以计算量就下来了。
所以一个one-stream的网络,在使用Octave Convolution之后,其实会变成two-stream结果,也就是高频流和低频流,在每一次的卷积结束之后,两个stream的信息会聚合一次。既然是中间是two-stream结构,那原有的网络怎么开始和收尾呢?
假如最开始时的输入只有一个,通道数还用 c i n c_{in} cin表示,那么要分出高频和低频两个流,就是只做(1)和(2),但是区别在于,输入特征图的通道数就是 c i n c_{in} cin。而最后就是只做(1)和(3),区别是输出特征图的通道数就是 c o u t c_{out} cout

上采样和下采样

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

上采样:
Octave Convolution的高频域输出可以用下面的公式表示:
Y p , q H = Y p , q H → H + Y p , q L → H Y_{p,q}^{H}=Y_{p,q}^{H\rightarrow H}+Y_{p,q}^{L\rightarrow H} Yp,qH=Yp,qHH+Yp,qLH
Y p , q H → H = ∑ i , j ∈ N k W i + k − 1 2 , j + k − 1 2 H → H X p + i , q + j H Y_{p,q}^{H\rightarrow H}=\sum_{i,j\in N{_{k}}}W_{i+\frac{k-1}{2},j+\frac{k-1}{2}}^{H\rightarrow H}X_{p+i,q+j}^{H} Yp,qHH=i,jNkWi+2k1,j+2k1HHXp+i,q+jH
Y p , q L → H = ∑ i , j ∈ N k W i + k − 1 2 , j + k − 1 2 H → L X p 2 + i , q 2 + j L Y_{p,q}^{L\rightarrow H}=\sum_{i,j\in N{_{k}}}W_{i+\frac{k-1}{2},j+\frac{k-1}{2}}^{H\rightarrow L}X_{\frac{p}{2}+i, \frac{q}{2}+j}^{L} Yp,qLH=i,jNkWi+2k1,j+2k1HLX2p+i,2q+jL
有了上面的介绍,这个就可以简单点说了,应特征图上而 X p 2 + i , q 2 + j L X_{\frac{p}{2}+i, \frac{q}{2}+j}^{L} X2p+i,2q+jL的点其实是一个点复制成了一个四方格,或者说,这分明是一个最邻近插值。
在这里插入图片描述

OctaveConv如何减低计算量

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

那么这样的一个操作按照上面提到的Octave Convolution实现,应该怎么计算呢?
假设系数 a i n = a o u t = 0.5 a_{in}=a_{out}=0.5 ain=aout=0.5
步骤(1):
C o m p o 1 = ( 3 × 3 + 8 ) × 1 2 c × 1 2 c × w × h = 1 4 C o m p a Comp_{o1} = \left ( 3\times3+8\right )\times \frac{1}{2}c \times \frac{1}{2}c \times w \times h = \frac{1}{4}Comp_{a} Compo1=(3×3+8)×21c×21c×w×h=41Compa
步骤(2)忽略下采样:
C o m p o 2 = ( 3 × 3 + 8 ) × 1 4 c × 1 4 c × 1 4 w × 1 4 h = 1 16 C o m p a Comp_{o2} = \left ( 3\times3+8\right )\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)×41c×41c×41w×41h=161Compa
步骤(3):
C o m p o 3 = ( 3 × 3 + 8 ) × 1 4 c × 1 4 c × 1 4 w × 1 4 h = 1 16 C o m p a Comp_{o3} = \left ( 3\times3+8\right )\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)×41c×41c×41w×41h=161Compa
步骤(4)忽略上采样:
C o m p o 4 = ( 3 × 3 + 8 ) × 1 4 c × 1 4 c × 1 4 w × 1 4 h = 1 16 C o m p a Comp_{o4} = \left ( 3\times3+8\right )\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)×41c×41c×41w×41h=161Compa
最后加起来:
C o m p o = = 7 16 C o m p a Comp_{o} = = \frac{7}{16}Comp_{a} Compo==167Compa

OctaveConv的Caffe实现

octave_upsample_layer

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

自定义层注册

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

message LayerParameter {
optional OctaveUpsampleParameter octaveupsample_param = 最后一个ID;
}

并且它有下面几个参数:

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可以节省下的时间比其余的开销要大,那么网络就会变快。

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

conv19   forward: 6.9 ms.

resnet18_octave_0.5conv19耗时6.9ms

 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.

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

©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页