模型参数量(空间复杂度):空间复杂度决定了模型的参数数量。
模型计算量(时间复杂度),直接决定模型的forward time ,决定了模型的训练/预测时间。如果复杂度过高,会导致模型训练和预测耗费大量时间,既无法快速的验证想法和改善模型,也无法做到快速的预测。
https://blog.csdn.net/laolu1573/article/details/79196160
时间复杂度
时间复杂度即模型的运算次数。
单个卷积层的时间复杂度:Time~O(M^2 * K^2 * Cin * Cout)
注1:为了简化表达式中的变量个数,这里统一假设输入和卷积核的形状都是正方形。
注2:严格来讲每层应该还包含1个Bias参数,这里为了简洁就省略了。
M:输出特征图(Feature Map)的尺寸。
K:卷积核(Kernel)的尺寸。
Cin:输入通道数。
Cout:输出通道数。
输出特征图尺寸又由输入尺寸X、卷积核尺寸K、Padding、 Stride 这四个参数所决定,表示如下:
M=(X - K + 2*Padding) / Stride + 1
空间复杂度
空间复杂度即模型的参数数量,体现为模型本身的提及。
Space~O(K^2 * Cin * Cout)
-空间复杂度只与卷积核的尺寸K、通道数C相关。而与输入图片尺寸无关。
-当我们需要裁剪模型时,由于卷积核的尺寸通常已经很小,而网络的深度又与模型的能力紧密相关,不宜过多削减,因此模型裁剪通常最先下手的地方就是通道数。
模型参数量计算比较简单,就是计算模型所有参数数量之和。
https://www.toutiao.com/a6649299295855968782/
https://kuaibao.qq.com/s/20190123A05UUJ00?refer=cp_1026
贴个五种CNN经典模型计算梁和参数量对比:http://m.elecfans.com/article/598165.html
FFNN
首先我们定义三个参数:
i:输入大小
h:隐藏层大小
o:输出大小
即在前馈神经网络中参数数量为:
num_params =各层之间参数+每层的偏差= (i×h + h×o)+(h + o)
RNN
首先我们定义三个参数:
g:门的数量(RNN有1个门,GRU有3个,LSTM有4个)
h:隐藏单元大小
i:输出大小
每个门中的权重实际上是一个输入大小(h + i)和输出大小为h的FFNN。所以每个门都有h(h + i)+ h个参数。即在RNN中参数数量为:
num_params= g ×[ h(h + i)+ h ]
CNN
首先我们定义三个参数:
i:输入尺寸
f:卷积核的大小
o:输出大小
则每个滤波器对应的输出映射参数为
num_params =权重+偏差= [ i×(f×f)×o ] + o
模型计算量:
比较详细:https://blog.csdn.net/leayc/article/details/81001801
前段时间看了几个笔试题,涉及模型复杂度,主要是参数量和计算复杂度的问题。当时搜了一下感觉中文网上的内容比较乱。刚好本文是对神经网络模型资源消耗情况的一篇介绍,就不自己写了,把关键内容搬运一下。
原文见 http://machinethink.net/blog/how-fast-is-my-model/ 。
深度网络的计算消耗是学术 paper 相对少见的话题。当然,早期网络精度不够的情况下讨论压缩也没有意义。工程师需要实现模型并让网络尽可能地在各类环境下工作,模型的资源消耗情况和运行速度非常关键。
原文以移动端的模型应用为例,列出了四个主要问题:
- 空间占用——单个模型的参数文件要占用多大空间
- 内存占用——运行在手机或平板上时需要占用多大的 RAM
- 运行速度——尤其考虑实时的视频和大图像处理情形
- 耗电情况——我可不想要暖手宝
原文内容还是挺丰富的。为了节约时间,博主只把关键的部分略作摘录和演绎,即网络中一般操作的运算量、内存占用的计算方法,其他的如 MobileNet 模型的优化就不做介绍了。如有需要请查阅原文。
原文以外的内容将斜体显示,以作区别。
正文
案例:作者的一位客户最近用 MobileNetV2 替换掉了 V1 模型,按理说V2 的计算量远小于 V1 ,
(网上随手找的图:https://blog.csdn.net/u011995719/article/details/79135818)
然鹅实际情况却是 V2 比 V1 慢得多。
(注:可参考
https://www.zhihu.com/question/265709710/answer/299136290,https://www.reddit.com/r/MachineLearning/comments/8a7sf6/d_mobilenet_v2_paper_said_depthwise_separable/。
官方已经放出模型 https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet 页面上也有实验测试结果。看完全文也会发现 V2 不比 V1 慢。作者这里有点标题党。)
为什么会这样呢?
1.计算消耗
可以用 FLOPS(floating point operations per second,每秒浮点运算数)来衡量模型的速度。另一种方法是 MACCs(multiply-accumulate operations,乘-加操作),也叫 MAdds。但说穿了,都是点积运算而已。
什么叫乘-加?神经网络里的运算大都是这样的:
y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n-1]*x[n-1]
- 1
w
和 x
都是向量,y
是标量。上式是全连接层或卷积层的典型运算。一次乘-加运算即一次乘法+一次加法运算,所以上式的 MACCs 是n
。
不过可以看到,加法运算的次数并非 n
而是 n-1
。但考虑 MACCs 时可以类比算法复杂度估算的 big-O
,即结果可以是近似的。
而换到 FLOPS 的情况,点积做了 2n-1
FLOPS,即 n-1
次加法和 n
次乘法。可以看到,MACCs 大约是 FLOPS 的一半。
1.1 全连接层
全连接层的计算
y = matmul(x, W) + b
- 1
权重 WW 维实值向量。FC 层的 MACCs 也不难计算。
上文例子是向量与向量的点积,FC 是向量与矩阵的点积,每一组点积发生在输入 xx,跟权重的尺寸一致。
偏置项 bb 刚好补上了这个缺。
所以,对 II 。
举例:
一个全连接层,输入 100 维,输出 300 维,MACCs 有 300×100=30,000300×100=30,000 了。
1.2 激活函数
FC 完了接下来通常有个激活函数,ReLU 或者 Sigmoid。激活函数的计算没有点积,所以只用 FLOPS 衡量。
对输出为 JJ FLOPS:
y = max(x, 0)
- 1
相比之下 Sigmoid 就复杂很多。
y = 1/(1+exp(-x))
- 1
我们把加减乘除、指数、平方根等等运算都算作一次 FLOPS,这里有除法、加法、指数和减法四种运算,所以 FLOPS 就是 J×4J×4 。
相对于全连接的矩阵运算,激活函数的计算量通常忽略不计(博主注:不一定,看情况)。
1.3 卷积层
卷积层要单独算而不是用全连接层的结论,是因为输入至少是三维的:H×W×CH×W×C 。对于这样的卷积层,MACCs 有:
K×K×Cin×Hout×Wout×CoutK×K×Cin×Hout×Wout×Cout
解释一下:
- 输出的 feature map 里每个通道上有 Hout×WoutHout×Wout 次
同样,这里也忽略了偏置和激活函数。不应该忽略的是 stride(步长)、dilation factors(漏孔/膨胀卷积)、padding(填充),这就是为什么直接从输出尺寸 Hout×WoutHout×Wout 开始算的原因——都已经考虑在内了。
举例:
3×33×3 ,stride=1
,padding=same
,MACCs 有:
3×3×64×112×112×128=924,844,0323×3×64×112×112×128=924,844,032
接近十亿的乘-加操作。
1.4 Batch Normalization
计算公式:
z = gamma * (y - mean) / sqrt(variance + epsilon) + beta
- 1
首先以输入为卷积层的情况为例。
每个通道上都存在一组 mean
、beta
、gamma
、variance
,CC 个可学习的参数。而且 BN 是作用在每一个元素上的,这样看来,造成的 FLOPS 应该不少。
但有趣的是,在 BN 直接连接卷积层的情况下,即 Conv-BN-ReLU 时,通过一组推导,可以将 BN 的计算整合到卷积层当中(注意这是 inference 的情况,跟训练阶段差别很大),从而消去的 BN 层造成的 FLOPS。如果是 Conv-ReLU-BN 的结构这一套就行不通了。
( BN 层的计算结合到 Conv 层中去,BN 层的 FLOPS 消失了,Conv 层需要乘一个常系数)
即从结果上来说,在 inference 时模型中的 BN 层实际被消去了。
1.5 其他层
像 Pooling 层虽然确实很关键,但没有用到点积运算,所以 MACCs 不能很好地衡量这部分计算消耗。如果用 FLOPS,可以取 feature map 的尺寸然后乘一个常系数。
如 maxpooling 层,stride=2
、filter_sz=2
(即输出保持相同尺寸),112 x 112 x 128
的feature map,FLOPS 就是 112 x 112 x 128 = 1,605,632
。相对卷积层和全连接层的运算,这个计算量比较小,所以也可以忽略不计。
RNN 这里不做讨论。简单来说,以 LSTM 为例,计算主要是两个大的矩阵乘法,sigmoid,tanh 和一些元素级的操作。可以看成两个全连接层的运算,所以 MACCs 主要取决于输入、输出和隐状态向量的尺寸。点积运算还是占了大头。
2. 内存占用
内存带宽其实比 MACCs 更重要。目前的计算机结构下,单次内存访问比单次运算慢得多的多。
对每一层网络,设备需要:
- 从主内存中读取输入向量 / feature map
- 从主内存中读取权重并计算点积
- 将输出向量或 feature map 写回主内存
涉及大量的内存访问。内存是很慢的,所以网络层的内存读写对速度有很大的影响,可能比计算耗时还要多。
2.1 权重的内存占用
全连接层有 I x J
大小的权重矩阵,加上偏置向量共计 (I + 1) x J
。
卷积层的 kernel 通常是正方形的,对 kernel_sz = K
和输入通道为 Cin
、输出 Cout
和额外的 Cout
个偏置的情况,共有 (K x K x Cin + 1) x Cout
个参数。对比之下卷积层的参数量远小于全连接。
举例:
全连接层有4096个输入和4096个输出,所以权重数 (4096+1) x 4096 = 16.8M
。
3 x 3
、48个卷积核,在64x64
、32个通道的输入上计算,共有 3 x 3 x 32 x 48 + 48 = 13, 872
个权重。
注意到此处卷积层的输入实际是全连接层的32倍(通道),输出是48倍,然鹅权重数只有后者的千分之一不到。全连接层的内存占用真的很可怕。
作者注:卷积层可以看作一个受限连接的全连接层,即权重对 k x k
以外的输入置零,不使用。
2.2 feature maps 和中间结果
CS231n 的 Lesson 9 专门花了很多篇幅讲 feature map 的计算,可以参考。
还是举例说明。卷积层的输入是 224x224x3
,把所有这些值读出来需要访问 150,528
次内存。如果卷积核是 KxKxCout
,还要乘上这个系数(因为每次卷积都要访问一遍)。拿 stride=2
, kernel
数为32的情况来说,输出的 feature map 尺寸为 112x112x32
,共计 401,408
次内存访问。
所以,每层的内存访问总数如下:
input = Hin x Win x Cin x K x K x Cout
output = Hout x Wout x Cout
weights = K x K x Cin x Cout + Cout
,按上例:
input = 224 x 224 x 3 x 3 x 3 x 32 = 43,352,064
output = 112 x 112 x 32 = 401,408
weights = 3 x 3 x 3 x 32 + 32 = 896
total = 43,754,368
- 1
- 2
- 3
- 4
当网络层数加深时,Hin
Win
会越来越小,但通道数会变得很大:
input = 28 x 28 x 256 x 3 x 3 x 512 = 924,844,032
output = 28 x 28 x 512 = 401,408
weights = 3 x 3 x 256 x 512 + 512 = 1,180,160
total = 926,425,600
- 1
- 2
- 3
- 4
这种情况下 weights 部分也会变得很大,所以是不能忽略的。
2.3 Fusion
这一节的意思是,像 ReLU 这样比较简单的运算,如果不做优化,在计算时近乎是从输入到输出做了一次拷贝。计算可以认为不耗时间,但内存访问还是有消耗的,所以可以把这一步同卷积层的计算合成,从而节省了一轮内存读写。
3. MobileNet V2 vs. V1
这部分作者讲了他认为 V2 不会比 V1 快的分析过程。结论跟开头博主引的图相近,即乘子都为1.0时,V2是显著快于V1的,但V2在乘子为1.4时速度比V1稍慢。
至于原因嘛,简单来说就是 V2 的层数更深,每层的输入输出参数读写导致内存访问量大增。因此作者认为影响 inference 速度的瓶颈其实不在 MACCs,而是内存访问数(memory accesses)。
V2 with multiplier=1.4 的速度略慢于 V1,但精度高出不少;V2 with multiplier=1.0 速度比 V1 快很多。可以根据需要进行取舍。官方页面上也给了很多实验参考。
然后作者对 VGG16 做了一点考察,结论很有意思。
VGG16 经常被当作图像方面的特征提取器,结构很简单,层数也不多,看起来好像计算比较多、内存访问会少一些,真的是这样吗?对比 MobileNet(输入按移动设备16:9的规格,是126x224
,可以算出以下结果:
VGG16 params: 15M
VGG16 MACCs : 8380M
VGG16 MAes : 8402M
- 1
- 2
- 3
所以更大的 feature map 导致了更多的内存访问。
4 结论
论文中 MobileNet V2 主要比较了 MACCs 和参数量,指出因为这两项规模更小所以速度更快。但实际上还要考虑内存访问的情况。
另外本文给出的 MACCs、内存访问、参数量都是估计值,只用于同类模型的复杂度比较,出了这个语境是毫无意义的。
进一步阅读
论文:
Convolutional Neural Networks at Constrained Time Cost by He & Sun (2014) gives a nice overview of the computation costs and trade-offs between depth, filter sizes, etc. in convnets.
Learning both Weights and Connections for Efficient Neural Networks by Han et al. (2015) has a table with the relative costs of computations versus memory accesses. Plus it talks about pruning neural networks, which is a cool topic in its own right.
工具:
Alchemy from fritz.ai lets you analyze ML models to see if they’re ready for mobile development.
Netscope shows the structure of models and also analyzes their computational cost. Currently supports Caffe only.
不过这俩工具还不支持对内存访问量的计算。