ShuffleNet v1

论文 https://arxiv.org/abs/1707.01083

目录

Group Convolution

Group Convolution的用途

pointwise group convolution

channel shuffle

ShuffleUnit

ShuffleNet v1的网络结构 

参考


Group Convolution

上图左边是普通卷积,右边是组卷积。 

假设输入feature map的shape为C×H×W,卷积核数量为N,则输出feature map的通道数也为N。每个卷积核的shape为C×K×K,则N个卷积核的总参数量为N×C×K×K,输入与输出的连接方式如上图左所示。

Group Convolution是对输入feature map进行分组,然后每组分别进行卷积。假设输入尺寸不变,输出通道数量也不变,假设要分成G个groups,则每组输入feature map的通道数量为C/G,每组的输出feature map的通道数为N/G,每个卷积核的shape为C/G×K×K,卷积核的总数仍为N,每组的卷积核数量为N/G,卷积核只与同组的输入进行卷积,则卷积核的总参数量为N×C/G×K×K,总参数量变为原来的1/G,在本例中,C=12,N=6,G=4,输入和输出的连接方式如上图右所示。

Group Convolution的用途

  • 减少参数量,分成G组,该层的参数量减少为原来的1/G
  • Group Convolution可以看成是structured sparse,每个卷积核的尺寸由C×K×K变成C/G×K×K,可以将其余(C-C/G)×K×K的参数视为0,有时甚至可以在减少参数量的同时获得更好的效果(相当于正则
  • 当分组数量等于输入通道数,卷积核数量也等于输入通道数,即C=G=N时,Group Convolution就变成了Depthwise Convolution,参数量进一步缩减
  • 更进一步,当C=G=N,卷积核的大小与输入feature map也相等,即K=H=W时,输出变成了1×1×C的向量,此时称之为Global Depthwise Convolution (GDC),见MobileFaceNet,可以看成是全局加权池化,与Global Average Pooling (GAP)不同的是,GDC给每个位置赋予了可学习的权重(对于已对齐的图像这很有效,比如人脸,中心位置和边界位置的权重自然应该不同)。而GAP是全局取平均,每个位置的权重相同。

pointwise group convolution

作者注意到,尽管在Xception和ResNeXt这类SOTA模型中,在设计block的过程中引入了深度可分离卷积(depthwise separable convolution)和组卷积(group convolution),从而在模型的表达能力和计算代价之间取得了一个比较好的平衡。但是这些模型在设计时都没有充分考虑到1×1卷积(即逐点卷积 pointwise convolution)的影响,而逐点卷积消耗了大量的时间。例如在ResNeXt中,只有3×3卷积层使用了组卷积,结果就是,在ResNeXt的每个残差单元中,逐点卷积占据了93.4%的乘加运算。因此作者将组卷积也引入到1×1卷积层中,提出pointwise group convolution,大大减小了计算量。

channel shuffle

但是,当多个组卷积堆叠在一起时,会产生一个副作用:某个通道的输出结果,仅来自于一小部分输入通道

从上图中可以很明显的看出,当堆叠两个组卷积时,最终某个组的输出仅和同组的输入有关系,这会导致组之间信息流动的阻塞以及模型表达能力的弱化。为了解决这个问题,作者提出了channel shuffle

如上图所示,channel shuffle就是将第一层组卷积得到的特征进行均匀的重组,使得下一层的组卷积中每一组的输入都来自不同的组,保证了组之间信息的流动。

channel shuffle的代码如下所示

def channel_shuffle(x, groups):
    """Channel Shuffle operation.

    This function enables cross-group information flow for multiple groups
    convolution layers.

    Args:
        x (Tensor): The input tensor.
        groups (int): The number of groups to divide the input tensor
            in the channel dimension.

    Returns:
        Tensor: The output tensor after channel shuffle operation.
    """

    batch_size, num_channels, height, width = x.size()
    assert (num_channels % groups == 0), ('num_channels should be '
                                          'divisible by groups')
    channels_per_group = num_channels // groups

    x = x.view(batch_size, groups, channels_per_group, height, width)
    x = torch.transpose(x, 1, 2).contiguous()
    x = x.view(batch_size, -1, height, width)

    return x

ShuffleUnit

ShuffleNet的基本单元是由残差单元改进而来的

如图所示,首先将一开始的1×1卷积改成1×1组卷积并接上channel shuffle层,然后是3×3的深度卷积,注意这里去掉了3×3卷积后的ReLU层,然后再接1×1组卷积,这一层后面没有channel shuffle层。

当stride=1时,shortcut直接进行Add。当stride=2时,将深度卷积层的步长设置为2,原始输入进行stride=2的3×3平均池化,然后两者进行Concat。注意,在论文给出的模型结构中,每个stage的output channel是一定的,因此当stride=2时,最后一个1×1组卷积的输出channel并不是这个stage的output channel,而是output channel - input channel,因此Concat之后得到了通道数为output channel的最终输出。

if self.combine == 'concat':
    self.out_channels -= self.in_channels

有一个细节需要注意,如论文中的结构图所示,ReLU是跟在Add和Concat之后的,但是在官方实现中,当stride=1时,ReLU是跟在Add之后。但当stride=2时,是先对图右边最后1×1组卷积+BN的结果进行ReLU操作,然后再与原始输入进行Concat。

if self.stride == 1:
    return F.relu(x + x_proj)
elif self.stride == 2:
    return torch.cat((self.branch_proj(x_proj), F.relu(x)), 1)

ShuffleNet v1的网络结构 

ShuffleNet v1的完整结构如图所示

还有一个细节需要注意,在第一个block也就是Stage2的第一个block中的第一个1×1卷积层不使用组卷积,也就是将conv的group参数设置为1。

参考

Group Convolution分组卷积,以及Depthwise Convolution和Global Depthwise Convolution - shine-lee - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

00000cj

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

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

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

打赏作者

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

抵扣说明:

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

余额充值