Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Debuggen numerischer Probleme in TensorFlow-Programmen mit TensorBoard Debugger V2

Während eines TensorFlow-Programms können manchmal katastrophale Ereignisse mit NaN auftreten, die die Modelltrainingsprozesse lähmen. Die Hauptursache für solche Ereignisse ist häufig unklar, insbesondere bei Modellen mit nicht trivialer Größe und Komplexität. Um das Debuggen dieser Art von Modellfehlern zu vereinfachen, bietet TensorBoard 2.3+ (zusammen mit TensorFlow 2.3+) ein spezielles Dashboard namens Debugger V2. Hier zeigen wir, wie dieses Tool verwendet wird, indem ein echter Fehler mit NaNs in einem in TensorFlow geschriebenen neuronalen Netzwerk behoben wird.

Die in diesem Lernprogramm dargestellten Techniken sind auf andere Arten von Debugging-Aktivitäten anwendbar, z. B. das Überprüfen der Laufzeit-Tensorformen in komplexen Programmen. Dieses Tutorial konzentriert sich auf NaNs aufgrund ihrer relativ hohen Häufigkeit.

Den Fehler beobachten

Der Quellcode des TF2-Programms, das wir debuggen, ist auf GitHub verfügbar . Das Beispielprogramm ist auch in das Tensorflow-Pip-Paket (Version 2.3+) gepackt und kann aufgerufen werden durch:

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

Dieses TF2-Programm erstellt eine mehrschichtige Wahrnehmung (MLP) und trainiert sie, um MNIST- Bilder zu erkennen. In diesem Beispiel wird absichtlich die Low-Level-API von TF2 verwendet, um benutzerdefinierte Layer-Konstrukte, Verlustfunktionen und Trainingsschleifen zu definieren, da die Wahrscheinlichkeit von NaN-Fehlern höher ist, wenn wir diese flexiblere, aber fehleranfälligere API verwenden, als wenn wir die einfachere verwenden -zu verwendende, aber etwas weniger flexible High-Level-APIs wie tf.keras .

Das Programm druckt nach jedem Trainingsschritt eine Testgenauigkeit. Wir können in der Konsole sehen, dass die Testgenauigkeit nach dem ersten Schritt auf einem nahezu zufälligen Niveau (~ 0,1) hängen bleibt. Dies ist sicherlich nicht das Verhalten des Modelltrainings: Wir erwarten, dass sich die Genauigkeit mit zunehmendem Schritt allmählich 1,0 (100%) nähert.

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

Eine fundierte Vermutung ist, dass dieses Problem durch eine numerische Instabilität wie NaN oder unendlich verursacht wird. Wie bestätigen wir jedoch, dass dies wirklich der Fall ist, und wie finden wir die TensorFlow-Operation (op), die für die Erzeugung der numerischen Instabilität verantwortlich ist? Um diese Fragen zu beantworten, instrumentieren wir das Buggy-Programm mit Debugger V2.

Instrumentieren von TensorFlow-Code mit Debugger V2

tf.debugging.experimental.enable_dump_debug_info() ist der API-Einstiegspunkt von Debugger V2. Es instrumentiert ein TF2-Programm mit einer einzigen Codezeile. Wenn Sie beispielsweise die folgende Zeile am Anfang des Programms hinzufügen, werden Debug-Informationen in das Protokollverzeichnis (logdir) unter / tmp / tfdbg2_logdir geschrieben. Die Debug-Informationen decken verschiedene Aspekte der TensorFlow-Laufzeit ab. In TF2 enthält es den vollständigen Verlauf der eifrigen Ausführung, die von @ tf.function durchgeführte Diagrammerstellung , die Ausführung der Diagramme, die durch die Ausführungsereignisse generierten Tensorwerte sowie die Codeposition (Python-Stack-Traces) dieser Ereignisse . Die Fülle der Debug-Informationen ermöglicht es Benutzern, obskure Fehler einzugrenzen.

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

Das Argument tensor_debug_mode steuert, welche Informationen Debugger V2 aus jedem eifrigen oder grafischen Tensor extrahiert. "FULL_HEALTH" ist ein Modus, der die folgenden Informationen zu jedem Tensor vom Floating-Typ erfasst (z. B. den häufig vorkommenden float32- und den weniger häufig vorkommenden bfloat16- Typ):

  • DType
  • Rang
  • Gesamtzahl der Elemente
  • Eine Aufteilung der Elemente vom schwebenden Typ in die folgenden Kategorien: negativ endlich ( - ), null ( 0 ), positiv endlich ( + ), negativ unendlich ( -∞ ), positiv unendlich ( +∞ ) und NaN .

Der Modus „FULL_HEALTH“ eignet sich zum Debuggen von Fehlern mit NaN und unendlich. tensor_debug_mode unten finden Sie weitere unterstützte tensor_debug_mode s.

