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.实验结果