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

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


文章速览

本文总结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_channel
        output=out_channel-in_channel
        self.stride=stride

        self.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_class
        self.inchannel=24
        if 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=2
                layer_list.append(bottleblock(self.inchannel,outchannel,outchannel//2,stride=stride))
                self.inchannel=outchannel
            else:
                stride=1
                layer_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论文阅读及代码实现

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炼丹代师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值