Das Argument circular_buffer_size steuert, wie viele Tensorereignisse im Protokollverzeichnis gespeichert werden. Der Standardwert ist 1000, wodurch nur die letzten 1000 Tensoren vor dem Ende des instrumentierten TF2-Programms auf der Festplatte gespeichert werden. Dieses Standardverhalten reduziert den Debugger-Overhead, indem die Vollständigkeit der Debug-Daten beeinträchtigt wird. Wenn die Vollständigkeit bevorzugt wird, wie in diesem Fall, können wir den Umlaufpuffer deaktivieren, indem wir das Argument auf einen negativen Wert setzen (z. B. -1 hier).

Das Beispiel debug_mnist_v2 ruft enable_dump_debug_info() auf, indem Befehlszeilenflags übergeben werden. Gehen Sie wie folgt vor, um unser problematisches TF2-Programm mit aktivierter Debugging-Instrumentierung erneut auszuführen:

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

Starten der Debugger V2-GUI in TensorBoard

Durch Ausführen des Programms mit der Debugger-Instrumentierung wird ein Protokollverzeichnis unter / tmp / tfdbg2_logdir erstellt. Wir können TensorBoard starten und auf das logdir zeigen mit:

 tensorboard --logdir /tmp/tfdbg2_logdir
 

Navigieren Sie im Webbrowser zur Seite von TensorBoard unter http: // localhost: 6006. Das Plugin "Debugger V2" sollte standardmäßig aktiviert sein und eine Seite anzeigen, die wie folgt aussieht:

Vollbild-Screenshot von Debugger V2

Verwenden der Debugger V2-Benutzeroberfläche, um die Hauptursache für NaNs zu ermitteln

Die Debugger V2-Benutzeroberfläche in TensorBoard ist in sechs Abschnitte unterteilt:

  • Warnungen : Dieser Abschnitt oben links enthält eine Liste der vom Debugger erkannten "Warn" -Ereignisse in den Debug-Daten des instrumentierten TensorFlow-Programms. Jede Warnung weist auf eine bestimmte Anomalie hin, die Aufmerksamkeit verdient. In unserem Fall werden in diesem Abschnitt 499 NaN / ∞-Ereignisse mit einer hervorstechenden rosaroten Farbe hervorgehoben. Dies bestätigt unseren Verdacht, dass das Modell aufgrund des Vorhandenseins von NaNs und / oder Unendlichkeiten in seinen internen Tensorwerten nicht lernen kann. Wir werden uns in Kürze mit diesen Warnungen befassen.
  • Python-Ausführungszeitleiste : Dies ist die obere Hälfte des oberen mittleren Abschnitts. Es zeigt die gesamte Geschichte der eifrigen Ausführung von Operationen und Grafiken. Jedes Feld der Zeitleiste wird durch den Anfangsbuchstaben des op oder Graph Namen (zB „T“ für die „TensorSliceDataset“ op „m“ für das „Modell“ markiert tf.function ). Wir können diese Zeitleiste mithilfe der Navigationsschaltflächen und der Bildlaufleiste über der Zeitleiste navigieren.
  • Diagrammausführung : Dieser Abschnitt befindet sich in der oberen rechten Ecke der GUI und ist von zentraler Bedeutung für unsere Debugging-Aufgabe. Es enthält eine Historie aller Floating-D-Typ-Tensoren, die in Graphen berechnet wurden (dh von @tf-function s kompiliert wurden).
  • Die Diagrammstruktur (untere Hälfte des oberen mittleren Abschnitts), der Quellcode (unterer linker Abschnitt) und die Stapelverfolgung (unterer rechter unterer Abschnitt) sind anfänglich leer. Ihre Inhalte werden ausgefüllt, wenn wir mit der GUI interagieren. Diese drei Abschnitte spielen auch eine wichtige Rolle in unserer Debugging-Aufgabe.

Nachdem wir uns an der Organisation der Benutzeroberfläche orientiert haben, gehen wir folgendermaßen vor, um herauszufinden, warum die NaNs angezeigt wurden. Klicken Sie zunächst im Abschnitt Warnungen auf die NaN / ∞- Warnung. Dies führt automatisch einen Bildlauf durch die Liste der 600 Graphentensoren im Abschnitt Graphausführung durch und konzentriert sich auf # 88, einen Tensor mit dem Namen "Log: 0", der durch eine Log (natürlicher Logarithmus) generiert wird. Eine hervorstechende rosa-rote Farbe hebt ein -∞-Element unter den 1000 Elementen des 2D-float32-Tensors hervor. Dies ist der erste Tensor in der Laufzeithistorie des TF2-Programms, der NaN oder Unendlichkeit enthielt: Tensoren, die zuvor berechnet wurden, enthalten weder NaN noch ∞; Viele (tatsächlich die meisten) später berechneten Tensoren enthalten NaNs. Wir können dies bestätigen, indem wir in der Graph Execution-Liste nach oben und unten scrollen. Diese Beobachtung liefert einen starken Hinweis darauf, dass der Log Op die Ursache für die numerische Instabilität in diesem TF2-Programm ist.

