正確的debug你的TensorFlow代碼(不用這麼痛苦)

2019-12-19     AI公園

作者:Galina Olejnik

編譯:ronghuaiyang

導讀

TensorFlow代碼很難調試,這個大家已達成共識,不過,就算是難,也還是需要調試的,畢竟誰也沒有把握不出bug,看看這篇文章能不能讓你減輕一點調試時的痛苦。

當討論在tensorflow上編寫代碼時,總是將其與PyTorch進行比較,討論框架有多複雜,以及為什麼要使用tf.contrib的某些部分,做得太爛了。此外,我認識很多數據科學家,他們只用Github上已有的repo來用tensorflow。對這個框架持這種態度的原因是非常不同的,但是今天讓我們關注更實際的問題:調試用tensorflow編寫的代碼並理解它的主要特性。

核心抽象

  • 計算圖。第一個抽象是計算圖tf.Graph,它使框架能夠處理惰性評估模式(不是立即執行,這是「傳統」命令式Python編程實現的)。基本上,這種方法允許程式設計師創建tf.Tensor(邊)和tf.Operation (節點),它不是立即計算的,而是在執行圖形時才計算的。這種構造機器學習模型的方法在許多框架中都很常見(例如,在Apache Spark中使用了類似的思想),並且具有不同的優缺點,這在編寫和運行代碼時表現得非常明顯。最主要和最重要的優點是,數據流圖可以很容易地實現並行性和分布式執行,而無需顯式地使用multiprocessing 模塊。在實踐中,編寫良好的tensorflow模型在啟動時立即使用所有核心的資源,而不需要任何額外的配置。然而,這個工作流的一個非常明顯的缺點是,一旦你構建圖的時候,沒有用提供的輸入來運行,你就不能確保它不會崩潰。它肯定會崩潰。此外,除非你已經執行了圖形,否則你無法估計它的運行時間。計算圖的主要組成部分是圖集合和圖結構。嚴格地說,圖的結構是前面討論的特定的節點集和邊集,而圖集合是可以邏輯地分組的變量集。例如,檢索圖形的可訓練變量的常用方法是'tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)。
  • 會話 。第二個抽象與第一個抽象高度相關,並且有更複雜的解釋:tensorflow會話的tf.Session用於客戶端程序和c++運行時之間的連接(如你所知,tensorflow是用c++編寫的)。為什麼是c++ ?答案是,通過這種語言實現的數學運算可以得到很好的優化,因此,計算圖運算可以得到很好的處理。如果你正在使用低級的tensorflow API(大多數Python開發人員都在使用),則tensorflow session將作為上下文管理器調用:with tf.Session() as sess:。沒有參數傳遞給構造函數(如前一個示例)的會話僅使用本地機器的資源和默認的tensorflow圖,但它也可以通過分布式tensorflow運行時訪問遠程設備。在實踐中,如果沒有會話,計算圖就不能存在(沒有會話,它就不能執行),而會話總是有一個指向全局圖的指針。深入研究運行會話的細節,值得注意的主要一點是它的語法:tf.Session.run()。它可以作為張量、操作或類張量對象的參數獲取(或獲取列表)。另外,可以傳遞feed_dict(這個可選參數是tf.placeholder的對象及其值)以及一組選項。

