『高性能模型』深度可分离卷积和MobileNet_v1
论文原址:MobileNets v1
TensorFlow实现:mobilenet_v1.py
TensorFlow预训练模型:mobilenet_v1.md
一、深度可分离卷积
标准的卷积过程可以看上图,一个2×2的卷积核在卷积时,对应图像区域中的所有通道均被同时考虑,问题在于,为什么一定要同时考虑图像区域和通道?我们为什么不能把通道和空间区域分开考虑?
深度可分离卷积提出了一种新的思路:对于不同的输入channel采取不同的卷积核进行卷积,它将普通的卷积操作分解为两个过程。

卷积过程
假设有 的输入,同时有
个
的卷积。如果设置
且
,那么普通卷积输出为
。

Depthwise 过程
Depthwise是指将 的输入分为
组,然后每一组做
卷积。这样相当于收集了每个Channel的空间特征,即Depthwise特征。

Pointwise 过程
Pointwise是指对 的输入做
个普通的
卷积。这样相当于收集了每个点的特征,即Pointwise特征。Depthwise+Pointwise最终输出也是
。

二、优势与创新
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×3×256 = 6,912
2.DW操作,分两步完成,参数量为:3×3×3 + 3×1×1×256 = 795(3个特征层*(3*3的卷积核)),卷积深度参数通常取为1
乘法运算次数降低
对比一下不同卷积的乘法次数:
- 普通卷积计算量为:
- Depthwise计算量为:
- Pointwise计算量为:
通过Depthwise+Pointwise的拆分,相当于将普通卷积的计算量压缩为:
通道区域分离
深度可分离卷积将以往普通卷积操作同时考虑通道和区域改变(卷积先只考虑区域,然后再考虑通道),实现了通道和区域的分离。
三、Mobilenet v1
Mobilenet v1利用深度可分离卷积进行加速,其架构如下,

还可以对所有卷积层 数量统一乘以缩小因子
(其中
)以压缩网络。这样Depthwise+Pointwise总计算量可以进一降低为:
当然,压缩网络计算量肯定是有代价的。下图展示了 不同时Mobilenet v1在ImageNet上的性能。可以看到即使
时Mobilenet v1在ImageNet上依然有63.7%的准确度。

下图展示Mobilenet v1 与GoogleNet和VGG16的在输入分辨率
情况下,准确度差距非常小,但是计算量和参数量都小很多。同时原文也给出了以Mobilenet v1提取特征的SSD/Faster R-CNN在COCO数据集上的性能。

