A volte durante un programma TensorFlow possono verificarsi eventi catastrofici che coinvolgono NaN s, paralizzando i processi di addestramento del modello. La causa principale di tali eventi è spesso oscura, soprattutto 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 elaborando un bug reale che coinvolge NaN 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 del tensore di runtime in programmi complessi. Questo tutorial si concentra sui NaN a causa della loro frequenza relativamente alta di occorrenza.
Osservando l'insetto
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 invocato 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 utilizziamo questa API più flessibile ma più soggetta a errori rispetto a quando utilizziamo la più semplice API di alto livello da utilizzare ma leggermente meno flessibili come tf.keras .
Il programma stampa un'accuratezza del test dopo ogni fase dell'allenamento. Possiamo vedere nella console che la precisione del test si blocca a un livello quasi casuale (~0,1) dopo il primo passaggio. Questo non è certamente il modo in cui ci si aspetta che si comporti 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 è davvero così e come troviamo l'operazione TensorFlow (op) responsabile della generazione dell'instabilità numerica? Per rispondere a queste domande, strumentiamo il programma buggy con Debugger V2.
Strumentazione del codice TensorFlow con Debugger V2
tf.debugging.experimental.enable_dump_debug_info() è il punto di ingresso API di Debugger V2. Strumenta un programma TF2 con una singola riga di codice. Ad esempio, l'aggiunta della riga seguente all'inizio del programma causerà la scrittura delle informazioni di debug 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 ansiosa, la creazione del grafico 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 tali eventi . La ricchezza delle informazioni di debug consente agli utenti di restringere il campo a bug oscuri.
tf.debugging.experimental.enable_dump_debug_info(
"/tmp/tfdbg2_logdir",
tensor_debug_mode="FULL_HEALTH",
circular_buffer_size=-1)
L'argomento tensor_debug_mode controlla quali informazioni Debugger V2 estrae da ciascun tensore ansioso o in-graph. "FULL_HEALTH" è una modalità che acquisisce le seguenti informazioni su ciascun tensore di tipo mobile (ad esempio, float32 comunemente visto e bfloat16 dtype meno comune):
- Tipo D
- Classifica
- Numero totale di elementi
- Una suddivisione degli elementi di tipo mobile nelle seguenti categorie: finito negativo (
-), zero (0), finito positivo (+), infinito negativo (-∞), infinito positivo (+∞) eNaN.
La modalità "FULL_HEALTH" è adatta per il debug di bug che coinvolgono NaN e infinity. Vedi sotto per altri tensor_debug_mode supportati.
L'argomento circular_buffer_size controlla quanti eventi tensor vengono salvati nella logdir. Il valore predefinito è 1000, il 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 (es. -1 qui).
L'esempio debug_mnist_v2 richiama enable_dump_debug_info() passandogli i flag della riga di comando. Per eseguire nuovamente il nostro programma problematico 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 Debugger V2 in TensorBoard
L'esecuzione del programma con la strumentazione del debugger crea una logdir in /tmp/tfdbg2_logdir. Possiamo avviare TensorBoard e puntarlo alla logdir con:
tensorboard --logdir /tmp/tfdbg2_logdir
Nel browser web, vai alla pagina di TensorBoard all'indirizzo http://localhost:6006. Il plug-in "Debugger V2" sarà inattivo per impostazione predefinita, quindi selezionalo dal menu "Plugin inattivi" in alto a destra. Una volta selezionato, dovrebbe apparire come segue:

Utilizzo della GUI di Debugger V2 per trovare la causa principale dei NaN
La GUI di 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 del programma TensorFlow strumentato. Ogni avviso indica una determinata anomalia che merita attenzione. Nel nostro caso, questa sezione mette in evidenza 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. A breve analizzeremo questi avvisi.
- Python Execution Timeline : questa è la metà superiore della sezione medio-alta. Presenta la storia completa dell'esecuzione ansiosa di operazioni e grafici. Ciascun riquadro della timeline è contrassegnato dalla lettera iniziale del nome dell'op o del grafico (es. “T” per “TensorSliceDataset” op, “m” per il “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à fondamentale per la nostra attività di debug. Contiene una cronologia di tutti i tensori di tipo d flottante calcolati all'interno dei grafici (cioè compilati da
@tf-functions). - La struttura del grafico (metà inferiore della sezione centrale in alto), il codice sorgente (sezione in basso a sinistra) e Stack Trace (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 nella nostra attività di debug.
Dopo esserci orientati all'organizzazione dell'interfaccia utente, procediamo come segue per andare a fondo del perché sono comparsi i NaN. Innanzitutto, fai clic sull'avviso NaN/∞ nella sezione Avvisi. Questo scorre automaticamente l'elenco di 600 tensori grafici nella sezione Graph Execution e si concentra sul #88, che è un tensore chiamato 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 in seguito contengono NaN. Possiamo confermarlo scorrendo su e giù l'elenco Esecuzione del grafico. Questa osservazione fornisce un forte indizio che il Log op è la fonte dell'instabilità numerica in questo programma TF2.

Perché questo Log op sputa un -∞? Per rispondere a questa domanda è necessario esaminare l'input dell'op. Facendo clic sul nome del tensore ( Log:0 ) viene visualizzata una visualizzazione semplice ma informativa delle vicinanze dell'operazione Log nel suo grafico TensorFlow nella sezione Struttura del grafico. Notare la direzione dall'alto verso il basso del flusso di informazioni. L'op stesso è mostrato in grassetto al centro. Immediatamente sopra di esso, possiamo vedere un'operazione Placeholder che fornisce l'unico input per l'operazione Log . Dov'è il tensore generato da questo segnaposto probs nell'elenco di esecuzione del grafico? Usando il colore di sfondo giallo come ausilio visivo, possiamo vedere che il tensore probs:0 è tre righe sopra il tensore Log:0 , cioè nella riga 85.

Uno sguardo più attento alla scomposizione numerica del tensore probs:0 nella riga 85 rivela perché il suo consumatore Log:0 produce un -∞: Tra i 1000 elementi di probs:0 , un elemento ha un valore di 0. 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 si verifichi NaN/∞. Ciò può essere ottenuto applicando il ritaglio (ad esempio, utilizzando tf.clip_by_value() ) sul tensore di Placeholder probs .
Ci stiamo avvicinando alla risoluzione del bug, ma non abbiamo ancora finito. Per applicare la correzione, è necessario sapere da dove ha avuto origine nel codice sorgente Python l'operazione Log e il relativo input Placeholder. 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 di cui stiamo effettivamente cercando di eseguire il debug). Facendo clic su "Riga 216" viene visualizzata una vista della riga di codice corrispondente nella sezione Codice sorgente.

Questo ci porta finalmente al codice sorgente che ha creato la problematica Log op dal suo input probs . Questa è la nostra funzione di perdita di entropia incrociata categoriale personalizzata decorata con @tf.function e quindi convertita in un grafico TensorFlow. Il segnaposto op probs corrisponde al primo argomento di input della funzione di perdita. L'operazione Log viene creata con la chiamata API tf.math.log().
La correzione del ritaglio del valore a questo bug sarà simile a:
diff = -(labels *
tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))
Risolverà l'instabilità numerica in questo programma TF2 e farà allenare con successo l'MLP. Un altro possibile approccio per correggere l'instabilità numerica consiste nell'usare tf.keras.losses.CategoricalCrossentropy .
Questo conclude il nostro viaggio dall'osservazione di un bug del modello TF2 alla creazione di una modifica al codice che corregge il bug, aiutato dallo strumento Debugger V2, che fornisce una visibilità completa nella cronologia di esecuzione del grafico e dell'avidità del programma TF2 strumentato, inclusi i riepiloghi numerici di valori tensoriale e associazione tra ops, tensors 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 il training 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 nella nostra pagina dei problemi di GitHub .
Compatibilità API di Debugger V2
Debugger V2 è implementato a un livello relativamente basso dello stack software di TensorFlow, e quindi è compatibile con tf.keras , tf.data e altre API basate sui livelli inferiori di TensorFlow. Debugger V2 è anche compatibile con le versioni precedenti di TF1, sebbene la linea temporale di esecuzione desiderosa sarà vuota per le directory di registro di debug generate 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 inizino 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_mode 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 del programma sottoposto a debug. Fare riferimento alla sezione args della documentazione di enable_dump_debug_info() .
Sovraccarico di prestazioni
L'API di debug introduce un sovraccarico delle prestazioni per il programma TensorFlow strumentato. L'overhead 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 sovraccarico del 15% durante l'addestramento di un modello Transformer con dimensione batch 64. L'overhead percentuale per altre modalità tensor_debug_mode è maggiore: 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ù alto.
Relazione con altre API di debug 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 API. Tra queste API la più usata è tf.print() . Quando si dovrebbe usare Debugger V2 e quando invece dovrebbe essere usato tf.print() ? tf.print() è conveniente nel caso in cui
- sappiamo esattamente quali tensori stampare,
- sappiamo dove esattamente nel codice sorgente inserire quelle
tf.print(), - il numero di tali tensori non è troppo grande.
Per altri casi (ad esempio, l'esame di molti valori tensoriali, l'esame dei valori tensoriali generati dal codice interno di TensorFlow e la ricerca dell'origine dell'instabilità numerica come mostrato sopra), Debugger V2 fornisce un modo più rapido per eseguire il debug. Inoltre, Debugger V2 fornisce un approccio unificato per l'ispezione dei tensori ansiosi e dei grafi. Fornisce inoltre informazioni sulla struttura del grafico e sulle posizioni del codice, che vanno oltre la capacità di tf.print() .
Un'altra API che può essere utilizzata per eseguire il debug dei 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 e gli errori con la posizione del codice di origine non appena qualsiasi operazione genera valori numerici così 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 viene fornito con un'interfaccia utente grafica come Debugger V2.
A volte durante un programma TensorFlow possono verificarsi eventi catastrofici che coinvolgono NaN s, paralizzando i processi di addestramento del modello. La causa principale di tali eventi è spesso oscura, soprattutto 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 elaborando un bug reale che coinvolge NaN 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 del tensore di runtime in programmi complessi. Questo tutorial si concentra sui NaN a causa della loro frequenza relativamente alta di occorrenza.
Osservando l'insetto
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 invocato 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 utilizziamo questa API più flessibile ma più soggetta a errori rispetto a quando utilizziamo la più semplice API di alto livello da utilizzare ma leggermente meno flessibili come tf.keras .
Il programma stampa un'accuratezza del test dopo ogni fase dell'allenamento. Possiamo vedere nella console che la precisione del test si blocca a un livello quasi casuale (~0,1) dopo il primo passaggio. Questo non è certamente il modo in cui ci si aspetta che si comporti 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 è davvero così e come troviamo l'operazione TensorFlow (op) responsabile della generazione dell'instabilità numerica? Per rispondere a queste domande, strumentiamo il programma buggy con Debugger V2.
Strumentazione del codice TensorFlow con Debugger V2
tf.debugging.experimental.enable_dump_debug_info() è il punto di ingresso API di Debugger V2. Strumenta un programma TF2 con una singola riga di codice. Ad esempio, l'aggiunta della riga seguente all'inizio del programma causerà la scrittura delle informazioni di debug 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 ansiosa, la creazione del grafico 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 tali eventi . La ricchezza delle informazioni di debug consente agli utenti di restringere il campo a bug oscuri.
tf.debugging.experimental.enable_dump_debug_info(
"/tmp/tfdbg2_logdir",
tensor_debug_mode="FULL_HEALTH",
circular_buffer_size=-1)
L'argomento tensor_debug_mode controlla quali informazioni Debugger V2 estrae da ciascun tensore ansioso o in-graph. "FULL_HEALTH" è una modalità che acquisisce le seguenti informazioni su ciascun tensore di tipo mobile (ad esempio, float32 comunemente visto e bfloat16 dtype meno comune):
- Tipo D
- Classifica
- Numero totale di elementi
- Una suddivisione degli elementi di tipo mobile nelle seguenti categorie: finito negativo (
-), zero (0), finito positivo (+), infinito negativo (-∞), infinito positivo (+∞) eNaN.
La modalità "FULL_HEALTH" è adatta per il debug di bug che coinvolgono NaN e infinity. Vedi sotto per altri tensor_debug_mode supportati.
L'argomento circular_buffer_size controlla quanti eventi tensor vengono salvati nella logdir. Il valore predefinito è 1000, il 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 (es. -1 qui).
L'esempio debug_mnist_v2 richiama enable_dump_debug_info() passandogli i flag della riga di comando. Per eseguire nuovamente il nostro programma problematico 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 Debugger V2 in TensorBoard
L'esecuzione del programma con la strumentazione del debugger crea una logdir in /tmp/tfdbg2_logdir. Possiamo avviare TensorBoard e puntarlo alla logdir con:
tensorboard --logdir /tmp/tfdbg2_logdir
Nel browser web, vai alla pagina di TensorBoard all'indirizzo http://localhost:6006. Il plug-in "Debugger V2" sarà inattivo per impostazione predefinita, quindi selezionalo dal menu "Plugin inattivi" in alto a destra. Una volta selezionato, dovrebbe apparire come segue:

Utilizzo della GUI di Debugger V2 per trovare la causa principale dei NaN
La GUI di 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 del programma TensorFlow strumentato. Ogni avviso indica una determinata anomalia che merita attenzione. Nel nostro caso, questa sezione mette in evidenza 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. A breve analizzeremo questi avvisi.
- Python Execution Timeline : questa è la metà superiore della sezione medio-alta. Presenta la storia completa dell'esecuzione ansiosa di operazioni e grafici. Ciascun riquadro della timeline è contrassegnato dalla lettera iniziale del nome dell'op o del grafico (es. “T” per “TensorSliceDataset” op, “m” per il “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à fondamentale per la nostra attività di debug. Contiene una cronologia di tutti i tensori di tipo d flottante calcolati all'interno dei grafici (cioè compilati da
@tf-functions). - La struttura del grafico (metà inferiore della sezione centrale in alto), il codice sorgente (sezione in basso a sinistra) e Stack Trace (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 nella nostra attività di debug.
Dopo esserci orientati all'organizzazione dell'interfaccia utente, procediamo come segue per andare a fondo del perché sono comparsi i NaN. Innanzitutto, fai clic sull'avviso NaN/∞ nella sezione Avvisi. Questo scorre automaticamente l'elenco di 600 tensori grafici nella sezione Graph Execution e si concentra sul #88, che è un tensore chiamato 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 in seguito contengono NaN. Possiamo confermarlo scorrendo su e giù l'elenco Esecuzione del grafico. Questa osservazione fornisce un forte indizio che il Log op è la fonte dell'instabilità numerica in questo programma TF2.

Perché questo Log op sputa un -∞? Per rispondere a questa domanda è necessario esaminare l'input dell'op. Facendo clic sul nome del tensore ( Log:0 ) viene visualizzata una visualizzazione semplice ma informativa delle vicinanze dell'operazione Log nel suo grafico TensorFlow nella sezione Struttura del grafico. Notare la direzione dall'alto verso il basso del flusso di informazioni. L'op stesso è mostrato in grassetto al centro. Immediatamente sopra di esso, possiamo vedere un'operazione Placeholder che fornisce l'unico input per l'operazione Log . Dov'è il tensore generato da questo segnaposto probs nell'elenco di esecuzione del grafico? Usando il colore di sfondo giallo come ausilio visivo, possiamo vedere che il tensore probs:0 è tre righe sopra il tensore Log:0 , cioè nella riga 85.

Uno sguardo più attento alla scomposizione numerica del tensore probs:0 nella riga 85 rivela perché il suo consumatore Log:0 produce un -∞: Tra i 1000 elementi di probs:0 , un elemento ha un valore di 0. 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 si verifichi NaN/∞. Ciò può essere ottenuto applicando il ritaglio (ad esempio, utilizzando tf.clip_by_value() ) sul tensore di Placeholder probs .
Ci stiamo avvicinando alla risoluzione del bug, ma non abbiamo ancora finito. Per applicare la correzione, è necessario sapere da dove ha avuto origine nel codice sorgente Python l'operazione Log e il relativo input Placeholder. 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 di cui stiamo effettivamente cercando di eseguire il debug). Facendo clic su "Riga 216" viene visualizzata una vista della riga di codice corrispondente nella sezione Codice sorgente.

Questo ci porta finalmente al codice sorgente che ha creato la problematica Log op dal suo input probs . Questa è la nostra funzione di perdita di entropia incrociata categoriale personalizzata decorata con @tf.function e quindi convertita in un grafico TensorFlow. Il segnaposto op probs corrisponde al primo argomento di input della funzione di perdita. L'operazione Log viene creata con la chiamata API tf.math.log().
La correzione del ritaglio del valore a questo bug sarà simile a:
diff = -(labels *
tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))
Risolverà l'instabilità numerica in questo programma TF2 e farà allenare con successo l'MLP. Un altro possibile approccio per correggere l'instabilità numerica consiste nell'usare tf.keras.losses.CategoricalCrossentropy .
Questo conclude il nostro viaggio dall'osservazione di un bug del modello TF2 alla creazione di una modifica al codice che corregge il bug, aiutato dallo strumento Debugger V2, che fornisce una visibilità completa nella cronologia di esecuzione del grafico e dell'avidità del programma TF2 strumentato, inclusi i riepiloghi numerici di valori tensoriale e associazione tra ops, tensors 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 il training 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 nella nostra pagina dei problemi di GitHub .
Compatibilità API di Debugger V2
Debugger V2 è implementato a un livello relativamente basso dello stack software di TensorFlow, e quindi è compatibile con tf.keras , tf.data e altre API basate sui livelli inferiori di TensorFlow. Debugger V2 è anche compatibile con le versioni precedenti di TF1, sebbene la linea temporale di esecuzione desiderosa sarà vuota per le directory di registro di debug generate 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 inizino 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_mode 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 del programma sottoposto a debug. Fare riferimento alla sezione args della documentazione di enable_dump_debug_info() .
Sovraccarico di prestazioni
L'API di debug introduce un sovraccarico delle prestazioni per il programma TensorFlow strumentato. L'overhead 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 sovraccarico del 15% durante l'addestramento di un modello Transformer con dimensione batch 64. L'overhead percentuale per altre modalità tensor_debug_mode è maggiore: 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ù alto.
Relazione con altre API di debug 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 API. Tra queste API la più usata è tf.print() . Quando si dovrebbe usare Debugger V2 e quando invece dovrebbe essere usato tf.print() ? tf.print() è conveniente nel caso in cui
- sappiamo esattamente quali tensori stampare,
- sappiamo dove esattamente nel codice sorgente inserire quelle
tf.print(), - il numero di tali tensori non è troppo grande.
Per altri casi (ad esempio, l'esame di molti valori tensoriali, l'esame dei valori tensoriali generati dal codice interno di TensorFlow e la ricerca dell'origine dell'instabilità numerica come mostrato sopra), Debugger V2 fornisce un modo più rapido per eseguire il debug. Inoltre, Debugger V2 fornisce un approccio unificato per l'ispezione dei tensori ansiosi e dei grafi. Fornisce inoltre informazioni sulla struttura del grafico e sulle posizioni del codice, che vanno oltre la capacità di tf.print() .
Un'altra API che può essere utilizzata per eseguire il debug dei 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 e gli errori con la posizione del codice di origine non appena qualsiasi operazione genera valori numerici così 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 viene fornito con un'interfaccia utente grafica come Debugger V2.