Se usó la API de Cloud Translation para traducir esta página.
Switch to English

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

Los eventos catastróficos que involucran NaN s a veces pueden ocurrir durante un programa TensorFlow, paralizando los procesos de entrenamiento del modelo. La causa raíz de tales eventos a menudo es oscura, especialmente para los 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 a través de 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 tensor de tiempo de ejecución en programas complejos. Este tutorial se centra en NaN debido a su frecuencia relativamente alta de ocurrencia.

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 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 capa 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 más fácil -to-use pero APIs de alto nivel ligeramente menos flexibles como tf.keras .

El programa imprime una precisión de prueba 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 modelo de capacitación: 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 educada 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 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 (rastros de pila de Python) de esos eventos . La riqueza de la información de depuración permite a los usuarios enfocarse en errores oscuros.

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

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

  • DType
  • Rango
  • Numero 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 que involucran NaN e infinito. Consulte a continuación otros tensor_debug_mode s tensor_debug_mode .

El argumento circular_buffer_size controla cuántos eventos tensoriales se guardan en el 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 deshabilitar 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 banderas de línea de comandos. Para volver a ejecutar nuestro programa TF2 problemático con esta instrumentación de depuración habilitada, haga 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 de Debugger V2 en TensorBoard

La ejecución del 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, navegue a la página de TensorBoard en http: // localhost: 6006. El complemento "Debugger V2" debe activarse de forma predeterminada, mostrando una página similar a la siguiente:

Captura de pantalla de la vista completa del depurador V2

Usando Debugger V2 GUI para encontrar la causa raíz de NaNs

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 instrumentado TensorFlow. Cada alerta indica una cierta anomalía que merece atención. En nuestro caso, esta sección destaca 499 eventos NaN / ∞ con un destacado color rosa-rojo. Esto confirma nuestra sospecha de que el modelo no puede aprender debido a la presencia de NaN y / o infinitos en sus valores internos de tensor. Profundizaremos en estas alertas en breve.
  • Línea de tiempo de ejecución de Python : esta es la mitad superior de la sección superior central. Presenta la historia completa de la ansiosa ejecución de operaciones y gráficos. Cada cuadro de la línea de tiempo está marcado por la letra inicial de la operación o el nombre del gráfico (por ejemplo, "T" para el "TensorSliceDataset" op, "m" para el "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 los 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 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 desempeñarán papeles importantes en nuestra tarea de depuración.

Habiéndonos orientado a la organización de la IU, tomemos los siguientes pasos para llegar al fondo de por qué aparecieron las NaN. Primero, haga clic en la alerta NaN / ∞ en la sección Alertas. Esto desplaza automáticamente la lista de 600 tensores de gráficos en la sección Ejecución de gráficos y se enfoca en el # 88, que es un tensor llamado "Log: 0" generado por un Log (logaritmo natural) op. Un destacado color rosa-rojo 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: tensores calculados antes de que no contengan NaN o ∞; 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 una fuerte pista de que el Log op es la fuente de la inestabilidad numérica en este programa TF2.

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

¿Por qué esta operación de Log escupe un -∞? Responder esa pregunta requiere examinar la entrada a la operación. 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 de él, podemos ver que una opción de Marcador de posición proporciona la única entrada para la opción de Log . ¿Dónde está el tensor generado por este marcador de posición logits en la lista de ejecución de gráficos? Al utilizar el color de fondo amarillo como ayuda visual, podemos ver que el tensor logits:0 está dos 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 "logits: 0" en la fila 85 revela por qué su consumidor Log:0 produce un -∞: entre los 1000 elementos de "logits: 0", un elemento tiene un valor de 0. ¡El -∞ es el resultado de calcular el logaritmo natural de 0! Si de alguna manera podemos asegurarnos de que la operación Log se exponga solo a entradas positivas, podremos evitar que ocurra el NaN / ∞. Esto se puede lograr aplicando recorte (por ejemplo, usando tf.clip_by_value () ) en el marcador de posición logits tensor.

Nos estamos acercando a resolver el error, pero aún no lo hemos hecho. Para aplicar la corrección, necesitamos saber en qué parte del código fuente de Python se originó el registro y su entrada de marcador de posición. Debugger V2 proporciona 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 completó con el seguimiento de pila original de la creación del Log op. El seguimiento de la pila es algo grande porque incluye muchos marcos del código interno de TensorFlow (por ejemplo, gen_math_ops.py y dump_callback.py), que podemos ignorar de manera segura 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 Python que realmente estamos tratando de depurar). Al hacer clic en "Línea 204", 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 Log problemática desde su entrada logits. 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 TensorFlow. El marcador de posición op "logits" corresponde al primer argumento de entrada a la función de pérdida. Log op 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.clip_by_value(tf.math.log(logits), 1e-6, 1.))
 

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

Esto concluye nuestro viaje desde la observación de un error del modelo TF2 hasta llegar a 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 de gráficos y del programa TF2 instrumentado, incluidos los resúmenes numéricos de valores de tensor y asociación entre operaciones, tensores y su código fuente original.

Compatibilidad de hardware del depurador V2

El depurador V2 es compatible con hardware de entrenamiento convencional, incluida la CPU y la GPU. También se admite la capacitación en múltiples GPU con tf.distributed.MirroredStrategy . El soporte para TPU todavía está en una etapa temprana 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 los TPU. Si tiene problemas al 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 registros de depuración generados por los programas TF1.

Consejos de uso de API

Una pregunta frecuente sobre esta API de depuración es dónde en el código TensorFlow se debe insertar la llamada a enable_dump_debug_info() . Por lo general, se debe llamar a la API 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 construcción y ejecución del gráfico. Esto asegurará una cobertura total de todas las operaciones y gráficos que potencian su modelo y su entrenamiento.

Los modos 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 para el programa depurado. Consulte la sección args de la enable_dump_debug_info() de enable_dump_debug_info() ].

Sobrecarga de rendimiento

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

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 examinar 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() 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 (por ejemplo, examinar muchos valores de tensor, examinar los 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 entusiastas y gráficos. Además, proporciona información sobre la estructura del gráfico y las ubicaciones de los códigos, que están más allá de la capacidad de tf.print() .

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