實驗中一些可能的問題以及可能的解決方案

  1. 會話加載並通過預先訓練的模型進行預測。這就是瓶頸,我花了幾周的時間來理解、調試和修復它。我想高度關注這個問題,並描述兩種重新加載預訓練模型(圖和會話)並使用它的可能技術。首先,當我們談論加載模型時,我們真正的意思是什麼?當然,為了做到這一點,我們需要事先訓練並保存它。後者通常是通過tf.train.Saver.save完成的。因此,我們有3個二進位文件.index、.meta和.data-00000-of-00001,其中包含恢復會話和圖所需的所有數據。要加載以這種方式保存的模型,需要通過tf.train.import_meta_graph() (參數是.meta的文件)來恢復。按照前一段描述的步驟之後,所有變量(包括所謂的「隱藏」變量,稍後將討論)都將被移植到當前圖中。檢索某個有自己名字的張量(記住,它可能不同於你初始化它時使用的張量,這取決於創建張量的範圍和操作的結果)應該執行graph.get_tensor_by_name()。這是第一種方法。第二種方法更顯式,也更難實現(對於我一直在使用的模型的架構,我還沒有成功用起來),它的主要思想是將圖的邊(張量)顯式地保存到.npy或.npz文件中,然後將它們加載回圖中(並根據創建它們的範圍分配適當的名稱)。這種方法的問題在於它有兩個巨大的缺點:首先,當模型架構變得非常複雜時,它也變得很難控制和保存所有的權重矩陣。其次,有一種「隱藏的」張量,它是在沒有顯式初始化的情況下創建的。例如,當你創建 tf.nn.rnn_cell.BasicLSTMCell時。它創建了所有需要的權值和偏差來實現LSTM cell。變量名也是自動分配的。這種行為看起來還可以,但實際上,在很多情況下,並不是很好用。這種方法的主要問題是,當你查看圖的集合時,看到一堆變量,你不知道它們的來源,你實際上不知道應該保存什麼以及在哪裡加載它們。坦率地說,很難將隱藏變量放到圖中正確的位置並適當地操作它們。
  2. 在沒有任何警告的情況下兩次創建同名張量(通過自動添加_index結尾)。我認為這個問題不像前一個問題那麼重要,但是這個問題確實困擾著我,因為它會導致很多圖執行錯誤。為了更好地解釋這個問題,我們來看一下這個例子。例如,你用tf.get_variable(name=』char_embeddings』, dtype=…)來創建張量。然後保存它並在新會話中加載。你已經忘記了這個變量是可訓練的,並通過tf.get_variable()以相同的方式再次創建了它。在圖執行過程中,會發生的這樣的錯誤:FailedPreconditionError (see above for traceback): Attempting to use uninitialized value char_embeddings_2。原因是,你已經創建了一個空變量,但是並沒有將它放到模型的適當的位置,而實際上只要它已經包含在計算圖中,就可以放到模型的某個地方。正如你所看到的,由於開發人員創建了同名張量兩次(甚至Windows也會這樣做),所以沒有出現錯誤或警告。也許這一點只對我很重要,但這是tensorflow的特性,我並不喜歡它的這個行為。
  3. 在編寫單元測試和其他問題時手動重置圖。由於許多原因,測試用tensorflow編寫的代碼總是很困難。第一個— 也是最明顯的一個,已經在這一段的開頭提到過了,可能聽起來很傻,但對我來說,讓人很惱火。由於在運行期間訪問的所有模塊的所有張量只有一個默認的tensorflow圖,因此不可能在不重置這個圖的情況下,使用不同的參數(例如)測試相同的功能。它只是一行代碼tf.reset_default_graph(),但是知道它應該寫在大多數方法的頂部,這個解決方案就變成了某種惡作劇,當然,一個明顯的代碼複製示例。我還沒有發現任何可能的方法處理這個問題(除了使用reuse參數,我們將在後面討論),只要所有的張量與默認圖並沒有辦法隔離(當然,可以有一個單獨的tensorflow圖方法,但在我看來這不是最佳實踐)。為tensorflow寫單元測試的代碼也困擾我很多的事情,在這種情況下,計算圖的一部分是不應該被執行的(裡面有張量未初始化,因為模型沒有訓練)。但是,計算圖本身並不知道我們應該測試什麼。我的意思是 self.assertEqual()的參數不清楚(我們應該測試輸出張量的名稱或它們的形狀嗎?如果形狀為None呢?如果張量的名字或形狀不足以得出這個結論,那該怎麼辦?)。在我的例子中,我只是簡單地斷言張量的名稱、形狀和維數,但是我確信,在不執行圖的情況下,只檢查這部分功能是不合理的。
  4. 混淆張量的名字。很多人會說這個關於tensorflow的評論是一種另外的抱怨方式,但是我們並不能說出來在做了某種運算之後得到的張量的名字是什麼。我的意思是,名稱bidirectional_rnn/bw/bw/while/Exit_4:0清楚嗎?對我來說,絕對不清楚。我得到這個張量是對動態雙向RNN的後向單元進行某種操作的結果,但是如果沒有顯式地調試代碼,就無法知道執行了哪些操作以及操作的順序。另外,索引的結尾也不太好理解,只要你想知道數字4是從哪裡來的,就需要閱讀tensorflow文檔並深入研究計算圖。前面討論的「隱藏」變量的情況也是一樣的:為什麼這裡有bias和kernel名稱?也許這是我的水平不夠,但是這樣的調試案例對我來說很不自然。
  5. tf.AUTO_REUSE,可訓練的變量,重新編譯的庫和其他的東西。這個列表的最後,我們看一些小的細節,這些細節只有在錯誤中才能學習到。第一件事是reuse=tf.AUTO_REUSE參數的作用域,它允許自動處理已經創建的變量,如果它們已經存在,就不會創建兩次。事實上,在許多情況下,它可以解決本段第二點所述的問題。但是,在實踐中,這個參數應該謹慎使用,並且只有在開發人員知道代碼的某些部分需要運行兩次或多次時才使用。第二點是可訓練變量,這裡最重要的一點是:所有的張量在默認情況下都是可訓練的。有時這可能是一個頭痛的行為,因為有時候我們並不想要所有的張量都可訓練,但是又很容易忘記,他們是都可以訓練的。第三件事只是一個優化技巧,我建議每個人都這麼做:幾乎在所有情況下,當你使用通過pip安裝的包時,你都會收到這樣的警告:Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX AVX2。如果你看到這類消息,最好卸載tensorflow,然後使用你喜歡的選項通過bazel重新編譯它。這樣做的主要好處是計算速度的提高和更好的總體性能。

總結

我希望這篇文章能夠對那些正在開發他們的第一個tensorflow模型的數據科學家有所幫助,他們正在努力解決框架中某些部分的不明顯的行為,這些行為很難理解,而且調試起來相當複雜。要點我想說的是,使用這個庫工作的時候,犯一些錯誤也不是壞事(對於其他的事情也是一樣的),這可以讓我們多問些問題,深入查看文檔和調試每一行代碼,這樣也是很好的。

就像跳舞或游泳一樣,任何事情都需要練習,我希望我能讓這種練習變得更愉快和有趣。

英文原文:https://towardsdatascience.com/debugging-your-tensorflow-code-right-without-so-many-painful-mistakes-b48bd9145d5c

文章來源: https://twgreatdaily.com/zh-tw/M7VAHG8BMH2_cNUg14vu.html