深度学习模型性能影响分析

模型参数,内存访问

Posted by BY J on July 12, 2019

http://machinethink.net/blog/how-fast-is-my-model/ 在移动端部署深度学习模型时,准确率并不是唯一需要衡量的因素,还需要考虑以下4个方面:

  1. 模型占用的app内存空间大小-一个模型可能给app增加100MB。
  2. 运行时占用的内存大小-iPhone和iPad的GPU会占用设备的全部RAM,但是只有几GB。
  3. 模型运行的速度-特别是当运行实时视频或者大的图像时。
  4. 耗电情况 测量模型速度的最好方式就是运行多次,取平均值。 案例研究: 作者的一个客户用MobileNetV2的层代替了MobileNetV1中的层。V2比V1用了更少的计算量,理论上V2的速度会快很多,但是V2却慢多了。 下面是通过数学分析原因 1.计算: 衡量模型的计算量,可以用FLOPS(每秒计算的浮点数),另外一个是MACCS(multiply-accumulate operations)也叫做MADDs. 说明: 衡量模型的计算量可以让你大概了解你的模型的计算消耗,但是其他因素比如内存带宽也非常重要。

    从头到尾其实就是点积运算

    神经网络模型的大部分计算其实是点积运算,比如:

    y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n-1]*x[n-1]
    

    w, x是向量,y是标量。 对于神经网络中的卷积层或者全连接层而言,w就是学习权重,x则是层的输入。 y是层的输入。通常1个层会有多个输出。 我们将w[0]*x[0] + …视为一个MACC(乘法与加法)。

上述方程为n MACCs. 上述点积运算执行了2n -1 FLOPs,即n个乘法与n-1加法。 所以一个MACC大概是两倍的FLOPS。 下面看如何计算不同层的MACCs.

全连接层

全连接层中,所有的输入与输出相连。对于输入为I,输出为J,权重W存储在I * J的矩阵中。全连接执行的计算为

y = matmul(x, W) + b

x 为I的向量。 点积运算是I中矩阵每行与J中的列相乘,所以共有I* J个MACCs.b被近似掉。 说明:以上假设batchsize为1.

激活函数

例如ReLu,sigmoid.由于他们不是点积运算,所以只能用FLOP衡量,但是他们的运算量也只是占用了极少的一部分,所以可以忽略掉。

卷积层

输入FeatureMap: H * W * C Kernel Size: K MACCs数量:

K × K × Cin × Hout × Wout × Cout

忽略了激活和bias。stride不可忽略。 对于3 * 3,128个filter,64个channel 的112 * 112的feature map:

3 × 3 × 64 × 112 × 112 × 128 = 924,844,032

此例:stride = 1 padding = “same”

Depthwise-separable convolution

这些是MobileNet的基础,稍微大点的模型是Xception。 首先是depthwise卷积,其总的MACCs数量为:

K × K × C × Hout × Wout

例如:一个33的depthwise卷积,作用于64通道112112的feature map:MACCs:

3 × 3 × 64 × 112 × 112 = 7,225,344

说明:卷积部分,filter的数量与输入通道的数量一样,每一个filter仅作用在一个通道上。所以与普通卷积相比,没有128. 说明:论文中还有“depthwise channel multiplier”,如果此参数大于1,则每一个输入通道,会有D个输出。即每个输入通道,会有D个filter. “Separable”部分,此部分为1*1的正常卷积,也叫做“pointwise”卷积。 MACCs:

Cin × Hout × Wout × Cout

例子:输入:112 * 112 * 64 输出:112 * 112 * 128 总体MACCs:

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.4倍。

(K × K × Cin × Hout × Wout) + (Cin × Hout × Wout × Cout)

简化为:

Cin × Hout × Wout × (K × K + Cout)

MobileNet V2应用了“expansion block”包含了下列三层:

  1. 1*1 的卷积给feature map增加了更多的channel(叫做”expansion”层)
  2. 3 * 3的depthwise卷积过滤数据
  3. 1 * 1的卷积减少通道数量(projection layer,act as bottleneck convolution)

上述expansion block的MACCs的计算公式:

Cexp = (Cin × expansion_factor)

expansion_layer = Cin × Hin × Win × Cexp

depthwise_layer = K × K × Cexp × Hout × Wout

projection_layer = Cexp × Hout × Wout × Cout

整合:

Cin × Hin × Win × Cexp + (K × K + Cout) × Cexp × Hout × Wout

stride =1 简化为:

(K × K + Cout + Cin) × Cexp × Hout × Wout

对比V1:输入112 * 112 * 64, expansion factor:6, stride = 1的3 * 3 depthwise convolution, 输出通道:128,总体MACCs:

(3 × 3 + 128 + 64) × (64 × 6) × 112 × 112 = 968,196,096

