【附源码】TensorFlow动态图(Eager模式)的那些神坑

导读: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等)交流~

 AI 项目技术 & 商务合作:bd@zhuanzhi.ai, 或扫描上面二维码联系!

请关注专知公众号,获取人工智能的专业知识!

点击“阅读原文”,使用专知

展开全文
Top
微信扫码咨询专知VIP会员