ShuffleNet-V2论文理解及代码复现

ShuffleNet-V2论文理解及代码复现

目录

    • ShuffleNet-V2论文理解及代码复现
  • 文章速览
  • 一、论文理解
    • 1.关键点及实验
    • 2.架构设计
  • 二、代码复现
    • 1.通道spilt及shuffle操作
    • 2.bottleblock实现
    • 3.网络实现
    • 4.实现效果
  • 总结


文章速览

本文总结Shufflenet-V2相对Shuffle-V1的改进,并对其代码使用Pytorch1.8进行了复现。下面给出论文地址及参考代码地址,以供学习和参考。
论文地址
参考代码地址

提示:以下是本篇文章正文内容,下面案例可供参考

一、论文理解

1.关键点及实验

该论文指出用间接指标FLOPs来衡量模型架构速度是不全面的,模型应当在目标平台上以运行时间等指标来衡量,并提出了四条网络架构设计原则:使用“平衡”卷积;了解使用群卷积的代价;降低碎片化程度;减少元素操作。
1)在3*3深度可分离卷积之前的pointwise卷积应保证输入输出通道不变,以节省内存访问成本。此处设计了一处实验,通过改变 pointwise卷积输入输出的比例为1,2,6,12来验证该原则,下图为实验结果。由实验结果可以看出,当c1:c2趋近1:1时,MAC变小,网络评估速度变快。
在这里插入图片描述
2)过度的群卷积会增加内存访问成本。此处通过设置不同的群卷积数1、2、4、8来评估其运行时间,在GPU上使用8组要比使用1组(标准密集卷积)慢两倍多。其实验结论如下:建议根据目标平台和任务仔细选择分组号。使用大的组数是不明智的,因为这可能会使用更多的通道,因为准确性的提高很容易被快速增加的计算成本所抵消。
在这里插入图片描述
3)网络碎片化降低了并行度。虽然这种碎片化结构已经被证明有助于提高精度,但它可能会降低效率,因为它不利于GPU等具有强大并行计算能力的设备。它还引入了额外的开销,如内核启动和同步。此处设计了不同分片操作对运行时间的影响。实验结论如下:分片在GPU上显著降低了速度,例如4个分片结构比1个分片结构慢3倍。
在这里插入图片描述
4)元素操作占用时间多。在GPU上元素操作符包括ReLU、AddTensor、AddBias等,此处对ReLU和short-cut的操作对GPU运行时间做了对比,结果表明,在移除ReLU和快捷方式后,GPU和ARM上都获得了约20%的加速。
在这里插入图片描述

2.架构设计

针对以上优化原则,该论文对其基本模块进行了设计。
1)基础bottleblock设计:
在这里插入图片描述
在不用降维的block的模块中,在输入增设通道分割操作将输入特征图一分为二,满足(3)原则减小网络碎片化,一侧特征图通过三个输入与输出相等的卷积层,其中pointwise卷积不再采用群卷积方式,满足(2)过量群卷积会加剧MAC,而后两个特征图进行连接并执行shuffle操作。之外shufflenet-V2放弃了Add及ReLU操作,满足原则(4)元素操作占用时间。
在下采样的bottleblock模块中,删除通道分割操作,继承Densenet的密集连接思想,下采样层变为了33深度可分离卷积和11卷积的组合,这样使输出通道增加一倍。此处作者对比Densenet和shufflenet-V2之间的特征重用的关系,结果表明:特性重用的数量随着两个块之间的距离呈指数衰减。
在这里插入图片描述
2)整体网络结构
在这里插入图片描述

二、代码复现

1.通道spilt及shuffle操作

