Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Debug di problemi numerici nei programmi TensorFlow utilizzando TensorBoard Debugger V2

Eventi catastrofici che coinvolgono NaN possono talvolta verificarsi durante un programma TensorFlow, paralizzando i processi di addestramento del modello. La causa principale di tali eventi è spesso oscura, specialmente per modelli di dimensioni e complessità non banali. Per semplificare il debug di questo tipo di bug del modello, TensorBoard 2.3+ (insieme a TensorFlow 2.3+) fornisce un dashboard specializzato chiamato Debugger V2. Qui dimostriamo come utilizzare questo strumento lavorando attraverso un vero bug che coinvolge NaNs in una rete neurale scritta in TensorFlow.

Le tecniche illustrate in questo tutorial sono applicabili ad altri tipi di attività di debug come l'ispezione delle forme di tensore del runtime in programmi complessi. Questo tutorial si concentra su NaN a causa della loro frequenza relativamente elevata di occorrenza.

Osservando il bug

Il codice sorgente del programma TF2 di cui eseguiremo il debug è disponibile su GitHub . Il programma di esempio è anche impacchettato nel pacchetto pip tensorflow (versione 2.3+) e può essere invocato da:

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

Questo programma TF2 crea una percezione multistrato (MLP) e la forma per riconoscere le immagini MNIST . Questo esempio utilizza intenzionalmente l'API di basso livello di TF2 per definire costrutti di livello personalizzati, funzione di perdita e ciclo di addestramento, poiché la probabilità di bug NaN è maggiore quando utilizziamo questa API più flessibile ma più soggetta ad errori rispetto a quando utilizziamo il più semplice API di alto livello ma leggermente meno flessibili come tf.keras .

Il programma stampa una precisione di prova dopo ogni fase di allenamento. Possiamo vedere nella console che l'accuratezza del test si blocca a un livello quasi casuale (~ 0.1) dopo il primo passo. Questo non è certo come dovrebbe comportarsi l'addestramento del modello: prevediamo che l'accuratezza si avvicini gradualmente all'1,0 (100%) man mano che il passo aumenta.

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

Un'ipotesi colta è che questo problema è causato da un'instabilità numerica, come NaN o infinito. Tuttavia, come possiamo confermare questo è davvero il caso e come troviamo l'operazione TensorFlow (op) responsabile della generazione dell'instabilità numerica? Per rispondere a queste domande, analizziamo il programma con errori con Debugger V2.

Strumentazione del codice TensorFlow con Debugger V2

tf.debugging.experimental.enable_dump_debug_info() è il punto di ingresso API del debugger V2. Strumenta un programma TF2 con una singola riga di codice. Ad esempio, l'aggiunta della seguente riga all'inizio del programma provocherà la scrittura delle informazioni di debug nella directory dei registri (logdir) in / tmp / tfdbg2_logdir. Le informazioni di debug coprono vari aspetti del runtime di TensorFlow. In TF2 include la cronologia completa dell'esecuzione desiderosa, la creazione di grafici eseguita da @ tf.function , l'esecuzione dei grafici, i valori del tensore generati dagli eventi di esecuzione, nonché la posizione del codice (tracce dello stack Python) di quegli eventi . La ricchezza delle informazioni di debug consente agli utenti di limitare i bug oscuri.

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

L'argomento tensor_debug_mode controlla quali informazioni estraggono il Debugger V2 da ciascun desideratore o tensore nel grafico. "FULL_HEALTH" è una modalità che acquisisce le seguenti informazioni su ciascun tensore di tipo mobile (ad esempio, il float32 comunemente visto e il tipo bfloat16 meno comune):

  • DTYPE
  • Rango
  • Numero totale di elementi
  • Una suddivisione degli elementi di tipo mobile nelle seguenti categorie: finito negativo ( - ), zero ( 0 ), finito positivo ( + ), infinito negativo ( -∞ ), infinito positivo ( +∞ ) e NaN .

La modalità "FULL_HEALTH" è adatta per il debug di bug che coinvolgono NaN e infinito. Vedi sotto per altri tensor_debug_mode supportati.

