(3)tesorflow 计算模型复杂度

简介: 目录1. 计算模型复杂度的衡量2 . 典型层的复杂性计算原理2.1 全连接层的复杂性计算2.2 卷积层的复杂性计算3. 全连接Tensorflow实现

1. 计算模型复杂度的衡量


FLOPS(即“每秒浮点运算次数”,“每秒峰值速度”),是“每秒所执行的浮点运算次数”(floating-point operations per second)的缩写。它常被用来估算电脑的执行效能,尤其是在使用到大量浮点运算的科学计算领域中。正因为FLOPS字尾的那个S,代表秒,而不是复数,所以不能省略掉。

FLOPs :s为小写,指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。(模型) 在论文中常用GFLOPs(1 G F L O P s = 1 0 9 F L O P s 1 GFLOPs = 10^9 FLOPs1GFLOPs=10

9

FLOPs)

MADD、MACC(multiply-accumulate operations):意指先乘起来再加起来的运算次数。 也用来衡量算法/模型的复杂度。大约是 FLOPs 的一半。

简单举例:

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

上面的等式,有n次浮点乘法,n − 1 n-1n−1次浮点加法,所以浮点运算的次数为F L O P s = 2 n − 1 FLOPs = 2n-1FLOPs=2n−1。

先乘起来再加起来的浮点运算次数为n次,所以M A C C = n MACC=nMACC=n

2 . 典型层的复杂性计算原理

2.1 全连接层的复杂性计算

对于y = matmul(X, W) + b,我们假设X XX为的维度为[ m , p ] [m,p][m,p],w ww的维度为[ p , n ] [p,n][p,n],b bb为[ n ] [n][n],所以我们有公式可以得出这个矩阵有 F L O P s = m × n × ( 2 p − 1 ) + m × n FLOPs=m×n×(2p-1)+m×nFLOPs=m×n×(2p−1)+m×n次浮点运算,M A C C = m × n × p MACC = m×n×pMACC=m×n×p次乘加浮点运算。

举例。对于一个全连接层,输入向量inputs为100维,输出向量outputs为200维,所以浮点运算个数为F L O P s = 200 × ( 200 − 1 ) + 200 FLOPs=200×(200-1)+200FLOPs=200×(200−1)+200

2.2 卷积层的复杂性计算

对于一个卷积层,我们输入为[ b a t c h , H , W , C ] [batch,H,W,C][batch,H,W,C],有G GG个[ f , f ] [f,f][f,f]卷积核,padding = SAME,stride=1。所以M A C C = b a t c h × H × W × f × f × C × G MACC=batch×H×W×f×f×C×GMACC=batch×H×W×f×f×C×G

3. 全连接Tensorflow实现

我们定义一个函数对Tensorflow的浮点计算量(FLOPs)和参数量进行统计。

import tensorflow as tf
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('FLOPs: {};    Trainable params: {}'.format(flops.total_float_ops, params.total_parameters))

我们定义一个矩阵A与矩阵B相乘的运算,去分析浮点计算量和参数量y = matmul(X, W) + b,假设X XX为的维度为[ 25 , 16 ] [25,16][25,16],w ww的维度为[ 16 , 9 ] [16,9][16,9],b bb为[ 9 ] [9][9],所以

F L O P s = 25 × 9 × ( 2 × 16 − 1 ) + 25 × 9 = 7200 FLOPs=25×9×(2×16-1)+25×9=7200

FLOPs=25×9×(2×16−1)+25×9=7200

p r a r m e t e r s = 25 × 16 + 16 × 9 + 9 = 553 prarmeters=25×16+16×9+9=553

prarmeters=25×16+16×9+9=553

F L O P s ( i n T F s t y l e s ) = 25 × 9 × ( 2 × 16 ) + 25 × 9 = 7425 FLOPs(inTFstyles)=25×9×(2×16)+25×9=7425

FLOPs(inTFstyles)=25×9×(2×16)+25×9=7425

利用正态分布对变量进行初始化的程序

