用tensorflow计算模型复杂度(浮点计算量,FLOPs)

1.应用模型说明

首先明确模型的计算量一般是衡量冻结模型(.pd)的,.ckpt在权重和偏置按照高斯分布初始化时一般计算量要大于冻结模型,所以我们要首生成模型的冻结文件(.pd)。

2.计算复杂度衡量标准

FLOPS:注意全大写,是floating point operations per second的缩写,意指每秒浮点运算次数,理解为计算速度。是一个衡量硬件性能的指标。
FLOPs:注意s小写,是floating point operations的缩写(s表复数),意指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。

MACCs:是multiply-accumulate operations),也叫MAdds,意指乘-加操作(点积运算),理解为计算量,也叫MAdds, 大约是 FLOPs 的一半。

1TFLOPs=1e3 GFLOPs=1e6 MFLOPs=1e9 BFLOPs=1e12

3.计算原理

3.1.1 全连接层

全连接层的计算

y = matmul(x, W) + b

权重 WW 是一个 I×JI×J 矩阵,输入 xx 是 II 维实值向量,bb 是 JJ 维偏置。输出 yy 也是 JJ 维实值向量。FC 层的 MACCs 也不难计算。

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

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

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

举例:

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

3.1.2 激活函数

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

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

y = max(x, 0)

相比之下 Sigmoid 就复杂很多。

y = 1/(1+exp(-x))

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

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

3.1.3 卷积层

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

K×K×Cin×Hout×Wout×CoutK×K×Cin×Hout×Wout×Cout

解释一下:

  • 输出的 feature map 里每个通道上有 Hout×WoutHout×Wout 个元素,
  • 权重以 K×KK×K 大小的窗口,在所有的 CinCin 个通道上做点积,
  • 共有 CoutCout 个卷积核,上述操作重复了 CoutCout 次

同样,这里也忽略了偏置和激活函数。不应该忽略的是 stride(步长)、dilation factors(漏孔/膨胀卷积)、padding(填充),这就是为什么直接从输出尺寸 Hout×WoutHout×Wout 开始算的原因——都已经考虑在内了。

举例:

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

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

接近十亿的乘-加操作。

3.1.4 Batch Normalization

计算公式:

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

 

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

每个通道上都存在一组 mean 、beta 、gamma 、variance ,CC 个通道就有 C×4C×4 个可学习的参数。而且 BN 是作用在每一个元素上的,这样看来,造成的 FLOPS 应该不少。

但有趣的是,在 BN 直接连接卷积层的情况下,即 Conv-BN-ReLU 时,通过一组推导,可以将 BN 的计算整合到卷积层当中(注意这是 inference 的情况,跟训练阶段差别很大),从而消去的 BN 层造成的 FLOPS。如果是 Conv-ReLU-BN 的结构这一套就行不通了。

( BN 层的计算结合到 Conv 层中去,BN 层的 FLOPS 消失了,Conv 层需要乘一个常系数)

即从结果上来说,在 inference 时模型中的 BN 层实际被消去了。

3.1.5 其他层

像 Pooling 层虽然确实很关键,但没有用到点积运算,所以 MACCs 不能很好地衡量这部分计算消耗。如果用 FLOPS,可以取 feature map 的尺寸然后乘一个常系数。

如 maxpooling 层,stride=2filter_sz=2(即输出保持相同尺寸),112 x 112 x 128 的feature map,FLOPS 就是 112 x 112 x 128 = 1,605,632 。相对卷积层和全连接层的运算,这个计算量比较小,所以也可以忽略不计。

RNN 这里不做讨论。简单来说,以 LSTM 为例,计算主要是两个大的矩阵乘法,sigmoid,tanh 和一些元素级的操作。可以看成两个全连接层的运算,所以 MACCs 主要取决于输入、输出和隐状态向量的尺寸。点积运算还是占了大头。

4.tensorflow实现:

#coding=utf-8
'''
模型复杂度测试
替换模型位置即可。
'''
from tensorflow.python.framework import graph_util
import tensorflow as tf
from tensorflow.contrib.layers import flatten
def stats_graph(graph):
    flops = tf.profiler.profile(graph, options=tf.profiler.ProfileOptionBuilder.float_operation())
    params = tf.profiler.profile(graph, options=tf.profiler.ProfileOptionBuilder.trainable_variables_parameter())
    print('GFLOPs: {};    Trainable params: {}'.format(flops.total_float_ops/1000000000.0, params.total_parameters))

def load_pb(pb):
    with tf.gfile.GFile(pb, "rb") as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
    with tf.Graph().as_default() as graph:
        tf.import_graph_def(graph_def, name='')
        return graph


with tf.Graph().as_default() as graph:
    #模型开始处××××××××××××××××××××××××××××
    # ***** (1) Create Graph *****
   
    #模型结束××××××××××××××××××××××××××××

    print('stats before freezing')
    stats_graph(graph)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        # ***** (2) freeze graph *****
        output_graph = graph_util.convert_variables_to_constants(sess, graph.as_graph_def(), ['output'])
        with tf.gfile.GFile('graph.pb', "wb") as f:
            f.write(output_graph.SerializeToString())
# ***** (3) Load frozen graph *****
with tf.Graph().as_default() as graph:
    graph = load_pb('./graph.pb')
    print('stats after freezing')
    stats_graph(graph)

结果:

/home/wds/anaconda3/envs/py27/bin/python /home/wds/project/CNN-test/TSIGN/FLOPs_Compute.py
stats before freezing
Parsing Inputs...

=========================Options=============================


======================End of Report==========================
MFLOPs: 1.438484;    Trainable params: 61666

=========================Options=============================

==================Model Analysis Report======================
node name | # parameters
_TFProfRoot (--/0 params)

======================End of Report==========================
MFLOPs: 1.198208;    Trainable params: 0

Process finished with exit code 0

注意:

1.计算时需替换模型实现部分:

2.placeholder 不要出现None, 要给一个具体值不然结果计算不对。

参考:

https://blog.csdn.net/leayc/article/details/81001801

 

 

你可能感兴趣的:(python)