當前位置:
首頁 > 科技 > 令人困惑的 TensorFlow!(II)

令人困惑的 TensorFlow!(II)


選自

Jacobbuckman


作者

Jacob Buckman


機器之心編譯

參與:高璇、王淑婷





六月底,機器之心發布了「令人困惑的 TensorFlow!」,講述了初上手 TensorFlow 時會遇到的麻煩。日前,作者更新博客,對該文寫了續篇,

主要講述了保存和載入 TensorFlow 模型以及上下文管理器的一些問題。




命名和域




命名變數和張量




正如我們在第一部分討論的,每次調用 tf.get_variable() 時,都需要為變數賦予一個新的唯一名稱。實際上,圖中的每個張量也需要一個唯一的名稱。可以通過張量、操作和變數的 .name 屬性訪問該名稱。絕大多數情況下,名稱會自動創建;例如,一個常量節點會以 Const 命名,當創建更多常量節點時,其名稱將是 Const_1,Const_2 等。還可以通過 name=的屬性設置節點名稱,列舉後綴仍會自動添加:




代碼:


import

 tensorflow 

as

 tf
a = tf.constant(

0.

)
b = tf.constant(

1.

)
c = tf.constant(

2.

, name=

"cool_const"

)
d = tf.constant(

3.

, name=

"cool_const"

)

print

 a.name, b.name, c.name, d.name



輸出:



Const:

0

 Const_1:

0

 cool_const:

0

 cool_const_1:

0




雖然節點命名並非必要,但在調試時非常有用。當 Tensorflow 代碼崩潰時,error trace 將指向一個特定的操作。如果有很多同類型的操作,那麼很難確定是哪一個出了問題。而通過明確命名每個節點,可以獲得信息詳細的 error trace,並更快地識別問題。




使用範圍




隨著圖形越來越複雜,手動命名所有內容變得愈加困難。Tensorflow 提供 tf.variable_scope 對象,它通過將圖形細分為更小的組塊,使圖形更易梳理。通過將一段圖形創建代碼封裝在 with tf.variable_scope(scope_name):語句中,創建的所有節點名稱都將自動以 scope_name 字元串作為前綴。此外,這些作用域堆棧,在另一個範圍內創建的作用域會簡單地將前綴鏈接在一起,用斜杠分隔。




代碼:



import

 tensorflow 

as

 tf
a = tf.constant(

0.

)
b = tf.constant(

1.

)

with

 tf.variable_scope(

"first_scope"

):
  c = a + b
  d = tf.constant(

2.

, name=

"cool_const"

)
  coef1 = tf.get_variable(

"coef"

, [], initializer=tf.constant_initializer(

2.

))
  

with

 tf.variable_scope(

"second_scope"

):
    e = coef1 * d
    coef2 = tf.get_variable(

"coef"

, [], initializer=tf.constant_initializer(

3.

))
    f = tf.constant(

1.

)
    g = coef2 * f

print

 a.name, b.name

print

 c.name, d.name

print

 e.name, f.name, g.name

print

 coef1.name

print

 coef2.name



輸出:



Const:

0

 Const_1:

0


first_scope/add:

0

 first_scope/cool_const:

0


first_scope/second_scope/mul:

0

 first_scope/second_scope/Const:

0

 first_scope/second_scope/mul_1:

0


first_scope/coef:

0


first_scope/second_scope/coef:

0




我們能夠使用代碼 coef 創建兩個名稱相同的變數。這是因為作用域可以將名稱轉換為 first_scope/coef:0 和 first_scope/second_scope/coef:0,它們是不同的。




保存和載入




訓練好的神經網路包括兩個基本組成部分:






  • 已經學習過某些任務優化的網路權重



  • 說明如何利用權重獲得結果的網路圖




Tensorflow 將這兩個組件分開,但很明顯它們需要緊密匹配。如果沒有圖結構進行說明,那權重也無用,而帶有隨機權重的圖也效果也不好。事實上,即使僅交換兩個權重矩陣也可能完全破壞模型。這通常會讓 Tensorflow 初學者感覺很挫敗。使用預先訓練好的模型作為神經網路的一個組成部分不失為加速訓練的好方法,但是也有可能搞砸一切。




保存模型




當只有單個模型時,Tensorflow 用於保存和載入的內置工具使用很方便:只需創建一個 tf.train.Saver()。類似於 tf.train.Optimizer,tf.train.Saver 本身並不是一個節點,而是在已有圖形上執行有用功能的更高級類別。你可能已經預料到 tf.train 的「有用功能」了,即保存和載入模型。




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
b = tf.get_variable(

"b"

, [])
init = tf.global_variables_initializer()

saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.save(sess, 

"./tftcp.model"

)



輸出




四個新文件:



checkpoint
tftcp.model.data

-00000

-of

-00001


tftcp.model.index
tftcp.model.meta



具體內容分析如下:




首先:當我們只保存一個模型時,為什麼會輸出四個文件?重建模型所需的信息被分散到它們當中。如果想複製或者備份模型,需要有四個文件(前綴為文件名)。下面簡述答案:






  • tftcp.model.data-00000-of-00001 包含模型權重(上述第一個要點)。它可能這裡最大的文件。



  • tftcp.model.meta 是模型的網路結構(上述第二個要點)。它包含重建圖形所需的所有信息。



  • tftcp.model.index 是連接前兩點的索引結構。用於在數據文件中找到對應節點的參數。



  • checkpoint 實際上不需要重建模型,但如果在整個訓練過程中保存了多個版本的模型,那它會跟蹤所有內容。




其次,我為什麼一定要為該示例創建 tf.Session 和 tf.global_variables_initializer 呢?




因為,如果要保存一個模型,我們需要保存相關的內容。計算存於圖中,但數值存於會話中。tf.train.Saver 可以通過指向圖表的全局指針訪問網路結構。但當我們保存變數的值(即網路權重)時,我們需要訪問 tf.Session 來確定這些值;這就是為什麼 sess 作為 save 函數的第一個參數傳入。此外,嘗試保存未初始化的變數會引發錯誤,因為嘗試訪問未初始化變數的值總是會引發錯誤。因此,我們需要一個會話和一個初始化程序(或等價的 tf.assign)。




載入模型




既然我們已經保存了模型,現在重新載入它。第一步是重新創建變數:我們希望變數的名稱、形狀和類型都與保存時一致。第二步是創建與之前一樣的 tf.train.Saver,並調用 restore 函數。




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
b = tf.get_variable(

"b"

, [])

saver = tf.train.Saver()
sess = tf.Session()
saver.restore(sess, 

"./tftcp.model"

)
sess.run([a,b])



輸出:



[

1.3106428

0.6413864

]



在運行之前,我們不需要初始化 a 或 b!這是因為 restore 運算將值從文件移動到會話的變數中。由於會話不再包含任何空值變數,因此不再需要初始化。(如果不小心,會適得其反:還原後運行 init 會使隨機初始化的值覆蓋載入的值。)




選擇變數




當一個 tf.train.Saver 程序初始化後,它會查看當前圖形並獲取變數列表;這是 saver「關心」的永久存儲的變數列表。我們可以用._var_list 屬性來檢查:




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
b = tf.get_variable(

"b"

, [])
saver = tf.train.Saver()
c = tf.get_variable(

"c"

, [])

print

 saver._var_list



輸出:



[<tf.Variable 

"a:0"

 shape=() dtype=float32_ref>, <tf.Variable 

"b:0"

 shape=() dtype=float32_ref>]

因為在創建 saver 時 c 還沒有出現,所以它並沒有成為函數的一部分。一般來說,你要在創建 saver 之前確保已經創建了所有的變數。




當然,在某些特定的情況下,可能只需保存變數的一個子集。當創建 var_list 以期望它跟蹤可用變數子集時,tf.train.Saver 允許傳遞 var_list。




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
b = tf.get_variable(

"b"

, [])
c = tf.get_variable(

"c"

, [])
saver = tf.train.Saver(var_list=[a,b])

print

 saver._var_list



輸出:



[<tf.Variable 

"a:0"

 shape=() dtype=float32_ref>, <tf.Variable 

"b:0"

 shape=() dtype=float32_ref>]



載入修正模型




上面例子中涵蓋的模型載入方案類似於物理中的「真空中無摩擦的完美球體」(perfect sphere in frictionless vacuum)場景。只要你使用自己的代碼保存和載入模型,且不擅自更改二者,實現保存和載入輕而易舉。但很多情況下,並不會有如此完美的場景。在這些情況下,我們需要多加思量。