with tf.Graph().as_default() as graph:
    X = tf.get_variable(initializer=tf.random_normal_initializer(dtype=tf.float32), shape=(25, 16), name='X')
    W = tf.get_variable(initializer=tf.random_normal_initializer(dtype=tf.float32), shape=(16, 9), name='W')
    b = tf.get_variable(initializer=tf.random_normal_initializer(dtype=tf.float32),shape=(9,),name="b")
    C = tf.matmul(X, W, name='ouput') 
    stats_graph(graph)

结果

FLOPs: 8531;    Trainable params: 553

考虑过到对变量初始化的过程也需要进行浮点运算,所以真实的输出要比7425大。

利用常量初始化器对变量进行初始化的程序

with tf.Graph().as_default() as graph:
    X = tf.get_variable(initializer=tf.constant_initializer(value=1, dtype=tf.float32), shape=(25, 16), name='X')
    W = tf.get_variable(initializer=tf.zeros_initializer(dtype=tf.float32), shape=(16, 9), name='W')
    b = tf.get_variable(initializer=tf.zeros_initializer(dtype=tf.float32),shape=(9,),name="b")
    C = tf.matmul(X, W, name='ouput')  + b
    stats_graph(graph)

结果:

FLOPs: 7425;    Trainable params: 553
• 1

由此我们可知,由常量初始化器对变量进行初始化,不会消耗FLOP。

4. GraphDef

Tensorflow所运行的代码,或者说用python代码表达的计算,所描述的对象实际上就是一张计算图,包含了各个运算节点和用于计算的张量。而Graph_def是图Graph的序列表示。python所描述的这个graph,并不是在运行Tensorflow,启动一个Session后就保持不变的,因为Tensorflow在实际运行过程中,真实的计算是会被下放到多CPU,或者GPU、ARM等异构设备上进行高性能计算的,如果仅仅单纯地使用python肯定是无法有效地完成计算的。所以Tensorflow的实际计算过程是这样的:

Tensorflow先将python代码所描绘的图进行转换,转化成由许多NodeDef的Protocol Buffer(即序列化),在概念上NoteDef与(Python Graph中的)Operation相对应,再通过C/C++/CUDA运行Protocol Buffer所定义的图

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
d = a*b

上文 Python 图对应的 GraphDef

node { name: 'Placeholder' # 注释:这是一个叫做 'Placeholder' 的
       node op: 'Placeholder' 
     attr { key: 'dtype' value { type: DT_FLOAT } } 
     attr { key: 'shape' value { shape { unknown_rank: true } } }
  }
node { name: 'Placeholder_1' # 注释:这是一个叫做 'Placeholder_1' 的
      node op: 'Placeholder'
    attr { key: 'dtype' value { type: DT_FLOAT } } 
    attr { key: 'shape' value { shape { unknown_rank: true } } }
  }
node { name: 'mul' # 注释:一个 Mul(乘法)操作
     op: 'Mul' 
     input: 'Placeholder' # 使用上面的node(即Placeholder和Placeholder_1)
     input: 'Placeholder_1' # 作为这个Node的输入 
       attr { key: 'T' value { type: DT_FLOAT } }
  }

以上三个 NodeDef 定义了两个Placeholder和一个Multiply。Placeholder 通过 attr(attribute的缩写)来定义数据类型和 Tensor 的形状。Multiply通过 input 属性定义了两个placeholder作为其输入。无论是 Placeholder 还是 Multiply 都没有关于输出(output)的信息。其实 Tensorflow 中都是通过 Input 来定义 Node 之间的连接信息。


从上面我们可以看出,GraphDef定义的均是Operation。没有变量Variable,因为GraphDef中不保存任何 Variable 的信息,所以如果我们从 graph_def 来构建图并恢复训练的话,是不能成功的。但是在实际上 inference 中,通常就是使用 GraphDef。那么GraphDef中连Variable都没有,怎么存储weight呢?原来GraphDef 虽然不能保存 Variable,但可以保存 Constant 。通过 tf.constant 将 weight 直接存储在 NodeDef 里,tensorflow 1.3.0 版本也提供了一套叫做 freeze_graph 的工具来自动的将图中的 Variable 替换成 constant 存储在 GraphDef 里面,并将该图导出为 Proto。

