失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 白给的性能不要?cvpr-Diverse branch block

白给的性能不要?cvpr-Diverse branch block

时间:2021-03-27 01:29:11

相关推荐

白给的性能不要?cvpr-Diverse branch block

论文地址:Diverse Branch Block: Building a Convolution as an Inception-like Unit

官方代码:DingXiaoH/DiverseBranchBlock

引言

本文是继前作ACNet的又一次对网络结构重参数化的探索,我们设计了一个类似Inception的模块,以多分支的结构丰富卷积块的特征空间,各分支结构包括平均池化,多尺度卷积等。最后在推理阶段前,把多分支结构中进行重参数化,融合成一个主分支。这样能在相同的推理速度下,“白嫖”模型性能

DBB结构转换

卷积的性质

常规卷积核本质上也是一个张量,其形状为(输出通道数,输入通道数,卷积核大小,卷积核大小)

而卷积操作本质上也是一个线性操作,因此卷积在某些情况下具备一些线性的性质

可加性

可加性即在两个卷积核形状一致的情况下,卷积结果满足可加性 即

其中 和 分别表示两个独立的卷积操作

同质性

后续我们针对多分支结构的转换都是基于这两种基本性质来操作的

论文提及的6种转换

转换1:Conv-BN融合

在CNN中,卷积层和BN层经常是成对出现的,我们可以把BN的参数融入到卷积层里(这里偷懒,直接复制粘贴以前RepVGG写的推导了) 卷积层公式为

BN层公式为

将卷积层结果带入到BN公式中

化简为

这其实就是一个卷积层,只不过权重考虑了BN的参数 令

融合的结果就是

转换2 分支相加

这就利用到我们前面讲的卷积可加性,这也比较好理解,我们可以看一段基于oneflow框架的验证代码

importoneflowasflowimportoneflow.typingastpimportnumpyasnpfromtypingimportTuple@flow.global_function()defconv_add(x:tp.Numpy.Placeholder(shape=(1,2,4,4)))->Tuple[tp.Numpy,tp.Numpy]:conv1=flow.layers.conv2d(x,4,kernel_size=3,padding="SAME",name="conv1")conv2=flow.layers.conv2d(x,4,kernel_size=3,padding="SAME",name="conv2")#MergeAddconv_merge_add=flow.layers.conv2d(x,4,kernel_size=3,padding="SAME",name="conv_merge_add")returnconv1+conv2,conv_merge_addx=np.ones(shape=(1,2,4,4)).astype(np.float32)weight_1=np.random.randn(4,2,3,3).astype(np.float32)weight_2=np.random.randn(4,2,3,3).astype(np.float32)#Loadnumpyweightflow.load_variables({"conv1-weight":weight_1,"conv2-weight":weight_2,"conv_merge_add-weight":weight_1+weight_2})original_conv_add,merge_conv_add=conv_add(x)print("Conv1+Conv2outputis:",original_conv_add)print("MergeAddoutputis:",merge_conv_add)print("IsMatch:",np.allclose(original_conv_add,merge_conv_add,atol=1e-5))

这里我们定义了一个方法,conv1和conv2分别表示两个独立的卷积操作,最后相加返回。而conv3表示的是融合后的卷积操作。定义好后,我们将设定好的权重分别导入给conv1和conv2,然后将相加后的权重,导入给conv3,最后用np.allclose来验证结果是否准确

转换3 序列卷积融合

在网络设计中,我们也会用到1x1卷积接3x3卷积这种设计(如ResNet的BottleNeck块),它能调整通道数,减少一定的参数量。

其原始公式如下

我们假设输入是一个三通道的图片,1x1卷积的输出通道为2,3x3卷积的输出通道为4,那么图示如下

1x1接3x3

作者提出了这么一个转换方法,首先将1x1卷积核的第零维和第一维互相调换位置

然后3x3卷积核权重与转置后的"1x1卷积核"进行卷积操作

1x1和KxK卷积转换

最后输入与其做卷积操作,整个流程可以写为

这里我也简单写了一个测试代码

