本章将会大致介绍Tensor Flow和keras的底层逻辑以理解TensorFlow是如何工作的。并且如何使用tf.function来将你的代码从Eager模式切换至graph模式。
图模式
前面几个章节,我们运行TensorFlow均为eager模式。即TesnorFlow的操作被Python逐个被Python解释执行。尽管eager执行模型是有自己的执行优点,但是图(graph)模式提供了Python环境之外执行的可能并且拥有更好的性能。图执行模式意味着张量计算被解释为TensorFlow图的一部分,也被称为tf.Graph或直接简称为“图”。
图是包含有tf.Operation(表示计算操作)和tf.Tensor对象(表示为计算操作之间流动的数据)的集合。作为数据结构,图可以被存储、运行和重载(纵然没有原始的Python程序)。
图模式的一些优点
图模式提供了更多的灵活性,你可以在任意的环境(如移动设备、嵌入式设备或后端服务器)上执行TensorFlow图,哪怕没有Python解释器。
图更容易被编译器优化,方法如下:
- 自动得到常量节点的值。
- 将相互没有依赖的计算划分入不同的线程或者设备中。
- 通过代数合并等方法消除子表达式。
图的优化是由一整个优化系统来执行的。
简单来说,图模式计算更快,并行更高,可执行的环境更灵活。
图之初体验
不论是用tf.function函数还是注解,均可实现图的创建。tf.function将一个传统的Python函数转换为一个TensorFlow的Function对象,而这个TensorFlow的Function对象是就像Python中Function对象一样,是一个可调用的函数(感觉有点儿像C++的函数指针)。
def a_regular_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
a_function_that_uses_a_graph = tf.function(a_regular_function)
x1 = tf.constant([[1., 2.]])
y1 = tf.constant([[2.], [3.]])
b1 = tf.constant(4.0)
orig_value = a_regular_function(x1, y1, b1).numpy()
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1)
assert(orig_value == tf_function_value)
表面上看来,TensorFlow创建的Function对象与普通的函数没有什么区别。但是,内部上有很大的差异。通过一个API,一个Function对象封装了一个到多个的tf.Graph对象(多态性)。一个Function对象可以使你享受到图模型带来的所有好处,比如速度和部署灵活的特点。
tf.function的使用是有递归性的,意味着一旦使用tf.function来转换一个函数,那么这个函数调用的所有函数也会被转换为图模型。
def inner_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
@tf.function
def outer_function(x):
y = tf.constant([[2.], [3.]])
b = tf.constant(4.0)
return inner_function(x, y, b)
print(outer_function(tf.constant([[1.0, 2.0]])).numpy())
结果为:
[[12.]]
将Python函数转为图
通常情况下,你编写的Python程序不仅包含了TensorFlow内置的操作,还会用到Python的逻辑控制,例如(if-else操作,循环,break,return,continue等)。尽管图模型很容易捕获TensorFlow的内置操作,对于Python自带的逻辑操作还需要额外的转换才能成为一个图。tf.function使用AutoGraph库来将Python代码转换为图模型。
def simple_relu(x):
if tf.greater(x, 0):
return x
else:
return 0
tf_simple_relu = tf.function(simple_relu)
print("First branch, with graph:", tf.simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf.simple_relu(tf.constant(-1)).numpy())
多态性
一个tf.Graph对象特指有特定输入的函数(例如特定的dtype或特定的类)。每次你调用Function时,TensorFlow会检查内存中是否已经存在可以处理输入参数的tf.Graph对象。若是存在,则可以直接调用;若是不存在,则会根据输入参数的类型来创建新的tf.Graph实例。tf.Graph对象的特定输入被称为“输入签名”或“签名”。
tf.Function会将已经创建的tf.Graph对象存储为ConcreteFunction,一个ConcreteFunction就是一个tf.Graph的包装器。这种多态性的存在,使得tf.Graph在表达上更加通用,优化起来也更加方便。
tf.function的使用
目前为止,已经演示了如何使用tf.function(注解或调用)来将Python函数转换为图。实际上,让tf.function起到效果还需要花点儿功夫。
Graph execution和eager execution
tf.Function代码可以以eager模式或graph模式被执行。默认情况下,tf.Function以graph模式被执行。
为了验证Function的图执行结果和Python的执行结果(即以eager模式执行)相同,可以通过设置tf.config.run_functions_eagerly(True)来强制代码以eager模式执行,这个设置将会关闭图的创建和运行的功能。但是,eager模式和graph模式终究有所区别。纯粹的Python计算在图创建后的首次运行中被执行但不进行捕获,这就是说再次调用的时候,纯粹的Python计算是不会被执行的。
# 若设置为False,则“Calculating MSE”会打印三次。若设置为True,则“Calculating MSE”只会打印一次。
tf.config.run_functions_eagerly(False)
@tf.function
def get_MSE(y_true, y_pred):
print("Calculating MSE")
sq_diff = tf.pow(y_true - y_pred, 2)
return tf.reduce_mean(sq_diff)
x = tf.random.uniform([5], maxval=10, dtype=tf.float32)
y = tf.random.uniform([5], maxval=10, dtype=tf.float32)
get_MSE(x, y)
get_MSE(x, y)
get_MSE(x, y)
惰性计算
Graph模式只会执行产生可观测结果的必要操作,包括:
- 函数的返回值
- 副作用语句:
- 输入输出语句,例如tf.print
- 测试语句,例如tf.debugging中的assert
- tf.Variable的赋值语句
这个行为被称为惰性计算,有别于eager模型下逐行执行(不论是否必要)的计算行为。特别注意,运行时的错误处理不认为是一个可观测的必要操作,若是一个操作被跳过了,那它不会产生任何运行时错误。
# 当设置为True时,会打印出错误结果,即执行了except下的print操作;当设置为False时,直接在tf.matmul上出现了抛出了异常然后中止执行,
tf.config.run_functions_eagerly(False)
@tf.function
def unused_return_graph(x):
tf.matmul(x, tf.constant([0.0, 1.0]))
return x
try:
print(unused_return_graph(tf.constant([0.0, 1.0])))
except Exception as e:
print("===========================")
print(f"{type(e).__name__}: {e}")
当tf.config.run_functions_eagerly设置为False时,执行结果为:
===========================
ValueError: in user code:
File "D:\PycharmProjects\pythonProject1\tensor_slicing.py", line 9, in unused_return_graph *
tf.matmul(x, tf.constant([0.0, 1.0]))
ValueError: Shape must be rank 2 but is rank 1 for '{{node MatMul}} = MatMul[T=DT_FLOAT, transpose_a=false, transpose_b=false](x, Const)' with input shapes: [2], [2].
当tf.config.run_functions_eagerly设置为True时,执行结果为:
===========================
InvalidArgumentError: In[0] and In[1] ndims must be == 2: 1 [Op:MatMul]
这和预想的差距太大了。原理的理解上应该有所出入,需要重新学习。
网友评论