Depuração de problemas numéricos em programas TensorFlow usando o TensorBoard Debugger V2

Às vezes, eventos catastróficos envolvendo NaN s podem ocorrer durante um programa TensorFlow, prejudicando os processos de treinamento do modelo. A causa raiz de tais eventos geralmente é obscura, especialmente para modelos de tamanho e complexidade não triviais. Para facilitar a depuração desse tipo de bug de modelo, o TensorBoard 2.3+ (junto com o TensorFlow 2.3+) fornece um painel especializado chamado Debugger V2. Aqui demonstramos como usar essa ferramenta trabalhando em um bug real envolvendo NaNs em uma rede neural escrita em TensorFlow.

As técnicas ilustradas neste tutorial são aplicáveis ​​a outros tipos de atividades de depuração, como inspecionar formas de tensor de tempo de execução em programas complexos. Este tutorial se concentra em NaNs devido à sua frequência relativamente alta de ocorrência.

Observando o erro

O código fonte do programa TF2 que vamos depurar está disponível no GitHub . O programa de exemplo também é empacotado no pacote pip tensorflow (versão 2.3+) e pode ser invocado por:

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

Este programa TF2 cria uma percepção multicamada (MLP) e a treina para reconhecer imagens MNIST . Este exemplo usa propositadamente a API de baixo nível do TF2 para definir construções de camada personalizadas, função de perda e loop de treinamento, porque a probabilidade de erros de NaN é maior quando usamos essa API mais flexível, mas mais propensa a erros do que quando usamos a mais fácil APIs de alto nível -to-use, mas um pouco menos flexíveis, como tf.keras .

O programa imprime uma precisão de teste após cada etapa de treinamento. Podemos ver no console que a precisão do teste fica travada em um nível quase de chance (~0,1) após a primeira etapa. Certamente não é assim que se espera que o treinamento do modelo se comporte: esperamos que a precisão se aproxime gradualmente de 1,0 (100%) à medida que a etapa aumenta.

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

Um palpite é que esse problema é causado por uma instabilidade numérica, como NaN ou infinito. No entanto, como confirmamos que esse é realmente o caso e como encontramos a operação TensorFlow (op) responsável por gerar a instabilidade numérica? Para responder a essas perguntas, vamos instrumentar o programa de buggy com o Debugger V2.

Como instrumentar o código do TensorFlow com o Debugger V2

tf.debugging.experimental.enable_dump_debug_info() é o ponto de entrada da API do Debugger V2. Ele instrumenta um programa TF2 com uma única linha de código. Por exemplo, adicionar a seguinte linha perto do início do programa fará com que as informações de depuração sejam gravadas no diretório de log (logdir) em /tmp/tfdbg2_logdir. As informações de depuração abrangem vários aspectos do tempo de execução do TensorFlow. No TF2, ele inclui o histórico completo de execução antecipada, construção de gráfico realizada por @tf.function , a execução dos gráficos, os valores de tensor gerados pelos eventos de execução, bem como a localização do código (Python stack traces) desses eventos . A riqueza das informações de depuração permite que os usuários se limitem a bugs obscuros.

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

O argumento tensor_debug_mode controla quais informações o Debugger V2 extrai de cada tensor ansioso ou no gráfico. “FULL_HEALTH” é um modo que captura as seguintes informações sobre cada tensor do tipo flutuante (por exemplo, o float32 comumente visto e o bfloat16 dtype menos comum):

  • Tipo D
  • Classificação
  • Número total de elementos
  • Uma divisão dos elementos do tipo flutuante nas seguintes categorias: finito negativo ( - ), zero ( 0 ), finito positivo ( + ), infinito negativo ( -∞ ), infinito positivo ( +∞ ) e NaN .

O modo “FULL_HEALTH” é adequado para depurar bugs envolvendo NaN e infinito. Veja abaixo outros tensor_debug_mode s suportados.

O argumento circular_buffer_size controla quantos eventos de tensor são salvos no logdir. O padrão é 1000, o que faz com que apenas os últimos 1000 tensores antes do final do programa TF2 instrumentado sejam salvos em disco. Esse comportamento padrão reduz a sobrecarga do depurador ao sacrificar a integridade dos dados de depuração. Se a integridade for preferida, como neste caso, podemos desabilitar o buffer circular definindo o argumento para um valor negativo (por exemplo, -1 aqui).

