Separable Convolutions: 可分离的卷积,让CNN再次伟大-程序员宅基地

技术标签: CNN  卷积  机器学习  深度学习  神经网络  

1. 回顾传统 CNN 及它的不足之处

1.1 时间!时间!

传统的卷积神经网络已经在很多领域大显身手,在许多机器学习项目中取得了巨大的成就。但是它仍然存在一个最致命的问题——花费过大。

花费过大,主要体现在两种方面:

第一个方面是计算资源的消耗大
人们开玩笑说,深度学习到最后比的就是谁的孔方兄多,虽然有开玩笑的成分,但也不无道理。不过考虑到做项目的人往往都可以申请经费,最后花的是公家的钱,因此这个问题并不是最主要的问题——就算买不起豪华级别的显卡,也可以租一台云服务器用来跑算法嘛!能用钱解决的问题都不是问题。

真正处于瓶颈位置的问题,其实是第2个方面——也就是时间消耗过大。君不见随随便便跑一个算法,两三天都是好的,跑得慢一点的,甚至需要四五天、一个星期、半个月。也难怪人们把深度学习戏称为“炼丹术”,毕竟大家都没有绯红之王这种神器,跑算法的这段时间就只能像守着炼丹炉一样熬着,到最后才能看看算法效果怎么样。

奇怪,卷积神经网络不是号称什么“参数共享”“稀疏连接”,可以有效降低全连接网络的消耗吗?反正教学的时候各种听起来高大上的词给我们反复灌输CNN如何如何能减少参数,节省计算资源。听起来很勇的样子还是怎么像彬彬一样逊呢?

这个问题我们要从头开始考虑。

1.2 回顾:从 Dense 连接到 Conv 连接

现在假设,我们不知道有 CNN 这种东西,而我们想要做一个简单的图像分类模型。输入是 12 × 12 × 3 12 \times 12 \times 3 12×12×3 的图片,我们首先得把它展开成一个 432 维的向量,然后后面跟上一个 64 维的 hidden layer。网络的结构大致是下面这个样子的:

在这里插入图片描述

这个结构需要多少参数呢?我们在 keras 里面敲一敲:

input_layer = layers.Input(shape=(12, 12, 3))
flat = layers.Flatten()(input_layer)
hid = layers.Dense(64, use_bias=False)(flat)
model = keras.Model(input=input_layer, output=hid)
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 12, 12, 3)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 432)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 64)                27648     
=================================================================
Total params: 27,648
Trainable params: 27,648
Non-trainable params: 0
_________________________________________________________________

我去!这谁受得了?好在,后来我们知道了有卷积神经网络这么一个神奇的东西。于是我们把模型改成了这幅样子:
在这里插入图片描述

input_layer = layers.Input(shape=(12, 12, 3))
conv_layer = layers.Conv2D(1, (5, 5), use_bias=False)(input_layer)
flat = layers.Flatten()(conv_layer)
model = keras.Model(input=input_layer, output=flat)
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 12, 12, 3)         0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 8, 8, 1)           75        
_________________________________________________________________
flatten_1 (Flatten)          (None, 64)                0         
=================================================================
Total params: 75
Trainable params: 75
Non-trainable params: 0
_________________________________________________________________

注意到了吗?同样的输入,同样的输出,一个简单的 Conv 网络就把参数的数量从惨不忍睹的27648减到了75,“稀疏连接”恐怖如斯!由于神经网络的运算本质就是矩阵相乘,所以更少的参数,意味着更少的乘法、更少的时间开销。也正是因为传统的卷积神经网络层具有如此卓越的削减运算开销的功能,基于 CNN 的计算机视觉才能如火如荼的发展。

1.3 新时代、新挑战

我们现在来定量地考虑一个问题:上面两种网络分别需要多少的运算量?

