快速理解ShuffleNetV1(结合代码)

1.简介

ShuffleNetv1主要的创新点在于,虽然组卷积能够很好的减少参数和计算量,但当你连续使用几个组卷积时,每个group之间是没有任何的信息交流的,这可能导致提取出来的特征图不够准确,对于全局的关注不够,影响感受野。所以该论文用了一个shuffle的操作,将几个group之间的信息打乱,这样就能够缓解group之间信息无交流的问题。其次,由于ResNeXt网络中1x1卷积所占的计算量比重很大,所以将block中的1x1卷积也替换成了组卷积。就这两个主要改动,shuffle和1x1组卷积。

上图为ShuffleNetv1的性能展示,可以看到,与AlexNet和Mobilenet相比,同等参数量下错误率、推理时间都是最优的。

2.ShuffleNetv1 block

下图能够很好的体现该文章核心思想,将feature map打乱,促进group间的信息交流。

 论文中说到,在ResNeXt中,1x1的PW卷积层占了93.4%的计算量,而group卷积占用的计算量很少,所以ShuffleNetv1 block将ResNeXt block(a图)中的1x1卷积替换成了组卷积(b图),b图是stride=1时的block,当stride=2,需要下采样时,就通过c图来进行。注意:C图中是将两个分支Concat在一起的,而不是前面的Add操作。

 上图是关于这三个block参数量的对比,可以发现ShuffleNetv1的参数量是最少的。

3.ShuffleNetv1整体网络结构

下图就是ShuffleNetv1的网络结构了,

需要注意的是,每个block中,第一个1x1组卷积层和3x3DW卷积的out_channel为整个block的out_channel的四分之一。比如block输入feature map的cahnnel为256,out_channel也是256,那按顺序1x1的out_channel=64,3x3的out_channel=64,1x1的out_channel=256,最后再与shortcut的256channel相加得到block的输出。

还有一点,stage2中由于输入的feature map通道数太少,不值得第一个1x1卷积用组卷积,所以stage2的第一个1x1卷积还是普通卷积。

4.ShuffleNetv1代码

由于相较于ResNet,ShuffleNetv1只是将1x1普通卷积变为组卷积,然后多了一个Shuffle模块,所以这里就只放Shuffle模块了,其他的部分在ResNet网络上改一下就能实现。

def channel_shuffle(x: Tensor, groups: int) -> Tensor:

    batch_size, num_channels, height, width = x.size()
    channels_per_group = num_channels // groups

    # reshape
    # [batch_size, num_channels, height, width] -> [batch_size, groups, channels_per_group, height, width]
    x = x.view(batch_size, groups, channels_per_group, height, width)

    x = torch.transpose(x, 1, 2).contiguous()

    # flatten
    x = x.view(batch_size, -1, height, width)

    return x

