个人模型能跑多快——神经网络模型速度调研(一)

前言

对于神经网络,咱们更多谈的是其精度怎么样,有百分之零点几的提高。可是若是谈到速度的话,深度学习神经网络相比于传统的算法来讲,速度恐怕没有那么快了。html

那么咱们何时须要提高速度呢?假若有如下的场景:git

  • 将模型运行在手机上
  • 须要实时的场景,好比高速摄像机捕捉动做
  • 在嵌入式设备上运行

对于有桌面级显卡这种利器来讲,速度彷佛很容易获得很快,可是放到以上这些设备时,在有效的硬件上若是速度提高不上来,那么所设计的算法也就没什么用处了。算法

《个人模型能跑多快——神经网络模型速度调研(一)》

所谓提高速度,不谈论硬件级别的优化,对于神经网络来讲无非也就两点:编程

  • 网络的设计
  • 输入数据的大小

输入数据大小咱们姑且不谈,而神经网络的设计这一点就显得比较重要了,网络的设计能够细分为:网络模型权重的大小、网络运行过程当中产生的中间变量的大小、网络设计中各类计算的执行速度等等这些都会对速度产生影响,通常来讲,模型参数和模型速度是成正比的。bash

关于速度和精度来讲,这每每是一个衡量,精度和速度通常没法兼顾,正如在工业界使用很火的YOLO和在学术界名声远扬的Mask-Rcnn,一个追求速度一个追求精度(固然速度的前提是精度在可接受范围以内)。网络

《个人模型能跑多快——神经网络模型速度调研(一)》

运算量

接触过ACM的童鞋必定知道时间复杂度空间复杂度这两个概念,时间复杂度即衡量一个算法运行的时间量级,而空间负责度则衡量一个算法所占用的空间大小。神经网络则相似,如何判断一个网络的速度快不快,最直观最直接地就是看其包含多少个浮点运算(固然与内存带宽也有关系)。ide

与这个概念密切相关的就是FLOPS(Floating-point operations per second,每秒执行的浮点运算数)。如今的桌面级显卡,大部分都是TFLOPs级别了,1TFLOP也就是每秒执行1,000,000,000,000次浮点运算。函数

矩阵乘法

在神经网络中,最多见的就是矩阵乘法:性能

正以下方的输入4×4的图像,卷积核为3×3,输出为2×2:学习

《个人模型能跑多快——神经网络模型速度调研(一)》

在计算机中将上述运算分解为:

《个人模型能跑多快——神经网络模型速度调研(一)》

结果中一个标量的计算过程能够用公式表示为:

y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n-1]*x[n-1]
复制代码

w和x都是向量,w是权重,x是输入。最终的结果是y是一个标量(scalar)。这个运算称做multipy-accumulate operations。而其中的一个操做w[0]*x[0] + ..(先乘后加)称为一个MACC(multipy-accumulate operation)。而上面的计算一共包含n个MACCs。

简而言之,两个n维向量的点乘所须要n个MACCs运算(其实能够说是n-1个,由于第一个不算,可是咱们近似地认为是n个)。而n个MACCs运算包括2n-1个FLOPs(n个乘法和n-1个加法),咱们近似为2n个FLOPs。

也就是说,两个n维向量的乘积所须要的FLOPs是2n个。

固然,在不少的硬件设施中(好比显卡),一个MACC就能够称做一个运算单位了,而不是将加法和乘法分开,由于硬件已经对其进行了大量的优化,咱们以后在测一个卷积运算量就能够按照MACC这样的单位来计算了。

全链接层

全链接层是除了卷积层最多见的层,在全链接层中,输入数量为I和输出数量为O,这些节点一一相连,而后权重W保存在I x J的矩阵中,因而对于一个全链接层来讲,其计算量为:

y = matmul(x,W) + b
复制代码

《个人模型能跑多快——神经网络模型速度调研(一)》
(来自:leonardoaraujosantos.gitbooks.io/artificial-…)

在上面的这个式子中(结合上图),咱们的x维数I为3,x是一个3维的向量,输出y是二维的向量,所以权重W的数量就是3 x 2,最后加上偏置量b

那咱们要计算全链接层一共执行了几个MACC,首先看全链接层中的运算matmul。这是一个矩阵运算。

矩阵运算说白了就是一堆乘法和加法的集合,咱们输入的维度是I输出维度是O,其次中间的W的维度为I x O(在上图中是3x2)那么很简单,咱们一共要作的就是I x O个MACCs,能够发现和权重矩阵的数量是同样的。

哦,还有个bias偏置b没有算里头,这个其实能够忽略不计了,在平时的计算量中这个偏置直接就不算了。

咱们在看一些全链接层计算公式的时候,可能会发现计算中将偏置移到了矩阵中而不是先矩阵运算完再加偏置向量。也就是执行了一个 (I + 1) x O的矩阵运算,这个就是为了计算步骤简便一些,对计算量没有任何影响。

也就是说,加入咱们的全链接层有100个输入,200个输出,那么一共执行了100 x 200 = 20,000个MACCs。