Debugger V2: Nan / Infinity-Warnungen und Liste der Diagrammausführung

Warum spuckt dieser Log op ein -∞ aus? Die Beantwortung dieser Frage erfordert die Prüfung der Eingabe für die Operation. Durch Klicken auf den Namen des Tensors („Log: 0“) wird eine einfache, aber informative Visualisierung der Umgebung des Log -Ops in seinem TensorFlow-Diagramm im Abschnitt „Diagrammstruktur“ angezeigt. Beachten Sie die Richtung des Informationsflusses von oben nach unten. Die Operation selbst ist in der Mitte fett dargestellt. Unmittelbar darüber sehen wir, dass ein Platzhalter-Op die einzige Eingabe für den Log Op liefert. Wo wird der Tensor generiert, der durch diesen Protokollplatzhalter in der Liste der Diagrammausführung generiert wird? Wenn Sie die gelbe Hintergrundfarbe als visuelle Hilfe verwenden, können Sie sehen, dass der Tensor logits:0 zwei Zeilen über dem Tensor Log:0 liegt, logits:0 in Zeile 85.

Debugger V2: Diagrammstrukturansicht und Ablaufverfolgung zum Eingabetensor

Ein genauerer Blick auf die numerische Aufschlüsselung des Tensors "logits: 0" in Zeile 85 zeigt, warum sein Consumer- Log:0 ein -∞ erzeugt: Unter den 1000 Elementen von "logits: 0" hat ein Element den Wert 0. Das -∞ ergibt sich aus der Berechnung des natürlichen Logarithmus von 0! Wenn wir irgendwie sicherstellen können, dass die Protokolloperation nur positiven Eingaben ausgesetzt wird, können wir verhindern, dass NaN / ∞ auftritt. Dies kann erreicht werden, indem Clipping (z. B. mithilfe von tf.clip_by_value () ) auf den Tensor für Platzhalterprotokolle angewendet wird.

Wir nähern uns der Lösung des Fehlers, sind aber noch nicht ganz fertig. Um das Update anwenden zu können, müssen wir wissen, woher im Python-Quellcode das Log op und seine Platzhalter-Eingabe stammen. Debugger V2 bietet erstklassige Unterstützung für die Verfolgung der Grafikoperationen und Ausführungsereignisse bis zu ihrer Quelle. Als wir in Graph Executions auf den Tensor Log:0 geklickt haben, wurde der Abschnitt Stack Trace mit dem ursprünglichen Stack Trace der Erstellung des Log op gefüllt. Der Stack-Trace ist etwas groß, da er viele Frames aus dem internen Code von TensorFlow enthält (z. B. gen_math_ops.py und dumping_callback.py), die wir für die meisten Debugging-Aufgaben ignorieren können. Der interessierende Rahmen ist Zeile 216 von debug_mnist_v2.py (dh die Python-Datei, die wir tatsächlich zu debuggen versuchen). Durch Klicken auf "Zeile 204" wird eine Ansicht der entsprechenden Codezeile im Abschnitt "Quellcode" angezeigt.

Debugger V2: Quellcode und Stack-Trace

Dies bringt uns schließlich zu dem Quellcode, der das problematische Log-Op aus seiner Log-Eingabe erstellt hat. Dies ist unsere benutzerdefinierte kategoriale Cross- @tf.function -Loss-Funktion, die mit @tf.function dekoriert und daher in ein TensorFlow-Diagramm konvertiert wurde. Der Platzhalter op "logits" entspricht dem ersten Eingabeargument für die Verlustfunktion. Die Log wird mit dem API-Aufruf tf.math.log () erstellt.

Das Value-Clipping-Update für diesen Fehler sieht ungefähr so ​​aus:

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

Dadurch wird die numerische Instabilität in diesem TF2-Programm behoben und der MLP wird erfolgreich trainiert. Ein anderer möglicher Ansatz zur Behebung der numerischen Instabilität ist die Verwendung von tf.keras.losses.CategoricalCrossentropy .

Dies schließt unsere Reise von der Beobachtung eines TF2-Modellfehlers bis zur Entwicklung einer Codeänderung ab, die den Fehler mithilfe des Debugger V2-Tools behebt und einen vollständigen Einblick in den eifrigen und grafischen Ausführungsverlauf des instrumentierten TF2-Programms einschließlich der numerischen Zusammenfassungen bietet von Tensorwerten und Assoziation zwischen Ops, Tensoren und ihrem ursprünglichen Quellcode.

