因为python中的函数仅仅会在跟踪执行函数以创建静态图的阶段使用,普通python函数无法嵌入到静态图中,所以在计算图构建好之后再次调用的时候,这些python函数并没有被计算,使用python函数会导致被装饰器装饰前(eager执行)和被装饰器修饰后(静态图执行)的输出不一致。
例如: 被@tf.function修饰过的函数利用np生成随机数每一次都是一样的,而使用tf就是不一样的。
import tensorflow as tf import numpy as np # 在tf.function中用input_signature限定输入张量的签名类型:shape和dtype @tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.float32)]) def np_random(): a = np.random.randn(3, 3) tf.print(a) @tf.function def tf_random(): b = tf.random.normal((3, 3)) tf.print(b) # 被修饰过的np随机数每一次都是一样的 np_random() np_random() # 被修饰过的tf随机数每一次不一样 tf_random() tf_random()输出:
array([[-0.15504732, 1.55624863, 0.05300533], [ 0.31764741, -0.74893782, -1.09177159], [ 0.99932818, 0.45183048, 0.52071062]]) array([[-0.15504732, 1.55624863, 0.05300533], [ 0.31764741, -0.74893782, -1.09177159], [ 0.99932818, 0.45183048, 0.52071062]]) [[-0.477426946 0.0352273062 1.25906813] [-1.60776 -0.919097424 0.199720368] [-0.819047332 -0.321983 -1.48350918]] [[0.992117703 -0.778428 0.158320323] [1.04978251 -0.56633848 -0.407015771] [1.45319021 0.473201066 0.672261238]] 避免在@tf.function修饰的函数内部定义tf.Variable如果函数内部定义了tf.Variable,那么在eager执行的时候,这种创建变量的行为在每次函数调用的时候都会发生,但是在静态图执行的时候,这种创建变量的行为只会发生在第一步跟踪python代码逻辑创建计算图时。这会导致修饰前后输出结果不一致
如: 在函数内部定义tf.Variable然后报错
# 在修饰外部创建 x = tf.Variable(1.0, dtype=tf.float32) @tf.function def out(): x.assign_add(1.0) tf.print(x) out() out() @tf.function def inside(): a = tf.Variable(1.0, dtype=tf.float32) a.assign_add(1.0) tf.print(a) # 报错 # inside() # inside() 被@tf.function修饰的函数不可修改该函数外部的python列表或者字典等结构类型变量静态计算图是被编译成c++代码在tensorflow内核中执行的,python中的列表和字典等数据结构变量时无法嵌入到计算图中的,他们仅仅能够在创建计算图的时候被读取,在执行计算图的时候无法修改Python中的列表和字典这样的数据结构变量的"""
lyst = [] @tf.function def append_list(x): lyst.append(x) tf.print(lyst) append_list(tf.constant(4.3)) append_list(tf.constant(2.3)) print(lyst)输出:
[<tf.Tensor 'x:0' shape=() dtype=float32>] [4.3] [2.3]工作机制:
创建计算图执行计算图第一次输入两个参数都为str的数据
@tf.function(autograph=True) def add(a, b): for i in tf.range(4): tf.print(i) c = a + b print('finish') return c # 第一次输入: add(tf.constant('hello'), tf.constant('world'))输出:
finish 0 1 2 3当再次使用相同的输入参数类型调用这个被@tf.function装饰的函数的时,只会发生一件事情,那就是执行上面步骤的第二步,执行计算图。所以没有看见打印’finish‘的结果
add(tf.constant('td'), tf.constant('new_world'))输出:
0 1 2 3当使用不同的类型参数作为输入的时候,会重新创建一个计算图,由于参数的类型发生了变化,已经创建的计算图不能再次使用
add(tf.constant(3.4), tf.constant(4.4))输出:
finish 0 1 2 3当输入的不是tensor类型的时候,每一次都会重新创建计算图,所以建议调用@tf.function的时候传入tensor类型的数据。例如
add('td', 'new_world')输出:
finish 0 1 2 3