O exemplo debug_mnist_v2 invoca enable_dump_debug_info() passando sinalizadores de linha de comando para ele. Para executar nosso programa TF2 problemático novamente com esta instrumentação de depuração habilitada, faça:

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

Iniciando a GUI do Debugger V2 no TensorBoard

A execução do programa com a instrumentação do depurador cria um logdir em /tmp/tfdbg2_logdir. Podemos iniciar o TensorBoard e apontá-lo para o logdir com:

tensorboard --logdir /tmp/tfdbg2_logdir

No navegador da Web, navegue até a página do TensorBoard em http://localhost:6006. O plug-in “Debugger V2” estará inativo por padrão, então selecione-o no menu “Plugins inativos” no canto superior direito. Uma vez selecionado, ele deve se parecer com o seguinte:

Captura de tela completa do Debugger V2

Usando a GUI do Debugger V2 para encontrar a causa raiz dos NaNs

A GUI do Debugger V2 no TensorBoard é organizada em seis seções:

  • Alertas : esta seção no canto superior esquerdo contém uma lista de eventos de "alerta" detectados pelo depurador nos dados de depuração do programa TensorFlow instrumentado. Cada alerta indica uma determinada anomalia que merece atenção. No nosso caso, esta seção destaca 499 eventos NaN/∞ com uma cor rosa-vermelha saliente. Isso confirma nossa suspeita de que o modelo falha em aprender devido à presença de NaNs e/ou infinitos em seus valores de tensores internos. Vamos nos aprofundar nesses alertas em breve.
  • Linha do tempo de execução do Python : Esta é a metade superior da seção do meio superior. Apresenta a história completa da execução ansiosa de operações e gráficos. Cada caixa da linha do tempo é marcada pela letra inicial do nome do op ou gráfico (por exemplo, “T” para o op “TensorSliceDataset”, “m” para o “model” tf.function ). Podemos navegar nessa linha do tempo usando os botões de navegação e a barra de rolagem acima da linha do tempo.
  • Execução do gráfico : Localizada no canto superior direito da GUI, esta seção será central para nossa tarefa de depuração. Ele contém um histórico de todos os tensores de tipo flutuante calculados dentro de gráficos (ou seja, compilados por @tf-function s).
  • A estrutura do gráfico (metade inferior da seção central superior), o código-fonte (seção inferior esquerda) e o rastreamento de pilha (seção inferior direita) estão inicialmente vazios. Seu conteúdo será preenchido quando interagirmos com a GUI. Essas três seções também desempenharão papéis importantes em nossa tarefa de depuração.

Tendo nos orientado para a organização da interface do usuário, vamos seguir os seguintes passos para entender por que os NaNs apareceram. Primeiro, clique no alerta NaN/∞ na seção Alertas. Isso rola automaticamente a lista de 600 tensores de gráfico na seção Graph Execution e se concentra no #88, que é um tensor chamado Log:0 gerado por uma Log (logaritmo natural). Uma cor rosa-vermelha saliente destaca um elemento -∞ entre os 1000 elementos do tensor 2D float32. Este é o primeiro tensor no histórico de tempo de execução do programa TF2 que continha qualquer NaN ou infinito: tensores calculados antes dele não contêm NaN ou ∞; muitos (na verdade, a maioria) tensores calculados posteriormente contêm NaNs. Podemos confirmar isso rolando para cima e para baixo na lista Graph Execution. Esta observação fornece uma forte indicação de que o Log op é a fonte da instabilidade numérica neste programa TF2.

Debugger V2: alertas Nan/Infinito e lista de execução de gráficos

Por que este Log op cuspir um -∞? Responder a essa pergunta requer examinar a entrada para o op. Clicar no nome do tensor ( Log:0 ) traz uma visualização simples, mas informativa da vizinhança do Log op em seu gráfico TensorFlow na seção Graph Structure. Observe a direção de cima para baixo do fluxo de informações. O próprio op é mostrado em negrito no meio. Imediatamente acima dele, podemos ver que um op Placeholder fornece a única entrada para o op Log . Onde está o tensor gerado por este probs Placeholder na lista Graph Execution? Usando a cor de fundo amarela como auxílio visual, podemos ver que o tensor probs:0 está três linhas acima do tensor Log:0 , ou seja, na linha 85.

Debugger V2: visualização da estrutura do gráfico e rastreamento para o tensor de entrada

