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, soprattutto per i 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 mostriamo come utilizzare questo strumento lavorando su un bug reale 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 dei tensori di runtime in programmi complessi. Questo tutorial si concentra sui NaN a causa della loro frequenza relativamente alta di occorrenza.

Osservando il bug

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

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

Questo programma TF2 crea una percezione multistrato (MLP) e la addestra a 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, perché la probabilità di bug NaN è maggiore quando usiamo questa API più flessibile ma più soggetta a errori rispetto a quando usiamo la più semplice API di alto livello da usare ma leggermente meno flessibili come tf.keras .

Il programma stampa una precisione del test dopo ogni fase di addestramento. Possiamo vedere nella console che l'accuratezza del test si blocca a un livello quasi casuale (~ 0,1) dopo il primo passaggio. Questo non è certamente il modo in cui dovrebbe comportarsi l'addestramento del modello: ci aspettiamo che l'accuratezza si avvicini gradualmente a 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 plausibile è che questo problema sia causato da un'instabilità numerica, come NaN o infinito. Tuttavia, come confermiamo che le cose stanno davvero così e come troviamo che l'operazione TensorFlow (op) sia responsabile della generazione dell'instabilità numerica? Per rispondere a queste domande, strumentiamo il programma difettoso 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 farà sì che le informazioni di debug vengano scritte nella directory di registro (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 tensoriali generati dagli eventi di esecuzione, nonché la posizione del codice (tracce dello stack Python) di tali 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 il Debugger V2 estrae da ogni tensore desideroso o in-graph. "FULL_HEALTH" è una modalità che cattura le seguenti informazioni su ogni tensore di tipo floating (ad esempio, il comune float32 e il meno comune bfloat16 dtype):

  • DType
  • Rango
  • Numero totale di elementi
  • Una ripartizione degli elementi di tipo floating 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 quanti eventi tensoriali vengono salvati nella directory di log. Il valore predefinito è 1000, che fa sì che solo gli ultimi 1000 tensori prima della fine del programma TF2 strumentato vengano salvati su disco. Questo comportamento predefinito riduce l'overhead del debugger sacrificando la completezza dei dati di debug. Se si preferisce la completezza, come in questo caso, possiamo disabilitare il buffer circolare impostando l'argomento su un valore negativo (ad esempio, -1 qui).

L'esempio debug_mnist_v2 richiama enable_dump_debug_info() passando i flag della riga di comando ad esso. 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 del debugger crea una directory di log in / tmp / tfdbg2_logdir. Possiamo avviare TensorBoard e puntarlo al logdir con:

tensorboard --logdir /tmp/tfdbg2_logdir

Nel browser web, vai alla pagina di TensorBoard su http: // localhost: 6006. Il plug-in "Debugger V2" dovrebbe essere attivato per impostazione predefinita, visualizzando una pagina simile alla seguente:

Schermata di visualizzazione completa del debugger V2

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

La GUI del debugger V2 in TensorBoard è organizzata in sei sezioni:

  • Avvisi : questa sezione in alto a sinistra contiene un elenco di eventi di "avviso" 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 / ∞ con un colore rosa-rosso saliente. Ciò conferma il nostro sospetto che il modello non riesca ad apprendere a causa della presenza di NaN e / o infiniti nei suoi valori tensoriali interni. Approfondiremo a breve questi avvisi.
  • Sequenza temporale dell'esecuzione di Python : questa è la metà superiore della sezione centrale in alto. Presenta la storia completa dell'esecuzione impaziente di operazioni e grafici. Ogni casella della timeline è contrassegnata dalla lettera iniziale del nome dell'operazione o del grafico (ad esempio, "T" per l'operazione "TensorSliceDataset", "m" per la funzione "modello" tf.function . 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 di tipo a virgola mobile calcolati all'interno dei grafici (cioè 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 lo Stack Trace (sezione in basso a destra) sono inizialmente vuoti. Il loro contenuto verrà popolato quando interagiamo con la GUI. Queste tre sezioni giocheranno anche un ruolo importante nella nostra attività di debug.

Essendoci orientati all'organizzazione della UI, procediamo come segue per arrivare in fondo al motivo per cui sono apparsi i NaN. Innanzitutto, fai clic sull'avviso NaN / ∞ nella sezione Avvisi. Questo scorre automaticamente l'elenco dei 600 tensori grafico nella sezione Esecuzione Grafico e si concentra sulla # 88, che è un tensore di nome “Log: 0” generato da un Log (logaritmo naturale) op. Un colore rosa-rosso saliente evidenzia un elemento -∞ tra i 1000 elementi del tensore 2D float32. Questo è il primo tensore nella cronologia di runtime del programma TF2 che conteneva NaN o infinito: i tensori calcolati prima di esso non contengono NaN o ∞; molti (in effetti, la maggior parte) tensori calcolati successivamente contengono NaN. Possiamo confermarlo scorrendo su e giù l'elenco di esecuzione del grafico. Questa osservazione fornisce un forte suggerimento che il Log op è la fonte dell'instabilità numerica in questo programma TF2.

Debugger V2: avvisi Nan / Infinity ed elenco di esecuzione del grafico

Perché questa operazione di Log sputa un -∞? Per rispondere a questa domanda è necessario esaminare l'input all'op. Facendo clic sul nome del tensore ("Log: 0") si ottiene una visualizzazione semplice ma informativa delle vicinanze del Log op nel suo grafico TensorFlow nella sezione Graph Structure. Notare la direzione dall'alto verso il basso del flusso di informazioni. L'operazione stessa è mostrata in grassetto al centro. Immediatamente sopra, possiamo vedere che un segnaposto fornisce l'unico input per l'operazione Log . Dov'è il tensore generato da questo segnaposto logit nell'elenco di esecuzione del grafico? Usando il colore di sfondo giallo come aiuto visivo, possiamo vedere che il tensore logits:0 è due righe sopra il tensore Log:0 , cioè nella riga 85.

Debugger V2: visualizzazione della struttura del grafico e tracciamento del tensore di input

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

Ci stiamo avvicinando alla risoluzione del bug, ma non ancora del tutto. Per applicare la correzione, dobbiamo sapere dove hanno avuto origine nel codice sorgente Python l'operazione Log e il suo input segnaposto. Debugger V2 fornisce un supporto di prima classe per tracciare le operazioni del grafico e gli eventi di esecuzione alla loro origine. Quando abbiamo fatto clic sul tensore Log:0 in Graph Executions, la sezione Stack Trace è stata popolata con la traccia dello stack originale della creazione dell'operazione Log. La traccia dello stack è piuttosto grande perché include molti frame dal codice interno di TensorFlow (ad esempio, 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 riga 216 di debug_mnist_v2.py (cioè, il file Python che stiamo effettivamente cercando di eseguire il debug). Facendo clic su "Riga 204" viene visualizzata una visualizzazione della riga di codice corrispondente nella sezione Codice sorgente.

Debugger V2: codice sorgente e traccia dello stack

Questo finalmente ci porta al codice sorgente che ha creato il problematico Log op dal suo input logit. Questa è la nostra funzione di perdita di entropia incrociata categorica personalizzata decorata con la funzione @tf.function e quindi convertita in un grafico TensorFlow. L'opzione Segnaposto "logits" corrisponde al primo argomento di input della funzione di perdita. L'operazione di Log viene creata con la chiamata API tf.math.log ().

La correzione del valore aggiunto a questo bug 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 risolve il bug, aiutato dallo strumento Debugger V2, che fornisce piena visibilità nella cronologia di esecuzione entusiasta e grafica del programma TF2 strumentato, inclusi i riepiloghi numerici di valori tensoriali e associazione tra op, tensori e il loro codice sorgente originale.

Compatibilità hardware del Debugger V2

Debugger V2 supporta l'hardware di formazione tradizionale, tra cui CPU e GPU. È supportato anche l'addestramento multi-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 sulle TPU. Se riscontri problemi con Debugger V2, segnala i bug sulla nostra pagina dei problemi di GitHub .

Compatibilità API del 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 sui livelli inferiori di TensorFlow. Il debugger V2 è anche retrocompatibile con TF1, sebbene la timeline di esecuzione di Eager sarà vuota per i logdir di debug generati dai programmi TF1.

Suggerimenti per l'utilizzo 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 dovrebbe essere chiamata il prima possibile nel programma TF2, preferibilmente dopo le righe di importazione di Python e prima che inizi la creazione e l'esecuzione del grafico. Ciò garantirà una copertura completa di tutte le operazioni e i grafici che alimentano il tuo modello e la sua formazione.

Le modalità tensor_debug_modalità attualmente supportate sono: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE . Variano nella quantità di informazioni estratte da ciascun tensore e nel sovraccarico delle prestazioni per il programma sottoposto a debug. Fare riferimento alla sezione args della documentazione di enable_dump_debug_info() ].

Spese generali

L'API di debug introduce l'overhead delle prestazioni nel programma TensorFlow strumentato. L'overhead varia in base a tensor_debug_mode , tipo di hardware e 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 dimensioni batch 64. Le percentuali di overhead per altre modalità tensor_debug_modes sono maggiori: circa il 50% per CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE modalità. Sulle CPU, l'overhead è leggermente inferiore. Sulle TPU, l'overhead è attualmente più elevato.

Relazione con altre API di debug di TensorFlow

Tieni presente che TensorFlow offre altri strumenti e API per il debug. Puoi sfogliare tali API nello spazio dei nomi tf.debugging.* Nella pagina dei documenti dell'API. Tra queste API quella più utilizzata è tf.print() . Quando si dovrebbe usare il Debugger V2 e quando invece dovrebbe essere usato tf.print() ? tf.print() è conveniente nel caso in cui

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

Per altri casi (ad esempio, esaminando molti valori tensoriali, esaminando valori tensoriali generati dal codice interno di TensorFlow e cercando l'origine dell'instabilità numerica come mostrato sopra), Debugger V2 fornisce un modo più veloce di debug. Inoltre, Debugger V2 fornisce un approccio unificato per l'ispezione di tensori desiderosi e grafici. Fornisce inoltre informazioni sulla struttura del grafico e sulle posizioni del codice, che sono 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. Invece, monitora semplicemente ∞ e NaN durante il runtime di TensorFlow ed effettua errori con la posizione del codice di origine non appena un op genera valori numerici così errati. Ha un overhead di prestazioni inferiore rispetto a enable_dump_debug_info() , ma non offre una traccia completa della cronologia di esecuzione del programma e non viene fornito con un'interfaccia utente grafica come Debugger V2.