以下是ShuffleNetV1的PyTorch代码实现,包括ShuffleNetV1的网络结构和训练代码: ```python import torch.nn as nn import math __all__ = ['ShuffleNetV1', 'shufflenetv1'] def conv3x3(in_planes, out_planes, stride=1): """3x3 convolution with padding""" return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False) class BasicBlock(nn.Module): def __init__(self, inplanes, planes, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = conv3x3(inplanes, planes, stride) self.bn1 = nn.BatchNorm2d(planes) self.relu = nn.ReLU(inplace=True) self.conv2 = conv3x3(planes, planes) self.bn2 = nn.BatchNorm2d(planes) self.downsample = downsample self.stride = stride def forward(self, x): residual = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) if self.downsample is not None: residual = self.downsample(x) out += residual out = self.relu(out) return out class ShuffleNetV1(nn.Module): def __init__(self, num_classes=1000, groups=3, width_mult=1): super(ShuffleNetV1, self).__init__() self.groups = groups self.stage_repeats = [3, 7, 3] if groups == 1: self.stage_out_channels = [-1, 24, 144, 288, 576] elif groups == 2: self.stage_out_channels = [-1, 24, 200, 400, 800] elif groups == 3: self.stage_out_channels = [-1, 24, 240, 480, 960] elif groups == 4: self.stage_out_channels = [-1, 24, 272, 544, 1088] elif groups == 8: self.stage_out_channels = [-1, 24, 384, 768, 1536] else: raise ValueError("""{} groups is not supported for 1x1 Grouped Convolutions""".format(num_groups)) # building first layer input_channels = 3 output_channels = self.stage_out_channels[1] output_channels = int(output_channels * width_mult) self.conv1 = nn.Conv2d(input_channels, output_channels, kernel_size=3, stride=2, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(output_channels) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) # building stages self.stage2 = self._make_stage(2, width_mult) self.stage3 = self._make_stage(3, width_mult) self.stage4 = self._make_stage(4, width_mult) # building last several layers self.conv_last = nn.Conv2d(self.stage_out_channels[-2], self.stage_out_channels[-1], kernel_size=1, stride=1, padding=0, bias=False) self.globalpool = nn.AvgPool2d(7) self.fc = nn.Linear(self.stage_out_channels[-1], num_classes) def _make_stage(self, stage, width_mult): modules = OrderedDict() stage_name = "ShuffleUnit_Stage{}".format(stage) # stage_repeats = self.stage_repeats[stage] unit1 = ShuffleUnit(self.stage_out_channels[stage-1], self.stage_out_channels[stage], 2, groups=self.groups, width_mult=width_mult) modules[stage_name+"_unit1"] = unit1 for i in range(self.stage_repeats[stage-2]): name = stage_name + "_unit" + str(i+2) module = ShuffleUnit(self.stage_out_channels[stage], self.stage_out_channels[stage], 1, groups=self.groups, width_mult=width_mult) modules[name] = module return nn.Sequential(modules) def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x = self.stage2(x) x = self.stage3(x) x = self.stage4(x) x = self.conv_last(x) x = self.globalpool(x) x = x.view(-1, self.stage_out_channels[-1]) x = self.fc(x) return x class ShuffleUnit(nn.Module): def __init__(self, in_channels, out_channels, stride, groups=3, width_mult=1): super(ShuffleUnit, self).__init__() if stride != 1 or in_channels != out_channels: self.use_res_connect = False self.shortcut = nn.Sequential( nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=stride, padding=1, groups=in_channels, bias=False), nn.BatchNorm2d(in_channels), nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False), nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True), ) else: self.use_res_connect = True self.shortcut = nn.Sequential() self.groups = groups mid_channels = int(out_channels / 4 * width_mult) self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, bias=False) self.bn1 = nn.BatchNorm2d(mid_channels) self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1, groups=groups, bias=False) self.bn2 = nn.BatchNorm2d(mid_channels) self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False) self.bn3 = nn.BatchNorm2d(out_channels) self.relu = nn.ReLU(inplace=True) def _channel_shuffle(self, x, groups): batchsize, num_channels, height, width = x.data.size() channels_per_group = num_channels // groups x = x.view(batchsize, groups, channels_per_group, height, width) x = torch.transpose(x, 1, 2).contiguous() x = x.view(batchsize, -1, height, width) return x def forward(self, x): if self.use_res_connect: shortcut = x x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.conv2(x) x = self.bn2(x) x = self.relu(x) x = self._channel_shuffle(x, self.groups) x = self.conv3(x) x = self.bn3(x) if self.use_res_connect: shortcut = self.shortcut(shortcut) x += shortcut x = self.relu(x) return x def shufflenetv1(**kwargs): """ Constructs a ShuffleNetV1 model """ return ShuffleNetV1(**kwargs) ``` 在使用ShuffleNetV1时,可以通过以下方式进行实例化: ```python import torch from shufflenetv1 import shufflenetv1 model = shufflenetv1(groups=3, width_mult=1) input = torch.randn(1, 3, 224, 224) output = model(input) print(output.shape) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值