首先看看全连接网络,Flatten 不算在计算中,所以核心的运算就是一个 W 64 × 432 ⋅ x ⃗ 432 × 1 W_{64\times 432} \cdot \vec{x}_{432\times 1} W64×432x 432×1 的矩阵乘法,也就是 64 个 432 维向量乘以 432 维向量,也就是 64 × ( 432 × 432 ) = 11943936 64 \times (432 \times 432) = 11943936 64×(432×432)=11943936
Dense 选手的表现很差劲呢。

下面来看卷积网络,同样 Flatten 是不算在计算量中的,所以我们抽出核心的卷积部分来分析。

在这里插入图片描述

一个 (5, 5, 3) 的卷积核,本身要做 5 × 5 × 3 = 75 5 \times 5\times 3 = 75 5×5×3=75 次的乘法操作,横着移动 8 次,竖着移动 8 次,一共是 75 × 8 × 8 = 4800 75 \times 8 \times 8 = 4800 75×8×8=4800 次的乘法操作。

如果要输出 N 个通道,那么就是 4800 N 4800N 4800N 次乘法。

这个结果很好嘛?当然好了!把全连接和卷机两个一对比,简直就是数量级的差别,这是飞跃性的提升!
这个结果足够好吗?这就见仁见智了,如果是在10年前,我们会满心欢喜的接受这样的结果;但是10年后技术在不断发展,新的问题也在不断涌现。这时候我们再来看传统的卷积操作,就会有些不满了。因为处在新时代,我们处理的数据量越来越大、神经网络的层数越来越多,带来的结果是乘法运算的数量再次几何倍数地正常。尽管传统的CNN和单纯的全连接相比有了质的提升,但是越来越无法满足我们现在的需要。

现在我们再一次回到了 1.1 节,那一节的标题是“时间!时间!”。明白是什么意思了嘛?

面对这样的窘境,不少人开启了新的思考:既然从全连接层到传统的卷积层,我们削减了计算的开支;那我们能不能进一步改进网络,让它的计算量进一步的减少呢

答案是——Separable Convolutions。

2. Separable Convolutions

Separable Convolutions,顾名思义,是“可分离”的卷积神经网络。有的童鞋可能就纳闷了:

干嘛叫 Separable?

在正式讲separable convolutions之前,我想谈一谈这个问题,因为我觉得这个方法的所体现出来的哲理具有一定的普适性。

以下部分是我自己的思考(胡诌),算是从一个感性的方面来认识所谓的 Separable 这个词。嫌我啰嗦的童鞋手动跳过。


我还记得在我小学的时候,有一天老师出了这样一道题,已知从北京到上海有多少多少种走法,从上海到广州又有多少多少种走法,那么从北京到上海再到广州有几种走法呢?
那时傻乎乎的我把从北京到上海再从上海到广州的路一条一条的列了出来,然而还没等我在草稿纸上列完,同桌就已经抢先报出答案了,算法很简单,把两个数字乘起来就行了。

这虽然是一个简单的小故事,但是其中却蕴含了一个深刻的哲理。那就是合而虑之,不如分而治之。这个道理在我进入大学学习计算机以后愈加的凸显——为了降低系统的复杂度,我们进行了模块化;为了简化问题的分析难度,我们使用了数学归纳法;为了提高系统的安全性,我们选择了去中心。

可以看到,“分”这个字简直有一股奇怪的魔力,任何东西只要一分,马上就不一样了。古人云“三个臭皮匠,顶一个诸葛亮”。虽然三个臭皮匠加起来可能都不如诸葛亮,却胜在普遍;昭烈皇帝三顾茅庐方得卧龙出山,而找三个臭皮匠却何其简单。

Separate,本质上就是把一个耦合严重的系统分离成多个高内聚低耦合的模块,多个模块组合在一起,比原来拧成一坨更精简高效。
——我说的

我仔细思考了如何利用 Separate 来做优化,我认为其中内在的道理和思路是这样的:

  1. 给定一个任务 O ( N ) O(N) O(N)
  2. O ( N ) O(N) O(N) 建模为 O ( P × Q ) O(P\times Q) O(P×Q)
  3. 想办法变成 O ( P + Q ) O(P + Q) O(P+Q)

