Cette page a été traduite par l'API Cloud Translation.
Switch to English

Débogage des problèmes numériques dans les programmes TensorFlow à l'aide de TensorBoard Debugger V2

Des événements catastrophiques impliquant des NaN peuvent parfois se produire pendant un programme TensorFlow, paralysant les processus de formation du modèle. La cause profonde de tels événements est souvent obscure, en particulier pour les modèles de taille et de complexité non triviales. Pour faciliter le débogage de ce type de bogues de modèle, TensorBoard 2.3+ (avec TensorFlow 2.3+) fournit un tableau de bord spécialisé appelé Debugger V2. Nous montrons ici comment utiliser cet outil en travaillant sur un vrai bogue impliquant des NaN dans un réseau neuronal écrit en TensorFlow.

Les techniques illustrées dans ce didacticiel s'appliquent à d'autres types d'activités de débogage, telles que l'inspection des formes de tenseurs d'exécution dans des programmes complexes. Ce didacticiel se concentre sur les NaN en raison de leur fréquence d'apparition relativement élevée.

Observer le bug

Le code source du programme TF2 que nous déboguerons est disponible sur GitHub . L'exemple de programme est également inclus dans le package tensorflow pip (version 2.3+) et peut être appelé par:

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

Ce programme TF2 crée une perception multicouche (MLP) et l'entraîne à reconnaître les images MNIST . Cet exemple utilise délibérément l'API de bas niveau de TF2 pour définir des constructions de couche personnalisées, une fonction de perte et une boucle d'apprentissage, car la probabilité de bogues NaN est plus élevée lorsque nous utilisons cette API plus flexible mais plus sujette aux erreurs que lorsque nous utilisons la méthode la plus simple. -à utiliser des API de haut niveau mais légèrement moins flexibles telles que tf.keras .

Le programme imprime une précision de test après chaque étape d'entraînement. Nous pouvons voir dans la console que la précision du test est bloquée à un niveau quasi-aléatoire (~ 0,1) après la première étape. Ce n'est certainement pas ainsi que l'entraînement du modèle devrait se comporter: nous nous attendons à ce que la précision s'approche progressivement de 1,0 (100%) à mesure que le pas augmente.

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

Une supposition éclairée est que ce problème est causé par une instabilité numérique, telle que NaN ou l'infini. Cependant, comment pouvons-nous confirmer que c'est vraiment le cas et comment trouver l'opération TensorFlow (op) responsable de la génération de l'instabilité numérique? Pour répondre à ces questions, instrumentons le programme buggy avec Debugger V2.

Instrumentation du code TensorFlow avec Debugger V2

tf.debugging.experimental.enable_dump_debug_info() est le point d'entrée API de Debugger V2. Il instrumente un programme TF2 avec une seule ligne de code. Par exemple, l'ajout de la ligne suivante au début du programme entraînera l'écriture des informations de débogage dans le répertoire du journal (logdir) à / tmp / tfdbg2_logdir. Les informations de débogage couvrent divers aspects de l'exécution de TensorFlow. Dans TF2, il inclut l'historique complet des exécutions hâtives , la construction de graphes effectuée par @ tf.function , l'exécution des graphes, les valeurs de tenseur générées par les événements d'exécution, ainsi que l'emplacement du code (traces de pile Python) de ces événements . La richesse des informations de débogage permet aux utilisateurs de se concentrer sur des bogues obscurs.

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

L'argument tensor_debug_mode contrôle les informations que le débogueur V2 extrait de chaque tenseur désireux ou dans le graphe. «FULL_HEALTH» est un mode qui capture les informations suivantes sur chaque tenseur de type flottant (par exemple, le float32 communément vu et le dtype bfloat16 moins commun):

  • DType
  • Rang
  • Nombre total d'éléments
  • Une ventilation des éléments de type flottant dans les catégories suivantes: fini négatif ( - ), zéro ( 0 ), fini positif ( + ), infini négatif ( -∞ ), infini positif ( +∞ ) et NaN .

Le mode «FULL_HEALTH» convient pour le débogage des bogues impliquant NaN et l'infini. Voir ci-dessous pour d'autres tensor_debug_mode pris en tensor_debug_mode .