importoneflowasflowimportoneflow.typingastpimportnumpyasnpfromtypingimportTuple@flow.global_function()defconv2d_Job(x:tp.Numpy.Placeholder((1,3,4,4)))->Tuple[tp.Numpy,tp.Numpy]:weight_1x1=flow.get_variable(name="weight1x1",shape=[2,3,1,1],#[O_c,I_c,ksize,ksize]initializer=flow.ones_initializer(),)weight_3x3=flow.get_variable(name="weight3x3",shape=[4,2,3,3],#[O_c,I_c,ksize,ksize]initializer=flow.ones_initializer(),)conv_1x1=flow.nn.conv2d(x,weight_1x1,strides=1,padding=(0,0,0,0),name="conv1x1")conv_1x1_3x3=flow.nn.conv2d(conv_1x1,weight_3x3,strides=1,padding=(0,0,0,0),name="conv3x3")weight_1x1_transposed=flow.transpose(weight_1x1,[1,0,2,3])#[2,3,1,1]->[3,2,1,1]weight_merge=flow.nn.conv2d(weight_3x3,weight_1x1_transposed,strides=1,padding=(0,0,0,0),name="weight_merge")#[4,3,3,3]conv_merge=flow.nn.conv2d(x,weight_merge,strides=1,padding=(0,0,0,0),name="conv_merge")returnconv_1x1_3x3,conv_mergex=np.ones(shape=(1,3,4,4)).astype(np.float32)weight_1x1=np.random.randn(2,3,1,1).astype(np.float32)weight_3x3=np.random.randn(4,2,3,3).astype(np.float32)#Loadnumpyweightflow.load_variables({"weight1x1":weight_1x1,"weight3x3":weight_3x3})conv1x1_3x3,conv_merge=conv2d_Job(x)print("Conv1x1and3x3is:",conv1x1_3x3)print("MergeConv:",conv_merge)print("IsMatch:",np.allclose(conv1x1_3x3,conv_merge,atol=1e-5))

转换4 拼接融合

在Inception模块中,我们经常会用到的一个操作就是concat,将各分支的特征,在通道维上进行拼接。

我们也可以将多个卷积拼接转换为一个卷积操作,只需要将多个卷积核权重在输出通道维度上进行拼接即可,下面是一个示例代码

importoneflowasflowimportoneflow.typingastpimportnumpyasnpfromtypingimportTuple@flow.global_function()defconv_concat(x:tp.Numpy.Placeholder(shape=(1,1,4,4)))->Tuple[tp.Numpy,tp.Numpy]:conv1=flow.layers.conv2d(x,2,kernel_size=3,padding="SAME",name="conv1")conv2=flow.layers.conv2d(x,2,kernel_size=3,padding="SAME",name="conv2")#MergeConcatconv_merge_concat=flow.layers.conv2d(x,4,kernel_size=3,padding="SAME",name="conv_merge_concat")returnflow.concat([conv1,conv2],axis=1),conv_merge_concatx=np.ones(shape=(1,1,4,4)).astype(np.float32)weight_1=np.random.randn(2,1,3,3).astype(np.float32)weight_2=np.random.randn(2,1,3,3).astype(np.float32)flow.load_variables({"conv1-weight":weight_1,"conv2-weight":weight_2,"conv_merge_concat-weight":np.concatenate([weight_1,weight_2],axis=0)})original_conv_concat,merge_conv_concat=conv_concat(x)print("Conv1concatConv2outputis:",original_conv_concat)print("MergeConcatoutputis:",merge_conv_concat)print("IsMatch:",np.allclose(original_conv_concat,merge_conv_concat,atol=1e-5))

转换5 平均池化层转换

我们简单回顾一下平均池化层操作,它也是一个滑动窗口,对特征图进行滑动,将窗口内的元素求出均值。与卷积层不一样的是,池化层是针对各个输入通道的(如Depthwise卷积),而卷积层会将所有输入通道的结果相加。一个平均池化层的示意图如下:

平均池化层

那其实平均池化层是可以等价一个固定权重的卷积层,假设平均池化层窗口大小为3x3,那么我可以设置3x3卷积层权重为 1/9,滑动过去就是取平均。另外要注意的是卷积层会将所有输入通道结果相加,所以我们需要对当前输入通道设置固定的权重,对其他通道权重设置为0

卷积层替换平均池化层

另外补充一下,由于最大池化层是一个非线性的操作,所以是不能用卷积层替换的 下面是测试代码:

importoneflowasflowimportoneflow.typingastpimportnumpyasnpfromtypingimportTuple@flow.global_function()defavg_pool(x:tp.Numpy.Placeholder(shape=(1,3,4,4)))->Tuple[tp.Numpy,tp.Numpy]:avg_pool_out=flow.nn.avg_pool2d(x,ksize=3,strides=1,padding=(0,0,0,0))#Useconvtoinsteadaveragepoolconv_avg_pool=flow.layers.conv2d(x,3,kernel_size=3,strides=1,name="conv_avg")returnavg_pool_out,conv_avg_poolx=np.ones(shape=(1,3,4,4)).astype(np.float32)weight=np.zeros(shape=(3,3,3,3)).astype(np.float32)foriinrange(3):weight[i,i,:,:]=1/9#Set3x3kernelweightvalueas1/9#Loadnumpyweightflow.load_variables({"conv_avg-weight":weight})avg_pool_out,conv_avg_pool=avg_pool(x)print("AveragePooloutputis:",avg_pool_out)print("ConvAveragePooloutputis:",conv_avg_pool)print("IsMatch:",np.allclose(avg_pool_out,conv_avg_pool,atol=1e-5))

转换6 多尺度卷积融合

这部分其实就是ACNet的思想,存在一个卷积核

那么我们可以把卷积核周围补0,来等效替代KxK卷积核 下面是一个示意图

多尺度卷积融合

Diverse Branch Block结构

介绍完六种等价转换方式后,我们简单看下DBB结构

DBB结构

其中一共有四个分支,分别是

1x1 卷积分支

1x1 - KxK卷积分支

1x1 - 平均池化分支

KxK 卷积分支 启发于Inception模块,各操作有不同的感受野以及计算复杂度,能够极大丰富整个模块的特征空间

因为最后都可以等价转换为一个KxK卷积,作者后续实验就是将这个Block替换到骨干网络中的KxK卷积部分。

实验

实验

作者基于ImageNet上,和前作ACNet在相同的超参数,数据增广条件下进行了对比。可以看到比起ACNet还有一定程度的提升,反正最后都能融合,性能能白嫖一点是一点。

消融实验

作者也针对DBB模块的各个路径做了消融实验,可以看到每个分支都能对模型性能有一定的提升,最后集合起来性能最好。

总结

作者在CVPR的一系列网络重参数化工作解读也算画上了句号,他本人也在实验DBB模块和RepVGG结合会不会有更强的性能。个人感觉这篇文章实用性很大,能白嫖模型性能就白嫖。而且DBB模块的潜力还很大,作者提出的六种等价转换方法都有一定的普适性,说不定后续会有NAS搜索DBB模块的工作。作者这两篇重参数化工作RepVGG和Diverse Branch Block都做的十分好,涉及到的一些转换方法也能加深我们对卷积操作的理解,推荐各位去拜读一下~

进入公众号,在消息对话框

回复【CVPR】即可获取CVPR最新论文集

DLer-CVPR论文分享交流群已成立!

大家好,这是CVPR论文分享群里,群里会第一时间发布CVPR的论文解读和交流分享会,主要设计方向有:图像分类、Transformer、目标检测、目标跟踪、点云与语义分割、GAN、超分辨率、人脸检测与识别、动作行为与时空运动、模型压缩和量化剪枝、迁移学习、人体姿态估计等内容。

进群请备注:研究方向+学校/公司+昵称(如图像分类+上交+小明)

????长按识别,邀请您进群!

如果觉得《白给的性能不要?cvpr-Diverse branch block》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。