L'argomento circular_buffer_size controlla il numero di eventi tensore salvati nel logdir. Il valore predefinito è 1000, che consente di salvare su disco solo gli ultimi 1000 tensori prima della fine del programma TF2 strumentato. Questo comportamento predefinito riduce l'overhead del debugger sacrificando la completezza dei dati di debug. Se la completezza è preferita, come in questo caso, possiamo disabilitare il buffer circolare impostando l'argomento su un valore negativo (es. -1 qui).

L'esempio debug_mnist_v2 richiama enable_dump_debug_info() passando ad essa i flag della riga di comando. Per eseguire nuovamente il nostro problematico programma TF2 con questa strumentazione di debug abilitata, eseguire:

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

Avvio della GUI del debugger V2 in TensorBoard

L'esecuzione del programma con la strumentazione di debugger crea un logdir in / tmp / tfdbg2_logdir. Possiamo avviare TensorBoard e puntarlo al logdir con:

 tensorboard --logdir /tmp/tfdbg2_logdir
 

Nel browser Web, accedere alla pagina di TensorBoard all'indirizzo http: // localhost: 6006. Il plug-in "Debugger V2" dovrebbe essere attivato per impostazione predefinita, visualizzando una pagina simile al seguente:

Debugger V2 screenshot a schermo intero

Utilizzo della GUI di Debugger V2 per trovare la causa principale di NaNs

La GUI di Debugger V2 in TensorBoard è organizzata in sei sezioni:

  • Avvisi : questa sezione in alto a sinistra contiene un elenco di eventi di "allarme" rilevati dal debugger nei dati di debug dal programma TensorFlow strumentato. Ogni avviso indica una certa anomalia che merita attenzione. Nel nostro caso, questa sezione evidenzia 499 eventi NaN / with con un colore rosa-rosso saliente. Ciò conferma il nostro sospetto che il modello non riesca a imparare a causa della presenza di NaN e / o infiniti nei suoi valori di tensore interni. Analizzeremo questi avvisi a breve.
  • Linea temporale dell'esecuzione di Python : questa è la metà superiore della sezione medio-alta. Presenta la storia completa dell'esecuzione entusiasta di operazioni e grafici. Ogni casella della sequenza temporale è contrassegnata dalla lettera iniziale del nome dell'operazione o del grafico (ad esempio, "T" per l'op "TensorSliceDataset", "m" per la funzione tf.function "modello"). Possiamo navigare in questa sequenza temporale utilizzando i pulsanti di navigazione e la barra di scorrimento sopra la sequenza temporale.
  • Esecuzione del grafico : situata nell'angolo in alto a destra della GUI, questa sezione sarà centrale per la nostra attività di debug. Contiene una cronologia di tutti i tensori a virgola mobile calcolati all'interno di grafici (ovvero compilati da @tf-function s).
  • La struttura del grafico (metà inferiore della sezione in alto al centro), il codice sorgente (sezione in basso a sinistra) e la traccia dello stack (sezione in basso a destra) sono inizialmente vuoti. I loro contenuti verranno popolati quando interagiamo con la GUI. Queste tre sezioni svolgeranno anche ruoli importanti nel nostro compito di debug.

Dopo esserci orientati verso l'organizzazione dell'interfaccia utente, facciamo i seguenti passi per arrivare alla fine del perché sono comparse le NaN. Innanzitutto, fai clic sull'avviso NaN / ∞ nella sezione Avvisi. Questo fa scorrere automaticamente l'elenco di 600 tensori grafici nella sezione Esecuzione grafico e si concentra su # 88, che è un tensore denominato "Log: 0" generato da un Log (logaritmo naturale) op. Un colore rosa-rosso saliente evidenzia un elemento -∞ tra i 1000 elementi del tensore float32 2D. Questo è il primo tensore nella cronologia di runtime del programma TF2 che conteneva qualsiasi NaN o infinito: tensori calcolati prima che non contengano NaN o ∞; molti (in effetti, la maggior parte) tensori calcolati successivamente contengono NaN. Possiamo confermarlo scorrendo su e giù nell'elenco Esecuzione grafico. Questa osservazione fornisce un forte indizio del fatto che Log op è la fonte dell'instabilità numerica in questo programma TF2.