讓我們通過幾個場景來說明這些問題。首先,如果我們想保存一個完整的模型,但只想載入其中的一部分怎麼辦?(在下面代碼示例中,我依次運行兩個腳本。)




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
b = tf.get_variable(

"b"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.save(sess, 

"./tftcp.model"

)

import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.restore(sess, 

"./tftcp.model"

)
sess.run(a)



輸出:



1.1700551




OK。當我們在相反的場景里,就會出現失敗的狀況:我們希望將一個模型作為大型模型的組件載入。




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.save(sess, 

"./tftcp.model"

)

import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
d = tf.get_variable(

"d"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.restore(sess, 

"./tftcp.model"

)



輸出:



Key d 

not

 found 

in

 checkpoint
         [[{{node save/RestoreV2}} = RestoreV2[dtypes=[DT_FLOAT, DT_FLOAT, DT_FLOAT], _device=

"/job:localhost/replica:0/task:0/device:CPU:0"

](_arg_save/Const_0_0, save/RestoreV2/tensor_names, save/RestoreV2/shape_and_slices)]]



我們只想載入 a,卻忽略了新變數 b。我們犯了一個錯誤,卻抱怨 d 沒有出現在 checkpoint 中。




第三種情況是,我們想將一個模型的參數載入到另一個模型的計算圖中。這也會引發一個錯誤,原因很明顯:Tensorflow 不知道把載入的所有參數放置在何處。幸好有個方法可以給它點提示。




還記得 var_list 嗎?或者更準確來說是「var_list_or_dictionary_mapping_names_to_vars」,但這個名字有點拗口,所以他們使用第一個。




保存模型是 Tensorflow 要求使用全局唯一變數名的關鍵原因之一。在保存-模型-文件中,每個保存變數的名稱都與其形狀和值有關。將其載入到新的計算圖中與將想要載入的變數的原始名稱映射到當前模型的變數中一樣簡單。示例如下:




代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.save(sess, 

"./tftcp.model"

)

import

 tensorflow 

as

 tf
d = tf.get_variable(

"d"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver(var_list={

"a"

: d})
sess = tf.Session()
sess.run(init)
saver.restore(sess, 

"./tftcp.model"

)
sess.run(d)



輸出:



-0.9303965




這是一種關鍵機制,通過這個機制,可以將沒有相同計算圖的模型組合在一起。例如,你可能從網上獲得了一個預訓練好的語言模型,希望重用詞嵌入。或者你可能在兩次訓練之間改變了模型的參數化,想讓這個新版本在舊版本的基礎上繼續前進;但你又不想重新訓練整個過程。在這兩種情況下,你只需手動創建一個字典,將舊變數名稱映射到新變數即可。




需要注意的是:你要明確地知道正在載入的參數是如何使用的。如果可以,你應該使用原作者用來構建模型的確切代碼,以確保計算圖的組件與訓練時看起來一樣。如果需要復現模型,務必記住,無論多微小的更改,都可能嚴重損害預訓練網路的性能。所以始終要將復現結果和原來的結果進行對比。




模型檢查




如果想載入的模型來源於網路或由自己創建(兩個月前),那你很可能不知道原始變數是如何命名的。要檢查保存的模型,需要使用官方 Tensorflow 庫的一些工具。




鏈接:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/framework/python/framework/checkpoint_utils.py 





代碼:



import

 tensorflow 

as

 tf
a = tf.get_variable(

"a"

, [])
b = tf.get_variable(

"b"

, [

10

,

20

])
c = tf.get_variable(

"c"

, [])
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.Session()
sess.run(init)
saver.save(sess, 

"./tftcp.model"

)

print

 tf.contrib.framework.list_variables(

"./tftcp.model"

)



輸出:



[(

"a"

, []), (

"b"

, [

10

20

]), (

"c"

, [])]



利用這些工具(結合原始代碼庫一起使用)通常可以找到你想要的變數名稱。




結論




希望本文能幫你了解關於保存和載入 Tensorflow 模型的基礎知識。還有其他一些高級技巧,比如自動 checkpoint 和保存/恢復元圖,可能會在以後的文章中提到;但是根據我的經驗,這些並不常用,特別是對於初學者來說。




原文鏈接:https://jacobbuckman.com/post/tensorflow-the-confusing-parts-2/






本文為機器之心編譯,

轉載請聯繫本公眾號獲得授權



?------------------------------------------------


加入機器之心(全職記者 / 實習生):hr@jiqizhixin.com


投稿或尋求報道:

content

@jiqizhixin.com


廣告 & 商務合作:bd@jiqizhixin.com

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 機器之心 的精彩文章:

李飛飛重回斯坦福,Andrew Moore接手谷歌雲AI
入門 | 機器學習中常用的損失函數你知多少?

TAG:機器之心 |