導讀: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都被忽略了。