Debugger V2: avvisi Nan / Infinity ed elenco di esecuzione dei grafici

Perché questa operazione di Log sputa a -∞? Per rispondere a questa domanda è necessario esaminare l'input dell'op. Facendo clic sul nome del tensore ("Registro: 0") viene visualizzata una visualizzazione semplice ma informativa della vicinanza dell'operazione Log nel relativo grafico TensorFlow nella sezione Struttura del grafico. Notare la direzione dall'alto verso il basso del flusso di informazioni. L'op stessa viene mostrata in grassetto nel mezzo. Immediatamente sopra di esso, possiamo vedere che un Operatore segnaposto fornisce l'unico e solo input all'operazione Log . Dove si trova il tensore generato da questo log segnaposto nell'elenco Esecuzione grafico? Usando il colore di sfondo giallo come aiuto visivo, possiamo vedere che il logits:0 tensore è due righe sopra il Log:0 tensore, cioè nella riga 85.

Debugger V2: vista della struttura del grafico e traccia per il tensore di input

Uno sguardo più attento alla suddivisione numerica del tensore "logits: 0" nella riga 85 rivela perché il suo consumer Log:0 produce un -∞: Tra i 1000 elementi di "logits: 0", un elemento ha un valore di 0. -∞ è il risultato del calcolo del logaritmo naturale di 0! Se in qualche modo possiamo garantire che l'Operazione Log venga esposta solo a input positivi, saremo in grado di impedire che si verifichi il NaN / ∞. Ciò può essere ottenuto applicando il clipping (ad esempio, utilizzando tf.clip_by_value () ) sul tensore dei log segnaposto.

Ci stiamo avvicinando alla risoluzione del bug, ma non ancora fatto. Per applicare la correzione, dobbiamo sapere da dove hanno origine nel codice sorgente di Python l'op log e il suo input segnaposto. Il debugger V2 fornisce un supporto di prima classe per tracciare le operazioni del grafico e gli eventi di esecuzione sul loro sorgente. Quando abbiamo fatto clic sul tensore Log:0 in Graph Executions, la sezione Stack Trace è stata popolata con la traccia stack originale della creazione dell'operazione Log. La traccia dello stack è piuttosto ampia perché include molti frame dal codice interno di TensorFlow (ad es. Gen_math_ops.py e dumping_callback.py), che possiamo tranquillamente ignorare per la maggior parte delle attività di debug. Il frame di interesse è la linea 216 di debug_mnist_v2.py (ovvero, il file Python che stiamo effettivamente cercando di eseguire il debug). Facendo clic su "Riga 204" si apre una vista della corrispondente riga di codice nella sezione Codice sorgente.

Debugger V2: codice sorgente e traccia stack

Questo infine ci porta al codice sorgente che ha creato la problematica Operazione di log dai suoi input di log. Questa è la nostra funzione personalizzata di perdita incrociata dell'entropia, decorata con la funzione @tf.function e quindi convertita in un grafico TensorFlow. Il "segnapunti" op segnaposto corrisponde al primo argomento di input per la funzione di perdita. L'operazione Log viene creata con la chiamata API tf.math.log ().

La correzione del valore-clipping di questo errore sarà simile a:

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

Risolverà l'instabilità numerica in questo programma TF2 e farà sì che l'MLP si alleni con successo. Un altro possibile approccio per correggere l'instabilità numerica è usare tf.keras.losses.CategoricalCrossentropy .

Questo conclude il nostro viaggio dall'osservazione di un bug del modello TF2 alla creazione di una modifica del codice che corregge il bug, aiutato dallo strumento Debugger V2, che fornisce piena visibilità sulla cronologia di esecuzione desiderosa e grafica del programma TF2 strumentato, inclusi i riepiloghi numerici dei valori di tensore e associazione tra operazioni, tensori e il loro codice sorgente originale.

Compatibilità hardware di Debugger V2

