Tensorflow执行模式:Eager Execution动态图模式、Graph Execution图模式、@tf.function实现Graph Execution图模式、tf.Session

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


4.7 Tensorflow执行模式

4.7.1 Eager Execution与Graph Execution

4.7.1.1 Graph Execution(图模式)

  • 特点:
    • 预先定义计算图,运行时反复使用,不能改变
    • 速度更快,适合大规模部署,适合嵌入式平台

TensorFlow 的图执行模式是一个符号式的(基于计算图的)计算框架。简而言之,如果你需要进行一系列计算,则需要依次进行如下两步:

  • 1、建立一个 “计算图”,这个图描述了如何将输入数据通过一系列计算而得到输出;

  • 2、建立一个会话,并在会话中与计算图进行交互,即向计算图传入计算所需的数据,并从计算图中获取结果。

    • Session 用来给定 Graph 的输入,指定 Graph 中的结果获取方式, 并启动数据在 Graph 中的流动
    • 拥有并管理 Tensorflow 程序运行时的所有资源,资源包括:硬件(CPU,GPU),数据

使用计算图与会话进行基本运算,这里做一个最基本的运算示例。

import tensorflow.compat.v1 as tf
tf.disable_eager_execution()

# 1、定义了一个简单的“计算图”
a = tf.constant(1)
b = tf.constant(1)
# 等价于 c = tf.add(a, b),c是张量a和张量b通过 tf.add 这一操作(Operation)所形成的新张量
c = a + b  

# 2、# 实例化一个会话(Session)
# 通过会话的 run() 方法对计算图里的节点(张量)进行实际的计算
sess = tf.Session()     
c_ = sess.run(c)
print(c_)

注:为了使用图执行模式,需要使用 TensorFlow 1.X 的 API 进行操作,所以使用 import tensorflow.compat.v1 as tf 导入 TensorFlow,并通过 tf.disable_eager_execution() 禁用默认的即时执行模式。

4.7.1.2 Eager Execution(动态图模式)

eager 模式是在 TF 1.4 版本之后引入的,在 TF 2.0之后将会把 eager 模式变为默认执行模式。TensorFlow 2.0 中的 Eager Execution 是一种命令式编程环境,可立即评估操作,无需构建图:操作会返回具体的值,而不是构建以后再运行的计算图。

Eager Execution 的优点如下:

  • 1、快速调试即刻的运行错误并通过 Python 工具进行整合
  • 2、借助易于使用的 Python 控制流支持动态模型
  • 3、为自定义和高阶梯度提供强大支持
  • 4、适用于几乎所有可用的 TensorFlow 运算

TensorFlow 2.0引入的eager提高了代码的简洁性,而且更容易debug。但是对于性能来说,eager执行相比Graph模式会有一定的损失。毕竟原生的Graph模式是先构建好静态图,然后才真正执行。这对于 在分布式训练、性能优化和生产部署方面具有优势。但是好在,TensorFlow 2.0引入了tf.function和AutoGraph来缩小eager执行和Graph模式的性能差距,其核心是将一系列的Python语法转化为高性能的graph操作。

注:实际上,Eager Execution 在 1.x 的后期版本中也存在,但需要单独执行 tf.enable_eager_execution() 进行手动启用。

4.7.2 @tf.function实现Graph Execution 模式

@tf.function一般只用来修饰“包含了 数学公式运算/tf.GradientTape()中的前向传播反向传播等计算流程”的函数,无法用来修饰“包含 数据读取等非数学运算流程”的函数。

在 TensorFlow 2.0 中,推荐使用 @tf.function (而非 1.X 中的 tf.Session )实现 Graph Execution,从而将模型转换为易于部署且高性能的 TensorFlow 图模型。只需要将我们希望以 Graph Execution 模式运行的代码封装在一个函数内,并在函数前加上 @tf.function 即可。

上面例子代码,可以通过基于 tf.function 的代码等价去执行图模式:

import tensorflow as tf

@tf.function
def graph():
    a = tf.constant(1)  # 定义一个常量张量(Tensor)
    b = tf.constant(1)
    c = a + b
    return c

c_ = graph()
print(c_.numpy())
  • 并不是任何函数都可以被 @tf.function 修饰!@tf.function 使用静态编译将函数内的代码转换成计算图,因此对函数内可使用的语句有一定限制,且需要函数内的操作本身能够被构建为计算图。
    • 建议在函数内只使用TensorFlow 的原生操作,不要使用过于复杂的 Python 语句,函数参数只包括 TensorFlow 张量或 NumPy 数组,并最好是能够按照计算图的思想去构建函数。
import tensorflow as tf

@tf.function
def train_one_step(X, y):    
    with tf.GradientTape() as tape:
        y_pred = model(X)
        loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
        loss = tf.reduce_mean(loss)
        # 注意这里使用了TensorFlow内置的tf.print()
        # @tf.function不支持Python内置的print方法去当做计算节点
        tf.print("loss", loss)  
    grads = tape.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

if __name__ == '__main__':
    model = CNN()
    optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
    start_time = time.time()
    for batch_index in range(num_batches):
        X, y = data_loader.get_batch(batch_size)
        train_one_step(X, y)
    end_time = time.time()
    print(end_time - start_time)

4.7.2.2 @tf.function 机制原理

