本頁面由 Cloud Translation API 翻譯而成。
Switch to English

使用TensorBoard Debugger V2調試TensorFlow程序中的數值問題

在TensorFlow程序期間,有時可能會發生涉及NaN的災難性事件,從而破壞了模型訓練過程。此類事件的根本原因通常是晦澀的,尤其是對於大小和復雜程度不高的模型。為了使調試這類模型錯誤更容易,TensorBoard 2.3+(與TensorFlow 2.3+一起)提供了一個稱為Debugger V2的專用儀表板。在這裡,我們通過在TensorFlow編寫的神經網絡中解決涉及NaN的實際錯誤,來演示如何使用此工具。

本教程中說明的技術適用於其他類型的調試活動,例如在復雜程序中檢查運行時張量形狀。本教程重點介紹NaN,因為它們的發生頻率相對較高。

觀察錯誤

我們將調試的TF2程序的源代碼可在GitHub上獲得 。該示例程序也打包在tensorflow pip包中(版本2.3+),可以通過以下方式調用:

 python -m tensorflow.python.debug.examples.v2.debug_mnist_v2
 

該TF2程序創建一個多層感知(MLP)並對其進行訓練以識別MNIST圖像。此示例有目的地使用TF2的低級API定義自定義層構造,損失函數和訓練循環,因為與使用簡單的API相比,使用更靈活但更易於出錯的API時,NaN錯誤的可能性更高使用但靈活性稍差的高級API,例如tf.keras

程序在每個訓練步驟之後都會打印測試準確性。我們可以在控制台中看到,第一步之後,測試準確性陷入了接近機會的水平(〜0.1)。當然,這不是模型訓練的預期表現:我們希望隨著步幅的增加,精度會逐漸接近1.0(100%)。

 Accuracy at step 0: 0.216
Accuracy at step 1: 0.098
Accuracy at step 2: 0.098
Accuracy at step 3: 0.098
...
 

有根據的猜測是,此問題是由數值不穩定(例如NaN或無窮大)引起的。但是,如何確定確實如此?如何找到負責產生數值不穩定性的TensorFlow操作(op)?為了回答這些問題,讓我們用Debugger V2來檢測越野車程序。

使用Debugger V2檢測TensorFlow代碼

tf.debugging.experimental.enable_dump_debug_info()是Debugger V2的API入口點。它使用單行代碼檢測TF2程序。例如,在程序開頭附近添加以下行將導致將調試信息寫入/ tmp / tfdbg2_logdir的日誌目錄(logdir)。調試信息涵蓋了TensorFlow運行時的各個方面。在TF2中,它包括急切執行的全部歷史記錄,由@ tf.function執行的圖構建,圖的執行,由執行事件生成的張量值以及這些事件的代碼位置(Python堆棧跟踪) 。調試信息的豐富性使用戶可以縮小模糊的錯誤的範圍。

 tf.debugging.experimental.enable_dump_debug_info(
    logdir="/tmp/tfdbg2_logdir",
    tensor_debug_mode="FULL_HEALTH",
    circular_buffer_size=-1)
 

tensor_debug_mode參數控制Debugger V2從每個渴望的或圖中的張量提取的信息。 “ FULL_HEALTH”是一種模式,捕獲有關每個浮點型張量的以下信息(例如,常見的float32和次要的bfloat16 dtype ):

  • D型
  • 元素總數
  • 浮點型元素的細分為以下類別:負有限( - ),零( 0 ),正有限( + ),負無窮大( -∞ ),正無窮大( +∞ )和NaN

“ FULL_HEALTH”模式適用於調試涉及NaN和無窮大的錯誤。請參閱下面的其他受支持的tensor_debug_mode

circular_buffer_size參數控制將多少張量事件保存到日誌目錄中。它的默認值為1000,這僅導致將已檢測的TF2程序結束之前的最後1000個張量保存到磁盤。此默認行為通過犧牲調試數據的完整性來減少調試器的開銷。如果首選完整性,在這種情況下,我們可以通過將參數設置為負值(例如,此處為-1)來禁用循環緩衝區。

debug_mnist_v2示例通過enable_dump_debug_info()傳遞命令行標誌來調用enable_dump_debug_info() 。要在啟用此調試工具的情況下再次運行有問題的TF2程序,請執行以下操作:

 python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
    --dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH
 

在TensorBoard中啟動Debugger V2 GUI

