卷积神经网络(CNN)加速

CNN加速,原文链接:Speeding up Convolutional Neural Networks

几种加速CNN训练的方法,同时不会对模型最终的准确率有较大的影响。

全连接层是网络占用大量内存的主要原因,但是运行速度却很快,而卷积虽然减少了参数数量,但却需要消耗更多的计算资源,因此,应该从卷积操作本身入手,寻找优化的方法。

以下几种方法可以在不严重影响准确率的前提下加速卷积计算(有些在现有的一些网络中出现过,比如ResNet,DenseNet)

1. 卷积核分解

首先看一个Numpy里面关于矩阵的例子

>>> from numpy.random import random
>>> random((3, 3)).shape == (random((3, 1)) * random((1, 3))).shape
>>> True

我们知道一个N*N的矩阵可以由一个N*1和一个1*N的矩阵相乘得到(线性代数知识),注意,这里是shape相同。通常,一次卷积计算产生的参数个数为input_channels*n*n*output_channels(filter_nums),当把卷积核大小分解成N*1和1*N以后,参数个数就变为2*input_channels*n*output_channels(filter_nums),参数个数减少了,自然计算资源消耗也会相应地减少,下面是一段使用Keras进行核分解的代码

# k - kernel size, for example 3, 5, 7...
# n_filters - number of filters/channels
# Note that you shouldn't apply any activation
# or normalization between these 2 layers
fact_conv1 = Conv(n_filters, (1, k))(inp)
fact_conv1 = Conv(n_filters, (k, 1))(fact_conv1)

需要注意的是,尽量避免在接近输入卷积层的地方进行分解。同时,分解一个3*3的卷积核会影响网络的性能,尽量在具有较大卷积核的地方进行分解。

另外,还有一种更稳定核分解方法,比如使用两层3*3卷积层代替一层5*5卷积层,或者三层3*3代替一层7*7

2. Bottleneck Layers

Bottleneck,瓶颈,顾名思义,输入和输出通道数保持,但是在中间进行卷积时,将通道数缩小从而达到减少参数的目的,可以看一下下面这段Keras代码

from keras.layers import Conv2D
# given that conv1 has shape (None, N, N, 128)
conv2 = Conv2D(96, (1, 1), ...)(conv1) # squeeze
conv3 = Conv2D(96, (3, 3), ...)(conv2) # map
conv4 = Conv2D(128, (1, 1), ...)(conv3) # expand

如果直接通过一个128*3*3的卷积层,那么需要的参数个数是128*3*3*128=147456,进行Bottleneck后的参数个数为128*1*1*96+96*3*3*96+96*1*1*128=107520,可以看到,参数个数减少了4w多个。当前一些著名网络都有使用这种方法,像ResNet和DenseNet等等。

3. 卷积核数合并

第三个方法是使用更少的但是更宽(更多的filter_nums)的卷积核,对于GPU来说,一次性处理一块大的数据比处理许多小的数据要快,下面是Keras代码:

# convert from
conv = Conv2D(96, (3, 3), ...)(conv)
conv = Conv2D(96, (3, 3), ...)(conv)
# to
conv = Conv2D(128, (3, 3), ...)(conv)
# roughly, take the sqrt of the number of layers you want
# to merge and multipy the number to
# the number of filters/channels in the initial convolutions
# to get the number of filters/channels in the new layer

如果卷积层合并后的宽度不是很确定,可以粗略地以被合并的层个数的平方根乘以初始卷积层的卷积核个数来作为合并后的卷积核个数

4. Depthwise Separable Convolutions

对于计算机来说,乘(除)法操作比加(减)法更费时,所以在计算的时候应该尽量减少乘(除)法运算。接下来,看一下正常卷积操作需要的计算

假设图片F,大小为DF*DF,通道数为M,用N个DK*DK大小的卷积核对F进行卷积,得到图片G,G的形状为DG*DG*N,卷积核在F上某一位置进行卷积时,所做的乘法次数为DK*DK*M,当卷积核滑动完整个F后,所做的乘法次数为DG*DG*DK*DK*M,因为卷积后的图像大小为DG*DG,这个还只是一个卷积核所做的乘法次数,N个卷积核的乘法次数为N*DG*DG*DK*DK*M

接下来,看看Depthwise Separable Convolutions是怎么做的

1. Depthwise Convolution,一个卷积核对应一个通道进行卷积,即对F的每个通道都给一卷积核,该卷积核只在自己的通道上进行卷积

2. Pointwise Convolution,对1的结果用1*1的卷积核对所有通道进行卷积,卷积核的个数为N,这样得到新图像G形状就和普通卷积操作的结果一样了

接下来,计算两次卷积所需要的乘法次数。首先在Depthwise中,一个卷积核的乘法次数是DK*DK,对于整个F,乘法次数为DG*DG*DK*DK,M个卷积核的乘法次数是M*DG*DG*DK*DK。然后是Pointwise,一个卷积核的乘法次数是M,对于整个F,乘法次数为DG*DG*M,N个卷积核的乘法次数为N*DG*DG*M。那么,一次Depthwise Separable Convolution所需的乘法次数为M*DG*DG*DK*DK+N*DG*DG*M=M*DG*DG*(DK*DK+N),而一次普通卷积所需的乘法次数为N*DG*DG*DK*DK*M,Depthwise Separable是普通卷积的(DK*DK+N)/ DK*DK*N倍,这里打个比方,假设N为1024,DK为3,普通卷积消耗1,而Depthwise Separable只消耗0.112,快了大概9倍。

Keras将Depthwise Separable封装为一个网络层接口,代码如下(参数远不止这几个,详见Keras官方文档):

# in Keras
from keras.layers import SeparableConv2D
...
net = SeparableConv2D(32, (3, 3))(net)
...
# it's almost 1:1 similar to the simple Keras Conv2D layer

除此之外,还有一些加速的小技巧,比如改变conv-BN-ReLU的顺序为BN-ReLU-conv,有时也会加快模型训练;在Bottleneck Block中,增加卷积层的个数会提高效果,但同时也要增加Block的个数,这好像与加速无关,Anyway,刚好看到,便记下来了。

  • 8
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值