当被 @tf.function 修饰的函数第一次被调用的时候,进行以下操作:

  • 1、在 Eager Execution 模式关闭的环境下,函数内的代码依次运行。也就是说,每个 tf. 方法都只是定义了计算节点,而并没有进行任何实质的计算。这与 TensorFlow 1.X 的 Graph Execution 是一致的;

  • 2、使用 AutoGraph 将函数中的 Python 控制流语句转换成 TensorFlow 计算图中的对应节点(比如说 while 和 for 语句转换为 tf.while , if 语句转换为 tf.cond 等等;

  • 3、基于上面的两步,建立函数内代码的计算图表示;然后运行一次这个计算图;

    • 基于函数的名字和输入的函数参数的类型生成一个哈希值,并将建立的计算图缓存到一个哈希表中。
  • 如果在被 @tf.function 修饰的函数之后再次被调用的时候,根据函数名和输入的函数参数的类型计算哈希值,检查哈希表中是否已经有了对应计算图的缓存。如果是,则直接使用已缓存的计算图,否则重新按上述步骤建立计算图。

1、使用下面例子体现tf.function整个计算步骤:

import tensorflow as tf
import numpy as np

@tf.function
def f(x):
      # 注意这里是print,不是tf.print
    print("The function is running in Python")
    tf.print(x)

# 运行过程
a = tf.constant(1, dtype=tf.int32)
f(a)
b = tf.constant(2, dtype=tf.int32)
f(b)
b_array = np.array(2, dtype=np.int32)
f(b_array)
c = tf.constant(0.1, dtype=tf.float32)
f(c)
d = tf.constant(0.2, dtype=tf.float32)
f(d)

# 对于Python的类型
f(1)
f(2)
f(1)

上述程序的计算结果是?答案是:

# Tensor类型
The function is running in Python
1
2
2
The function is running in Python
0.1
0.2

# Python类型
The function is running in Python
1
The function is running in Python
2
1

当计算 f(a) 时,由于是第一次调用该函数,TensorFlow 进行了以下操作:

  • 1、将函数内的代码依次运行了一遍;

  • 2、构建了计算图,然后运行了一次该计算图(因此输出了 1)。这里 tf.print(x) 可以作为计算图的节点,但 Python 内置的 print 则不能被转换成计算图的节点。

  • 3、将该计算图缓存到了一个哈希表中(如果之后再有类型为 tf.int32 ,shape 为空的张量输入,则重复使用已构建的计算图)。

接下来第二次计算之后

  • 一、计算 f(b) 时,由于 b 的类型与 a 相同,所以 TensorFlow 重复使用了之前已构建的计算图并运行(因此输出了 2)。这里由于并没有真正地逐行运行函数中的代码,所以函数第一行的文本输出代码没有运行。计算 f(b_array) 时,TensorFlow 自动将 numpy 的数据结构转换成了 TensorFlow 中的张量,因此依然能够复用之前已构建的计算图。

  • 二、计算 f(c) 时,虽然张量 c 的 shape 和 a 、 b 均相同,但类型为 tf.float32 ,因此 TensorFlow 重新运行了函数内代码(从而再次输出了文本)并建立了一个输入为 tf.float32 类型的计算图。

  • 三、计算 f(d) 时,由于 d 和 c 的类型相同,所以 TensorFlow 复用了计算图,同理没有输出文本。

问题:如果打印字符串的print()替换成tf.print(),结果会是?

正常的按照顺序输出:

The function is running in Python
1
The function is running in Python
2
The function is running in Python
2
The function is running in Python
0.1
The function is running in Python
0.2
The function is running in Python
1
The function is running in Python
2
The function is running in Python
1

总结:之后的计算结果则显示出 @tf.function 对 Python 内置的整数和浮点数类型的处理方式。只有当值完全一致的时候, @tf.function 才会复用之前建立的计算图,而并不会自动将 Python 内置的整数或浮点数等转换成张量。一般而言,应当只在指定超参数等少数场合使用 Python 内置类型作为被 @tf.function 修饰的函数的参数。

4.7.3 使用传统的 tf.Session(了解)

TensorFlow 2.0 提供了 tf.compat.v1 模块以支持 TensorFlow 1.X 版本的 API。同时,只要在编写模型的时候稍加注意,Keras 的模型是可以同时兼容 Eager Execution 模式和 Graph Execution 模式的。

例如,之前的MLP 或 CNN 模型去训练MNIST数据的代码如下:


optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate)
num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
# 1、建立计算图
X_placeholder = tf.compat.v1.placeholder(name='X', shape=[None, 28, 28, 1], dtype=tf.float32)
y_placeholder = tf.compat.v1.placeholder(name='y', shape=[None], dtype=tf.int32)
y_pred = model(X_placeholder)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y_placeholder, y_pred=y_pred)
loss = tf.reduce_mean(loss)
train_op = optimizer.minimize(loss)
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()

# 2、建立Session,并运行图计算
with tf.compat.v1.Session() as sess:
  sess.run(tf.compat.v1.global_variables_initializer())
  for batch_index in range(num_batches):
    X, y = data_loader.get_batch(batch_size)
    # 使用Session.run()将数据送入计算图节点,进行训练以及计算损失函数
    _, loss_value = sess.run([train_op, loss], feed_dict={X_placeholder: X, y_placeholder: y})
    print("batch %d: loss %f" % (batch_index, loss_value))

    num_batches = int(data_loader.num_test_data // batch_size)
    for batch_index in range(num_batches):
      start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
      y_pred = model.predict(data_loader.test_data[start_index: end_index])
# 运行预测结果
sess.run(sparse_categorical_accuracy.update(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred))
      print("test accuracy: %f" % sess.run(sparse_categorical_accuracy.result()))

4.7.4 总结

  • tf的图执行模式原理与会话模式的区别
  • tf.function的作用和原理
发布了389 篇原创文章 · 获赞 129 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/zimiao552147572/article/details/105034481