01.21 將Tensorflow調試時間減少90%

介紹可應用於Tensorflow代碼的VeriTensor代碼方法,以使調試更加有效。

將Tensorflow調試時間減少90%

Image from Pixabay

Tensorflow代碼很難調試。 我以前花了數週時間調試代碼。 更糟糕的是,在大多數情況下,我不知道如何進行-我可以看到我的代碼沒有訓練好,但是我不知道是因為該模型無法學習,或者是由於實現存在錯誤。 如果是後者,錯誤在哪裡?

這是許多機器學習從業者面臨的挫敗感。 本文介紹了我設計用來調試Tensorflow代碼的VeriTensor方法。 VeriTensor基於"Design by Contract"方法。 它包括三種技術。 這種方法將我的調試時間從數週縮短至數小時,提高了90%以上。 更好的是,在完成調試後,我知道代碼中沒有錯誤。 真是太好了!

通過斷言進行規範

有效調試的關鍵是編寫規範以定義代碼的正確性。 規範描述了代碼應該執行的操作,而實現則描述瞭如何執行代碼。 一段代碼僅在其規範方面是正確的。 在Python中,您可以使用斷言來編寫規範,如下面所示。

<code>def square_root(x):
assert x >= 0
result = math.sqrt(x)
assert result == math.sqrt(x)
return result/<code>

你說,但是很難寫斷言。 我同意你的看法。 我花了15年的時間用斷言驗證代碼。 我開發了基於斷言的技術,Microsoft將其包含在Bing搜索引擎中。 我知道規格可能會很棘手。 這就是為什麼當我開發VeriTensor時,我確保它是實用的。

有效調試的關鍵是通過斷言告訴調試器代碼應該做什麼。

VeriTensor方法

VeriTensor包括3種技術。 您可以在編寫Tensorflow代碼後應用它們。 這意味著這些技術是很簡單的,您無需從頭開始就可以使用它們。

技術1:張量形狀斷言

引入張量時,需要編寫斷言以檢查其形狀。 關於張量形狀的錯誤假設通常會導致棘手的錯誤。 而且TensorFlow的廣播機制可以將它們隱藏得很深。

例如,在諸如Deep Q Network(DQN)之類的迴歸算法中,您有一個來自神經網絡的預測張量,目標張量和損失張量:

<code>prediction = q_function.output_tensor
target = reward + gamma* bootstrapped_q
loss = tf.reduce_mean(tf.square(target - prediction))/<code>

該預測代表預測值。 目標張量表示期望值,由獎勵張量和bootstrapped_q張量計算得出,而γ是浮點數。損失張量表示我們的訓練損失為均方誤差。

現在,我們為引入的張量添加斷言,如下清單所示。 這些斷言檢查預測的形狀和目標的形狀必須在batch_size和action_dimension方面相同。 這些是DQN算法中使用的一些數量。 如果您不熟悉它們,不必擔心。 這裡重要的是我們編寫斷言來檢查張量形狀。 最後,由於損失評估為數字,因此斷言聲明其形狀為[]。

<code>prediction = q_function.output_tensor
assert prediction.shape.to_list() == [batch_size, action_dimension]

target = reward + gamma* bootstrapped_q
assert target.shape.to_list() == [batch_size, action_dimension]

loss = tf.reduce_mean(tf.square(target - prediction))
assert loss.shape.to_list() == []/<code>

如果張量的形狀與它們的期望值不匹配,則會違反聲明。 您不會相信違反形狀聲明的可能性會如此的大!

技術2:張量間的依賴

Tensorflow程序是一個計算圖。 因此,您需要確保正確構建張量圖。 如果張量B的值取決於張量A的值(例如B = A + 1),則圖中的節點B到節點A之間應該有一條邊。

您使用TensorBoard可視化Tensorflow圖。 但是,瞭解此圖很困難,因為實際的張量圖通常具有數百個節點和邊。 下圖顯示了典型的TensorBoard可視化。

將Tensorflow調試時間減少90%

這裡的關鍵見解是:要檢查張量圖的結構,只需要可視化所引入的張量之間的關係即可。 而且,您通常可以將許多張量分組到一個節點中。 例如,在具有許多變量的多層神經網絡中,每個變量都是張量。 但是您只需要將整個網絡可視化為一個節點。

我開發了Python包VeriTensor,以簡化張量圖的可視化。 我將很快將此程序包開源。 它包含一個TensorGroupDependency類。 此類允許您僅註冊要可視化的張量。 它為註冊的張量生成一個新的,更小的可視化圖像。

下一個清單顯示瞭如何使用TensorGroupDependency。 您首先調用add方法來註冊張量。 然後,調用generate_dot_representation方法為您提供可視化效果。 此可視化僅顯示已註冊的張量及其相關性。