L'argument circular_buffer_size contrôle le nombre d'événements tensoriels enregistrés dans le logdir. La valeur par défaut est 1000, ce qui entraîne la sauvegarde sur disque des 1000 derniers tenseurs avant la fin du programme TF2 instrumenté. Ce comportement par défaut réduit la surcharge du débogueur en sacrifiant l'exhaustivité des données de débogage. Si l'exhaustivité est préférée, comme dans ce cas, nous pouvons désactiver le tampon circulaire en définissant l'argument sur une valeur négative (par exemple, -1 ici).

L'exemple debug_mnist_v2 appelle enable_dump_debug_info() en lui passant des indicateurs de ligne de commande. Pour exécuter à nouveau notre programme problématique TF2 avec cette instrumentation de débogage activée, faites:

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

Démarrage de l'interface graphique du débogueur V2 dans TensorBoard

L'exécution du programme avec l'instrumentation du débogueur crée un logdir dans / tmp / tfdbg2_logdir. Nous pouvons démarrer TensorBoard et le pointer sur le logdir avec:

tensorboard --logdir /tmp/tfdbg2_logdir

Dans le navigateur Web, accédez à la page de TensorBoard à l'adresse http: // localhost: 6006. Le plugin «Debugger V2» doit être activé par défaut, affichant une page qui ressemble à ce qui suit:

Capture d'écran de la vue complète du débogueur V2

Utilisation de l'interface graphique du débogueur V2 pour trouver la cause première des NaN

L'interface graphique du débogueur V2 dans TensorBoard est organisée en six sections:

  • Alertes : cette section en haut à gauche contient une liste d'événements «d'alerte» détectés par le débogueur dans les données de débogage du programme TensorFlow instrumenté. Chaque alerte indique une certaine anomalie qui mérite l'attention. Dans notre cas, cette section met en évidence 499 événements NaN / ∞ avec une couleur rose-rouge saillante. Cela confirme notre suspicion que le modèle ne parvient pas à apprendre en raison de la présence de NaNs et / ou d'infinis dans ses valeurs de tenseur internes. Nous examinerons ces alertes sous peu.
  • Chronologie d'exécution de Python : il s'agit de la moitié supérieure de la section supérieure-centrale. Il présente l'historique complet de l'exécution avide d'opérations et de graphiques. Chaque case de la chronologie est marquée par la lettre initiale du nom de l'op ou du graphe (par exemple, «T» pour l'opération «TensorSliceDataset», «m» pour la fonction tf.function «modèle»). Nous pouvons naviguer dans cette chronologie en utilisant les boutons de navigation et la barre de défilement au-dessus de la chronologie.
  • Exécution de graphes : située dans le coin supérieur droit de l'interface graphique, cette section sera au cœur de notre tâche de débogage. Il contient un historique de tous les tenseurs de type flottant calculés à l'intérieur des graphes (c'est-à-dire compilés par @tf-function s).
  • La structure du graphique (moitié inférieure de la section centrale supérieure), le code source (section inférieure gauche) et la trace de pile (section inférieure droite) sont initialement vides. Leur contenu sera rempli lorsque nous interagirons avec l'interface graphique. Ces trois sections joueront également un rôle important dans notre tâche de débogage.

Après nous être orientés vers l'organisation de l'interface utilisateur, prenons les étapes suivantes pour comprendre pourquoi les NaN sont apparus. Tout d'abord, cliquez sur l'alerte NaN / ∞ dans la section Alertes. Cela fait automatiquement défiler la liste des 600 tenseurs de graphe dans la section Exécution de graphe et se concentre sur le # 88, qui est un tenseur nommé «Log: 0» généré par une opération Log (logarithme naturel). Une couleur rose-rouge saillante met en évidence un élément -∞ parmi les 1000 éléments du tenseur 2D float32. C'est le premier tenseur de l'historique d'exécution du programme TF2 qui contenait n'importe quel NaN ou infini: les tenseurs calculés avant ne contiennent ni NaN ni ∞; beaucoup (en fait, la plupart) des tenseurs calculés par la suite contiennent des NaN. Nous pouvons le confirmer en faisant défiler de haut en bas la liste d'exécution de graphe. Cette observation donne un indice fort que le Log op est à l'origine de l'instabilité numérique de ce programme TF2.

Debugger V2: alertes Nan / Infinity et liste d'exécution des graphiques

Pourquoi cette opération Log crache-t-elle un -∞? Pour répondre à cette question, il faut examiner la contribution à l'op. Cliquer sur le nom du tenseur («Log: 0») fait apparaître une visualisation simple mais informative du voisinage du Log op dans son graphe TensorFlow dans la section Structure du graphe. Notez la direction de haut en bas du flux d'informations. L'op elle-même est affichée en gras au milieu. Immédiatement au-dessus, nous pouvons voir qu'une opération Placeholder fournit la seule et unique entrée à l'opération Log . Où est le tenseur généré par cet espace réservé logits dans la liste d'exécution de graphe? En utilisant la couleur de fond jaune comme aide visuelle, nous pouvons voir que le tenseur logits:0 correspond à deux lignes au-dessus du tenseur Log:0 , c'est-à-dire à la ligne 85.

Debugger V2: Vue de la structure du graphe et traçage vers le tenseur d'entrée

Un examen plus attentif de la décomposition numérique du tenseur «logits: 0» à la ligne 85 révèle pourquoi son consommateur Log:0 produit un -∞: Parmi les 1000 éléments de «logits: 0», un élément a une valeur de 0. Le -∞ est le résultat du calcul du logarithme naturel de 0! Si nous pouvons d'une manière ou d'une autre nous assurer que l'opération Log n'est exposée qu'à des entrées positives, nous pourrons empêcher le NaN / ∞ de se produire. Ceci peut être réalisé en appliquant un découpage (par exemple, en utilisant tf.clip_by_value () ) sur le tenseur Logits de Placeholder.

Nous nous rapprochons de la résolution du bogue, mais pas encore tout à fait terminé. Afin d'appliquer le correctif, nous devons savoir où dans le code source Python provient l'opération Log et son entrée Placeholder. Debugger V2 fournit un support de première classe pour tracer les opérations graphiques et les événements d'exécution jusqu'à leur source. Lorsque nous avons cliqué sur le tenseur Log:0 dans Graph Executions, la section Stack Trace était remplie avec la trace de pile d'origine de la création de l'opération Log. La trace de la pile est assez volumineuse car elle comprend de nombreuses trames du code interne de TensorFlow (par exemple, gen_math_ops.py et dumping_callback.py), que nous pouvons ignorer en toute sécurité pour la plupart des tâches de débogage. La trame d'intérêt est la ligne 216 de debug_mnist_v2.py (c'est-à-dire le fichier Python que nous essayons en fait de déboguer). Cliquez sur «Ligne 204» pour afficher la ligne de code correspondante dans la section Code source.

Debugger V2: code source et trace de pile

Cela nous amène enfin au code source qui a créé l'opération Log problématique à partir de son entrée logits. Il s'agit de notre fonction de perte d'entropie croisée catégorique personnalisée décorée avec @tf.function et donc convertie en un graphe TensorFlow. L'opération d'espace réservé «logits» correspond au premier argument d'entrée de la fonction de perte. L'opération Log est créée avec l'appel d'API tf.math.log ().

Le correctif de coupure de valeur de ce bogue ressemblera à quelque chose comme:

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

Il résoudra l'instabilité numérique de ce programme TF2 et entraînera le succès de l'entraînement du MLP. Une autre approche possible pour corriger l'instabilité numérique consiste à utiliser tf.keras.losses.CategoricalCrossentropy .

Cela conclut notre voyage de l'observation d'un bogue du modèle TF2 à la création d'un changement de code qui corrige le bogue, aidé par l'outil Debugger V2, qui offre une visibilité complète sur l'historique d'exécution rapide et graphique du programme TF2 instrumenté, y compris les résumés numériques. des valeurs de tenseur et association entre les ops, les tenseurs et leur code source d'origine.

Compatibilité matérielle de Debugger V2

Debugger V2 prend en charge le matériel de formation grand public, y compris le processeur et le GPU. La formation multi-GPU avec tf.distributed.MirroredStrategy est également prise en charge. La prise en charge de TPU en est encore à ses débuts et nécessite un appel

tf.config.set_soft_device_placement(True)

avant d'appeler enable_dump_debug_info() . Il peut également avoir d'autres limitations sur les TPU. Si vous rencontrez des problèmes avec Debugger V2, veuillez signaler les bogues sur notre page de problèmes GitHub .

Compatibilité API de Debugger V2

Debugger V2 est implémenté à un niveau relativement bas de la pile logicielle de TensorFlow et est donc compatible avec tf.keras , tf.data et d'autres API construites au-dessus des niveaux inférieurs de TensorFlow. Debugger V2 est également rétrocompatible avec TF1, bien que la chronologie Eager Execution soit vide pour les journaux de débogage générés par les programmes TF1.

Conseils d'utilisation de l'API

Une question fréquemment posée sur cette API de débogage est où dans le code TensorFlow il faut insérer l'appel à enable_dump_debug_info() . En règle générale, l'API doit être appelée le plus tôt possible dans votre programme TF2, de préférence après les lignes d'importation Python et avant le début de la construction et de l'exécution du graphe. Cela garantira une couverture complète de toutes les opérations et graphiques qui alimentent votre modèle et son entraînement.

Les modes tensor_debug_modes actuellement pris en charge sont: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH et SHAPE . Ils varient dans la quantité d'informations extraites de chaque tenseur et la surcharge de performance du programme débogué. Veuillez vous référer à la section args de la enable_dump_debug_info() de enable_dump_debug_info() ].

Frais généraux de performance

L'API de débogage introduit une surcharge de performances pour le programme TensorFlow instrumenté. La surcharge varie en fonction du tensor_debug_mode , du type de matériel et de la nature du programme TensorFlow instrumenté. Comme point de référence, sur un GPU, le mode NO_TENSOR ajoute une surcharge de 15% lors de l'apprentissage d'un modèle Transformer sous la taille de lot 64. Les pourcentages de surcharge pour les autres modes tensor_debug_modes sont plus élevés: environ 50% pour CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH et SHAPE modes. Sur les processeurs, la surcharge est légèrement inférieure. Sur les TPU, la surcharge est actuellement plus élevée.

Relation avec d'autres API de débogage TensorFlow

Notez que TensorFlow propose d'autres outils et API pour le débogage. Vous pouvez parcourir ces API dans l' espace de noms tf.debugging.* la page de documentation de l'API. Parmi ces API, la plus fréquemment utilisée est tf.print() . Quand faut-il utiliser Debugger V2 et quand tf.print() doit- tf.print() être utilisé à la place? tf.print() est pratique dans le cas où

  1. on sait exactement quels tenseurs imprimer,
  2. nous savons où exactement dans le code source insérer ces tf.print() ,
  3. le nombre de tels tenseurs n'est pas trop grand.

Pour d'autres cas (par exemple, l'examen de nombreuses valeurs de tenseur, l'examen des valeurs de tenseur générées par le code interne de TensorFlow et la recherche de l'origine de l'instabilité numérique comme nous l'avons montré ci-dessus), Debugger V2 fournit un moyen plus rapide de débogage. De plus, Debugger V2 fournit une approche unifiée de l'inspection des tenseurs désireux et graphiques. Il fournit en outre des informations sur la structure du graphe et les emplacements de code, qui sont au-delà de la capacité de tf.print() .

Une autre API qui peut être utilisée pour déboguer les problèmes impliquant ∞ et NaN est tf.debugging.enable_check_numerics() . Contrairement à enable_dump_debug_info() , enable_check_numerics() n'enregistre pas les informations de débogage sur le disque. Au lieu de cela, il surveille simplement ∞ et NaN pendant l'exécution de TensorFlow et les erreurs avec l'emplacement du code d'origine dès qu'une opération génère ces mauvaises valeurs numériques. Il a une surcharge de performances inférieure à celle de enable_dump_debug_info() , mais ne permet pas une trace complète de l'historique d'exécution du programme et n'est pas livré avec une interface utilisateur graphique comme Debugger V2.