使用調試器工具運行程序會在/ tmp / tfdbg2_logdir中創建一個日誌目錄。我們可以啟動TensorBoard並將其指向logdir:

 tensorboard --logdir /tmp/tfdbg2_logdir
 

在Web瀏覽器中,瀏覽至TensorBoard的頁面,網址為http:// localhost:6006。默認情況下,“ Debugger V2”插件應處於激活狀態,顯示的頁面如下所示:

Debugger V2全屏截圖

使用Debugger V2 GUI查找NaN的根本原因

TensorBoard中的Debugger V2 GUI分為六個部分:

  • 警報 :此左上部分包含調試器在檢測到的TensorFlow程序的調試數據中檢測到的“警報”事件列表。每個警報均指示需要引起注意的特定異常。在我們的案例中,本節重點介紹了499個NaN /∞事件,其顏色為顯著的粉紅色。這證實了我們的懷疑,即該模型由於其內部張量值中存在NaN和/或無窮而無法學習。我們將在短期內深入研究這些警報。
  • Python執行時間軸 :這是上半部分的上半部分。它顯示了急切執行操作和圖形的完整歷史。時間軸的每個框均由操作或圖形名稱的首字母標記(例如,“ TensorSliceDataset”操作為“ T”,“模型” tf.function為“ m”)。我們可以使用時間線上方的導航按鈕和滾動條來瀏覽時間線。
  • 圖形執行 :位於GUI的右上角,此部分將是我們調試任務的中心。它包含在圖內計算的所有float-dtype張量的歷史記錄(即,由@tf-function s編譯)。
  • 圖形結構 (上中下部的下半部分), 源代碼 (左下部分)和堆棧跟踪 (右下部分)最初是空的。當我們與GUI交互時,將填充它們的內容。這三個部分還將在我們的調試任務中扮演重要角色。

使自己適應UI的組織之後,讓我們採取以下步驟來深入了解NaN出現的原因。首先,在“警報”部分中單擊NaN /∞警報。這將自動滾動“圖形執行”部分中的600個圖形張量的列表,並聚焦於#88,這是由Log (自然對數)運算符生成的名為“ Log:0”的張量。顯著的粉紅色突出顯示2D float32張量的1000個元素中的-∞元素。這是TF2程序運行時歷史中的第一個張量,其中包含任何NaN或無窮大:在它之前計算的張量不包含NaN或∞;此後計算的許多(實際上是大多數)張量包含NaN。我們可以通過上下滾動圖形執行列表來確認這一點。該觀察結果強烈暗示了Log op是此TF2程序中數值不穩定的根源。

調試器V2:Nan / Infinity警報和圖形執行列表

為什麼此Log op吐出-∞?要回答該問題,需要檢查操作的輸入。單擊張量的名稱(“ Log:0”)可在“圖形結構”部分的TensorFlow圖中顯示Log op附近的簡單但信息豐富的可視化。注意信息流的從上到下的方向。 op本身以中間的粗體顯示。在它的緊上方,我們可以看到一個佔位符op提供了一個唯一的輸入到Log op。此logits佔位符在“圖形執行”列表中生成的張量在哪裡?通過使用黃色背景色作為視覺輔助,我們可以看到logits:0張量在Log:0張量上方兩行,即第85行。

調試器V2:圖形結構視圖和對輸入張量的跟踪

更仔細地看一下第85行中“ logits:0”張量的數值細分,揭示了為什麼其使用者Log:0產生-∞的原因:在“ logits:0”的1000個元素中,一個元素的值為0。 -∞是計算自然對數0的結果!如果我們能以某種方式確保Log op僅暴露於正輸入,我們將能夠防止NaN /∞的發生。這可以通過在佔位符logits張量上應用裁剪(例如,通過使用tf.clip_by_value() )來實現。

我們正在接近解決錯誤,但尚未完成。為了應用此修復程序,我們需要知道Log op及其占位符輸入在Python源代碼中的何處。 Debugger V2為跟踪圖形操作和執行事件提供了一流的支持。當我們單擊“圖形執行”中的Log:0張量時,“堆棧跟踪”部分填充了Log op創建的原始堆棧跟踪。堆棧跟踪有些大,因為它包含來自TensorFlow內部代碼的許多幀(例如gen_math_ops.py和dumping_callback.py),對於大多數調試任務,我們可以放心地忽略它們。感興趣的框架是debug_mnist_v2.py的第216行(即,我們實際上正在嘗試調試的Python文件)。單擊“第204行”將在“源代碼”部分中顯示相應代碼行的視圖。

