导读:TensorFlow的动态图(Eager模式)为TensorFlow提供了Pythonic的API,让开发者可以像使用PyTorch一样使用TensorFlow。然而从静态图迁移到动态图有许多注意事项,其中一些事项导致的后果可能无法被开发者轻易感知(例如参数丢失等)。本文介绍TensorFlow动态图(Eager模式)使用中的那些坑。
tf.layers.dense等需要被替换为tf.layers.Dense
tf.layers.dense等API可以帮助开发者快速定义一个全连接层、卷积层等,在静态图中,如果要复用一层网络,只需要在使用tf.layers.dense时指定相同的名称即可(还需要设置reuse之类的模式)。然而在动态图模式下,该方法在每次被调用时都会重新创建变量。
因此,我们需要使用另一套以大写字母开头的API,如tf.layers.Dense和tf.layers.Conv2d。执行这些方法会生成一个层,而并会执行前向传播。生成的层可以被当成一个方法使用,执行生成的层等于执行一次前向传播。多次执行同一个生成的层,等于复用该层。
下面的代码希望定义一个全连接层(my_dense)并对同一个输入(x)执行两次,希望两次传播能够得到相同的结果。上面错误的方法会得到两种不同的结果,下面正确地方法可以得到两个相同的结果:
# coding=utf-8
import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
x = tf.get_variable("x", shape=[2, 10], initializer=tf.truncated_normal_initializer)
# wrong way
output0 = tf.layers.dense(x, 3, name="my_dense")
output1 = tf.layers.dense(x, 3, name="my_dense")
print("two outputs are different:\n{}\n{}".format(output0.numpy(), output1.numpy()))
# correct way
layer = tf.layers.Dense(3, name="my_dense")
output0 = layer(x)
output1 = layer(x)
print("two outputs are same:\n{}\n{}".format(output0.numpy(), output1.numpy()))
输出:
two outputs are different:
[[-0.5627856 0.34903422 -0.03416935]
[-0.19647026 0.10851201 1.9948754 ]]
[[ 0.33768964 -0.25924882 1.3221115 ]
[-0.5716297 0.49181187 1.0023197 ]]
two outputs are same:
[[-1.5864964 -0.2829675 1.3516748 ]
[-1.9249387 0.09994069 -1.0029143 ]]
[[-1.5864964 -0.2829675 1.3516748 ]
[-1.9249387 0.09994069 -1.0029143 ]]
自定义模型需要用tf.keras.Model来维护
用习惯TensorFlow静态图的开发者可能还不大习惯用tf.keras.Model来维护模型(类似PyTorch),因为TensorFlow的静态图直接可以维护所有的变量,所以不需要tf.keras.Model等模型类来做变量的维护。而在动态图模式下,没有直接维护变量的机制,例如:
# coding=utf-8
import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
x = tf.get_variable("x", shape=[2, 10], initializer=tf.truncated_normal_initializer)
print(tf.global_variables())
会产生空的结果:
[]
在动态图中,变量需要通过tf.keras.Model等模型类来完成:
# coding=utf-8
import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
x = tf.get_variable("x", shape=[2, 10], initializer=tf.truncated_normal_initializer)
class MyModel(tf.keras.Model):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layer = tf.layers.Dense(3, activation=tf.nn.relu)
def call(self, inputs, training=None, mask=None):
return self.layer(x)
model = MyModel()
print("variables before execution is empty:")
print(model.variables)
output = model(x)
print("variables after execution:")
print(model.variables)
执行结果:
variables before execution is empty:
[]
variables after execution:
[<tf.Variable 'dense/kernel:0' shape=(10, 3) dtype=float32, numpy=
array([[ 0.39169478, -0.0829891 , -0.56730807],
[ 0.6777066 , 0.6458894 , 0.13732117],
[ 0.1364708 , -0.59459007, -0.24752942],
[ 0.64061725, -0.009094 , 0.28879136],
[ 0.23388344, -0.2294389 , -0.4679362 ],
[-0.2833714 , 0.48690748, 0.3674612 ],
[-0.5997251 , 0.42447817, -0.07094294],
[ 0.2881773 , -0.43044046, -0.27440923],
[ 0.3394152 , 0.5459987 , -0.557847 ],
[-0.3695452 , -0.62086284, -0.38887706]], dtype=float32)>, <tf.Variable 'dense/bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]
tf.keras.Model.variables方法可以直接获取模型所包含的所有变量。但要注意,在模型执行之前,该方法获得的变量列表为空。由于一些变量的定义依赖于输入(例如输入的维度等),在执行模型(知道输入)之前,tf.keras.Model也无法创建变量。
自定义模型需要用tf.keras.Model来维护
除了用tf.keras.Model.add_variable等方法手动添加的变量,tf.keras.Model会将其成员变量中的TensorFlow Variable认为是Model的变量,但如果我们将多个TensorFlow Variables放在一个list中,将list作为tf.keras.Model的成员变量,tf.keras.Model就会忽略这些变量。
在实际应用中,这回导致严重的错误。例如在模型保存时,这些变量会被忽略。在模型导入时,由于这些变量没有被保存,会继续沿用随机初始化的值。并且,整个过程中解释器并不会提示出现错误。
示例代码如下:
# coding=utf-8
import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
x = tf.get_variable("x", initializer=0.0)
class MyModel(tf.keras.Model):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# variables must be directly set as member variables of tf.keras.Model
self.a = tf.get_variable("a", initializer=1.0)
# variables in list will be ignored by tf.keras.Model.variables
self.bs = [
tf.get_variable("b0", initializer=2.0),
tf.get_variable("b1", initializer=3.0)
]
def call(self, inputs, training=None, mask=None):
return inputs + self.a + self.bs[0] + self.bs[1]
model = MyModel()
print(model(x))
print(model.variables)
执行结果:
tf.Tensor(6.0, shape=(), dtype=float32)
[<tf.Variable 'a:0' shape=() dtype=float32, numpy=1.0>]
可以看到list成员变量中的TensorFlow Variables都被忽略了。
-END-
专 · 知
人工智能领域26个主题知识资料全集获取与加入专知人工智能服务群: 欢迎微信扫一扫加入专知人工智能知识星球群,获取专业知识教程视频资料和与专家交流咨询!
请PC登录www.zhuanzhi.ai或者点击阅读原文,注册登录专知,获取更多AI知识资料!
请加专知小助手微信(扫一扫如下二维码添加),加入专知主题群(请备注主题类型:AI、NLP、CV、 KG等)交流~
请关注专知公众号,获取人工智能的专业知识!
点击“阅读原文”,使用专知