Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Отладка числовых ошибок в программах TensorFlow с использованием TensorBoard Debugger V2

Катастрофические события с участием NaN могут иногда происходить во время программы TensorFlow, нанося вред процессу обучения модели. Основная причина таких событий часто неясна, особенно для моделей нетривиального размера и сложности. Чтобы упростить отладку ошибок этого типа, TensorBoard 2.3+ (вместе с TensorFlow 2.3+) предоставляет специальную панель мониторинга, которая называется Debugger V2. Здесь мы покажем, как использовать этот инструмент, работая над реальной ошибкой с участием NaN в нейронной сети, написанной на TensorFlow.

Методы, проиллюстрированные в этом руководстве, применимы к другим типам действий по отладке, таким как проверка тензорных форм времени выполнения в сложных программах. Этот учебник фокусируется на NaN из-за их относительно высокой частоты появления.

Наблюдая за ошибкой

Исходный код программы TF2, которую мы будем отлаживать, доступен на GitHub . Пример программы также упакован в пакет tenorflow pip (версия 2.3+) и может быть вызван:

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

Эта программа TF2 создает многоуровневое восприятие (MLP) и обучает его распознавать изображения MNIST . В этом примере целенаправленно используется низкоуровневый API TF2 для определения пользовательских конструкций уровней, функции потерь и цикла обучения, поскольку вероятность ошибок NaN выше, когда мы используем этот более гибкий, но более подверженный ошибкам API, чем когда мы используем более простой использовать, но немного менее гибкие высокоуровневые API, такие как tf.keras .

Программа печатает тест точности после каждого шага тренировки. Мы видим в консоли, что точность теста застревает на уровне почти вероятности (~ 0,1) после первого шага. Это, конечно, не то, как поведет себя модельное обучение: мы ожидаем, что точность будет постепенно приближаться к 1,0 (100%) с увеличением шага.

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

Образованное предположение состоит в том, что эта проблема вызвана численной нестабильностью, такой как NaN или бесконечность. Однако, как мы можем подтвердить, что это действительно так, и как мы находим операцию TensorFlow (op), ответственную за генерацию числовой нестабильности? Чтобы ответить на эти вопросы, давайте оснастим программу с ошибками Debugger V2.

Инструментирование кода TensorFlow с помощью отладчика V2

tf.debugging.experimental.enable_dump_debug_info() является точкой входа API отладчика V2. Это инструмент программы TF2 с одной строкой кода. Например, добавление следующей строки в начале программы приведет к записи отладочной информации в каталог журналов (logdir) по адресу / tmp / tfdbg2_logdir. Отладочная информация охватывает различные аспекты времени выполнения TensorFlow. В TF2 он включает в себя полную историю активного выполнения, построение графиков, выполняемых @ tf.function , выполнение графиков, тензорные значения, генерируемые событиями выполнения, а также местоположение кода (трассировки стека Python) этих событий , Богатство отладочной информации позволяет пользователям сгладить неясные ошибки.

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

Аргумент tensor_debug_mode управляет тем, какую информацию Debugger V2 извлекает из каждого трезвого или графического тензора. «FULL_HEALTH» - это режим, который собирает следующую информацию о каждом тензоре плавающего типа (например, обычно видимый тип float32 и менее распространенный тип df bfloat16):

  • DTYPE
  • Ранг
  • Общее количество элементов
  • Разбивка элементов плавающего типа на следующие категории: отрицательный конечный ( - ), ноль ( 0 ), положительный конечный ( + ), отрицательный бесконечность ( -∞ ), положительный бесконечность ( +∞ ) и NaN .

Режим «FULL_HEALTH» подходит для отладки ошибок, включающих NaN и бесконечность. Смотрите ниже другие поддерживаемые tensor_debug_mode s.

В circular_buffer_size контролирует аргумент , сколько тензорные событий сохраняются в LogDir. По умолчанию он равен 1000, что приводит к тому, что только последние 1000 тензоров перед окончанием инструментальной программы TF2 будут сохранены на диск. Такое поведение по умолчанию уменьшает издержки отладчика, жертвуя полнотой данных отладки. Если полнота предпочтительна, как в этом случае, мы можем отключить циклический буфер, установив аргумент в отрицательное значение (например, -1 здесь).

Пример debug_mnist_v2 вызывает enable_dump_debug_info() , передавая ему флаги командной строки. Чтобы снова запустить нашу проблемную программу TF2 с включенным отладочным инструментарием, выполните:

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

Запуск Debugger V2 GUI в TensorBoard

При запуске программы с помощью инструментария отладчика создается logdir в / tmp / tfdbg2_logdir. Мы можем запустить TensorBoard и указать его в logdir:

 tensorboard --logdir /tmp/tfdbg2_logdir
 

В веб-браузере перейдите на страницу TensorBoard по адресу http: // localhost: 6006. Плагин «Debugger V2» должен быть активирован по умолчанию, отображая страницу, которая выглядит следующим образом:

Отладчик V2 полный просмотр скриншот

Использование Debugger V2 GUI для поиска первопричины NaN

Графический интерфейс Debugger V2 в TensorBoard состоит из шести разделов:

  • Оповещения . Этот верхний левый раздел содержит список событий «оповещения», обнаруженных отладчиком в данных отладки из инструментальной программы TensorFlow. Каждое предупреждение указывает на определенную аномалию, которая заслуживает внимания. В нашем случае этот раздел выделяет 499 событий NaN / ∞ с ярко выраженным розово-красным цветом. Это подтверждает наше подозрение, что модель не в состоянии изучать из-за присутствия NaN и / или бесконечности в ее внутренних значениях тензора. Мы скоро углубимся в эти предупреждения.
  • Временная шкала исполнения Python : это верхняя половина верхней средней части. Он представляет полную историю нетерпеливого выполнения операций и графиков. Каждая коробка на временной шкале отмечен начальной буквой оп или имя графа (например, «T» для «TensorSliceDataset» ор «м» для «модели» tf.function ). Мы можем перемещаться по этой временной шкале с помощью кнопок навигации и полосы прокрутки над временной шкалой.
  • Выполнение графика. Этот раздел, расположенный в верхнем правом углу графического интерфейса пользователя, будет центральным в нашей задаче отладки. Он содержит историю всех тензоров с плавающим d-типом, вычисленных внутри графов (т. Е. Скомпилированных @tf-function s).
  • Структура графика (нижняя половина верхней-средней части), исходный код (нижняя левая часть) и трассировка стека (нижняя правая часть) изначально пусты. Их содержимое будет заполнено, когда мы будем взаимодействовать с GUI. Эти три раздела также будут играть важную роль в нашей задаче отладки.

Ориентируясь на организацию пользовательского интерфейса, давайте сделаем следующие шаги, чтобы понять причину появления NaN. Сначала щелкните предупреждение NaN / ∞ в разделе «Предупреждения». Это автоматически прокручивает список из 600 тензоров графа в разделе «Выполнение графа» и фокусируется на # 88, который является тензором с именем «Log: 0», сгенерированным операцией Log (натуральный логарифм). Выдающийся розово-красный цвет выделяет -∞ элемент среди 1000 элементов 2D-тензора float32. Это первый тензор в истории времени выполнения программы TF2, который содержал любой NaN или бесконечность: тензор, вычисленный до того, как он не содержит NaN или ∞; многие (на самом деле, большинство) вычисленные впоследствии тензоры содержат NaN. Мы можем подтвердить это, прокручивая вверх и вниз список выполнения графиков. Это наблюдение дает намек на то, что Log op является источником численной нестабильности в этой программе TF2.

Отладчик V2: предупреждения Nan / Infinity и список выполнения графиков

Почему эта Log выдает -∞? Ответ на этот вопрос требует изучения входных данных для оп. Нажав на имя тензора («Log: 0»), вы увидите простую, но информативную визуализацию окрестности Log op на графике TensorFlow в разделе «Структура графика». Обратите внимание сверху вниз направление потока информации. Сама операция показана жирным шрифтом в середине. Непосредственно над ним мы видим, что операция Placeholder обеспечивает единственный вход для операции Log . Где находится тензор, сгенерированный этим logits Placeholder в списке выполнения графика? Используя желтый цвет фона в качестве наглядного пособия, мы видим, что тензор logits:0 находится на две строки выше тензора Log:0 , то есть в строке 85.

Отладчик V2: представление структуры графика и трассировка до входного тензора

Более внимательный анализ числовой разбивки тензора «logits: 0» в строке 85 показывает, почему его потребительский Log:0 выдает a -∞: среди 1000 элементов «logits: 0» один элемент имеет значение 0. -∞ является результатом вычисления натурального логарифма 0! Если мы сможем каким-то образом гарантировать, что в журнале операций будут только положительные входные данные, мы сможем предотвратить возникновение NaN / ∞. Это может быть достигнуто путем применения отсечения (например, с помощью tf.clip_by_value () ) к тензору заполнителей логитов.

Мы приближаемся к исправлению ошибки, но пока не совсем. Чтобы применить исправление, нам нужно знать, откуда в исходном коде Python возникла операция Log и ее заполнитель. Отладчик V2 обеспечивает первоклассную поддержку для отслеживания операций с графиками и событий выполнения до их источника. Когда мы щелкнули по тензору Log:0 в Выполнениях графиков, секция Stack Trace была заполнена исходной трассировкой стека создания операции Log. Трассировка стека несколько велика, потому что она включает в себя много кадров из внутреннего кода TensorFlow (например, gen_math_ops.py и dumping_callback.py), которые мы можем спокойно игнорировать для большинства задач отладки. Интерес представляет строка 216 файла debug_mnist_v2.py (т. Е. Файл Python, который мы фактически пытаемся отлаживать). Нажатие «Строка 204» открывает вид соответствующей строки кода в разделе «Исходный код».

Отладчик V2: исходный код и трассировка стека

Это, наконец, подводит нас к исходному коду, который создал проблемную опцию Log из своего входа logits. Это наша пользовательская категориальная функция кросс-энтропийной потери, украшенная функцией @tf.function и, следовательно, преобразованная в график TensorFlow. Заполнитель op «logits» соответствует первому входному аргументу функции потерь. Операция Log создается с помощью вызова API tf.math.log ().

Исправление отсечения значения этой ошибки будет выглядеть примерно так:

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

Это разрешит числовую нестабильность в этой программе TF2 и приведет к успешному обучению MLP. Другой возможный подход к исправлению числовой нестабильности заключается в использовании tf.keras.losses.CategoricalCrossentropy .

На этом мы завершаем наш путь от наблюдения за ошибкой модели TF2 до внесения изменений в код, которые исправляют ошибку, с помощью инструмента Debugger V2, который обеспечивает полную наглядность истории активных и графических операций инструментальной программы TF2, включая числовые сводки тензорных значений и связь между операциями, тензорами и их исходным кодом.

Аппаратная совместимость Debugger V2

Отладчик V2 поддерживает основное учебное оборудование, включая CPU и GPU. Обучение нескольких графических процессоров с tf.distributed.MirroredStrategy также поддерживается. Поддержка ТПУ все еще находится на ранней стадии и требует вызова

 tf.config.set_soft_device_placement(True)
 

перед вызовом enable_dump_debug_info() . Это может иметь и другие ограничения для TPU. Если вы столкнулись с проблемами при использовании Debugger V2, пожалуйста, сообщайте об ошибках на нашей странице проблем GitHub .

API-совместимость Debugger V2

Отладчик V2 реализован на относительно низком уровне программного стека TensorFlow и, следовательно, совместим с tf.keras , tf.data и другими API, созданными поверх нижних уровней TensorFlow . Отладчик V2 также обратно совместим с TF1, хотя временная шкала Eager Execution будет пустой для журналов отладки, созданных программами TF1.

Советы по использованию API

Часто задаваемый вопрос об этом API отладки: где в коде enable_dump_debug_info() нужно вставить вызов enable_dump_debug_info() . Как правило, API должен вызываться как можно раньше в вашей программе TF2, предпочтительно после строк импорта Python и до начала построения и выполнения графика. Это обеспечит полный охват всех операций и графиков, которые приводят в действие вашу модель и ее обучение.

В настоящее время поддерживаются режимы tenor_debug_modes: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH и SHAPE . Они различаются по объему информации, извлекаемой из каждого тензора, и снижению производительности отлаживаемой программы. Пожалуйста, обратитесь к разделу args документации enable_dump_debug_info() ].

Снижение производительности

API отладки приводит к снижению производительности инструментальной программы TensorFlow. Накладные расходы варьируются в зависимости от tensor_debug_mode , типа оборудования и характера инструментальной программы TensorFlow. В качестве ориентира в графическом NO_TENSOR режим NO_TENSOR добавляет 15% накладных расходов во время обучения модели Transformer при размере пакета 64. Процентные издержки для других тензорных режимов выше: примерно 50% для CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH и SHAPE режимы. На процессорах накладные расходы немного ниже. На TPU накладные расходы в настоящее время выше.

Связь с другими API отладки TensorFlow

Обратите внимание, что TensorFlow предлагает другие инструменты и API для отладки. Вы можете просмотреть такие API в пространстве имен tf.debugging.* На странице документации API. Среди этих API наиболее часто используемым является tf.print() . Когда следует использовать Debugger V2 и когда вместо него следует использовать tf.print() ? tf.print() удобна в том случае, если

  1. мы точно знаем, какие тензоры печатать,
  2. мы знаем, где именно в исходном коде вставить эти tf.print() ,
  3. количество таких тензоров не слишком велико.

Для других случаев (например, проверка множества значений тензоров, проверка значений тензоров, сгенерированных внутренним кодом TensorFlow, и поиск источника числовой нестабильности, как мы показали выше), Debugger V2 обеспечивает более быстрый способ отладки. Кроме того, Debugger V2 обеспечивает унифицированный подход к проверке активных и графических тензоров. Кроме того, он предоставляет информацию о структуре графа и расположении кода, что недоступно tf.print() .

Другой API, который можно использовать для устранения проблем, связанных с ∞ и NaN, - это tf.debugging.enable_check_numerics() . В отличие от enable_dump_debug_info() , enable_check_numerics() не сохраняет отладочную информацию на диске. Вместо этого он просто контролирует ∞ и NaN во время выполнения TensorFlow и выдает ошибки с расположением исходного кода, как только любой оператор генерирует такие неверные числовые значения. Он снижает производительность по сравнению с enable_dump_debug_info() , но не позволяет полностью отслеживать историю выполнения программы и не имеет графического пользовательского интерфейса, такого как Debugger V2.