转载请注明作者和出处: http://blog.csdn.net/john_bh/
论文链接:MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
作者及团队:Google
会议及时间:CVPR 2017
TensorFlow实现: mobilenet_v1.py
1.深度可分离卷积
标准的卷积过程可以看上图,一个3×3的卷积核在卷积时,对应图像区域中的所有通道均被同时考虑,问题在于,为什么一定要同时考虑图像区域和通道?我们为什么不能把通道和空间区域分开考虑?
深度可分离卷积提出了一种新的思路:对于不同的输入channel采取不同的卷积核进行卷积,它将普通的卷积操作分解为两个过程。
卷积过程
假设有
N
×
H
×
W
×
C
N\times H\times W \times C
N×H×W×C的输入,同时有
k
个
3
×
3
k 个 3\times3
k个3×3 的卷积。如果设置
p
a
d
=
1
且
s
t
r
i
d
e
=
1
pad=1 且 stride=1
pad=1且stride=1 ,那么普通卷积输出为
N
×
H
×
W
×
k
N\times H\times W \times k
N×H×W×k 。
Depthwise 过程
Depthwise是指将
N
×
H
×
W
×
C
N\times H\times W \times C
N×H×W×C 的输入分为
g
r
o
u
p
=
C
group=C
group=C 组,然后每一组做
3
×
3
3\times3
3×3 卷积。这样相当于收集了每个Channel的空间特征,即Depthwise特征。逐层卷积对每个输入通道(输入特征图的深度)执行单个滤波器卷积。
Pointwise 过程
逐点卷积(1x1卷积)用来创建逐深度卷积层的线性组合。Pointwise是指对
N
×
H
×
W
×
C
N\times H\times W \times C
N×H×W×C 的输入做
k
k
k 个普通的
1
×
1
1\times1
1×1 卷积。这样相当于收集了每个点的特征,即Pointwise特征。Depthwise+Pointwise最终输出也是
N
×
H
×
W
×
k
N\times H\times W \times k
N×H×W×k 。
逐层卷积相对于标准的卷积效率极高,但他只是给输入通道做了个滤波,而不能结合各个通道的特征图生成新的特征。所以为了生成这些新的特征,一个额外的,由1x1卷积构成的逐点卷积被运用在了MobileNet中。深度可分离卷积,就是上述两种卷积层操作的组合。
TensorFlow 分步执行
1、depthwise_conv2d 分离卷积部分
我们定义一张4*4的双通道图片
import tensorflow as tf
img1 = tf.constant(value=[[[[1],[2],[3],[4]],
[[1],[2],[3],[4]],
[[1],[2],[3],[4]],
[[1],[2],[3],[4]]]],dtype=tf.float32)
img2 = tf.constant(value=[[[[1],[1],[1],[1]],
[[1],[1],[1],[1]],
[[1],[1],[1],[1]],
[[1],[1],[1],[1]]]],dtype=tf.float32)
img = tf.concat(values=[img1,img2],axis=3)
img
<tf.Tensor ‘concat_1:0’ shape=(1, 4, 4, 2) dtype=float32>
使用3*3的卷积核,输入channel为2,输出channel为2(卷积核数目为2),
filter1 = tf.constant(value=0, shape=[3,3,1,1],dtype=tf.float32)
filter2 = tf.constant(value=1, shape=[3,3,1,1],dtype=tf.float32)
filter3 = tf.constant(value=2, shape=[3,3,1,1],dtype=tf.float32)
filter4 = tf.constant(value=3, shape=[3,3,1,1],dtype=tf.float32)
filter_out1 = tf.concat(values=[filter1,filter2],axis=2)
filter_out2 = tf.concat(values=[filter3,filter4],axis=2)
filter = tf.concat(values=[filter_out1,filter_out2],axis=3)
filter
<tf.Tensor ‘concat_4:0’ shape=(3, 3, 2, 2) dtype=float32>
同时执行卷积操作,和深度可分离卷积操作,
out_img_conv = tf.nn.conv2d(input=img, filter=filter,
strides=[1,1,1,1], padding='VALID')
out_img_depthwise = tf.nn.depthwise_conv2d(input=img,
filter=filter, strides=[1,1,1,1],
rate=[1,1], padding='VALID')
with tf.Session() as sess:
res1 = sess.run(out_img_conv)
res2 = sess.run(out_img_depthwise)
print(res1, '\n', res1.shape)
print(res2, '\n', res2.shape)
[[[[ 9. 63.]
[ 9. 81.]]
[[ 9. 63.]
[ 9. 81.]]]]
(1, 2, 2, 2) # 《----------
[[[[ 0. 36. 9. 27.]
[ 0. 54. 9. 27.]]
[[ 0. 36. 9. 27.]
[ 0. 54. 9. 27.]]]]
(1, 2, 2, 4)# 《----------
对比输出shape,depthwise_conv2d输出的channel数目为in_channel * 卷积核数目,每一个卷积核对应通道都会对对应的channel进行一次卷积,所以输出通道数更多,
看到这里大家可能会误解深度可分离卷积的输出通道数大于普通卷积,其实这只是“分离”部分,后面还有组合的步骤,而普通卷积只不过直接完成了组合:通过对应点相加,将四个卷积中间结果合并为卷积核个数(这里是2)
2、合并特征
合并过程如下,可分离卷积中的合并过程变成可学习的了,使用一个1*1的普通卷积进行特征合并,
point_filter = tf.constant(value=1, shape=[1,1,4,4],dtype=tf.float32)
out_img_s = tf.nn.conv2d(input=out_img_depthwise, filter=point_filter, strides=[1,1,1,1], padding='VALID')
with tf.Session() as sess:
res3 = sess.run(out_img_s)
print(res3, '\n', res3.shape)
[[[[ 72. 72. 72. 72.]
[ 90. 90. 90. 90.]]
[[ 72. 72. 72. 72.]
[ 90. 90. 90. 90.]]]]
(1, 2, 2, 4)
TensorFlow 一步执行
out_img_se = tf.nn.separable_conv2d(input=img,
depthwise_filter=filter,
pointwise_filter=point_filter,
strides=[1,1,1,1], rate=[1,1], padding='VALID')
with tf.Session() as sess:
print(sess.run(out_img_se))
[[[[ 72. 72. 72. 72.]
[ 90. 90. 90. 90.]]
[[ 72. 72. 72. 72.]
[ 90. 90. 90. 90.]]]]
(1, 2, 2, 4)
slim 库API介绍
def separable_convolution2d(
inputs,
num_outputs,
kernel_size,
depth_multiplier=1,
stride=1,
padding='SAME',
data_format=DATA_FORMAT_NHWC,
rate=1,
activation_fn=nn.relu,
normalizer_fn=None,
normalizer_params=None,
weights_initializer=initializers.xavier_initializer(),
pointwise_initializer=None,
weights_regularizer=None,
biases_initializer=init_ops.zeros_initializer(),
biases_regularizer=None,
reuse=None,
variables_collections=None,
outputs_collections=None,
trainable=True,
scope=None):
"""一个2维的可分离卷积,可以选择是否增加BN层。
这个操作首先执行逐通道的卷积(每个通道分别执行卷积),创建一个称为depthwise_weights的变量。如果num_outputs
不为空,它将增加一个pointwise的卷积(混合通道间的信息),创建一个称为pointwise_weights的变量。如果
normalizer_fn为空,它将给结果加上一个偏置,并且创建一个为biases的变量,如果不为空,那么归一化函数将被调用。
最后再调用一个激活函数然后得到最终的结果。
Args:
inputs: 一个形状为[batch_size, height, width, channels]的tensor
num_outputs: pointwise 卷积的卷积核个数,如果为空,将跳过pointwise卷积的步骤.
kernel_size: 卷积核的尺寸:[kernel_height, kernel_width],如果两个的值相同,则可以为一个整数。
depth_multiplier: 卷积乘子,即每个输入通道经过卷积后的输出通道数。总共的输出通道数将为:
num_filters_in * depth_multiplier。
stride:卷积步长,[stride_height, stride_width],如果两个值相同的话,为一个整数值。
padding: 填充方式,'VALID' 或者 'SAME'.
data_format:数据格式, `NHWC` (默认) 和 `NCHW`
rate: 空洞卷积的膨胀率:[rate_height, rate_width],如果两个值相同的话,可以为整数值。如果这两个值
任意一个大于1,那么stride的值必须为1.
activation_fn: 激活函数,默认为ReLU。如果设置为None,将跳过。
normalizer_fn: 归一化函数,用来替代biase。如果归一化函数不为空,那么biases_initializer
和biases_regularizer将被忽略。 biases将不会被创建。如果设为None,将不会有归一化。
normalizer_params: 归一化函数的参数。
weights_initializer: depthwise卷积的权重初始化器
pointwise_initializer: pointwise卷积的权重初始化器。如果设为None,将使用weights_initializer。
weights_regularizer: (可选)权重正则化器。
biases_initializer: 偏置初始化器,如果为None,将跳过偏置。
biases_regularizer: (可选)偏置正则化器。
reuse: 网络层和它的变量是否可以被重用,为了重用,网络层的scope必须被提供。
variables_collections: (可选)所有变量的collection列表,或者是一个关键字为变量值为collection的字典。
outputs_collections: 输出被添加的collection.
trainable: 变量是否可以被训练
scope: (可选)变量的命名空间。
Returns:
代表这个操作的输出的一个tensor"""
2.优势与创新
Depthwise+Pointwise可以近似看作一个卷积层:
- 普通卷积:3x3 Conv+BN+ReLU
- Mobilenet卷积:3x3 Depthwise Conv+BN+ReLU 和 1x1 Pointwise Conv+BN+ReLU
计算加速
参数量降低:假设输入通道数为3,要求输出通道数为256,两种做法:
1.直接接一个 3 × 3 × 256 3×3×256 3×3×256的卷积核,参数量为: 3 × 3 × 3 × 256 = 6 , 912 3×3×3×256 = 6,912 3×3×3×256=6,912
2.DW操作,分两步完成,参数量为: 3 × 3 × 3 + 3 × 1 × 1 × 256 = 795 ( 3 个 特 征 层 ∗ ( 3 ∗ 3 的 卷 积 核 ) ) 3×3×3 + 3×1×1×256 = 795(3个特征层*(3*3的卷积核)) 3×3×3+3×1×1×256=795(3个特征层∗(3∗3的卷积核)),卷积深度参数通常取为1
乘法运算次数降低:对比一下不同卷积的乘法次数:
普通卷积计算量为:
H
×
W
×
C
×
k
×
3
×
3
H\times W \times C\times k \times 3\times 3
H×W×C×k×3×3
Depthwise计算量为:
H
×
W
×
C
×
3
×
3
H\times W \times C \times 3\times 3
H×W×C×3×3
Pointwise计算量为:
H
×
W
×
C
×
k
H\times W\times C\times k
H×W×C×k
通过Depthwise+Pointwise的拆分,相当于将普通卷积的计算量压缩为:
d
e
p
t
h
w
i
s
e
+
p
o
i
n
t
w
i
s
e
c
o
n
v
=
H
×
W
×
C
×
3
×
3
+
H
×
W
×
C
×
k
H
×
W
×
C
×
k
×
3
×
3
=
1
k
+
1
3
×
3
\frac{depthwise+pointwise}{conv}=\frac{H\times W \times C \times 3\times 3 + H\times W\times C\times k}{H\times W \times C\times k \times 3\times 3}=\frac{1}{k} +\frac{1}{3\times 3}
convdepthwise+pointwise=H×W×C×k×3×3H×W×C×3×3+H×W×C×k=k1+3×31
通道区域分离
深度可分离卷积将以往普通卷积操作同时考虑通道和区域改变(卷积先只考虑区域,然后再考虑通道),实现了通道和区域的分离。
3.Mobilenet v1
Mobilenet v1利用深度可分离卷积进行加速,MobileNets结构建立在深度可分解卷积中(只有第一层是标准卷积)。该网络允许算法探索网络拓扑,找到一个适合的良好网络。其具体架构在表1说明。除了最后的全连接层,所有层后面跟了batchnorm和ReLU,最终输入到softmax进行分类。图3对比了标准卷积和分解卷积的结构,二者都附带了BN和ReLU层。按照原文的计算方法,MobileNets总共28层(1 + 2 × 13 + 1 = 28),
MobileNet将95%的计算时间用于有75%的参数的1×1卷积,其他额外的参数几乎都集中于全连接层,在论文中,采用tensorflow框架进行训练。
Width Multiplier: Thinner Models: 还可以对所有卷积层
k
e
r
n
e
l
kernel
kernel 数量统一乘以缩小因子
α
(
其
中
α
∈
(
0
,
1
]
,
典
型
值
为
1
,
0.75
,
0.5
和
0.25
)
\alpha (其中 \alpha\in(0,1],典型值为1,0.75,0.5和0.25 )
α(其中α∈(0,1],典型值为1,0.75,0.5和0.25)以压缩网络。这样Depthwise+Pointwise总计算量可以进一降低为:
H
×
W
×
α
C
×
3
×
3
+
H
×
W
×
α
C
×
α
k
H\times W \times \alpha C \times 3\times 3 + H\times W\times \alpha C\times \alpha k
H×W×αC×3×3+H×W×αC×αk
Resolution Multiplier: Reduced Representation 通过设置ρ来隐式的设置输入的分辨率大小。我们现在可以对网络中的核心层的深度可分离卷积加上宽度乘法器α以及分辨率乘法器ρ来表达计算量:
D
K
∗
D
K
∗
α
M
∗
ρ
D
F
∗
ρ
D
F
+
α
M
∗
α
N
∗
ρ
D
F
∗
ρ
D
F
D_K∗D_K∗αM∗ρD_F∗ρD_F+αM∗αN∗ρD_F∗ρD_F
DK∗DK∗αM∗ρDF∗ρDF+αM∗αN∗ρDF∗ρDF其中ρ∈(0,1],一般隐式的设置以便于输入网络的图像分辨率为224\192\160\128等。当ρ=1时为最基本的MobileNet,当ρ<1时,则为薄化的MobileNet。分辨率乘法器对网络约化大约ρ2倍。接下来举个例子,MobileNet中的一个典型的层以及深度可分离卷积、宽度乘法器、分辨率乘法器是如何约化计算量和参数量。表3中展示了一层的计算量和参数量以及结构收缩的这些方法应用在这些层之后的变化。第一行显示了全连接层的Mult-Adds和参数量,其输入特征图为
14
×
14
×
512
14\times14\times512
14×14×512,并且卷积核的尺寸为
3
×
3
×
512
×
512
3\times3\times512\times512
3×3×512×512
4.实验结果
运用深度可分离卷积的MobileNet与全标准卷积网络的对比,如表4,我们可以看见在ImageNet数据集上使用深度可分离卷积相较于标准卷积准确率只减少了1%,但在计算量和参数量上却减少了很多。
利用宽度是乘法器的薄化模型与只有少数层的千层神经网络进行对比,为了使MobileNet更浅,表1中的5层14x14x512的特征尺寸的可分离卷积层都被去掉了。表5展示了相同计算量参数量的情况下,让MobileNets薄化3%比让它更浅效果更好。
模型压缩超参数
表6展示了利用宽度乘法器α对MobileNet网络结构进行薄化后准确率,计算量和尺寸之间的权衡关系。准确率直到宽度乘法器α下降到0.25才显示下降很多。
表7展示了通过利用约化的MobileNets时不同分辨率乘法器时准确率、计算量和尺寸之间的权衡关系。准确率随着分辨率下降而平滑减小。
图4该图显示了计算之间的权衡(mult - add)和ImageNet基准测试的准确性。注意精度和计算之间的对数线性关系。
图5图显示了在ImageNet基准测试中参数数量和精度之间的权衡。颜色编码输入分辨率。参数的数量不随输入分辨率的不同而变化。
表8比较了最基本的MobileNet与原始GoogleNet和VGG16。MobileNet和VGG16准确率几乎一样,但是参数量少了32倍,计算量少了27倍。相较于GoogleNet而言,准确率更高,并且参数量和计算量都少了2.5倍。
表9比较了约化后的MobileNet(α=0.5,并且分辨率为160x160,原本为224x224)与AlexNet以及SqueezeNet( Squeezenet: Alexnet-level accuracy with 50x fewer parameters and¡ 1mb model size. )。约化后的MobileNet相较于这两个模型,准确率都高,并且计算量相较于AlexNet少了9.4倍比SqueezeNet少了22倍。
表10中使用MobileNet来进行细粒度识别,在斯坦福狗数据集实现最好的结果,并且大大减少了计算量和尺寸。
表11 表示使用MobileNet架构的PlaNet的性能。
表12使用MobileNet架构的人脸属性分类,每行对应一个不同的超参数设置(宽度倍数和图像分辨率)。
在表13中,使用不同的框架和网络架构比较COCO对象检测结果。 报告的mAP具有COCO主要挑战指标(AP在IoU = 0.50:0.05:0.95)。
图6.使用MobileNet SSD的目标检测结果。
FaceNet是艺术人脸识别模型中最好的(Facenet: A uni- fied embedding for face recognition and clustering.)它构建了基于三次损失的人脸嵌入。为了构建移动端FaceNet模型,我们在训练集上通过最小化FaceNet和MobileNet之间的平方差来蒸馏训练。结果展示在表14中。
5.关于速度的思考
深度可分离卷积将一个标准卷积分割成了两个卷积(逐深度,逐点),因此减小了参数量,对应也减小了总计算量,但深度可分离卷积的层数变多了。在cpu是 一般的计算是采用串行计算的方式(一个一个算),层数的增多换来的较高的缓存命中率,就会加速计算速度,但是GPU是并行处理大规模数据(矩阵内积)的运算平台,在gpu上采用并行计算,有足够大的显存,并不会有提升。
若GPU的显存足够大(假设无限大),因为每层的计算都可以并行一次处理,则此时总运算时间的主导因素是网络的层数。
而对于缺乏并行能力的CPU,总的运算时间的主导因素则是总计算量有关。
参考链接:
『高性能模型』深度可分离卷积和MobileNet_v1
MobileNet 详解深度可分离卷积,它真的又好又快吗