<code>d = TensorGroupDependency()
d.add(prediction, 'prediction')
d.add(target, 'target')
d.add(bootstrapped_q, 'bootstrapped_q')
d.add(loss, 'loss')

# Generate tensor dependency graph in DOT notation.
dot = d.generate_dot_representation()
print(dot)

# Generate assertions describing the above dependency graph.
assertions = d.generate_assertions(target_exp='d')
print(assertions)/<code>


第1至5行創建一個張量依賴圖對象,並註冊要可視化其關係的張量。 第8行和第9行以DOT語言生成並打印那些張量依賴關係,這些依賴關係可以以圖形方式呈現:

將Tensorflow調試時間減少90%

讓我們瞭解以上依賴關係圖:

  • 圖中的節點表示張量或張量集(例如神經網絡中的所有變量)。
  • 如果B中的至少一個張量取決於A中的一個張量,則從節點B到節點A會有一個有向邊。在我們的示例中,損耗張量取決於預測和目標張量。 因此,從預測節點和目標節點到損失節點有兩個方向性邊緣。
  • 在每個節點中,您可以看到其種類,例如[Tensor],[Placeholder],[Variable]。
  • 在每個節點中,您還會看到張量形狀,例如(None,1),表示二維張量,其中第一維為動態長度None,第二維為長度1。損耗張量具有形狀(),因為它 是標量。

要檢查圖結構的正確性,您需要解釋為什麼每個邊都存在。 這意味著解釋這些張量之間的依賴關係。 如果您無法解釋某些邊的存在,則您腦海中的想法與您實際構建的圖形之間會有差異。 這通常表示一個錯誤。

解釋完所有邊緣之後,您可以通過調用generate_assertions方法來生成描述圖的斷言,如上面片段中的第12行所示。 以下清單顯示了生成的斷言。 它們描述了相同的依賴圖。 它們成為您代碼的一部分,並將在以後的所有執行中進行檢查:

<code>d.assert_immediate_ancestors('target', {'bootstrapped_q', 'reward'})
d.assert_immediate_ancestors('prediction', set())
d.assert_immediate_ancestors('bootstrapped_q', set())
d.assert_immediate_ancestors('loss', {'prediction', 'target'})
d.assert_immediate_ancestors('reward', set())/<code>

順便說一句,如果您在Tensorflow代碼中精心設計了名稱範圍,並且在TensorBoard可視化文件中進行了認真的摺疊,您將獲得與上述庫相同的功能。 但我認為庫很不錯,因為:

  • 您很可能沒有仔細設計名稱範圍-是嗎?
  • 使用該庫,您可以生成那些張量依賴斷言,這將幫助您在以後的所有執行中進行調試。

技術3:張量方程評估

到目前為止,您已經驗證了定義的張量之間的依賴關係。 最後一步是檢查張量是否執行正確的數值計算。 例如,方程B = A +1和B = A -1都引入了從B到A的依存關係,因此它們的依存關係圖是相同的。 但是您需要指定B = A + 1是正確的實現。 使用張量方程評估對算法中的每個方程執行以下操作:

  • 在每個優化步驟中,通過在session.run中添加它們來評估所涉及的張量。
  • 用這些張量求值以numpy編寫相同的方程式,以計算所需的值。 然後斷言期望值與實際值相同。

接下來的清單顯示了損失張量的張量方程評估。 session.run會評估parameter_update_operations,這是您常用的東西,例如漸變下降步驟。 除了這項常規工作之外,session.run現在還評估預測,目標和損失張量。 您可以從這三個張量評估中計算出所需的損失。 最後,您斷言實際損失等於第4行和第5行的期望損失。請注意,第4行和第5行在Python世界中。 在Python世界中,您可以使用循環,調用任意函數; 它比Tensorflow世界中的方法容易得多。

<code>prediction_, target_, loss_, _ = session.run(
[prediction, target, loss, parameter_update_opeations],
feed_dict={...})
desired_loss = np.mean(np.power(target_ - prediction_, 2))
np.testing.assert_almost_equal(actual=loss_, desired=desired_loss, decimal=3)/<code>

這些技術有效且實用嗎?

我們已將這些技術應用於所有Tensorflow學習者。 下表報告了我們花在驗證五個模型上的時間以及發現的錯誤數量。

將Tensorflow調試時間減少90%

Table 1. Bugs detected with assertion techniques

"學習模塊"列列出了機器學習模型的名稱。 這些是深度強化學習中的Actor-Critic算法。

"編碼時間"列報告了我們花費在編寫這些學習者代碼上的時間(以小時為單位)。 總共我們花了24個小時。

"驗證時間"列報告了我們在驗證上花費的時間。 這包括編寫斷言,運行代碼,觀察斷言衝突並修復檢測到的錯誤。 總共我們花了5個小時。 換句話說,驗證需要20%的工作量。