調試器V2:源代碼和堆棧跟踪

最終,我們進入了從其logits輸入創建有問題的Log op的源代碼。這是用@tf.function裝飾的自定義分類交叉熵損失函數,因此將其轉換為TensorFlow圖。佔位符op“ logits”對應於損失函數的第一個輸入參數。 Log操作是使用tf.math.log()API調用創建的。

此錯誤的價值削減修復將類似於:

   diff = -(labels *
           tf.clip_by_value(tf.math.log(logits), 1e-6, 1.))
 

它將解決該TF2程序中的數值不穩定性,並使MLP成功訓練。解決數值不穩定性的另一種可能方法是使用tf.keras.losses.CategoricalCrossentropy

這總結了我們從觀察TF2模型錯誤到提出修改該錯誤的代碼更改的過程,並藉助Debugger V2工具,該工具提供了對所檢測的TF2程序的渴望和圖形執行歷史的完整可見性,包括數字摘要張量值以及操作,張量及其原始源代碼之間的關聯。

Debugger V2的硬件兼容性

Debugger V2支持主流的培訓硬件,包括CPU和GPU。還支持使用tf.distributed.MirroredStrategy進行多GPU訓練。對TPU的支持仍處於早期階段,需要致電

 tf.config.set_soft_device_placement(True)
 

在調用enable_dump_debug_info()之前。它還可能對TPU有其他限制。如果您在使用Debugger V2時遇到問題,請在GitHub問題頁面上報告錯誤。

Debugger V2的API兼容性

Debugger V2在TensorFlow的軟件堆棧的較低級別上實現,因此與tf.kerastf.data以及在TensorFlow較低級別之上構建的其他API兼容。調試器V2也與TF1向後兼容,儘管對於TF1程序生成的調試日誌目錄,“急切執行時間軸”將為空。

API使用技巧

關於此調試API的一個常見問題是,應該在TensorFlow代碼中的哪個位置插入對enable_dump_debug_info()的調用。通常,應該在TF2程序中儘早調用該API,最好在Python導入行之後,在圖形構建和執行開始之前調用該API。這將確保為模型及其訓練提供支持的所有操作和圖形的完整覆蓋。

當前支持的tensor_debug_modes是: NO_TENSORCURT_HEALTHCONCISE_HEALTHFULL_HEALTHSHAPE 。它們從每個張量提取的信息量以及調試程序的性能開銷各不相同。請參考enable_dump_debug_info()的文檔的args部分 ]。

性能開銷

調試API將性能開銷引入了已檢測的TensorFlow程序。開銷因tensor_debug_mode ,硬件類型和已檢測TensorFlow程序的性質而異。作為參考,在GPU上,在批量大小為64的Transformer模型訓練期間, NO_TENSOR模式增加了15%的開銷。其他tensor_debug_modes的開銷百分比更高: CURT_HEALTHCONCISE_HEALTHFULL_HEALTHSHAPE大約為50%模式。在CPU上,開銷略低。在TPU上,開銷目前較高。

與其他TensorFlow調試API的關係

請注意,TensorFlow提供了其他工具和API進行調試。您可以在API文檔頁面的tf.debugging.*名稱空間下瀏覽此類API。在這些API中,最常用的是tf.print() 。什麼時候應該使用Debugger V2,什麼時候應該使用tf.print()tf.print()在以下情況下很方便

  1. 我們確切知道要打印哪些張量
  2. 我們知道在源代碼中確切的位置插入這些tf.print()語句,
  3. 這樣的張量的數量不是太大。

對於其他情況(例如,檢查許多張量值,檢查由TensorFlow的內部代碼生成的張量值,並如上所述搜索數值不穩定的起源),Debugger V2提供了一種更快的調試方式。另外,Debugger V2提供了一種檢查渴望和圖形張量的統一方法。它還提供了有關圖結構和代碼位置的信息,這超出了tf.print()的能力。

可以用來調試涉及∞和NaN的問題的另一個API是tf.debugging.enable_check_numerics() 。與enable_dump_debug_info()不同, enable_check_numerics()不會在磁盤上保存調試信息。取而代之的是,它僅在TensorFlow運行時期間監視∞和NaN,並在任何操作生成此類不良數值後立即將原始代碼位置錯誤排除。與enable_dump_debug_info()相比,它具有較低的性能開銷,但是無法提供程序執行歷史的完整記錄,並且沒有像Debugger V2這樣的圖形用戶界面。