【附源碼】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都被忽略了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章