结构实现一探
在实现代码中(链接见本文开头),作者使用具名元组存储了网络结构信息,
Conv = namedtuple('Conv', ['kernel', 'stride', 'depth'])
DepthSepConv = namedtuple('DepthSepConv', ['kernel', 'stride', 'depth'])
# MOBILENETV1_CONV_DEFS specifies the MobileNet body
MOBILENETV1_CONV_DEFS = [
Conv(kernel=[3, 3], stride=2, depth=32),
DepthSepConv(kernel=[3, 3], stride=1, depth=64),
DepthSepConv(kernel=[3, 3], stride=2, depth=128),
DepthSepConv(kernel=[3, 3], stride=1, depth=128),
DepthSepConv(kernel=[3, 3], stride=2, depth=256),
DepthSepConv(kernel=[3, 3], stride=1, depth=256),
DepthSepConv(kernel=[3, 3], stride=2, depth=512),
DepthSepConv(kernel=[3, 3], stride=1, depth=512),
DepthSepConv(kernel=[3, 3], stride=1, depth=512),
DepthSepConv(kernel=[3, 3], stride=1, depth=512),
DepthSepConv(kernel=[3, 3], stride=1, depth=512),
DepthSepConv(kernel=[3, 3], stride=1, depth=512),
DepthSepConv(kernel=[3, 3], stride=2, depth=1024),
DepthSepConv(kernel=[3, 3], stride=1, depth=1024)
]
然后,在生成结构中迭代这个具名元组列表,根据信息生成网路结构,这仅仅给出深度可分离层的实现部分,
elif isinstance(conv_def, DepthSepConv):
end_point = end_point_base + '_depthwise' # By passing filters=None
# separable_conv2d produces only a depthwise convolution layer
if use_explicit_padding:
net = _fixed_padding(net, conv_def.kernel, layer_rate)
net = slim.separable_conv2d(net, None, conv_def.kernel, # <---Depthwise
depth_multiplier=1,
stride=layer_stride,
rate=layer_rate,
scope=end_point) end_points[end_point] = net
if end_point == final_endpoint:
return net, end_points
end_point = end_point_base + '_pointwise'
net = slim.conv2d(net, depth(conv_def.depth), [1, 1], # <---Pointwise
stride=1,
scope=end_point)
四、相关框架实现
TensorFlow 分步执行
顺便一提,tf的实现可以接收rate参数,即可以采用空洞卷积的方式进行操作。
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"""
『高性能模型』深度可分离卷积和MobileNet_v1的更多相关文章
- 『高性能模型』HetConv: HeterogeneousKernel-BasedConvolutionsforDeepCNNs
论文地址:HetConv 一.现有网络加速技术 1.卷积加速技术 作者对已有的新型卷积划分如下:标准卷积.Depthwise 卷积.Pointwise 卷积.群卷积(相关介绍见『高性能模型』深度可分离 ...
- 『高性能模型』轻量级网络ShuffleNet_v1及v2
项目实现:GitHub 参考博客:CNN模型之ShuffleNet v1论文:ShuffleNet: An Extremely Efficient Convolutional Neural Netwo ...
- 『高性能模型』轻量级网络MobileNet_v2
论文地址:MobileNetV2: Inverted Residuals and Linear Bottlenecks 前文链接:『高性能模型』深度可分离卷积和MobileNet_v1 一.Mobil ...
- 『高性能模型』卷积复杂度以及Inception系列
转载自知乎:卷积神经网络的复杂度分析 之前的Inception学习博客: 『TensorFlow』读书笔记_Inception_V3_上 『TensorFlow』读书笔记_Inception_V3_下 ...
- 『高性能模型』Roofline Model与深度学习模型的性能分析
转载自知乎:Roofline Model与深度学习模型的性能分析 在真实世界中,任何模型(例如 VGG / MobileNet 等)都必须依赖于具体的计算平台(例如CPU / GPU / ASIC 等 ...
- Github 恶搞教程(一起『玩坏』自己的 Github 吧)
最近在伯乐在线读到一篇趣文,<如何在 Github『正确』做贡献>,里面各种能人恶搞 Github 的『Public contributions』,下面截取几个小伙伴的战绩: 顺藤摸瓜,发 ...
- 2017-2018-2 165X 『Java程序设计』课程每周成绩公布
2017-2018-2 165X 『Java程序设计』课程 每周成绩公布 本博客将跟随教学进度不定期更新,每次更新后将在课程群公布.如对成绩有疑问,请于公布成绩后的1天之内联系助教,进行审核确认. - ...
- 20172325『Java程序设计』课程 结对编程练习_四则运算第三周阶段总结
20172325『Java程序设计』课程 结对编程练习_四则运算第三周阶段总结 结对伙伴 学号:20172306 姓名:刘辰 在这次项目的完成过程中刘辰同学付出了很多,在代码的实践上完成的很出色,在技 ...
- 2017-2018-2 1723 『Java程序设计』课程 结对编程练习-四则运算-准备阶段
2017-2018-2 1723 『Java程序设计』课程 结对编程练习-四则运算-准备阶段 在一个人孤身奋斗了将近半个学期以后,终于迎来的我们的第一次团队协作共同编码,也就是,我们的第一个结对编程练 ...
随机推荐
- eclipse 安装 lombok插件
下载插件 官网下载 :https://projectlombok.org/ 有些小伙伴官网可能打不开,现提供我的下载地址:https://files.cnblogs.com/files/niceyoo ...
- 万能的DBHelper帮助类
DBHelper类: 简单的理解就是一个工具箱,我要用锤子的时候就在里面拿,我要用剪刀的时候也可以在里面拿,前提是我们写的DBHelper够不够强大! 软件中的四大功能:增.删.改.查 我们要实现这 ...
- python基础(15)-socket网络编程&socketserver
socket 参数及方法说明 初始化参数 sk = socket.socket(参数1,参数2,参数3) 参数1:地址簇 socket.AF_INET IPv4(默认) socket.AF_INET6 ...
- vue -about
j基于webpack4 搭建vue 环境:https://juejin.im/post/5bc30d5fe51d450ea1328877
- SparkML之推荐引擎(二)---推荐模型评估
本文内容和代码是接着上篇文章来写的,推荐先看一下哈~ 我们上一篇文章是写了电影推荐的实现,但是推荐内容是否合理呢,这就需要我们对模型进行评估 针对推荐模型,这里根据 均方差 和 K值平均准确率 来对模 ...
- windows程序设计 vs2012 新建win32项目
1.文件->新建->项目 2.选择win32项目,确定 3.下一步 4.选择windows应用程序,选中空项目,安全开发生命周期不选.点击完成. 5.空项目建好了.
- 实现html页面自动刷新的几种方式
自动页面刷新通常会用在对数据的实时性比较敏感的网站中,比如股票走势等,另外在普通的页面自动跳转中也会使用到页面自动刷新技术. 页面刷新我见过的有三种方式,下面来一一说明 1.通过在<head&g ...
- spring 获取对象的注解
BeanDefinition definition = registry.getBeanDefinition(name); if (definition instanceof AnnotatedBea ...
- 千行代码入门Python
这个是从网上找到的一份快速入门python的极简教程,大概一千行左右,个人觉得不错,特此收藏以备后用. # _*_ coding: utf-8 _*_ """类型和运算- ...
- MongoDB集群单mongos的问题总结
问题发现 在使用过程中,通过spark访问集群的效率不是很令人满意,80核心同时运行的速度比单核心也就快了20倍左右,预测瓶颈在mongodb读写上.当然,此时没遇到其他问题暂时没进行问题梳理. 在数 ...