使用autograph需要注意的地方
- 使用tf自带的函数,而不是原始的Python函数。
因为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]
autograph的工作机制
工作机制:
- 创建计算图
- 执行计算图
第一次输入两个参数都为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