Um olhar mais cuidadoso na divisão numérica do tensor probs:0 na linha 85 revela por que seu consumidor Log:0 produz um -∞: Entre os 1000 elementos de probs:0 , um elemento tem um valor de 0. O -∞ é resultado do cálculo do logaritmo natural de 0! Se de alguma forma pudermos garantir que o Log op seja exposto apenas a entradas positivas, poderemos evitar que o NaN/∞ aconteça. Isso pode ser obtido aplicando recorte (por exemplo, usando tf.clip_by_value() ) no tensor probs Placeholder.

Estamos chegando mais perto de resolver o bug, mas ainda não terminamos. Para aplicar a correção, precisamos saber onde no código-fonte Python o Log op e sua entrada Placeholder se originaram. O Debugger V2 fornece suporte de primeira classe para rastrear as operações de gráfico e eventos de execução até sua origem. Quando clicamos no tensor Log:0 em Graph Executions, a seção Stack Trace foi preenchida com o stack trace original da criação do Log op. O rastreamento de pilha é um pouco grande porque inclui muitos quadros do código interno do TensorFlow (por exemplo, gen_math_ops.py e dumping_callback.py), que podemos ignorar com segurança para a maioria das tarefas de depuração. O quadro de interesse é a linha 216 de debug_mnist_v2.py (ou seja, o arquivo Python que estamos tentando depurar). Clicar em “Linha 216” traz uma visão da linha de código correspondente na seção Código-Fonte.

Depurador V2: código-fonte e rastreamento de pilha

Isso finalmente nos leva ao código-fonte que criou o Log op problemático a partir de sua entrada probs . Esta é a nossa função de perda de entropia cruzada categórica personalizada decorada com @tf.function e, portanto, convertida em um gráfico do TensorFlow. O Placeholder op probs corresponde ao primeiro argumento de entrada para a função de perda. A operação Log é criada com a chamada da API tf.math.log().

A correção de recorte de valor para esse bug será algo como:

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

Isso resolverá a instabilidade numérica neste programa TF2 e fará com que o MLP treine com sucesso. Outra abordagem possível para corrigir a instabilidade numérica é usar tf.keras.losses.CategoricalCrossentropy .

Isso conclui nossa jornada desde a observação de um bug do modelo TF2 até a criação de uma alteração de código que corrige o bug, auxiliado pela ferramenta Debugger V2, que fornece visibilidade total do histórico de execução ansioso e gráfico do programa TF2 instrumentado, incluindo os resumos numéricos de valores de tensor e associação entre ops, tensores e seu código fonte original.

Compatibilidade de hardware do Debugger V2

O Debugger V2 suporta hardware de treinamento convencional, incluindo CPU e GPU. O treinamento multi-GPU com tf.distributed.MirroredStrategy também é suportado. O suporte para TPU ainda está em estágio inicial e requer chamadas

tf.config.set_soft_device_placement(True)

antes de chamar enable_dump_debug_info() . Pode ter outras limitações em TPUs também. Se você tiver problemas ao usar o Debugger V2, informe os bugs em nossa página de problemas do GitHub .

Compatibilidade da API do Debugger V2

O Debugger V2 é implementado em um nível relativamente baixo da pilha de software do TensorFlow e, portanto, é compatível com tf.keras , tf.data e outras APIs criadas nos níveis inferiores do TensorFlow. O Debugger V2 também é compatível com versões anteriores do TF1, embora a Eager Execution Timeline esteja vazia para os logdirs de depuração gerados pelos programas TF1.

Dicas de uso da API

Uma pergunta frequente sobre essa API de depuração é onde no código do TensorFlow deve-se inserir a chamada para enable_dump_debug_info() . Normalmente, a API deve ser chamada o mais cedo possível em seu programa TF2, preferencialmente após as linhas de importação do Python e antes do início da construção e execução do gráfico. Isso garantirá a cobertura total de todas as operações e gráficos que potencializam seu modelo e seu treinamento.

Os tensor_debug_modes atualmente suportados são: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE . Eles variam na quantidade de informações extraídas de cada tensor e na sobrecarga de desempenho do programa depurado. Consulte a seção args da documentação de enable_dump_debug_info() .

Sobrecarga de desempenho

