本文总结Shufflenet-V2相对Shuffle-V1的改进,并对其代码使用Pytorch1.8进行了复现。下面给出论文地址及参考代码地址,以供学习和参考。
论文地址
参考代码地址
提示:以下是本篇文章正文内容,下面案例可供参考
该论文指出用间接指标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%的加速。
针对以上优化原则,该论文对其基本模块进行了设计。
1)基础bottleblock设计:
在不用降维的block的模块中,在输入增设通道分割操作将输入特征图一分为二,满足(3)原则减小网络碎片化,一侧特征图通过三个输入与输出相等的卷积层,其中pointwise卷积不再采用群卷积方式,满足(2)过量群卷积会加剧MAC,而后两个特征图进行连接并执行shuffle操作。之外shufflenet-V2放弃了Add及ReLU操作,满足原则(4)元素操作占用时间。
在下采样的bottleblock模块中,删除通道分割操作,继承Densenet的密集连接思想,下采样层变为了33深度可分离卷积和11卷积的组合,这样使输出通道增加一倍。此处作者对比Densenet和shufflenet-V2之间的特征重用的关系,结果表明:特性重用的数量随着两个块之间的距离呈指数衰减。
2)整体网络结构
代码如下:
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]
代码如下:
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)
代码如下:
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)
结果如下:可以看出一张(224,224)的彩图经shufflenet-V2所需内存大小为56.44M,其轻量化程度属实不错!!!
本文介绍了shuffleNetV2的核心思想及其代码实现,以供大家交流讨论!
往期回顾:
(1)CBAM论文解读+CBAM-ResNeXt的Pytorch实现
(2)SENet论文解读及代码实例
(3)ShuffleNet-V1论文理解及代码复现
下期预告:
GhostNet论文阅读及代码实现