5. Freeze graph

convert_variables_to_constants函数,会将计算图中的变量取值以常量的形式保存。在保存模型文件的时候,我们只是导出了GraphDef部分,GraphDef保存了从输入层到输出层的计算过程。在保存的时候,通过convert_variables_to_constants函数来指定保存的节点名称而不是张量的名称,“add:0”是张量的名称而"add"表示的是节点的名称。


程序实现

from tensorflow.python.framework import graph_util
import tensorflow as tf
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('FLOPs: {};    Trainable params: {}'.format(flops.total_float_ops, 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 *****
    X = tf.get_variable(initializer=tf.random_normal_initializer(dtype=tf.float32), shape=(25, 16), name='X')
    W = tf.get_variable(initializer=tf.random_normal_initializer(dtype=tf.float32), shape=(16, 9), name='W')
    b = tf.get_variable(initializer=tf.random_normal_initializer(dtype=tf.float32),shape=(9,),name="b")
    C = tf.matmul(X, W) + b
    print('stats before freezing')
    stats_graph(graph)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        # ***** (2) freeze graph *****
        #graph.as_graph_def(),导出当前计算图的GraphDef部分
        #将此图仅保存操作Operation,并将节点保存为常数
        output_graph = graph_util.convert_variables_to_constants(sess, graph.as_graph_def(), ['add'])
        #将计算图写入到模型文件中
        with tf.gfile.GFile('graph.pb', "wb") as f:
            f.write(output_graph.SerializeToString())
# ***** (3) Load frozen graph *****
graph = load_pb('./graph.pb')
print('stats after freezing')
stats_graph(graph)

结果

stats before freezing
FLOPs: 8531;    Trainable params: 553
stats after freezing
FLOPs: 7425;    Trainable params: 0
相关文章
|
4天前
|
机器学习/深度学习 存储 算法
如何评判算法好坏?复杂度深度解析
如何评判算法好坏?复杂度深度解析
31 0
|
4天前
|
机器学习/深度学习
复杂度练习
复杂度练习
|
4天前
线性回归前特征离散化可简化模型、增强稳定性、选有意义特征、降低过拟合、提升计算效率及捕捉非线性关系。
【5月更文挑战第2天】线性回归前特征离散化可简化模型、增强稳定性、选有意义特征、降低过拟合、提升计算效率及捕捉非线性关系。但过多离散特征可能增加复杂度,丢失信息,影响模型泛化和精度。需谨慎平衡离散化利弊。
17 0
|
4天前
|
机器学习/深度学习 存储 缓存
BurstAttention:可对非常长的序列进行高效的分布式注意力计算
研究人员探索了提高LLM注意力机制效率的策略,包括FlashAttention(利用SRAM加速)和RingAttention(分布式多设备处理)。新提出的BurstAttention结合两者,优化跨设备计算与通信,减少40%通信开销,使128K长度序列在8×A100 GPU上的训练速度翻倍。论文于3月发布,但实现未公开
31 3
|
4天前
|
人工智能 自然语言处理 算法
2024年,将出现更大、更优的大模型
【1月更文挑战第21天】2024年,将出现更大、更优的大模型
51 3
2024年,将出现更大、更优的大模型
|
6月前
|
缓存 分布式计算 并行计算
平摊复杂度
平摊复杂度(Amortized Complexity)是一种在计算复杂度时使用的技术,用于描述算法在多次运行中的平均性能。平摊复杂度能够将一次性计算的复杂度分摊到多次运行中,从而更准确地衡量算法在实际应用中的性能。
35 3
|
7月前
|
存储 算法
【算法的复杂度】
【算法的复杂度】
25 0
|
7月前
|
算法
算法中的复杂度
算法中的复杂度
29 0
|
8月前
|
存储 算法 数据库
算法:复杂度
算法:复杂度
50 0
|
机器学习/深度学习 编解码 算法
引入特征空间,显著降低计算量:双边局部注意力ViT性能媲美全局注意力
引入特征空间,显著降低计算量:双边局部注意力ViT性能媲美全局注意力