Hardwarekompatibilität von Debugger V2

Debugger V2 unterstützt die gängige Trainingshardware einschließlich CPU und GPU. Multi-GPU-Training mit tf.distributed.MirroredStrategy wird ebenfalls unterstützt. Die Unterstützung für TPU befindet sich noch in einem frühen Stadium und erfordert einen Anruf

 tf.config.set_soft_device_placement(True)
 

vor dem Aufruf von enable_dump_debug_info() . TPUs können auch andere Einschränkungen aufweisen. Wenn Sie Probleme mit Debugger V2 haben, melden Sie Fehler auf unserer GitHub-Seite .

API-Kompatibilität von Debugger V2

Debugger V2 wird auf einer relativ niedrigen Ebene des TensorFlow-Software-Stacks implementiert und ist daher mit tf.keras , tf.data und anderen APIs kompatibel, die auf den niedrigeren Ebenen von TensorFlow basieren . Debugger V2 ist auch abwärtskompatibel mit TF1, obwohl die Eager Execution Timeline für die von TF1-Programmen generierten Debug-Protokollverzeichnisse leer ist.

Tipps zur API-Verwendung

Eine häufig gestellte Frage zu dieser Debugging-API ist, wo im TensorFlow-Code der Aufruf von enable_dump_debug_info() . Normalerweise sollte die API in Ihrem TF2-Programm so früh wie möglich aufgerufen werden, vorzugsweise nach den Python-Importzeilen und bevor mit dem Erstellen und Ausführen von Diagrammen begonnen wird. Dadurch wird sichergestellt, dass alle Operationen und Grafiken, die Ihr Modell und sein Training unterstützen, vollständig abgedeckt sind.

Die derzeit unterstützten tensor_debug_modes sind: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH und SHAPE . Sie variieren in der Menge an Informationen, die aus jedem Tensor extrahiert werden, und im Leistungsaufwand für das debuggte Programm. enable_dump_debug_info() Sie im Abschnitt args in der enable_dump_debug_info() von enable_dump_debug_info() .

Leistungsaufwand

Die Debugging-API führt zu Leistungsaufwand für das instrumentierte TensorFlow-Programm. Der Overhead variiert je nach tensor_debug_mode , Hardwaretyp und Art des instrumentierten TensorFlow-Programms. Als Referenzpunkt auf einer GPU fügt der NO_TENSOR Modus während des Trainings eines Transformer-Modells unter NO_TENSOR einen Overhead von 15% hinzu. Der prozentuale Overhead für andere tensor_debug_modes ist höher: ungefähr 50% für CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH und SHAPE Modi. Auf CPUs ist der Overhead etwas geringer. Bei TPUs ist der Overhead derzeit höher.

Beziehung zu anderen TensorFlow-Debugging-APIs

Beachten Sie, dass TensorFlow andere Tools und APIs zum Debuggen bietet. Sie können solche APIs unter dem Namespace tf.debugging.* Auf der Seite API-Dokumente durchsuchen. Unter diesen APIs wird am häufigsten tf.print() . Wann sollte man Debugger V2 verwenden und wann sollte tf.print() verwendet werden? tf.print() ist praktisch, wenn

  1. wir wissen genau, welche Tensoren gedruckt werden sollen,
  2. Wir wissen, wo genau im Quellcode diese tf.print() sollen.
  3. Die Anzahl solcher Tensoren ist nicht zu groß.

In anderen Fällen (z. B. Untersuchen vieler Tensorwerte, Untersuchen von Tensorwerten, die durch den internen Code von TensorFlow generiert wurden, und Suchen nach dem Ursprung der numerischen Instabilität, wie oben gezeigt) bietet Debugger V2 eine schnellere Möglichkeit zum Debuggen. Darüber hinaus bietet Debugger V2 einen einheitlichen Ansatz für die Überprüfung von Eifersucht- und Graphentensoren. Es enthält außerdem Informationen zur Diagrammstruktur und zu den Codepositionen, die außerhalb der Möglichkeiten von tf.print() .

Eine weitere API, die zum Debuggen von Problemen mit ∞ und NaN verwendet werden kann, ist tf.debugging.enable_check_numerics() . Im Gegensatz zu enable_dump_debug_info() enable_check_numerics() keine Debug-Informationen auf der Festplatte. Stattdessen werden lediglich ∞ und NaN während der TensorFlow-Laufzeit überwacht und Fehler mit der Position des Ursprungscodes ausgegeben, sobald eine Operation solche schlechten Zahlenwerte generiert. Es hat einen geringeren Leistungsaufwand als enable_dump_debug_info() , bietet jedoch keinen vollständigen enable_dump_debug_info() über den Ausführungsverlauf des Programms und verfügt nicht über eine grafische Benutzeroberfläche wie Debugger V2.