代码如下:

    def channel_shuffle(self, x):batchsize, num_channels, height, width = x.data.size()assert (num_channels % 4 == 0)x = x.reshape(batchsize * num_channels // 2, 2, height * width)x = x.permute(1, 0, 2)x = x.reshape(2, -1, num_channels // 2, height, width)return x[0], x[1]

2.bottleblock实现

代码如下:

class bottleblock(nn.Module):def __init__(self,in_channel,out_channel,mid_channel,stride):super(bottleblock, self).__init__()self.midchannel=mid_channeloutput=out_channel-in_channelself.stride=strideself.pointwise_conv1=nn.Sequential(nn.Conv2d(in_channels=in_channel,out_channels=mid_channel,kernel_size=1,stride=1,bias=False),nn.BatchNorm2d(mid_channel),nn.ReLU(inplace=True))self.depth_conv=nn.Sequential(nn.Conv2d(in_channels=mid_channel,out_channels=mid_channel,kernel_size=3,padding=1,stride=stride,groups=mid_channel,bias=False),nn.BatchNorm2d(mid_channel))self.pointwise_conv2=nn.Sequential(nn.Conv2d(in_channels=mid_channel,out_channels=output,kernel_size=1,stride=1,bias=False),nn.BatchNorm2d(output),nn.ReLU(inplace=True))if stride==2:self.shortcut=nn.Sequential(nn.Conv2d(in_channels=in_channel,out_channels=in_channel,kernel_size=3,padding=1,stride=stride,groups=in_channel,bias=False),nn.BatchNorm2d(in_channel),nn.Conv2d(in_channels=in_channel,out_channels=in_channel,kernel_size=1,stride=1,bias=False),nn.BatchNorm2d(in_channel),nn.ReLU(inplace=True))else:self.shortcut=nn.Sequential()def channel_shuffle(self, x):batchsize, num_channels, height, width = x.data.size()assert (num_channels % 4 == 0)x = x.reshape(batchsize * num_channels // 2, 2, height * width)x = x.permute(1, 0, 2)x = x.reshape(2, -1, num_channels // 2, height, width)return x[0], x[1]def forward(self,x):if self.stride==2:residual=self.shortcut(x)x=self.pointwise_conv1(x)x=self.depth_conv(x)x=self.pointwise_conv2(x)return torch.cat((residual,x),dim=1)elif self.stride==1:x1,x2=self.channel_shuffle(x)residual=self.shortcut(x2)x1=self.pointwise_conv1(x1)x1=self.depth_conv(x1)x1=self.pointwise_conv2(x1)return torch.cat((residual,x1),dim=1)

3.网络实现

代码如下:

class shufflenet(nn.Module):def __init__(self,num_class,size):"""size表示模型大小"""super(shufflenet, self).__init__()self.num_class=num_classself.inchannel=24if size==0.5:stage_dict={'bolck_num':[4,8,4],'outchannel':[48,96,192],'last_conv':1024,'size':size}elif size==1:stage_dict = {'bolck_num': [4, 8, 4],'outchannel': [116, 232, 464],'last_conv': 1024,'size':size}elif size==1.5:stage_dict = {'bolck_num': [4, 8, 4],'outchannel': [176, 352, 704],'last_conv': 1024,'size':size}elif size==2:stage_dict = {'bolck_num': [4, 8, 4],'outchannel': [244, 488, 976],'last_conv': 2048,'size':size}block_num=stage_dict['bolck_num']outchannel=stage_dict['outchannel']last_conv=stage_dict['last_conv']self.initial=nn.Sequential(nn.Conv2d(kernel_size=3,padding=1,in_channels=3,out_channels=24,stride=2),nn.BatchNorm2d(24),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3,stride=2,padding=1))self.layer1 = self.make_layer(block_num[0],outchannel[0])self.layer2 = self.make_layer(block_num[1], outchannel[1])self.layer3 = self.make_layer(block_num[2], outchannel[2])self.last_conv=nn.Conv2d(in_channels=outchannel[2],out_channels=last_conv,stride=1,kernel_size=1,bias=False)self.pool=nn.AdaptiveAvgPool2d(1)self.fc=nn.Linear(last_conv,num_class)def make_layer(self,block_num,outchannel):layer_list=[]for i in range(block_num):if i==0:stride=2layer_list.append(bottleblock(self.inchannel,outchannel,outchannel//2,stride=stride))self.inchannel=outchannelelse:stride=1layer_list.append(bottleblock(self.inchannel//2,outchannel,outchannel//2,stride=stride))return nn.Sequential(*layer_list)def forward(self,x):x=self.initial(x)x=self.layer1(x)x=self.layer2(x)x=self.layer3(x)x=self.last_conv(x)x=self.pool(x)x=x.view(x.size(0),-1)x=self.fc(x)return F.softmax(x,dim=1)

4.实现效果

结果如下:可以看出一张(224,224)的彩图经shufflenet-V2所需内存大小为56.44M,其轻量化程度属实不错!!!
在这里插入图片描述


总结

本文介绍了shuffleNetV2的核心思想及其代码实现,以供大家交流讨论!
往期回顾:
(1)CBAM论文解读+CBAM-ResNeXt的Pytorch实现
(2)SENet论文解读及代码实例
(3)ShuffleNet-V1论文理解及代码复现
下期预告:
GhostNet论文阅读及代码实现


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部