轻量型骨干网络之ShuffleNet系列论文学习笔记

ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices

0.摘要

CVPR:2017年年末
1.作者觉得之前的网络都使用很多1*1卷积,所以使用了mobile net中的分组点卷积减少1 x 1卷积的计算复杂度
2.mobile net的一个缺点就是深层的特征是不能联通广域的信息,所以使用了channel shuffle,来帮助信息在特征通道中流动
在这里插入图片描述在这里插入图片描述

1.Shuffle module

1.1通道洗牌

在这里插入图片描述
shuffle这个单词形容的十分形象,之前有技术在训练前进行数据洗牌,可以提高网络的鲁棒性,而在shuffle net中就使用类似的思想,在组卷的的后面近接着一个打乱通道的操作。

1.2ShuffleNet基本单元

基于残差模块,加入了shuffle操作和dw卷积操作
a是改进的resnet模块,
b是stride=1的情况下Shuffle module,而且将密集的1x1卷积替换成1x1的group convolution,
c是stride=2,对原输入采用stride=2的3x3 avg pool,然后将得到特征图与输出进行连接concat,而不是相加。大大的降低计算量与参数大小。

在这里插入图片描述

2.网络结构

**加粗样式**

可以看到开始使用的普通的3x3的卷积和max pool层。然后是三个阶段,每个阶段都是重复堆积了几个ShuffleNet的基本单元。对于每个阶段,第一个基本单元采用的是stride=2,这样特征图width和height各降低一半,而通道数增加一倍。后面的基本单元都是stride=1,特征图和通道数都保持不变。对于基本单元来说,其中瓶颈层,就是3x3卷积层的通道数为输出通道数的1/4,这和残差单元的设计理念是一样的。

3.实验结果

g为分组的组数,x是通道数的缩小系数。可以看到基本上当g越大时,效果越好,这是因为采用更多的分组后,在相同的计算约束下可以使用更多的通道数,或者说特征图数量增加,网络的特征提取能力增强,网络性能得到提升。
在这里插入图片描述

消融实验:

在这里插入图片描述

对比实验

在这里插入图片描述

ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design

0.摘要

eccv2018,没开源
本文主要提供的是作者对轻型高效网络设计的思想,他觉得模型在训练的时候内存读取所花费的时间也是巨大的,而现在衡量模型只考虑计算量不考虑内存读取时间是不合理的。文章是基于硬件写的,十分硬核。先解释文章中的一些概念:
FLOPs(floating point operations):浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。
FLOPS(floating point operations per second):意指每秒浮点运算次数,理解为计算速度。是一个衡量硬件性能的指标。
MAC(memory access cost):内存访问消耗的时间
在这里插入图片描述

1.四个原则

1.1卷积层的输入和输出特征通道数相等时MAC最小,此时模型速度最快 Equal channel width minimizes memory access cost

比如一个1 x 1卷积层输入通道数是c1,输出通道数是c2,输出特征尺寸是h和w,那么这一层的FLOPs就是

在这里插入图片描述

它需要的存储空间是

在这里插入图片描述
根据均值不等式得到当c1=c2的时候(输入输出通道数相等时),在给定的FLOPs时,MAC最小
在这里插入图片描述

在这里插入图片描述

1.2 过多分组组数会增大MAC,从而使模型速度变慢Excessive group convolution increases MAC

在这里插入图片描述
一般在这种网络里组数都是三
在这里插入图片描述

1.3模型中的分支数量越少,模型速度越快Network fragmentation reduces degree of parallelism

在这里插入图片描述
Googlenet中喜欢使用这种多分枝的,而resnet只多一个分支

1.4不要小瞧元组操作Element-wise operations are non-negligible

element-wise指的是relu,bn,bias等小操作。
element-wise操作带来的带来的消耗,要比我们想象的消耗多得多。element-wise操作所带来的时间消耗远比在FLOPs上的体现的数值要多,他们的FLOPs很小,但MAC相对较重,因此要尽可能减少element-wise操作。
在这里插入图片描述

2.shuffle v2

2.1全新的shuffle模块

基于上面的四条准则:1)使用“平衡”卷积(等通道宽度);2)了解使用组卷积的成本;3)降低碎片化程度;以及4)减少元素级操作,作者设计了新的模型结构ShuffleNet V2。
v2中干脆不适用分组卷积,又回到了原来的1*1卷积
在这里插入图片描述
ShuffleNet V2在开始处增加了一个channel split,将输入特征的通道分成两部分,论文中c’=c/2,对半分割。使用“信道混洗”操作来实现两个分支之间的信息通信。不再使用add,而是使用concat了。三个连续的元素操作,“连接”、“通道混洗”和“通道分离”被合并成一个元素操作。(d)中的分支是模仿了densenet中信息会有一条支路直接传递的模式,但是不一样的是采取了洗牌操作,V2洗牌的结构通过设计实现了这种类型的特征重用模式

2.2 网络结构在这里插入图片描述

2.3代码部分

首先我们先定义网络中最基本的单元:Conv2D->BN->ReLU和DepthwiseConv2D->BN:

class Conv2D_BN_ReLU(tf.keras.Model):
    """Conv2D -> BN -> ReLU"""
    def __init__(self, channel, kernel_size=1, stride=1):
        super(Conv2D_BN_ReLU, self).__init__()

        self.conv = Conv2D(channel, kernel_size, strides=stride,
                            padding="SAME", use_bias=False)
        self.bn = BatchNormalization(axis=-1, momentum=0.9, epsilon=1e-5)
        self.relu = Activation("relu")

    def call(self, inputs, training=True):
        x = self.conv(inputs)
        x = self.bn(x, training=training)
        x = self.relu(x)
        return x

class DepthwiseConv2D_BN(tf.keras.Model):
    """DepthwiseConv2D -> BN"""
    def __init__(self, kernel_size=3, stride=1):
        super(DepthwiseConv2D_BN, self).__init__()

        self.dconv = DepthwiseConv2D(kernel_size, strides=stride,
                                     depth_multiplier=1,
                                     padding="SAME", use_bias=False)
        self.bn = BatchNormalization(axis=-1, momentum=0.9, epsilon=1e-5)

    def call(self, inputs, training=True):
        x = self.dconv(inputs)
        x = self.bn(x, training=training)

对于channel shuffle,只需要通过transpose操作即可

def channle_shuffle(inputs, group):
    """Shuffle the channel
    Args:
        inputs: 4D Tensor
        group: int, number of groups
    Returns:
        Shuffled 4D Tensor
    """
    in_shape = inputs.get_shape().as_list()
    h, w, in_channel = in_shape[1:]
    assert in_channel % group == 0
    l = tf.reshape(inputs, [-1, h, w, in_channel // group, group])
    l = tf.transpose(l, [0, 1, 2, 4, 3])
    l = tf.reshape(l, [-1, h, w, in_channel])

    return l

下面,定义v2中的基本模块,先定义stride=1的模块:

class ShufflenetUnit1(tf.keras.Model):
    def __init__(self, out_channel):
        """The unit of shufflenetv2 for stride=1
        Args:
            out_channel: int, number of channels
        """
        super(ShufflenetUnit1, self).__init__()

        assert out_channel % 2 == 0
        self.out_channel = out_channel

        self.conv1_bn_relu = Conv2D_BN_ReLU(out_channel // 2, 1, 1)
        self.dconv_bn = DepthwiseConv2D_BN(3, 1)
        self.conv2_bn_relu = Conv2D_BN_ReLU(out_channel // 2, 1, 1)

    def call(self, inputs, training=False):
        # split the channel
        shortcut, x = tf.split(inputs, 2, axis=3)

        x = self.conv1_bn_relu(x, training=training)
        x = self.dconv_bn(x, training=training)
        x = self.conv2_bn_relu(x, training=training)

        x = tf.concat([shortcut, x], axis=3)
        x = channle_shuffle(x, 2)
        return x

对于stride=2的下采样模块,与上面模块略有不同:

class ShufflenetUnit2(tf.keras.Model):
    """The unit of shufflenetv2 for stride=2"""
    def __init__(self, in_channel, out_channel):
        super(ShufflenetUnit2, self).__init__()

        assert out_channel % 2 == 0
        self.in_channel = in_channel
        self.out_channel = out_channel

        self.conv1_bn_relu = Conv2D_BN_ReLU(out_channel // 2, 1, 1)
        self.dconv_bn = DepthwiseConv2D_BN(3, 2)
        self.conv2_bn_relu = Conv2D_BN_ReLU(out_channel - in_channel, 1, 1)

        # for shortcut
        self.shortcut_dconv_bn = DepthwiseConv2D_BN(3, 2)
        self.shortcut_conv_bn_relu = Conv2D_BN_ReLU(in_channel, 1, 1)

    def call(self, inputs, training=False):
        shortcut, x = inputs, inputs

        x = self.conv1_bn_relu(x, training=training)
        x = self.dconv_bn(x, training=training)
        x = self.conv2_bn_relu(x, training=training)

        shortcut = self.shortcut_dconv_bn(shortcut, training=training)
        shortcut = self.shortcut_conv_bn_relu(shortcut, training=training)

        x = tf.concat([shortcut, x], axis=3)
        x = channle_shuffle(x, 2)
        return x
class ShuffleNetv2(tf.keras.Model):
    """Shufflenetv2"""
    def __init__(self, num_classes, first_channel=24, channels_per_stage=(116, 232, 464)):
        super(ShuffleNetv2, self).__init__()

        self.num_classes = num_classes

        self.conv1_bn_relu = Conv2D_BN_ReLU(first_channel, 3, 2)
        self.pool1 = MaxPool2D(3, strides=2, padding="SAME")
        self.stage2 = ShufflenetStage(first_channel, channels_per_stage[0], 4)
        self.stage3 = ShufflenetStage(channels_per_stage[0], channels_per_stage[1], 8)
        self.stage4 = ShufflenetStage(channels_per_stage[1], channels_per_stage[2], 4)
        self.conv5_bn_relu = Conv2D_BN_ReLU(1024, 1, 1)
        self.gap = GlobalAveragePooling2D()
        self.linear = Dense(num_classes)

    def call(self, inputs, training=False):
        x = self.conv1_bn_relu(inputs, training=training)
        x = self.pool1(x)
        x = self.stage2(x, training=training)
        x = self.stage3(x, training=training)
        x = self.stage4(x, training=training)
        x = self.conv5_bn_relu(x, training=training)
        x = self.gap(x)
        x = self.linear(x)
        return x

3.实验结果

在这里插入图片描述
请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值