Il debugger V2 supporta hardware di addestramento tradizionale, inclusi CPU e GPU. È supportato anche l'addestramento su più GPU con tf.distributed.MirroredStrategy . Il supporto per TPU è ancora in una fase iniziale e richiede una chiamata

 tf.config.set_soft_device_placement(True)
 

prima di chiamare enable_dump_debug_info() . Potrebbe avere anche altre limitazioni sui TPU. Se riscontri problemi con Debugger V2, segnala i bug nella nostra pagina dei problemi di GitHub .

Compatibilità API di Debugger V2

Il debugger V2 è implementato a un livello relativamente basso dello stack software di TensorFlow ed è quindi compatibile con tf.keras , tf.data e altre API costruite al di sopra dei livelli inferiori di TensorFlow. Il debugger V2 è anche retrocompatibile con TF1, sebbene la linea temporale di esecuzione desiderosa sarà vuota per i log di debug generati dai programmi TF1.

Suggerimenti per l'uso dell'API

Una domanda frequente su questa API di debug è dove nel codice TensorFlow si dovrebbe inserire la chiamata a enable_dump_debug_info() . In genere, l'API deve essere richiamata il più presto possibile nel programma TF2, preferibilmente dopo le righe di importazione di Python e prima dell'inizio della creazione e dell'esecuzione del grafico. Ciò garantirà la copertura completa di tutte le operazioni e i grafici che alimentano il tuo modello e la sua formazione.

I tensor_debug_modes attualmente supportati sono: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE . Variano la quantità di informazioni estratte da ciascun tensore e il sovraccarico delle prestazioni del programma di cui è stato eseguito il debug. Fare riferimento alla sezione args della documentazione di enable_dump_debug_info() ].

Prestazioni generali

L'API di debug introduce un sovraccarico di prestazioni nel programma TensorFlow con strumentazione. Il sovraccarico varia in base a tensor_debug_mode , al tipo di hardware e alla natura del programma TensorFlow strumentato. Come punto di riferimento, su una GPU, la modalità NO_TENSOR aggiunge un overhead del 15% durante l'addestramento di un modello Transformer con dimensione batch 64. I sovraccarichi percentuali per altri metodi tensor_debug_ sono più elevati: circa il 50% per CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE modalità. Sulle CPU, l'overhead è leggermente inferiore. Su TPU, l'overhead è attualmente più elevato.

Relazione con altre API di debug di TensorFlow

TensorFlow offre altri strumenti e API per il debug. Puoi sfogliare tali API sotto lo spazio dei nomi tf.debugging.* Nella pagina dei documenti API. Tra queste API la più utilizzata è tf.print() . Quando si dovrebbe usare Debugger V2 e quando invece si dovrebbe tf.print() ? tf.print() è conveniente nel caso in cui

  1. sappiamo esattamente quali tensori stampare,
  2. sappiamo esattamente dove nel codice sorgente inserire quelle tf.print() ,
  3. il numero di tali tensori non è troppo grande.

Per altri casi (ad esempio, esaminando molti valori di tensore, esaminando i valori di tensore generati dal codice interno di TensorFlow e cercando l'origine dell'instabilità numerica come abbiamo mostrato sopra), Debugger V2 fornisce un modo più rapido di debug. Inoltre, Debugger V2 fornisce un approccio unificato all'ispezione di tensori desiderosi e grafici. Fornisce inoltre informazioni sulla struttura del grafico e le posizioni dei codici, che vanno oltre la capacità di tf.print() .

Un'altra API che può essere utilizzata per eseguire il debug di problemi che coinvolgono ∞ e NaN è tf.debugging.enable_check_numerics() . A differenza di enable_dump_debug_info() , enable_check_numerics() non salva le informazioni di debug sul disco. Al contrario, controlla semplicemente ∞ e NaN durante il runtime di TensorFlow ed esegue errori con la posizione del codice di origine non appena qualsiasi operazione genera tali valori numerici errati. Ha un sovraccarico di prestazioni inferiore rispetto a enable_dump_debug_info() , ma non offre una traccia completa della cronologia di esecuzione del programma e non ha un'interfaccia utente grafica come Debugger V2.