仔细观察,你会发现 Separable Convolutions 完美符合这个思路。


好了,废话完毕。现在我们来考虑怎么分解卷积操作,首先要搞清楚的问题卷积操作的过程是什么样的? O ( N ) O(N) O(N)到底是个什么东西?

  1. 卷积核放在image上
  2. 卷积核对 channel 1、2、3分别做卷积
  3. 卷积核在 image 上移动

我们发现,卷积操作涉及到了两个维度:

  1. 空间维度,卷积核是如何在一个 image 上跑来跑去的
  2. 深度维度,卷积核在不同深度的 channel 上依次做卷积操作

两种维度,提供两种视角,基于两种不同的视角,人们提出了两种不同的 Separable Convolution 方法。

  • 基于空间视角,人们提出了 Spatial Separable Convolutions
  • 基于深度视角,人们提出了 Depthwise Separable Convolutions

2.1 Spatial Separable Convolutions

在这个视角下,人们尝试着 将卷积核分解为若干个小卷积核。譬如,我们可以将卷积核分解为两个(或者若干个)向量的外积,如下图所示:

在这里插入图片描述

怎么用呢?如下图所示,我们在原来的 image 上先用 kern1 做一次卷积,然后再在得到的结果上用 kern2 做一次卷积操作。

在这里插入图片描述

我们还是以之前 (12, 12, 3) 的图像举例,这次我们不用 (5, 5) 的卷积核,而是分别用两个维度为 5 的行列向量作为卷积核,看看效果如何:

input_layer = layers.Input(shape=(12, 12, 3))
conv1 = layers.Conv2D(1, (5, 1), use_bias=False)(input_layer)
conv2 = layers.Conv2D(1, (1, 5), use_bias=False)(conv1)
flat = layers.Flatten()(conv2)
model = keras.Model(input=input_layer, output=flat)
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 12, 12, 3)         0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 8, 12, 1)          15        
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 8, 8, 1)           5         
_________________________________________________________________
flatten_1 (Flatten)          (None, 64)                0         
=================================================================
Total params: 20
Trainable params: 20
Non-trainable params: 0
_________________________________________________________________

之前是 75,这一下变成了 20。输入输出的维度都没变,参数数量却只有原来的 1/3 !那么乘法运算量变化如何?

显然,第一次的卷积需要用到 ( 5 × 1 × 3 ) × ( 8 × 12 ) = 1440 (5 \times 1 \times 3) \times (8 \times 12) = 1440 (5×1×3)×(8×12)=1440;第二次卷积需要 ( 1 × 5 × 3 ) × ( 8 × 8 ) = 960 (1 \times 5 \times 3) \times (8 \times 8) = 960 (1×5×3)×(8×8)=960。所以总共需要 1440 + 960 = 2400 1440 + 960 = 2400 1440+960=2400次。有了可观的削减。

但是有个坏消息:从实际情况而言,并不是所有的卷积核都可以被有效地分解为几个更小的卷积核的。所以这种卷积方法用的也不多[1]。

2.2 Depthwise Separable Convolutions

同 Spatial 相比,Depthwise 的可分离卷积网络着眼于 channel 这个维度。

现在假定一个完整的卷积操作可以把一个 H × W × C H \times W \times C H×W×C 的图像转变为 H ′ × W ′ × N H^\prime \times W^\prime \times N H×W×N的输出,其中 (H, W) 是图像的尺寸, C 是输入的通道,通常是 RGB 为3; N 则是输出的通道。DSC 把一个完整的卷积过程分成了两步:

  1. Depthwise convolutions

H × W × C ⇒ H ′ × W ′ × C H \times W \times C \Rightarrow H^\prime \times W^\prime \times C H×W×CH×W×C

  1. Pointwise convolutions

H ′ × W ′ × C ⇒ H ′ × W ′ × N H^\prime \times W^\prime \times C \Rightarrow H^\prime \times W^\prime \times N H×W×CH×W×N

2.2.1 Depthwise convolutions

首先来回忆一下,传统的卷积的做法是不是每个卷积核一次性处理所有通道?就像这样:

在这里插入图片描述

Ok,DC 换了一种做法:一个卷积核只处理一个通道
等等?一次只处理一个通道?这要怎么搞?那么我要使用多个卷积核使得输出有多个通道的时候该去处理哪个输入通道呢?

因为这只是 DSC 的第一步啊!都说了 Separable 了嘛!所以第一步就只考虑 In-Channels ,至于 Out-Channels 是第二步要考虑的。

Depthwise convolutions 做的事情只有一个:用 C (channels)个卷积核分别作用在图像的各个通道上:

在这里插入图片描述

在这里插入图片描述
keras里面有 DepthwiseConv 的 API,我们可以看一下效果如何:

input_layer = layers.Input(shape=(12, 12, 3))
conv1 = layers.DepthwiseConv2D((5, 5), use_bias=False)(input_layer)
model = keras.Model(input=input_layer, output=conv1)
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 12, 12, 3)         0         
_________________________________________________________________
depthwise_conv2d_1 (Depthwis (None, 8, 8, 3)           75        
=================================================================
Total params: 75
Trainable params: 75
Non-trainable params: 0
_________________________________________________________________

3 个卷积核,每个都是 (5, 5),所以 75 个参数。

注意到:

  1. DepthwiseConv2D 不需要指定 filters 也就是卷积核的数量参数,因为这个数量只会和输入的通道数量相同
  2. 输出的 shape 为 (8, 8, 3) ,显然只改变了宽高并不改变通道

所以说,这一步完成了 H × W × C ⇒ H ′ × W ′ × C H \times W \times C \Rightarrow H^\prime \times W^\prime \times C H×W×CH×W×C的操作。

2.2.2 Pointwise convolutions

DepthwiseConv 后就是 Pointwise Conv。其实,别看叫得那么玄乎,这一步做的工作也很简单,就是用 (1, 1) 的卷积核在上一步的输出上做标准的卷积操作,就像这样:
在这里插入图片描述
输出的只有一个通道,想要多个通道怎么办?很简单,用 N 个 1 x 1 卷积核就行了。

这一步完成了 H ′ × W ′ × C ⇒ H ′ × W ′ × N H^\prime \times W^\prime \times C \Rightarrow H^\prime \times W^\prime \times N H×W×CH×W×N的操作。

2.2.3 把两步加起来

把两步加起来,就得到了 Depthwise Separable Convolutions。

在这里插入图片描述

在 keras 中有现成的 SeparableConv API,完成了 DSC 的功能(然而没有 Spatial……),我们再来试一次:

input_layer = layers.Input(shape=(12, 12, 3))
conv1 = layers.SeparableConv2D(1, (5, 5), use_bias=False)(input_layer)
model = keras.Model(input=input_layer, output=conv1)
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 12, 12, 3)         0         
_________________________________________________________________
separable_conv2d_1 (Separabl (None, 8, 8, 1)           78        
=================================================================
Total params: 78
Trainable params: 78
Non-trainable params: 0
_________________________________________________________________

第一步 75 个参数;第二步中一个 (1, 1, 3) 的卷积核;一共 78 个参数。

那么,需要用到多少次的乘法运算呢?

首先第一步中,一共是 ( ( 5 × 5 ) × ( 8 × 8 ) ) × 3 = 4800 ((5 \times 5) \times (8 \times 8)) \times 3 = 4800 ((5×5)×(8×8))×3=4800次;第二步中,需要 ( 3 × 8 × 8 ) × N (3 \times 8 \times 8) \times N (3×8×8)×N次,所以一共是 4800 + 192 N 4800 + 192 N 4800+192N次。

还记得原始的卷积神经网络需要多少吗? 4800 N 4800N 4800N!我们成功地把 O ( P × Q ) O(P \times Q) O(P×Q)变成了 O ( P + Q ) O(P+Q) O(P+Q)

Nice Job!

然而不要太得意,我们要注意到,4800N 并不是永远都比 4800 + 192N 大的,虽然二者在算法复杂度上确实有差距,但如果想让优化后的差距产生明显的效果,前提是 N 要足够大!这就意味着,DSC 只有在网络结构足够复杂时才能大显身手;如果网络结构本身很简单,那么使用 DSC 反而会拖后腿,这就是需要权衡的了!

总结

Separable Convolutions makes CNN GREAT AGAIN!!!

Reference

[1]
Chi-Feng Wang, 《A Basic Introduction to Separable Convolutions》, 24-4月-2018. [在线]. 载于: https://towardsdatascience.com/a-basic-introduction-to-separable-convolutions-b99ec3102728.

[2]
Chris, 《Creating depthwise separable convolutions in Keras》, 24-9月-2019. [在线]. 载于: https://www.machinecurve.com/index.php/2019/09/24/creating-depthwise-separable-convolutions-in-keras/.

[3]
Chris, 《Understanding separable convolutions》, 23-9月-2019. [在线]. 载于: https://www.machinecurve.com/index.php/2019/09/23/understanding-separable-convolutions/.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/frostime/article/details/105020420

智能推荐

艾美捷Epigentek DNA样品的超声能量处理方案-程序员宅基地

文章浏览阅读15次。空化气泡的大小和相应的空化能量可以通过调整完全标度的振幅水平来操纵和数字控制。通过强调超声技术中的更高通量处理和防止样品污染,Epigentek EpiSonic超声仪可以轻松集成到现有的实验室工作流程中,并且特别适合与表观遗传学和下一代应用的兼容性。Epigentek的EpiSonic已成为一种有效的剪切设备,用于在染色质免疫沉淀技术中制备染色质样品,以及用于下一代测序平台的DNA文库制备。该装置的经济性及其多重样品的能力使其成为每个实验室拥有的经济高效的工具,而不仅仅是核心设施。

11、合宙Air模块Luat开发:通过http协议获取天气信息_合宙获取天气-程序员宅基地

文章浏览阅读4.2k次,点赞3次,收藏14次。目录点击这里查看所有博文  本系列博客,理论上适用于合宙的Air202、Air268、Air720x、Air720S以及最近发布的Air720U(我还没拿到样机,应该也能支持)。  先不管支不支持,如果你用的是合宙的模块,那都不妨一试,也许会有意外收获。  我使用的是Air720SL模块,如果在其他模块上不能用,那就是底层core固件暂时还没有支持,这里的代码是没有问题的。例程仅供参考!..._合宙获取天气

EasyMesh和802.11s对比-程序员宅基地

文章浏览阅读7.7k次,点赞2次,收藏41次。1 关于meshMesh的意思是网状物,以前读书的时候,在自动化领域有传感器自组网,zigbee、蓝牙等无线方式实现各个网络节点消息通信,通过各种算法,保证整个网络中所有节点信息能经过多跳最终传递到目的地,用于数据采集。十多年过去了,在无线路由器领域又把这个mesh概念翻炒了一下,各大品牌都推出了mesh路由器,大多数是3个为一组,实现在面积较大的住宅里,增强wifi覆盖范围,智能在多热点之间切换,提升上网体验。因为节点基本上在3个以内,所以mesh的算法不必太复杂,组网形式比较简单。各厂家都自定义了组_802.11s

线程的几种状态_线程状态-程序员宅基地

文章浏览阅读5.2k次,点赞8次,收藏21次。线程的几种状态_线程状态

stack的常见用法详解_stack函数用法-程序员宅基地

文章浏览阅读4.2w次,点赞124次,收藏688次。stack翻译为栈,是STL中实现的一个后进先出的容器。要使用 stack,应先添加头文件include<stack>,并在头文件下面加上“ using namespacestd;"1. stack的定义其定义的写法和其他STL容器相同, typename可以任意基本数据类型或容器:stack<typename> name;2. stack容器内元素的访问..._stack函数用法

2018.11.16javascript课上随笔(DOM)-程序员宅基地

文章浏览阅读71次。<li> <a href = "“#”>-</a></li><li>子节点:文本节点(回车),元素节点,文本节点。不同节点树:  节点(各种类型节点)childNodes:返回子节点的所有子节点的集合,包含任何类型、元素节点(元素类型节点):child。node.getAttribute(at...

随便推点

layui.extend的一点知识 第三方模块base 路径_layui extend-程序员宅基地

文章浏览阅读3.4k次。//config的设置是全局的layui.config({ base: '/res/js/' //假设这是你存放拓展模块的根目录}).extend({ //设定模块别名 mymod: 'mymod' //如果 mymod.js 是在根目录,也可以不用设定别名 ,mod1: 'admin/mod1' //相对于上述 base 目录的子目录}); //你也可以忽略 base 设定的根目录,直接在 extend 指定路径(主要:该功能为 layui 2.2.0 新增)layui.exten_layui extend

5G云计算:5G网络的分层思想_5g分层结构-程序员宅基地

文章浏览阅读3.2k次,点赞6次,收藏13次。分层思想分层思想分层思想-1分层思想-2分层思想-2OSI七层参考模型物理层和数据链路层物理层数据链路层网络层传输层会话层表示层应用层OSI七层模型的分层结构TCP/IP协议族的组成数据封装过程数据解封装过程PDU设备与层的对应关系各层通信分层思想分层思想-1在现实生活种,我们在喝牛奶时,未必了解他的生产过程,我们所接触的或许只是从超时购买牛奶。分层思想-2平时我们在网络时也未必知道数据的传输过程我们的所考虑的就是可以传就可以,不用管他时怎么传输的分层思想-2将复杂的流程分解为几个功能_5g分层结构

基于二值化图像转GCode的单向扫描实现-程序员宅基地

文章浏览阅读191次。在激光雕刻中,单向扫描(Unidirectional Scanning)是一种雕刻技术,其中激光头只在一个方向上移动,而不是来回移动。这种移动方式主要应用于通过激光逐行扫描图像表面的过程。具体而言,单向扫描的过程通常包括以下步骤:横向移动(X轴): 激光头沿X轴方向移动到图像的一侧。纵向移动(Y轴): 激光头沿Y轴方向开始逐行移动,刻蚀图像表面。这一过程是单向的,即在每一行上激光头只在一个方向上移动。返回横向移动: 一旦一行完成,激光头返回到图像的一侧,准备进行下一行的刻蚀。

算法随笔:强连通分量-程序员宅基地

文章浏览阅读577次。强连通:在有向图G中,如果两个点u和v是互相可达的,即从u出发可以到达v,从v出发也可以到达u,则成u和v是强连通的。强连通分量:如果一个有向图G不是强连通图,那么可以把它分成躲个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任一点强连通,成这样的一个“极大连通”子图是G的一个强连通分量(SCC)。强连通分量的一些性质:(1)一个点必须有出度和入度,才会与其他点强连通。(2)把一个SCC从图中挖掉,不影响其他点的强连通性。_强连通分量

Django(2)|templates模板+静态资源目录static_django templates-程序员宅基地

文章浏览阅读3.9k次,点赞5次,收藏18次。在做web开发,要给用户提供一个页面,页面包括静态页面+数据,两者结合起来就是完整的可视化的页面,django的模板系统支持这种功能,首先需要写一个静态页面,然后通过python的模板语法将数据渲染上去。1.创建一个templates目录2.配置。_django templates

linux下的GPU测试软件,Ubuntu等Linux系统显卡性能测试软件 Unigine 3D-程序员宅基地

文章浏览阅读1.7k次。Ubuntu等Linux系统显卡性能测试软件 Unigine 3DUbuntu Intel显卡驱动安装,请参考:ATI和NVIDIA显卡请在软件和更新中的附加驱动中安装。 这里推荐: 运行后,F9就可评分,已测试显卡有K2000 2GB 900+分,GT330m 1GB 340+ 分,GT620 1GB 340+ 分,四代i5核显340+ 分,还有写博客的小盒子100+ 分。relaybot@re...

推荐文章

热门文章

相关标签