Depuración de problemas numéricos en programas TensorFlow utilizando TensorBoard Debugger V2

A veces pueden ocurrir eventos catastróficos que involucran NaN s durante un programa TensorFlow, lo que paraliza los procesos de entrenamiento del modelo. La causa raíz de tales eventos a menudo es oscura, especialmente para modelos de tamaño y complejidad no triviales. Para facilitar la depuración de este tipo de errores de modelo, TensorBoard 2.3+ (junto con TensorFlow 2.3+) proporciona un tablero especializado llamado Debugger V2. Aquí demostramos cómo usar esta herramienta trabajando con un error real que involucra NaN en una red neuronal escrita en TensorFlow.

Las técnicas ilustradas en este tutorial son aplicables a otros tipos de actividades de depuración, como la inspección de formas de tensores en tiempo de ejecución en programas complejos. Este tutorial se centra en los NaN debido a su frecuencia de aparición relativamente alta.

Observando el error

El código fuente del programa TF2 que depuraremos está disponible en GitHub . El programa de ejemplo también está empaquetado en el paquete pip de tensorflow (versión 2.3+) y puede ser invocado por:

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

Este programa TF2 crea una percepción multicapa (MLP) y la entrena para reconocer imágenes MNIST . Este ejemplo utiliza a propósito la API de bajo nivel de TF2 para definir construcciones de capas personalizadas, función de pérdida y bucle de entrenamiento, porque la probabilidad de errores de NaN es mayor cuando usamos esta API más flexible pero más propensa a errores que cuando usamos la API más sencilla. API de alto nivel fáciles de usar pero un poco menos flexibles, como tf.keras .

El programa imprime una prueba de precisión después de cada paso de entrenamiento. Podemos ver en la consola que la precisión de la prueba se atasca en un nivel cercano al azar (~0,1) después del primer paso. Ciertamente, no es así como se espera que se comporte el entrenamiento del modelo: esperamos que la precisión se acerque gradualmente a 1,0 (100 %) a medida que aumenta el paso.

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

Una suposición fundamentada es que este problema es causado por una inestabilidad numérica, como NaN o infinito. Sin embargo, ¿cómo confirmamos que este es realmente el caso y cómo encontramos que la operación TensorFlow (op) es responsable de generar la inestabilidad numérica? Para responder a estas preguntas, instrumentemos el programa con errores con Debugger V2.

Instrumentación del código de TensorFlow con Debugger V2

tf.debugging.experimental.enable_dump_debug_info() es el punto de entrada de API de Debugger V2. Instrumenta un programa TF2 con una sola línea de código. Por ejemplo, agregar la siguiente línea cerca del comienzo del programa hará que la información de depuración se escriba en el directorio de registro (logdir) en /tmp/tfdbg2_logdir. La información de depuración cubre varios aspectos del tiempo de ejecución de TensorFlow. En TF2, incluye el historial completo de la ejecución ansiosa, la creación de gráficos realizada por @tf.function , la ejecución de los gráficos, los valores de tensor generados por los eventos de ejecución, así como la ubicación del código (seguimiento de la pila de Python) de esos eventos. . La riqueza de la información de depuración permite a los usuarios concentrarse en errores oscuros.

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

El argumento tensor_debug_mode controla qué información extrae Debugger V2 de cada tensor ansioso o en gráfico. "FULL_HEALTH" es un modo que captura la siguiente información sobre cada tensor de tipo flotante (por ejemplo, el float32 comúnmente visto y el bfloat16 dtype menos común):

  • Tipo D
  • Rango
  • Número total de elementos
  • Un desglose de los elementos de tipo flotante en las siguientes categorías: finito negativo ( - ), cero ( 0 ), finito positivo ( + ), infinito negativo ( -∞ ), infinito positivo ( +∞ ) y NaN .

El modo "FULL_HEALTH" es adecuado para depurar errores relacionados con NaN e infinito. Consulte a continuación para conocer otros tensor_debug_mode s compatibles.

El argumento circular_buffer_size controla cuántos eventos de tensor se guardan en logdir. El valor predeterminado es 1000, lo que hace que solo los últimos 1000 tensores antes del final del programa TF2 instrumentado se guarden en el disco. Este comportamiento predeterminado reduce la sobrecarga del depurador al sacrificar la integridad de los datos de depuración. Si se prefiere la integridad, como en este caso, podemos desactivar el búfer circular estableciendo el argumento en un valor negativo (por ejemplo, -1 aquí).

El ejemplo debug_mnist_v2 invoca enable_dump_debug_info() pasándole indicadores de línea de comandos. Para volver a ejecutar nuestro problemático programa TF2 con esta instrumentación de depuración habilitada, haz lo siguiente:

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

Inicio de la GUI del depurador V2 en TensorBoard

Ejecutar el programa con la instrumentación del depurador crea un logdir en /tmp/tfdbg2_logdir. Podemos iniciar TensorBoard y apuntarlo al logdir con:

tensorboard --logdir /tmp/tfdbg2_logdir

En el navegador web, vaya a la página de TensorBoard en http://localhost:6006. El complemento "Debugger V2" estará inactivo de forma predeterminada, así que selecciónelo en el menú "Complementos inactivos" en la parte superior derecha. Una vez seleccionado, debería verse como lo siguiente:

Captura de pantalla de vista completa del depurador V2

Uso de la interfaz gráfica de usuario de Debugger V2 para encontrar la causa raíz de los NaN

La GUI de Debugger V2 en TensorBoard está organizada en seis secciones:

  • Alertas : esta sección superior izquierda contiene una lista de eventos de "alerta" detectados por el depurador en los datos de depuración del programa TensorFlow instrumentado. Cada alerta indica una determinada anomalía que merece atención. En nuestro caso, esta sección destaca los eventos de 499 NaN/∞ con un color rosa-rojo saliente. Esto confirma nuestra sospecha de que el modelo no aprende debido a la presencia de NaN y/o infinitos en sus valores de tensor interno. Profundizaremos en estas alertas en breve.
  • Línea de tiempo de ejecución de Python : Esta es la mitad superior de la sección media superior. Presenta la historia completa de la ansiosa ejecución de operaciones y gráficos. Cada cuadro de la línea de tiempo está marcado con la letra inicial del nombre de la operación o del gráfico (por ejemplo, "T" para la operación "TensorSliceDataset", "m" para la función "modelo" tf.function .). Podemos navegar por esta línea de tiempo usando los botones de navegación y la barra de desplazamiento sobre la línea de tiempo.
  • Ejecución de gráficos : ubicada en la esquina superior derecha de la GUI, esta sección será fundamental para nuestra tarea de depuración. Contiene un historial de todos los tensores de tipo flotante calculados dentro de gráficos (es decir, compilados por @tf-function s).
  • La estructura del gráfico (mitad inferior de la sección superior central), el código fuente (sección inferior izquierda) y el seguimiento de la pila (sección inferior derecha) están inicialmente vacíos. Sus contenidos se completarán cuando interactuemos con la GUI. Estas tres secciones también jugarán un papel importante en nuestra tarea de depuración.

Habiéndonos orientado a la organización de la interfaz de usuario, sigamos los siguientes pasos para llegar al fondo de por qué aparecieron los NaN. Primero, haga clic en la alerta NaN/∞ en la sección Alertas. Esto desplaza automáticamente la lista de 600 tensores gráficos en la sección Ejecución gráfica y se enfoca en el #88, que es un tensor llamado Log:0 generado por una Log (logaritmo natural). Un color rosa-rojo saliente resalta un elemento -∞ entre los 1000 elementos del tensor 2D float32. Este es el primer tensor en el historial de tiempo de ejecución del programa TF2 que contenía NaN o infinito: los tensores calculados antes no contienen NaN ni ∞; muchos (de hecho, la mayoría) de los tensores calculados posteriormente contienen NaN. Podemos confirmar esto desplazándonos hacia arriba y hacia abajo en la lista de ejecución de gráficos. Esta observación proporciona un fuerte indicio de que Log op es la fuente de la inestabilidad numérica en este programa TF2.

Depurador V2: alertas Nan/Infinity y lista de ejecución de gráficos

¿Por qué este Log op escupe un -∞? Responder a esa pregunta requiere examinar la entrada a la op. Al hacer clic en el nombre del tensor ( Log:0 ), aparece una visualización simple pero informativa de la vecindad de Log op en su gráfico TensorFlow en la sección Estructura del gráfico. Tenga en cuenta la dirección de arriba a abajo del flujo de información. La operación en sí se muestra en negrita en el medio. Inmediatamente encima, podemos ver una operación de marcador de posición que proporciona la única entrada para la operación de Log . ¿Dónde está el tensor generado por este marcador de posición de probs en la lista de ejecución de gráfico? Usando el color de fondo amarillo como ayuda visual, podemos ver que el tensor probs:0 está tres filas por encima del tensor Log:0 , es decir, en la fila 85.

Depurador V2: vista de estructura gráfica y seguimiento al tensor de entrada

Una mirada más cuidadosa al desglose numérico del tensor probs:0 en la fila 85 revela por qué su consumidor Log:0 produce un -∞: Entre los 1000 elementos de probs:0 , un elemento tiene un valor de 0. El -∞ es resultado de calcular el logaritmo natural de 0! Si de alguna manera podemos asegurarnos de que Log op quede expuesto solo a entradas positivas, podremos evitar que ocurra el NaN/∞. Esto se puede lograr aplicando recorte (p. ej., usando tf.clip_by_value() ) en el tensor de probabilidad de probs de posición.

Estamos cada vez más cerca de resolver el error, pero aún no lo hemos hecho del todo. Para aplicar la solución, necesitamos saber en qué parte del código fuente de Python se originaron la operación de Log y su entrada de marcador de posición. Debugger V2 brinda soporte de primera clase para rastrear las operaciones gráficas y los eventos de ejecución hasta su origen. Cuando hicimos clic en el tensor Log:0 en Graph Executions, la sección Stack Trace se llenó con el stack trace original de la creación de Log op. El seguimiento de la pila es algo grande porque incluye muchos marcos del código interno de TensorFlow (p. ej., gen_math_ops.py y dumping_callback.py), que podemos ignorar con seguridad para la mayoría de las tareas de depuración. El marco de interés es la línea 216 de debug_mnist_v2.py (es decir, el archivo de Python que estamos tratando de depurar). Al hacer clic en "Línea 216", aparece una vista de la línea de código correspondiente en la sección Código fuente.

Depurador V2: código fuente y seguimiento de pila

Esto finalmente nos lleva al código fuente que creó la operación de Log problemática a partir de su probs de problemas. Esta es nuestra función de pérdida de entropía cruzada categórica personalizada decorada con @tf.function y, por lo tanto, convertida en un gráfico de TensorFlow. El marcador de posición op probs corresponde al primer argumento de entrada de la función de pérdida. La operación Log se crea con la llamada a la API tf.math.log().

La solución de recorte de valor para este error se verá así:

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

Resolverá la inestabilidad numérica en este programa TF2 y hará que el MLP se entrene con éxito. Otro enfoque posible para arreglar la inestabilidad numérica es usar tf.keras.losses.CategoricalCrossentropy .

Esto concluye nuestro viaje desde la observación de un error en el modelo TF2 hasta la creación de un cambio de código que corrige el error, con la ayuda de la herramienta Debugger V2, que proporciona una visibilidad completa del historial de ejecución gráfica y entusiasta del programa TF2 instrumentado, incluidos los resúmenes numéricos. de valores de tensores y asociación entre operaciones, tensores y su código fuente original.

Compatibilidad de hardware de Debugger V2

Debugger V2 es compatible con el hardware de entrenamiento convencional, incluidas la CPU y la GPU. También se admite el entrenamiento de varias GPU con tf.distributed.MirroredStrategy . El soporte para TPU aún se encuentra en una etapa inicial y requiere llamar

tf.config.set_soft_device_placement(True)

antes de llamar a enable_dump_debug_info() . También puede tener otras limitaciones en las TPU. Si tiene problemas para usar Debugger V2, informe los errores en nuestra página de problemas de GitHub .

Compatibilidad API de Debugger V2

Debugger V2 se implementa en un nivel relativamente bajo de la pila de software de TensorFlow y, por lo tanto, es compatible con tf.keras , tf.data y otras API creadas sobre los niveles inferiores de TensorFlow. El depurador V2 también es compatible con versiones anteriores de TF1, aunque la línea de tiempo de ejecución ansiosa estará vacía para los directorios de registro de depuración generados por los programas TF1.

Consejos de uso de la API

Una pregunta frecuente sobre esta API de depuración es dónde se debe insertar la llamada a enable_dump_debug_info() en el código de TensorFlow. Por lo general, la API debe llamarse lo antes posible en su programa TF2, preferiblemente después de las líneas de importación de Python y antes de que comience la creación y ejecución de gráficos. Esto garantizará una cobertura completa de todas las operaciones y gráficos que impulsan su modelo y su entrenamiento.

Los tensor_debug_modes admitidos actualmente son: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH y SHAPE . Varían en la cantidad de información extraída de cada tensor y la sobrecarga de rendimiento del programa depurado. Consulte la sección de argumentos de la documentación de enable_dump_debug_info() .

Gastos generales de rendimiento

La API de depuración introduce una sobrecarga de rendimiento en el programa TensorFlow instrumentado. La sobrecarga varía según tensor_debug_mode , el tipo de hardware y la naturaleza del programa TensorFlow instrumentado. Como punto de referencia, en una GPU, el modo NO_TENSOR agrega una sobrecarga del 15 % durante el entrenamiento de un modelo de Transformer con un tamaño de lote 64. El porcentaje de sobrecarga para otros modos tensor_debug_mode es mayor: aproximadamente un 50 % para CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH y SHAPE modos. En las CPU, la sobrecarga es ligeramente inferior. En las TPU, los gastos generales son actualmente más altos.

Relación con otras API de depuración de TensorFlow

Tenga en cuenta que TensorFlow ofrece otras herramientas y API para la depuración. Puede explorar dichas API en el espacio de nombres tf.debugging.* en la página de documentación de la API. Entre estas API, la más utilizada es tf.print() . ¿Cuándo se debe usar Debugger V2 y cuándo se debe tf.print() en su lugar? tf.print() es conveniente en caso de que

  1. sabemos exactamente qué tensores imprimir,
  2. sabemos dónde exactamente en el código fuente para insertar esas declaraciones tf.print() ,
  3. el número de tales tensores no es demasiado grande.

Para otros casos (p. ej., examinar muchos valores de tensor, examinar valores de tensor generados por el código interno de TensorFlow y buscar el origen de la inestabilidad numérica como mostramos anteriormente), Debugger V2 proporciona una forma más rápida de depuración. Además, Debugger V2 proporciona un enfoque unificado para inspeccionar tensores ansiosos y gráficos. Además, proporciona información sobre la estructura del gráfico y las ubicaciones del código, que están más allá de la capacidad de tf.print() .

Otra API que se puede usar para depurar problemas relacionados con ∞ y NaN es tf.debugging.enable_check_numerics() . A diferencia enable_dump_debug_info() , enable_check_numerics() no guarda información de depuración en el disco. En cambio, simplemente monitorea ∞ y NaN durante el tiempo de ejecución de TensorFlow y genera errores con la ubicación del código de origen tan pronto como cualquier operación genera valores numéricos tan malos. Tiene una sobrecarga de rendimiento más baja en comparación con enable_dump_debug_info() , pero no ofrece un seguimiento completo del historial de ejecución del programa y no viene con una interfaz gráfica de usuario como Debugger V2.

,

A veces pueden ocurrir eventos catastróficos que involucran NaN s durante un programa TensorFlow, lo que paraliza los procesos de entrenamiento del modelo. La causa raíz de tales eventos a menudo es oscura, especialmente para modelos de tamaño y complejidad no triviales. Para facilitar la depuración de este tipo de errores de modelo, TensorBoard 2.3+ (junto con TensorFlow 2.3+) proporciona un tablero especializado llamado Debugger V2. Aquí demostramos cómo usar esta herramienta trabajando con un error real que involucra NaN en una red neuronal escrita en TensorFlow.

Las técnicas ilustradas en este tutorial son aplicables a otros tipos de actividades de depuración, como la inspección de formas de tensores en tiempo de ejecución en programas complejos. Este tutorial se centra en los NaN debido a su frecuencia de aparición relativamente alta.

Observando el error

El código fuente del programa TF2 que depuraremos está disponible en GitHub . El programa de ejemplo también está empaquetado en el paquete pip de tensorflow (versión 2.3+) y puede ser invocado por:

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

Este programa TF2 crea una percepción multicapa (MLP) y la entrena para reconocer imágenes MNIST . Este ejemplo utiliza a propósito la API de bajo nivel de TF2 para definir construcciones de capas personalizadas, función de pérdida y bucle de entrenamiento, porque la probabilidad de errores de NaN es mayor cuando usamos esta API más flexible pero más propensa a errores que cuando usamos la API más sencilla. API de alto nivel fáciles de usar pero un poco menos flexibles, como tf.keras .

El programa imprime una prueba de precisión después de cada paso de entrenamiento. Podemos ver en la consola que la precisión de la prueba se atasca en un nivel cercano al azar (~0,1) después del primer paso. Ciertamente, no es así como se espera que se comporte el entrenamiento del modelo: esperamos que la precisión se acerque gradualmente a 1,0 (100 %) a medida que aumenta el paso.

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

Una suposición fundamentada es que este problema es causado por una inestabilidad numérica, como NaN o infinito. Sin embargo, ¿cómo confirmamos que este es realmente el caso y cómo encontramos que la operación TensorFlow (op) es responsable de generar la inestabilidad numérica? Para responder a estas preguntas, instrumentemos el programa con errores con Debugger V2.

Instrumentación del código de TensorFlow con Debugger V2

tf.debugging.experimental.enable_dump_debug_info() es el punto de entrada de API de Debugger V2. Instrumenta un programa TF2 con una sola línea de código. Por ejemplo, agregar la siguiente línea cerca del comienzo del programa hará que la información de depuración se escriba en el directorio de registro (logdir) en /tmp/tfdbg2_logdir. La información de depuración cubre varios aspectos del tiempo de ejecución de TensorFlow. En TF2, incluye el historial completo de la ejecución ansiosa, la creación de gráficos realizada por @tf.function , la ejecución de los gráficos, los valores de tensor generados por los eventos de ejecución, así como la ubicación del código (seguimiento de la pila de Python) de esos eventos. . La riqueza de la información de depuración permite a los usuarios concentrarse en errores oscuros.

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

El argumento tensor_debug_mode controla qué información extrae Debugger V2 de cada tensor ansioso o en gráfico. "FULL_HEALTH" es un modo que captura la siguiente información sobre cada tensor de tipo flotante (por ejemplo, el float32 comúnmente visto y el bfloat16 dtype menos común):

  • Tipo D
  • Rango
  • Número total de elementos
  • Un desglose de los elementos de tipo flotante en las siguientes categorías: finito negativo ( - ), cero ( 0 ), finito positivo ( + ), infinito negativo ( -∞ ), infinito positivo ( +∞ ) y NaN .

El modo "FULL_HEALTH" es adecuado para depurar errores relacionados con NaN e infinito. Consulte a continuación para conocer otros tensor_debug_mode s compatibles.

El argumento circular_buffer_size controla cuántos eventos de tensor se guardan en logdir. El valor predeterminado es 1000, lo que hace que solo los últimos 1000 tensores antes del final del programa TF2 instrumentado se guarden en el disco. Este comportamiento predeterminado reduce la sobrecarga del depurador al sacrificar la integridad de los datos de depuración. Si se prefiere la integridad, como en este caso, podemos desactivar el búfer circular estableciendo el argumento en un valor negativo (por ejemplo, -1 aquí).

El ejemplo debug_mnist_v2 invoca enable_dump_debug_info() pasándole indicadores de línea de comandos. Para volver a ejecutar nuestro problemático programa TF2 con esta instrumentación de depuración habilitada, haz lo siguiente:

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

Inicio de la GUI del depurador V2 en TensorBoard

Ejecutar el programa con la instrumentación del depurador crea un logdir en /tmp/tfdbg2_logdir. Podemos iniciar TensorBoard y apuntarlo al logdir con:

tensorboard --logdir /tmp/tfdbg2_logdir

En el navegador web, vaya a la página de TensorBoard en http://localhost:6006. El complemento "Debugger V2" estará inactivo de forma predeterminada, así que selecciónelo en el menú "Complementos inactivos" en la parte superior derecha. Una vez seleccionado, debería verse como lo siguiente:

Captura de pantalla de vista completa del depurador V2

Uso de la interfaz gráfica de usuario de Debugger V2 para encontrar la causa raíz de los NaN

La GUI de Debugger V2 en TensorBoard está organizada en seis secciones:

  • Alertas : esta sección superior izquierda contiene una lista de eventos de "alerta" detectados por el depurador en los datos de depuración del programa TensorFlow instrumentado. Cada alerta indica una determinada anomalía que merece atención. En nuestro caso, esta sección destaca los eventos de 499 NaN/∞ con un color rosa-rojo saliente. Esto confirma nuestra sospecha de que el modelo no aprende debido a la presencia de NaN y/o infinitos en sus valores de tensor interno. Profundizaremos en estas alertas en breve.
  • Línea de tiempo de ejecución de Python : Esta es la mitad superior de la sección media superior. Presenta la historia completa de la ansiosa ejecución de operaciones y gráficos. Cada cuadro de la línea de tiempo está marcado con la letra inicial del nombre de la operación o del gráfico (por ejemplo, "T" para la operación "TensorSliceDataset", "m" para la función "modelo" tf.function .). Podemos navegar por esta línea de tiempo usando los botones de navegación y la barra de desplazamiento sobre la línea de tiempo.
  • Ejecución de gráficos : ubicada en la esquina superior derecha de la GUI, esta sección será fundamental para nuestra tarea de depuración. Contiene un historial de todos los tensores de tipo flotante calculados dentro de gráficos (es decir, compilados por @tf-function s).
  • La estructura del gráfico (mitad inferior de la sección superior central), el código fuente (sección inferior izquierda) y el seguimiento de la pila (sección inferior derecha) están inicialmente vacíos. Sus contenidos se completarán cuando interactuemos con la GUI. Estas tres secciones también jugarán un papel importante en nuestra tarea de depuración.

Habiéndonos orientado a la organización de la interfaz de usuario, sigamos los siguientes pasos para llegar al fondo de por qué aparecieron los NaN. Primero, haga clic en la alerta NaN/∞ en la sección Alertas. Esto desplaza automáticamente la lista de 600 tensores gráficos en la sección Ejecución gráfica y se enfoca en el #88, que es un tensor llamado Log:0 generado por una Log (logaritmo natural). Un color rosa-rojo saliente resalta un elemento -∞ entre los 1000 elementos del tensor 2D float32. Este es el primer tensor en el historial de tiempo de ejecución del programa TF2 que contenía NaN o infinito: los tensores calculados antes no contienen NaN ni ∞; muchos (de hecho, la mayoría) de los tensores calculados posteriormente contienen NaN. Podemos confirmar esto desplazándonos hacia arriba y hacia abajo en la lista de ejecución de gráficos. Esta observación proporciona un fuerte indicio de que Log op es la fuente de la inestabilidad numérica en este programa TF2.

Depurador V2: alertas Nan/Infinity y lista de ejecución de gráficos

¿Por qué este Log op escupe un -∞? Responder a esa pregunta requiere examinar la entrada a la op. Al hacer clic en el nombre del tensor ( Log:0 ), aparece una visualización simple pero informativa de la vecindad de Log op en su gráfico TensorFlow en la sección Estructura del gráfico. Tenga en cuenta la dirección de arriba a abajo del flujo de información. La operación en sí se muestra en negrita en el medio. Inmediatamente encima, podemos ver una operación de marcador de posición que proporciona la única entrada para la operación de Log . ¿Dónde está el tensor generado por este marcador de posición de probs en la lista de ejecución de gráfico? Usando el color de fondo amarillo como ayuda visual, podemos ver que el tensor probs:0 está tres filas por encima del tensor Log:0 , es decir, en la fila 85.

Depurador V2: vista de estructura gráfica y seguimiento al tensor de entrada

Una mirada más cuidadosa al desglose numérico del tensor probs:0 en la fila 85 revela por qué su consumidor Log:0 produce un -∞: Entre los 1000 elementos de probs:0 , un elemento tiene un valor de 0. El -∞ es resultado de calcular el logaritmo natural de 0! Si de alguna manera podemos asegurarnos de que Log op quede expuesto solo a entradas positivas, podremos evitar que ocurra el NaN/∞. Esto se puede lograr aplicando recorte (p. ej., usando tf.clip_by_value() ) en el tensor de probabilidad de probs de posición.

Estamos cada vez más cerca de resolver el error, pero aún no lo hemos hecho del todo. Para aplicar la solución, necesitamos saber en qué parte del código fuente de Python se originaron la operación de Log y su entrada de marcador de posición. Debugger V2 brinda soporte de primera clase para rastrear las operaciones gráficas y los eventos de ejecución hasta su fuente. Cuando hicimos clic en el tensor Log:0 en Graph Executions, la sección Stack Trace se llenó con el stack trace original de la creación de Log op. El seguimiento de la pila es algo grande porque incluye muchos marcos del código interno de TensorFlow (p. ej., gen_math_ops.py y dumping_callback.py), que podemos ignorar con seguridad para la mayoría de las tareas de depuración. El marco de interés es la línea 216 de debug_mnist_v2.py (es decir, el archivo de Python que estamos tratando de depurar). Al hacer clic en "Línea 216", aparece una vista de la línea de código correspondiente en la sección Código fuente.

Depurador V2: código fuente y seguimiento de pila

Esto finalmente nos lleva al código fuente que creó la operación de Log problemática a partir de su probs de problemas. Esta es nuestra función de pérdida de entropía cruzada categórica personalizada decorada con @tf.function y, por lo tanto, convertida en un gráfico de TensorFlow. El marcador de posición op probs corresponde al primer argumento de entrada de la función de pérdida. La operación Log se crea con la llamada a la API tf.math.log().

La solución de recorte de valor para este error se verá así:

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

Resolverá la inestabilidad numérica en este programa TF2 y hará que el MLP se entrene con éxito. Otro enfoque posible para arreglar la inestabilidad numérica es usar tf.keras.losses.CategoricalCrossentropy .

Esto concluye nuestro viaje desde la observación de un error en el modelo TF2 hasta la creación de un cambio de código que corrige el error, con la ayuda de la herramienta Debugger V2, que proporciona una visibilidad completa del historial de ejecución gráfica y entusiasta del programa TF2 instrumentado, incluidos los resúmenes numéricos. de valores de tensores y asociación entre operaciones, tensores y su código fuente original.

Compatibilidad de hardware de Debugger V2

Debugger V2 es compatible con el hardware de entrenamiento convencional, incluidas la CPU y la GPU. También se admite el entrenamiento de varias GPU con tf.distributed.MirroredStrategy . El soporte para TPU aún se encuentra en una etapa inicial y requiere llamar

tf.config.set_soft_device_placement(True)

antes de llamar a enable_dump_debug_info() . También puede tener otras limitaciones en las TPU. Si tiene problemas para usar Debugger V2, informe los errores en nuestra página de problemas de GitHub .

Compatibilidad API de Debugger V2

Debugger V2 se implementa en un nivel relativamente bajo de la pila de software de TensorFlow y, por lo tanto, es compatible con tf.keras , tf.data y otras API creadas sobre los niveles inferiores de TensorFlow. El depurador V2 también es compatible con versiones anteriores de TF1, aunque la línea de tiempo de ejecución ansiosa estará vacía para los directorios de registro de depuración generados por los programas TF1.

Consejos de uso de la API

Una pregunta frecuente sobre esta API de depuración es dónde se debe insertar la llamada a enable_dump_debug_info() en el código de TensorFlow. Por lo general, la API debe llamarse lo antes posible en su programa TF2, preferiblemente después de las líneas de importación de Python y antes de que comience la creación y ejecución de gráficos. Esto garantizará una cobertura completa de todas las operaciones y gráficos que impulsan su modelo y su entrenamiento.

Los tensor_debug_modes admitidos actualmente son: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH y SHAPE . Varían en la cantidad de información extraída de cada tensor y la sobrecarga de rendimiento del programa depurado. Consulte la sección de argumentos de la documentación de enable_dump_debug_info() .

Gastos generales de rendimiento

La API de depuración introduce una sobrecarga de rendimiento en el programa TensorFlow instrumentado. La sobrecarga varía según tensor_debug_mode , el tipo de hardware y la naturaleza del programa TensorFlow instrumentado. Como punto de referencia, en una GPU, el modo NO_TENSOR agrega una sobrecarga del 15 % durante el entrenamiento de un modelo de Transformer con un tamaño de lote 64. El porcentaje de sobrecarga para otros modos tensor_debug_mode es mayor: aproximadamente un 50 % para CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH y SHAPE modos. En las CPU, la sobrecarga es ligeramente menor. En las TPU, los gastos generales son actualmente más altos.

Relación con otras API de depuración de TensorFlow

Tenga en cuenta que TensorFlow ofrece otras herramientas y API para la depuración. Puede explorar dichas API en el espacio de nombres tf.debugging.* en la página de documentación de la API. Entre estas API, la más utilizada es tf.print() . ¿Cuándo se debe usar Debugger V2 y cuándo se debe tf.print() en su lugar? tf.print() es conveniente en caso de que

  1. sabemos exactamente qué tensores imprimir,
  2. sabemos dónde exactamente en el código fuente para insertar esas declaraciones tf.print() ,
  3. el número de tales tensores no es demasiado grande.

Para otros casos (p. ej., examinar muchos valores de tensor, examinar valores de tensor generados por el código interno de TensorFlow y buscar el origen de la inestabilidad numérica como mostramos anteriormente), Debugger V2 proporciona una forma más rápida de depuración. Además, Debugger V2 proporciona un enfoque unificado para inspeccionar tensores ansiosos y gráficos. Además, proporciona información sobre la estructura del gráfico y las ubicaciones del código, que están más allá de la capacidad de tf.print() .

Otra API que se puede usar para depurar problemas relacionados con ∞ y NaN es tf.debugging.enable_check_numerics() . A diferencia enable_dump_debug_info() , enable_check_numerics() no guarda información de depuración en el disco. En cambio, simplemente monitorea ∞ y NaN durante el tiempo de ejecución de TensorFlow y genera errores con la ubicación del código de origen tan pronto como cualquier operación genera valores numéricos tan malos. Tiene una sobrecarga de rendimiento más baja en comparación con enable_dump_debug_info() , pero no ofrece un seguimiento completo del historial de ejecución del programa y no viene con una interfaz gráfica de usuario como Debugger V2.