一般,输入I向量输出O向量的时候,执行了I x J个MACCs和(2I - 1) x J个FLOPs。

全链接层就是向量以前的运算,一般会将全链接层放在卷积层的后面,而咱们在编程计算这些值的时候都要对卷积后的值进行Flatten操做,相比你们应该很熟悉了,Flatten就是将一个(N,C,H,W)的张量变形为(N,I)的形状,从而去执行全链接运算。

激活函数

一般咱们会在卷积层或者全链接层以后加一个非线性的激活函数,好比RELU或者Sigmoid。在这里咱们使用FLOPs去衡量其计算量,由于激活函数不涉及到点乘操做,因此用不到MACCs

对于RELU来讲:

y = max(x, 0)
复制代码

x为输入,这里的输入就是其余层的输出,假如其它层传递给RELU层n个向量,那么RELU层对这n个向量进行计算,也就是n个FLOPs。

对于sigmoid来讲:

y = 1 / (1 + exp(-x))
复制代码

上式包含了一个加法、一个减法、一个除法和一个取幂运算,咱们将这些运算都归结为一个单独的FLOP(还有乘法、求根号等)。所以一个sigmoid的运算量为4个FLOPs。假如输入时n那个计算量为4 x n个FLOPs。

但通常咱们只关心比较大的矩阵运算,像这种计算量通常也就忽略了。

卷积层

卷积层中主要的处理对象不是以前提到的向量,而是咱们日常见到的(C,H,W)三通道的张量,其中C表明通道数,HW表明这个特征图的高和宽。

对于一个kernel为K的卷积层来讲(这里只说方形的卷积层,咱们平时用到的也都是方形的卷积),所须要的MACCs为:

K  x  K  x  Cin  x  Hout  x  Wout  x  Cout
复制代码

怎么来的:

  • 输出的特征图大小为Hout x Wout,由计算中的每一个像素点组成
  • 权重(weights)和输入特征图的计算的窗口大小为K x K
  • 输入特征图的通道数为Cin
  • 对应每个通道的卷积产生的通道数为Cout

这里忽略了偏置,一般咱们在计算参数时会算上偏置,可是在计算FLOPs则直接忽略。

举个例子,假如输入三通道256*256的图像,使用的卷积核大小为3,卷积的层数为128,计算总共的运算量为:

256 x 256 x 3 x 3 x 3 x 128 = 226,492,416 
复制代码

差很少226M-FLOPs,计算量仍是蛮大的。

以上使用的stride为1,也就是每隔一步在特征图上进行卷积操做,若是上述的卷积层的strid为2,那么至关于在一半大小的图像中进行卷积,上面的256×256则变成128×128

深度可分离卷积结构

深度可分离的卷积构架是众多高效网络的基本结构,例如MobileNetXception。都采用了depthwise-separable convolution的网络结构,该网络结构并不复杂,能够分为两个部分:

《个人模型能跑多快——神经网络模型速度调研(一)》

(来源于 machinethink.net/blog/mobile… )

须要注意下,下文中的深度可分离卷积对应是Depthwise Separable Convolution,它分别两个部分,分别是深度分离(depthwise)的卷积和点(pointwise)卷积(也就是所谓的1×1卷积)。

其中深度分离的卷积运算和普通的运算相似,只不过再也不将三个通道(RGB)变成一个通道了(普通卷积核通常是对图像的三通道分别进行卷积再相加化为一个通道),此次是直接三个通道输入三个通道输出,也就是对应三个独立参数,不一样参数内容的卷积,每个卷积核对应一个通道(输入一个通道,输出一个通道)。

有一个称之为 depthwise channel multiplier 的概念,也就是深度分离通道放大器,若是这个放大器大于1,好比为5,那么一个卷积核就至关于输入一个通道输出5个通道了,这个参数就是调整模型大小的一个超参数。

执行的运算次数为:

K x K x C X Hout X Wout

注意相比以前普通的卷积运算能够说少乘了个C,运算量能够说是大大提高了。

举个例子,好比利用3x3的深度可分离卷积去对一张112 x 112的特征图作卷积操做,通道为64,那么咱们所须要的MACCs为:

3 x 3 x 64 x 112 x 112 = 7,225,344
复制代码

对于点(pointwise)卷积运算来讲,须要的运算量为:

Cin X Hout X Wout X Cout
复制代码

这里的K,核大小为1。

一样举个例子,假如咱们有个112x112x64维数的数据,咱们利用点分离卷积将其投射到128维中,去建立一个112x112x128维数的数据,这时咱们须要的MACCs为:

64 x 112 x 112 x 128 = 102,760,448
复制代码

能够看到点分离运算所须要的运算量还大于深度分离运算。

咱们将上述两个运算加起来和普通的3x3卷积操做运算相比:

3×3 depthwise          : 7,225,344
1×1 pointwise          : 102,760,448
depthwise separable    : 109,985,792 MACCs

regular 3×3 convolution: 924,844,032 MACCs
复制代码

能够发现速度提高了8倍(8.4)多~