"檢測到的錯誤"列是每種斷言技術的細分。 它顯示了花費在每種技術上的時間百分比以及檢測到的錯誤數量。 總共,我們僅在5小時內檢測到23個錯誤。 更重要的是,應用這些技術後,我們知道我們的代碼是正確的。

我們可以清楚地看到VeriTensor在檢測錯誤方面很有效。

為什麼VeriTensor對檢測錯誤有效?

首先,它們要求您通過斷言定義代碼的正確性。 編寫規範並不是一個新主意,但VeriTensor使其實用:

  • 形狀斷言要求您寫下所引入的張量的形狀-簡單!
  • 張量依賴性僅要求您關注引入的張量。 在此階段無需檢查數值運算。 這樣可以將圖形從數百個節點減少到十二個左右,從而使人類研究變得切實可行。 自動斷言生成減少了寫下斷言所需的時間。
  • 在張量方程評估中,您將檢查Python世界中的每個方程。 Python世界比Tensorflow世界更容易。

其次,在Tensorflow中發現錯誤的來源令人生畏。 人們花費大部分時間來定位錯誤的來源。 一旦知道了來源,通常即可輕鬆修復該錯誤。 按順序應用時,VeriTensor技術可幫助您定位故障。 在張量依賴階段有問題時,您會知道所有涉及的張量都具有正確的形狀。 當張量方程式有問題時,您就會知道依賴關係結構是正確的。 簡而言之,您可以更好地關注和定位每個問題。

第三,VeriTensor將Tensorflow代碼調試從一門藝術變成了一個軟件工程過程。 如果遵循簡單的任務清單,該過程將確保代碼正確:

  • 為您引入的所有張量編寫一個形狀斷言。
  • 解釋這些張量之間的所有依賴關係邊,並自動生成結構性斷言。
  • 編寫一個斷言以檢查算法中的每個方程。

驗證和/或測試代碼時的常見問題是知道如何進行和何時停止。 您從代碼的哪一部分開始? 您應該檢查哪些方面? 經過足夠的測試,您怎麼知道?

我們的三種技術消除了這些疑慮。 您一一應用它們,每種技術都有有限且可管理的步驟。 步驟的數量受您在代碼中引入的張量和方程式的數量限制(通常約為12個)。 最後,您知道您的代碼是正確的。 這是一個工程過程,而不是一門藝術。 不要忽略這種工程過程的力量。 人們知道確切的步驟後,他們的效率就會大大提高。

不要忽略這種工程過程的力量。 人們知道確切的步驟後,他們的效率就會大大提高。

驗證代碼正確性,而不是性能

我需要明確說明VeriTensor會驗證代碼的正確性。 它不會評估代碼的性能。 正確性意味著您實現的代碼符合您的想法。 績效是學習有意義模型的能力。 通常通過繪製損失,交叉驗證和測試數據來衡量性能。

您必須先確定代碼的正確性,然後再查看其性能。 我稱這是性能原則之前的正確性。 否則,您需要擔心性能不好是因為學習算法不夠好,還是代碼中存在一些錯誤。 顯然,您需要後者,但是很難證明您的代碼沒有錯誤。

性能先於原則:只有在確定正確性之後,才能查看代碼的性能。

可悲的是,我看到很多人都採用的模式是使用性能指標來進行調試。 當他們的代碼不學習時,他們將通過繪製損失函數來開始調試。 這違反了性能原則之前的正確性,因此無法有效地發現錯誤。 這是因為:

  • 性能指標是漸近定向的,而不是單調的。 例如,損失函數應隨時間減少。 但是在任何時間點,包括調試時,這些數字都可以上升或下降。 沒有正確的值使您很難識別出是否有問題。 將此與斷言進行比較:您知道發生斷言衝突時情況不對。
  • 即使您發現性能指標顯然是錯誤的,它們也不會告訴您錯誤的來源。 將此與VeriTensor的故障定位支持進行比較。 您可以在階段中找到錯誤-張量成形階段,張量依賴階段和張量值階段。 您可以在每個階段集中精力。
  • 修復錯誤後,很難為該錯誤編寫回歸測試。 這是因為基於性能指標的錯誤和症狀的根源很遠。 將此與使用斷言的測試用例編寫經驗進行比較。 您只需要將主學習循環變成具有較小學習時間步長的單元測試,以使測試儘快終止。 您可以使用真實輸入,也可以使用隨機輸入。

影片介紹

一年半以前,我在Tensorflow Deep Dive活動中介紹了VeriTensor。 演講受到好評。 這是演示。

在那之後的20個月中,我將VeriTensor應用於所有的機器學習代碼,並且一次又一次地起作用。 希望對您有幫助。

(本文翻譯自Wei Yi的文章《Reducing Tensorflow Debugging Time by 90 Percent》,參考:https://towardsdatascience.com/reducing-tensorflow-debugging-time-by-90-percent-41e8d60f9494)


分享到:


相關文章: