我的模型有多快?——深度学习网络模型的运算复杂度、空间占用和内存访问情况计算

前段时间看了几个笔试题,涉及模型复杂度,主要是参数量和计算复杂度的问题。当时搜了一下感觉中文网上的内容比较乱。刚好本文是对神经网络模型资源消耗情况的一篇介绍,就不自己写了,把关键内容搬运一下。

原文见 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 ] y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n-1]*x[n-1] y=w[0]x[0]+w[1]x[1]+w[2]x[2]+...+w[n1]x[n1]

w 和 x 都是向量,y 是标量。上式是全连接层或卷积层的典型运算。一次乘-加运算即一次乘法+一次加法运算,所以上式的 MACCs 是n 。

不过可以看到,加法运算的次数并非 n 而是 n-1 。但考虑 MACCs 时可以类比算法复杂度估算的 big-O ,即结果可以是近似的。

而换到 FLOPS 的情况,点积做了 2n-1 FLOPS,即 n-1 次加法和 n 次乘法。可以看到,MACCs 大约是 FLOPS 的一半。

1.1 全连接层

全连接层的计算

y = m a t m u l ( x , W ) + b y = matmul(x, W) + b y=matmul(x,W)+b

权重 W W W 是一个 I × J I×J I×J 矩阵,输入 x x x I I I 维实值向量, b b b J J J 维偏置。输出 y y y 也是 J J J 维实值向量。FC 层的 MACCs 也不难计算。

上文例子是向量与向量的点积,FC 是向量与矩阵的点积,每一组点积发生在输入 x x x 同权重 W W W 某一列之间,计有 I I I MACCs,一共要计算 J J J 组点积,所以 FC 层的 MACCs 总计 I × J I×J I×J,跟权重的尺寸一致。

偏置项 b b b 对 MACCs 的影响可以忽略不计。而上面也提到 MACCs 中加法比乘法少一次, b b b 刚好补上了这个缺。

所以,对 I I I 的输入、权重为 I × J I×J I×J 的权重矩阵和 J J J 的输出,MACCs 为 I × J I×J I×J ,FLOPS 为 ( 2 I − 1 ) × J (2I−1)×J (2I1)×J

举例:

一个全连接层,输入 100 维,输出 300 维,MACCs 有 300 × 100 = 30 , 000 300×100=30,000 300×100=30,000。不过,如果一个全连接层紧接着卷积层,输入可能没有指定长度 I I I 但有 feature map 的尺寸比如 ( 512 , 7 , 7 ) (512, 7, 7) (512,7,7)。在 Keras 里就需要写一行 Flatten 把它展平,这样此时的 I I I 就是 512×7×7了。

1.2 激活函数

FC 完了接下来通常有个激活函数,ReLU 或者 Sigmoid。激活函数的计算没有点积,所以只用 FLOPS 衡量。

对输出为 J J J 的 FC 层,ReLU 有 J J J FLOPS:

y = m a x ( x , 0 ) y = max(x, 0) y=max(x,0)

相比之下 Sigmoid 就复杂很多。

y = 1 / ( 1 + e x p ( − x ) ) y = 1/(1+exp(-x)) y=1/(1+exp(x))

我们把加减乘除、指数、平方根等等运算都算作一次 FLOPS,这里有除法、加法、指数和减法四种运算,所以 FLOPS 就是 J × 4 J×4 J×4

相对于全连接的矩阵运算,激活函数的计算量通常忽略不计(博主注:不一定,看情况)。

1.3 卷积层

卷积层要单独算而不是用全连接层的结论,是因为输入至少是三维的:H×W×CH×W×C 。对于这样的卷积层,MACCs 有:

K × K × C i n × H o u t × W o u t × C o u t K×K×C_{in}×H_{out}×W_{out}×C_{out} K×K×Cin×Hout×Wout×Cout
解释一下:

输出的 feature map 里每个通道上有 H o u t × W o u t H_{out}×W_{out} Hout×Wout 个元素,权重以 K × K K×K K×K 大小的窗口,在所有的 C i n C_{in} Cin 个通道上做点积,共有 C o u t C_{out} Cout 个卷积核,上述操作重复了 C o u t C_{out} Cout 次。
同样,这里也忽略了偏置和激活函数。不应该忽略的是 stride(步长)、dilation factors(漏孔/膨胀卷积)、padding(填充),这就是为什么直接从输出尺寸 H o u t × W o u t H_{out}×W_{out} Hout×Wout 开始算的原因——都已经考虑在内了。

举例:

3 × 3 3×3 3×3 卷积,128 个 filer,输入的 feature map 是 112 × 112 × 64 112×112×64 112×112×64 ,stride=1,padding=same,MACCs 有:

3 × 3 × 64 × 112 × 112 × 128 = 924 , 844 , 032 3×3×64×112×112×128=924,844,032 3×3×64×112×112×128=924,844,032
接近十亿的乘-加操作。

1.4 Batch Normalization

计算公式:

z = g a m m a ∗ ( y − m e a n ) / s q r t ( v a r i a n c e + e p s i l o n ) + b e t a z = gamma * (y - mean) / sqrt(variance + epsilon) + beta z=gamma(ymean)/sqrt(variance+epsilon)+beta

首先以输入为卷积层的情况为例。

每个通道上都存在一组 mean 、beta 、gamma 、variance ,C 个通道就有 C×4 个可学习的参数。而且 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 次内存访问。

所以,每层的内存访问总数如下:

i n p u t = H i n × W i n × C i n × K × K × C o u t input = H_{in}× W_{in} × C_{in} ×K × K × C_{out} input=Hin×Win×Cin×K×K×Cout
o u t p u t = H o u t × W o u t × C o u t output = H_{out} × W_{out} × C_{out} output=Hout×Wout×Cout
w e i g h t s = K × K × C i n × C o u t + C o u t weights = K × K × C_{in} × C_{out} + C_{out} weights=K×K×Cin×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

当网络层数加深时, H i n H_{in} Hin W i n W_{in} 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

这种情况下 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

所以更大的 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.

不过这俩工具还不支持对内存访问量的计算。

### 如何评估算法模型的时间复杂度空间复杂度 #### 时间复杂度的定义与计算方法 时间复杂度是用来描述算法执行时间随输入规模变化的增长趋势的一种方式。通常情况下,可以通过分析算法中最基本操作的数量来估算其时间复杂度[^1]。对于一个给定的算法,其时间复杂度可以用大O符号表示,该符号仅保留表达式的最高阶项并忽略常数因子[^5]。 以下是几种常见的时间复杂度及其含义: - **O(1)**:无论输入大小如何,算法始终以固定步数完成。 - **O(n)**:随着输入规模线性增加,运算次数也随之线性增长。 - **O(log n)**:每一步减少一定比例的数据处理量,典型例子是对分查找。 - **O(n log n)**:常见的高效排序算法(如速排序、归并排序)具有这样的复杂度。 - **O(n²)**:嵌套循环结构常常导致平方级别的复杂度。 具体来说,在实际应用中还需要考虑不同场景下的表现差异,比如最好情况、最坏情况以及平均情况下的时间消耗。 #### 空间复杂度的概念及测量手段 除了运行效率外,另一个重要的考量因素就是内存占用状况——即所谓的“空间复杂度”。它反映了程序运行期间额外所需的临时存储资源数量[^3]。一般而言,当提到某个特定实现的空间需求时,我们会关注除原始输入之外新增加的部分;例如递归调用栈深度或者动态分配数组长度等等[^4]。 同样地,也可以利用类似的渐近记号体系来刻画这种特性,并遵循相同的简化原则去除无关紧要的小项或乘积系数。 值得注意的是,在现代计算机环境中由于硬盘容量日益增大且成本降低的缘故,相较于过去更加倾向于优先优化速度而容忍较大的内存开销。然而这并不意味着可以完全忽视后者的影响,特别是在移动设备或其他受限平台上开发软件的时候仍需谨慎对待两者间的权衡取舍问题。 ```python def example_function(n): array = [0] * n # 额外创建了一个长度为n的列表 -> O(n) 的空间复杂度 total = sum(array) # 计算的操作不会显著影响空间使用 return total ``` 上述代码片段展示了简单的函数定义过程里涉及到的一些基础概念实例化展示形式之一。 #### 综合评价标准的应用案例探讨 针对某些特殊领域像机器学习等领域内的大型神经网络架构设计过程中不仅需要关心单次前向传播预测耗时长短同时也得兼顾训练阶段参数更新迭代所需要的庞大中间结果保存区域安排合理与否等问题因此往往采用更为精细复杂的理论框架来进行全方位角度深入剖析研究工作以便最终得出科学合理的结论指导实践操作环节顺利开展下去[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值