可是这样比较有点不是很公平,由于普通的3x3卷积学习到的信息更加完整,能够学习到更多的信息,可是咱们要知道在同等的计算量下,相比传统的3x3卷积,咱们可使用8个多的深度可分离卷积,这样比下来差距就显现出来了。

关于模型中的参数量计算请看这篇文章:浅谈深度学习:如何计算模型以及中间变量的显存占用大小

咱们整理一下,深度可分离具体须要的MACCs为:

(K x K x Cin X Hout X Wout) + (Cin x Hout x Wout x Cout)
复制代码

简化为:

Cin x Hout x Wout X (K x K + Cout)
复制代码

若是咱们将其跟普通的3x3卷积对比的话就会发现,上式最后的+ Cout在普通的3x3卷积中为x Cout。就这个小小的差异形成了性能上极大的差别。

深度可分离卷积核传统的卷积的提速比例能够认为为K x K(也就是卷积越大,提速越快),上面咱们按照3x3卷积举例发现提速8.4倍,其实和3x3=9倍是相差无几的。

其实实际上的提速比例是:K x K x Cout / (K x K + Cout)
另外须要注意的是,深度可分离卷积也能够像传统卷积同样,使用stride大于1,当这个时候深度可分离卷积的第一部分输出的特征大小会降低,而深度可分离的第二部分点卷积则保持输入卷积的维度。

上面介绍的深度可分离卷积是MobileNet V1中的经典结构,在MobileNet V2中,这个结构稍微变化了一下下,具体来讲就是多了一个扩张和缩小的部分:

  • 第一个部分是1×1卷积,这个卷积用来在输入特征图像上添加更多的通道(这个能够理解为扩张层-expansion_layer)
  • 第二个部分就是已经提到的3×3深度分离卷积(depthwise)
  • 第三部分又是一个1×1卷积,这个卷积用来减小输入特征图像上的通道(这个称之为投射层-projection_layer,也就是所谓的瓶颈层-bottleneck convolution)

再讨论下上面这个结构的计算数量:

Cexp = (Cin × expansion_factor)

expansion_layer = Cin × Hin × Win × Cexp

depthwise_layer = K × K × Cexp × Hout × Wout

projection_layer = Cexp × Hout × Wout × Cout
复制代码

上式中的Cexp表明扩张层扩张后的层数,虽然不管是扩张层仍是瓶颈层都不会改变特征图的H和W,可是其中的深度分离层若是stride大于1的话会发生改变,因此这里的Hin WinHout Wout有时候会不一样。

将上式进行简化:

Cin x Hin X Win X Cexp + (K x K + Cout) x Cexp x Hout x Wout
复制代码

当stride=1的时候,上式简化为:(K x K + Cout + Cin) x Cexp x Hout x Wout

和以前MobileNet V1版的深度可分离卷积对比一下,咱们一样使用112x112x64做为输入,取扩张参数(expansion_factor)为6,3x3的深度分离卷积的stride为1,这时V2版的计算量为:

(3 × 3 + 128 + 64) × (64 × 6) × 112 × 112 = 968,196,096
复制代码

能够发现,这个计算量貌似比以前的V1版大了不少,并且比普通的3x3卷积都大了很多,为何,缘由很简单,咱们设置了扩张系数为6,这样的话咱们计算了64 x 6 = 384个通道,比以前的64 -> 128学习到更多的参数,可是计算量却差很少。

批标准化-BatchNorm

批标准化能够说是现代神经网络中除了卷积操做以外必不可少的操做了,批标准化一般是放在卷积层或者全链接层以后,激活函数以前。对于上一个层中输出的y来讲,批标准化采起的操做为:

z = gamma * (y - mean) / sqrt(variance + epsilon) + beta
复制代码

首先将上一次输出的y进行标准化(减去其平均值并处以方差,这里的epsilon是0.001从而避免计算问题)。可是咱们又将标准化后的数于gamma相乘,加上beta。这两个参数是可学习的。

也就是对于每一个通道来讲,咱们须要的参数为4个,也就是对于C个通道,批标准化须要学习C x 4个参数。

看来貌似须要计算的参数还很多,可是实际中咱们还能够对其进行优化,将批标准化和卷积或者全链接层合并起来,这样的话速度会进一步提高,这里暂时先不讨论。

总之,咱们在讨论模型计算量的时候,通常不讨论批标准化产生的计算量,由于咱们在inference的时候并不使用它。

其余层

除了上述的一些基本层以外(卷积,全链接,特殊卷积,批标准化),池化层也会产生一部分计算量,可是相比卷积层和全链接层池化层产生的也能够忽略不计了,并且在新型的神经网络的设计中,池化层能够经过卷积层进行代替,因此咱们通常来讲对这些层并不着重讨论。

下一步

这篇文章仅仅是讨论了一些模型计算量的问题,一个网络运行的快否,与不只与网络的计算量有关,网络的大小、网络参数精度的高低、中间变量的优化以及混合精度等等均可以做为提速的一部分,限于篇幅将在下一部分进行讨论。

文章来源于OLDPAN博客,欢迎来访:Oldpan博客

欢迎关注Oldpan博客公众号,持续酝酿深度学习质量文:

相关文章
相关标签/搜索