A API de depuração apresenta sobrecarga de desempenho ao programa TensorFlow instrumentado. A sobrecarga varia de acordo com tensor_debug_mode , tipo de hardware e natureza do programa TensorFlow instrumentado. Como ponto de referência, em uma GPU, o modo NO_TENSOR adiciona uma sobrecarga de 15% durante o treinamento de um modelo Transformer com tamanho de lote 64. A sobrecarga percentual para outros tensor_debug_modes é maior: aproximadamente 50% para CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE modos. Em CPUs, a sobrecarga é um pouco menor. Em TPUs, a sobrecarga é atualmente maior.

Relação com outras APIs de depuração do TensorFlow

Observe que o TensorFlow oferece outras ferramentas e APIs para depuração. Você pode navegar por essas APIs no namespace tf.debugging.* na página de documentos da API. Dentre essas APIs, a mais utilizada é tf.print() . Quando se deve usar o Debugger V2 e quando se deve tf.print() ? tf.print() é conveniente no caso de

  1. sabemos exatamente quais tensores imprimir,
  2. sabemos onde exatamente no código-fonte inserir essas tf.print() ,
  3. o número de tais tensores não é muito grande.

Para outros casos (por exemplo, examinar muitos valores de tensor, examinar valores de tensor gerados pelo código interno do TensorFlow e pesquisar a origem da instabilidade numérica como mostramos acima), o Debugger V2 oferece uma maneira mais rápida de depuração. Além disso, o Debugger V2 fornece uma abordagem unificada para inspecionar tensores ansiosos e gráficos. Além disso, fornece informações sobre a estrutura do gráfico e as localizações do código, que estão além da capacidade de tf.print() .

Outra API que pode ser usada para depurar problemas envolvendo ∞ e NaN é tf.debugging.enable_check_numerics() . Ao contrário enable_dump_debug_info() , enable_check_numerics() não salva informações de depuração no disco. Em vez disso, ele apenas monitora ∞ e NaN durante o tempo de execução do TensorFlow e elimina os erros com a localização do código de origem assim que qualquer operação gera esses valores numéricos ruins. Ele tem uma sobrecarga de desempenho menor em comparação com enable_dump_debug_info() , mas não oferece um rastreamento completo do histórico de execução do programa e não vem com uma interface gráfica de usuário como o Debugger V2.

,

Às vezes, eventos catastróficos envolvendo NaN s podem ocorrer durante um programa TensorFlow, prejudicando os processos de treinamento do modelo. A causa raiz de tais eventos geralmente é obscura, especialmente para modelos de tamanho e complexidade não triviais. Para facilitar a depuração desse tipo de bug de modelo, o TensorBoard 2.3+ (junto com o TensorFlow 2.3+) fornece um painel especializado chamado Debugger V2. Aqui demonstramos como usar essa ferramenta trabalhando em um bug real envolvendo NaNs em uma rede neural escrita em TensorFlow.

As técnicas ilustradas neste tutorial são aplicáveis ​​a outros tipos de atividades de depuração, como inspecionar formas de tensor de tempo de execução em programas complexos. Este tutorial se concentra em NaNs devido à sua frequência relativamente alta de ocorrência.

Observando o erro

O código fonte do programa TF2 que vamos depurar está disponível no GitHub . O programa de exemplo também é empacotado no pacote pip tensorflow (versão 2.3+) e pode ser invocado por:

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

Este programa TF2 cria uma percepção multicamada (MLP) e a treina para reconhecer imagens MNIST . Este exemplo usa propositadamente a API de baixo nível do TF2 para definir construções de camada personalizadas, função de perda e loop de treinamento, porque a probabilidade de erros de NaN é maior quando usamos essa API mais flexível, mas mais propensa a erros do que quando usamos a mais fácil APIs de alto nível -to-use, mas um pouco menos flexíveis, como tf.keras .

O programa imprime uma precisão de teste após cada etapa de treinamento. Podemos ver no console que a precisão do teste fica travada em um nível quase de chance (~0,1) após a primeira etapa. Certamente não é assim que se espera que o treinamento do modelo se comporte: esperamos que a precisão se aproxime gradualmente de 1,0 (100%) à medida que a etapa aumenta.

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

Um palpite é que esse problema é causado por uma instabilidade numérica, como NaN ou infinito. No entanto, como confirmamos que esse é realmente o caso e como encontramos a operação TensorFlow (op) responsável por gerar a instabilidade numérica? Para responder a essas perguntas, vamos instrumentar o programa de buggy com o Debugger V2.

Como instrumentar o código do TensorFlow com o Debugger V2

tf.debugging.experimental.enable_dump_debug_info() é o ponto de entrada da API do Debugger V2. Ele instrumenta um programa TF2 com uma única linha de código. Por exemplo, adicionar a seguinte linha perto do início do programa fará com que as informações de depuração sejam gravadas no diretório de log (logdir) em /tmp/tfdbg2_logdir. As informações de depuração abrangem vários aspectos do tempo de execução do TensorFlow. No TF2, ele inclui o histórico completo de execução antecipada, construção de gráfico realizada por @tf.function , a execução dos gráficos, os valores de tensor gerados pelos eventos de execução, bem como a localização do código (Python stack traces) desses eventos . A riqueza das informações de depuração permite que os usuários se limitem a bugs obscuros.

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

O argumento tensor_debug_mode controla quais informações o Debugger V2 extrai de cada tensor ansioso ou no gráfico. “FULL_HEALTH” é um modo que captura as seguintes informações sobre cada tensor do tipo flutuante (por exemplo, o float32 comumente visto e o bfloat16 dtype menos comum):

  • Tipo D
  • Classificação
  • Número total de elementos
  • Uma divisão dos elementos do tipo flutuante nas seguintes categorias: finito negativo ( - ), zero ( 0 ), finito positivo ( + ), infinito negativo ( -∞ ), infinito positivo ( +∞ ) e NaN .

O modo “FULL_HEALTH” é adequado para depurar bugs envolvendo NaN e infinito. Veja abaixo outros tensor_debug_mode s suportados.

O argumento circular_buffer_size controla quantos eventos de tensor são salvos no logdir. O padrão é 1000, o que faz com que apenas os últimos 1000 tensores antes do final do programa TF2 instrumentado sejam salvos em disco. Esse comportamento padrão reduz a sobrecarga do depurador ao sacrificar a integridade dos dados de depuração. Se a integridade for preferida, como neste caso, podemos desabilitar o buffer circular definindo o argumento para um valor negativo (por exemplo, -1 aqui).

O exemplo debug_mnist_v2 invoca enable_dump_debug_info() passando sinalizadores de linha de comando para ele. Para executar nosso programa TF2 problemático novamente com esta instrumentação de depuração habilitada, faça:

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

Iniciando a GUI do Debugger V2 no TensorBoard

A execução do programa com a instrumentação do depurador cria um logdir em /tmp/tfdbg2_logdir. Podemos iniciar o TensorBoard e apontá-lo para o logdir com:

tensorboard --logdir /tmp/tfdbg2_logdir

No navegador da Web, navegue até a página do TensorBoard em http://localhost:6006. O plug-in “Debugger V2” estará inativo por padrão, então selecione-o no menu “Plugins inativos” no canto superior direito. Uma vez selecionado, ele deve se parecer com o seguinte:

Captura de tela completa do Debugger V2

Usando a GUI do Debugger V2 para encontrar a causa raiz dos NaNs

A GUI do Debugger V2 no TensorBoard é organizada em seis seções:

  • Alertas : esta seção no canto superior esquerdo contém uma lista de eventos de "alerta" detectados pelo depurador nos dados de depuração do programa TensorFlow instrumentado. Cada alerta indica uma determinada anomalia que merece atenção. No nosso caso, esta seção destaca 499 eventos NaN/∞ com uma cor rosa-vermelha saliente. Isso confirma nossa suspeita de que o modelo falha em aprender devido à presença de NaNs e/ou infinitos em seus valores de tensores internos. Vamos nos aprofundar nesses alertas em breve.
  • Linha do tempo de execução do Python : Esta é a metade superior da seção do meio superior. Apresenta a história completa da execução ansiosa de operações e gráficos. Cada caixa da linha do tempo é marcada pela letra inicial do nome do op ou gráfico (por exemplo, “T” para o op “TensorSliceDataset”, “m” para o “model” tf.function ). Podemos navegar nessa linha do tempo usando os botões de navegação e a barra de rolagem acima da linha do tempo.
  • Execução do gráfico : Localizada no canto superior direito da GUI, esta seção será central para nossa tarefa de depuração. Ele contém um histórico de todos os tensores de tipo flutuante calculados dentro de gráficos (ou seja, compilados por @tf-function s).
  • A estrutura do gráfico (metade inferior da seção central superior), o código-fonte (seção inferior esquerda) e o rastreamento de pilha (seção inferior direita) estão inicialmente vazios. Seu conteúdo será preenchido quando interagirmos com a GUI. Essas três seções também desempenharão papéis importantes em nossa tarefa de depuração.

Tendo nos orientado para a organização da interface do usuário, vamos seguir os seguintes passos para entender por que os NaNs apareceram. Primeiro, clique no alerta NaN/∞ na seção Alertas. Isso rola automaticamente a lista de 600 tensores de gráfico na seção Graph Execution e se concentra no #88, que é um tensor chamado Log:0 gerado por uma Log (logaritmo natural). Uma cor rosa-vermelha saliente destaca um elemento -∞ entre os 1000 elementos do tensor 2D float32. Este é o primeiro tensor no histórico de tempo de execução do programa TF2 que continha qualquer NaN ou infinito: tensores calculados antes dele não contêm NaN ou ∞; muitos (na verdade, a maioria) tensores calculados posteriormente contêm NaNs. Podemos confirmar isso rolando para cima e para baixo na lista Graph Execution. Esta observação fornece uma forte indicação de que o Log op é a fonte da instabilidade numérica neste programa TF2.

Debugger V2: alertas Nan/Infinito e lista de execução de gráficos

Por que este Log op cuspir um -∞? Responder a essa pergunta requer examinar a entrada para o op. Clicar no nome do tensor ( Log:0 ) traz uma visualização simples, mas informativa da vizinhança do Log op em seu gráfico TensorFlow na seção Graph Structure. Observe a direção de cima para baixo do fluxo de informações. O próprio op é mostrado em negrito no meio. Imediatamente acima dele, podemos ver que um op Placeholder fornece a única entrada para o op Log . Onde está o tensor gerado por este probs Placeholder na lista Graph Execution? Usando a cor de fundo amarela como auxílio visual, podemos ver que o tensor probs:0 está três linhas acima do tensor Log:0 , ou seja, na linha 85.

Debugger V2: visualização da estrutura do gráfico e rastreamento para o tensor de entrada

Um olhar mais cuidadoso na divisão numérica do tensor probs:0 na linha 85 revela por que seu consumidor Log:0 produz um -∞: Entre os 1000 elementos de probs:0 , um elemento tem um valor de 0. O -∞ é resultado do cálculo do logaritmo natural de 0! Se de alguma forma pudermos garantir que o Log op seja exposto apenas a entradas positivas, poderemos evitar que o NaN/∞ aconteça. Isso pode ser obtido aplicando recorte (por exemplo, usando tf.clip_by_value() ) no tensor probs Placeholder.

Estamos chegando mais perto de resolver o bug, mas ainda não terminamos. Para aplicar a correção, precisamos saber onde no código-fonte Python o Log op e sua entrada Placeholder se originaram. O Debugger V2 fornece suporte de primeira classe para rastrear as operações de gráfico e eventos de execução até sua origem. Quando clicamos no tensor Log:0 em Graph Executions, a seção Stack Trace foi preenchida com o stack trace original da criação do Log op. O rastreamento de pilha é um pouco grande porque inclui muitos quadros do código interno do TensorFlow (por exemplo, gen_math_ops.py e dumping_callback.py), que podemos ignorar com segurança para a maioria das tarefas de depuração. O quadro de interesse é a linha 216 de debug_mnist_v2.py (ou seja, o arquivo Python que estamos tentando depurar). Clicar em “Linha 216” traz uma visão da linha de código correspondente na seção Código-Fonte.

Depurador V2: código-fonte e rastreamento de pilha

Isso finalmente nos leva ao código-fonte que criou o Log op problemático a partir de sua entrada probs . Esta é a nossa função de perda de entropia cruzada categórica personalizada decorada com @tf.function e, portanto, convertida em um gráfico do TensorFlow. O Placeholder op probs corresponde ao primeiro argumento de entrada para a função de perda. A operação Log é criada com a chamada da API tf.math.log().

A correção de recorte de valor para esse bug será algo como:

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

Isso resolverá a instabilidade numérica neste programa TF2 e fará com que o MLP treine com sucesso. Outra abordagem possível para corrigir a instabilidade numérica é usar tf.keras.losses.CategoricalCrossentropy .

Isso conclui nossa jornada desde a observação de um bug do modelo TF2 até a criação de uma alteração de código que corrige o bug, auxiliado pela ferramenta Debugger V2, que fornece visibilidade total do histórico de execução ansioso e gráfico do programa TF2 instrumentado, incluindo os resumos numéricos de valores de tensor e associação entre ops, tensores e seu código fonte original.

Compatibilidade de hardware do Debugger V2

O Debugger V2 suporta hardware de treinamento convencional, incluindo CPU e GPU. O treinamento multi-GPU com tf.distributed.MirroredStrategy também é suportado. O suporte para TPU ainda está em estágio inicial e requer chamadas

tf.config.set_soft_device_placement(True)

antes de chamar enable_dump_debug_info() . Pode ter outras limitações em TPUs também. Se você tiver problemas ao usar o Debugger V2, informe os bugs em nossa página de problemas do GitHub .

Compatibilidade da API do Debugger V2

O Debugger V2 é implementado em um nível relativamente baixo da pilha de software do TensorFlow e, portanto, é compatível com tf.keras , tf.data e outras APIs criadas nos níveis inferiores do TensorFlow. O Debugger V2 também é compatível com versões anteriores do TF1, embora a Eager Execution Timeline esteja vazia para os logdirs de depuração gerados pelos programas TF1.

Dicas de uso da API

Uma pergunta frequente sobre essa API de depuração é onde no código do TensorFlow deve-se inserir a chamada para enable_dump_debug_info() . Normalmente, a API deve ser chamada o mais cedo possível em seu programa TF2, preferencialmente após as linhas de importação do Python e antes do início da construção e execução do gráfico. Isso garantirá a cobertura total de todas as operações e gráficos que potencializam seu modelo e seu treinamento.

Os tensor_debug_modes atualmente suportados são: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE . Eles variam na quantidade de informações extraídas de cada tensor e na sobrecarga de desempenho do programa depurado. Consulte a seção args da documentação de enable_dump_debug_info() .

Sobrecarga de desempenho

A API de depuração apresenta sobrecarga de desempenho ao programa TensorFlow instrumentado. A sobrecarga varia de acordo com tensor_debug_mode , tipo de hardware e natureza do programa TensorFlow instrumentado. Como ponto de referência, em uma GPU, o modo NO_TENSOR adiciona uma sobrecarga de 15% durante o treinamento de um modelo Transformer com tamanho de lote 64. A sobrecarga percentual para outros tensor_debug_modes é maior: aproximadamente 50% para CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH e SHAPE modos. Em CPUs, a sobrecarga é um pouco menor. Em TPUs, a sobrecarga é atualmente maior.

Relação com outras APIs de depuração do TensorFlow

Observe que o TensorFlow oferece outras ferramentas e APIs para depuração. Você pode navegar por essas APIs no namespace tf.debugging.* na página de documentos da API. Dentre essas APIs, a mais utilizada é tf.print() . Quando se deve usar o Debugger V2 e quando se deve tf.print() ? tf.print() é conveniente no caso de

  1. sabemos exatamente quais tensores imprimir,
  2. sabemos onde exatamente no código-fonte inserir essas tf.print() ,
  3. o número de tais tensores não é muito grande.

Para outros casos (por exemplo, examinar muitos valores de tensor, examinar valores de tensor gerados pelo código interno do TensorFlow e pesquisar a origem da instabilidade numérica como mostramos acima), o Debugger V2 oferece uma maneira mais rápida de depuração. Além disso, o Debugger V2 fornece uma abordagem unificada para inspecionar tensores ansiosos e gráficos. Além disso, fornece informações sobre a estrutura do gráfico e as localizações do código, que estão além da capacidade de tf.print() .

Outra API que pode ser usada para depurar problemas envolvendo ∞ e NaN é tf.debugging.enable_check_numerics() . Ao contrário enable_dump_debug_info() , enable_check_numerics() não salva informações de depuração no disco. Em vez disso, ele apenas monitora ∞ e NaN durante o tempo de execução do TensorFlow e elimina os erros com a localização do código de origem assim que qualquer operação gera esses valores numéricos ruins. Ele tem uma sobrecarga de desempenho menor em comparação com enable_dump_debug_info() , mas não oferece um rastreamento completo do histórico de execução do programa e não vem com uma interface gráfica de usuário como o Debugger V2.