这比普通的卷积计算量还要多,但是在block内部,我们实际计算的64 * 6 = 384个channnel。这比普通卷积要多的多。

Batch normalization

Batch normalization对输出层每个元素应用下列公式:

z = gamma * (y - mean) / sqrt(variance + epsilon) + beta

每个通道均有自己的gamma, beta, mean和variance。 每个卷积层需要学习C * 4个参数。 通常batch normalization被应用于ReLU之前,这样,我们可以通过数学运算使batch norm layer消失。 convolution与batch均为线性变换,

z = gamma * ((x[0]*w[0] + x[1]*w[1] + ... + x[n-1]*w[n-1] + b) - mean) 
      / sqrt(variance + epsilon) + beta

整合则有:

w_new[i] = w[i]       * gamma / sqrt(variance + epsilon)
b_new    = (b - mean) * gamma / sqrt(variance + epsilon) + beta

则,新的卷积运算为:

z = x[0]*w_new[0] + x[1]*w_new[1] + ... + x[n-1]*w_new[n-1] + b_new

所以我们可以忽视batch norm layer的影响。 说明:仅convolution, batch norm, ReLU顺序时有效。

Memory

内存带宽其实比计算更重要。 在当前并行计算机架构下,单次内存获取比单词计算要慢的多-大概在100倍或者更多。 每一层,设备需要:

  1. 从主机内存中读取输入向量
  2. 计算点积-从主机内存中读取权重
  3. 将新向量或者特征写回主内存。 涉及的内存访问很多,所以会严重影响速度。

    权重的内存占用

    一般来讲,模型的权重越少,模型运行越快。

    Feature map 和中间结果

    对于224 * 224 * 3的输入,全部读取需要150528次内存访问。 如果卷积核为3 * 3,则需要对每个元素读取9次得到输出。 由于有Cout个卷积核,则每个输入元素需要读取K * K * Cout次。 如果这个卷积层的stride为2, 32个filter,则会写入112 * 112 * 32个值,内存访问次数401408. 一般,每一层的内存访问次数为:

    input = Hin × Win × Cin × K × K × Cout
    output = Hout × Wout × Cout
    weights = K × K × Cin × Cout + Cout
    

    这里假设权重只读取一次。 假设256的input, 512的输出。

    input = 28 × 28 × 256 × 3 × 3 × 512 = 924,844,032
    output = 28 × 28 × 512 = 401,408
    weights = 3 × 3 × 256 × 512 + 512 = 1,180,160
    total = 926,425,600
    

    对depthwise-separable,3 * 3的depthwise 1* 1的pointwise,则 ``` depthwise layer input = 28 × 28 × 256 × 3 × 3 = 1,806,336 output = 28 × 28 × 256 = 200,704 weights = 3 × 3 × 256 + 256 = 2,560 total = 2,009,600

pointwise layer input = 28 × 28 × 256 × 1 × 1 × 512 = 102,760,448 output = 28 × 28 × 512 = 401,408 weights = 1 × 1 × 256 × 512 + 512 = 131,584 total = 103,293,440

total of both layers = 105,303,040

可以看到计算量少8.4倍,访问量大概也少8.8倍。
# Fusion
对于ReLu,应用于28 * 28 * 512,有

input = 28 × 28 × 512 = 401,408 output = 28 × 28 × 512 = 401,408 total = 802,816

此部分可以与卷积层融合,以减少内存访问。
## MobileNet V2 versus V1
之前我们提到Mobilenet V2(with depth multiplier 1.4)跟V1运行的速度差不多,虽然它的参数更少。
此具体的实际使用案例为:用MobileNet作为特征提取器,
V1截止到conv_pw_11(共23层),v2则到expanded_conv_12(共47层)。
输入图像为126 * 224,是720 * 1280摄像机的缩小版。
参数量

MobileNet V1 parameters (multiplier = 1.0): 1.6M MobileNet V2 parameters (multiplier = 1.0): 0.5M MobileNet V2 parameters (multiplier = 1.4): 1.0M

MACCs数量

MobileNet V1 MACCs (multiplier = 1.0): 255M MobileNet V2 MACCs (multiplier = 1.0): 111M MobileNet V2 MACCs (multiplier = 1.4): 214M

以上两者差不多
内存访问对比:

MobileNet V1 memory accesses (multiplier = 1.0): 283M MobileNet V2 memory accesses (multiplier = 1.0): 159M MobileNet V2 memory accesses (multiplier = 1.4): 286M

multiplier = 1.4的版本跟v1比,内存访问几乎没区别。

VGG16也作为特征提取器

VGG16 parameters: 15M VGG16 MACCs: 8380M VGG16 memory accesses: 8402M ``` 虽然VGG层数更少,但是它执行操作的feature map很大并且它的内存访问相当大。 需要注意的是,以上比较只有在两个模型在相同软件架构下,相同的硬件上进行比较才有意义。