Problemas de Gradiente

Visão geral

“Problemas de gradiente” normalmente se referem a gradientes que desaparecem (vanishing gradients) e gradientes explosivos (exploding gradients) durante o treinamento de redes neurais profundas (deep neural networks). Ambos os problemas surgem porque o treinamento usa otimização baseada em gradiente (gradient-based optimization) (por exemplo, variantes de Descida do Gradiente (Gradient Descent)) e os gradientes são calculados via a regra da cadeia (chain rule) ao longo de muitas camadas/passos de tempo (veja Retropropagação (Backpropagation) e Diferenciação automática (Autodiff)).

  • Gradientes que desaparecem: os gradientes se tornam extremamente pequenos à medida que se propagam para trás, então as camadas iniciais (ou passos de tempo iniciais em redes neurais recorrentes) aprendem muito lentamente ou não aprendem.
  • Gradientes explosivos: os gradientes se tornam extremamente grandes, levando a atualizações instáveis, overflow numérico/NaNs e treinamento divergente.

Esses problemas não são apenas teóricos: eles influenciam fortemente quais ativações, inicializações, normalizações e arquiteturas são práticas no aprendizado profundo moderno.

Por que os gradientes desaparecem ou explodem (a intuição matemática central)

Amplificação/atenuação pela regra da cadeia

Considere uma rede profunda como uma composição de funções:

[ y = f_L(f_{L-1}(\dots f_1(x))) ]

O gradiente de uma perda ( \mathcal{L} ) em relação a parâmetros iniciais depende de produtos de derivadas:

[ \frac{\partial \mathcal{L}}{\partial h_1} = \frac{\partial \mathcal{L}}{\partial h_L} \prod_{k=2}^{L} \frac{\partial h_k}{\partial h_{k-1}} ]

Mesmo que cada fator seja “razoável”, multiplicar muitos termos pode resultar em:

  • um produto cuja magnitude tende a 0 (desaparecimento), ou
  • um produto cuja magnitude cresce sem limite (explosão).

Um exemplo escalar simples deixa isso claro. Se cada camada tem derivada ≈ 0.9, então após 100 camadas:

[ 0.9^{100} \approx 0.000026 ]

Se cada derivada ≈ 1.1:

[ 1.1^{100} \approx 13780 ]

O aprendizado profundo é de alta dimensionalidade, mas o mesmo efeito multiplicativo está presente.

Visão Jacobiana (mais realista)

Para camadas com saída vetorial, cada camada contribui com uma matriz Jacobiana (Jacobian matrix) (J_k). A retropropagação efetivamente multiplica os gradientes por Jacobianas transpostas:

[ \nabla_{h_{k-1}}\mathcal{L} = J_k^\top \nabla_{h_k}\mathcal{L} ]

A norma dos gradientes é influenciada pelos valores singulares (singular values) dessas Jacobianas:

  • Se valores singulares típicos são < 1, os gradientes encolhem com a profundidade.
  • Se valores singulares típicos são > 1, os gradientes crescem com a profundidade.

Um objetivo importante de um treinamento estável é manter essas transformações próximas de preservadoras de norma (norm-preserving) (às vezes discutido como “isometria dinâmica (dynamical isometry)” na literatura mais avançada).

Onde os problemas de gradiente aparecem mais

Redes profundas feedforward (feedforward networks) (perceptrons multicamadas, pilhas profundas de CNNs)

Pilhas muito profundas de camadas lineares + não linearidades saturantes (saturating nonlinearities) (por exemplo, sigmoide/tangente hiperbólica) são cenários clássicos para gradientes que desaparecem. Redes neurais convolucionais (convolutional neural networks, CNNs) também podem sofrer se construídas como pilhas longas e “planas”, sem conexões de atalho (skip connections) ou normalização.

Redes neurais recorrentes (recurrent neural networks, RNNs)

Redes neurais recorrentes são especialmente propensas porque a “profundidade” corresponde a passos de tempo (time steps). A retropropagação através do tempo (backpropagation through time, BPTT) multiplica por Jacobianas recorrentes ao longo de muitos passos, então:

  • sequências longas frequentemente causam gradientes que desaparecem (não conseguem aprender dependências de longo alcance),
  • ou gradientes explosivos (o treinamento fica instável).

Essa foi uma das principais motivações para redes recorrentes com portas como LSTM/GRU.

Arquiteturas modernas muito profundas (arquiteturas Transformer (Transformers), redes residuais muito profundas (ResNets))

Arquiteturas modernas mitigam esses problemas usando conexões residuais (residual connections) e normalização (normalization) (por exemplo, normalização por camada), mas a instabilidade de gradientes ainda pode ocorrer com:

  • profundidade muito grande,
  • taxas de aprendizado mal ajustadas,
  • certas posições de normalização (por exemplo, detalhes de pré-normalização vs pós-normalização),
  • casos-limite numéricos de precisão mista (mixed precision).

Como contexto, arquiteturas Transformer dependem fortemente de caminhos residuais e normalização para uma otimização estável (veja Arquitetura Transformer).

Sintomas e diagnósticos práticos

Sintomas de gradientes que desaparecem

  • A perda de treinamento diminui muito lentamente ou entra em platô cedo.
  • Os pesos das camadas iniciais mal mudam (normas de gradiente pequenas).
  • As características nas camadas iniciais permanecem próximas da inicialização.
  • Em redes neurais recorrentes, falha em aprender dependências de longo prazo; o modelo foca apenas em entradas recentes.

Sintomas de gradientes explosivos

  • A perda vira NaN/Inf.
  • Picos súbitos na perda.
  • Normas de gradiente ficam extremamente grandes.
  • Valores de parâmetros “explodem” (e podem causar overflow em cenários float16/bfloat16).

Como diagnosticar (ferramentas práticas)

  1. Acompanhe as normas de gradiente durante o treinamento (norma global e/ou norma por camada).
  2. Inspecione distribuições de ativação (activation distributions) (há muitas ativações saturadas ou “mortas”?).
  3. Verifique razões atualização/peso (update-to-weight ratios) (se as atualizações superam muito os pesos, a instabilidade é provável).
  4. Observe NaNs/Infs e identifique qual tensor primeiro se torna não finito.

Um snippet simples em PyTorch para registrar normas de gradiente:

total_norm_sq = 0.0
for p in model.parameters():
    if p.grad is None:
        continue
    param_norm = p.grad.data.norm(2)
    total_norm_sq += param_norm.item() ** 2

total_norm = total_norm_sq ** 0.5
print("grad_global_norm:", total_norm)

Normas por camada podem ser ainda mais informativas ao depurar.

Técnicas de mitigação (o que de fato funciona na prática)

Muitas mitigações são complementares. No aprendizado profundo moderno, é comum combinar várias: boa inicialização + normalização + conexões residuais + configurações apropriadas do otimizador + corte (quando necessário).

1) Use funções de ativação não saturantes (ou menos saturantes)

Ativações saturantes têm derivadas próximas de zero para entradas muito positivas/negativas.

  • Sigmoide (sigmoid) satura fortemente; a derivada máxima é 0.25 e rapidamente cai para perto de 0.
  • Tangente hiperbólica (tanh) também satura, embora seja centrada em zero e frequentemente melhor que a sigmoide.

Escolhas modernas comuns:

  • Unidade linear retificada (ReLU): evita saturação no lado positivo; a derivada é 1 (região positiva) ou 0 (região negativa).
  • Unidade linear retificada com vazamento (Leaky ReLU) / unidade linear retificada parametrizada (PReLU) / unidade linear exponencial (ELU): reduzem o risco de “ReLU morrendo”.
  • Unidade linear de erro gaussiano (GELU) / unidade linear sigmoide (SiLU) (Swish): suaves, amplamente usadas em arquiteturas Transformer.

A escolha de ativação interage com inicialização e normalização (veja Ativações e Perdas).

2) Inicialização adequada de pesos (Xavier/He, ortogonal para redes neurais recorrentes)

A inicialização controla a escala dos sinais na ida e dos gradientes na volta.

  • Inicialização Xavier/Glorot (Xavier/Glorot initialization) foi projetada para manter a variância estável para ativações do tipo tangente hiperbólica.
  • Inicialização He/Kaiming (He/Kaiming initialization) foi projetada para ativações do tipo unidade linear retificada.
  • Inicialização ortogonal (orthogonal initialization) frequentemente ajuda em matrizes recorrentes para manter transformações próximas de preservadoras de norma ao longo do tempo.

Essas ideias (e sua relação com normalização) são abordadas em Inicialização e Normalização.

Regra prática:

  • família ReLU → inicialização He
  • tangente hiperbólica/sigmoide → inicialização Xavier (mas considere evitar sigmoide em pilhas profundas)

3) Camadas de normalização (BatchNorm, LayerNorm, RMSNorm)

A normalização reduz efeitos do tipo mudança interna de covariáveis (internal covariate shift) e ajuda a manter ativações em faixas onde as derivadas são saudáveis.

  • Normalização em lote (BatchNorm) é comum em redes neurais convolucionais/perceptrons multicamadas (depende de estatísticas do lote).
  • Normalização por camada (LayerNorm) é padrão em arquiteturas Transformer (normaliza ao longo de características).
  • Normalização RMS (RMSNorm) é uma variante de normalização por camada usada em muitos LLMs modernos.

A normalização frequentemente melhora o fluxo de gradiente indiretamente ao estabilizar as escalas de ativação. Veja Inicialização e Normalização.

4) Conexões residuais (skip)

Conexões residuais fornecem caminhos curtos para o fluxo de gradiente:

[ h_{k+1} = h_k + F(h_k) ]

A retropropagação através de uma soma inclui uma rota de “identidade”, tornando muito mais difícil que os gradientes desapareçam ao longo de muitas camadas (uma razão-chave pela qual redes residuais e arquiteturas Transformer treinam bem em grande profundidade).

Nota prática: o desenho residual interage com a posição da normalização (pré-normalização vs pós-normalização) e escolhas de taxa de aprendizado; Transformers com pré-normalização frequentemente são mais fáceis de otimizar em grande profundidade.

5) Mecanismos de portas (LSTM/GRU) para modelos de sequência

Para sequências longas, redes neurais recorrentes com portas mitigam gradientes que desaparecem ao criar caminhos onde a informação (e os gradientes) podem fluir com menos decaimento.

  • LSTM introduz um estado da célula (cell state) com atualizações aditivas, o que ajuda a preservar gradientes ao longo do tempo.
  • GRU oferece um mecanismo de portas mais simples com benefícios semelhantes.

Mesmo na era de arquiteturas Transformer, redes neurais recorrentes com portas continuam relevantes para certas restrições de streaming/borda.

6) Corte de gradiente (gradient clipping) (especialmente para gradientes explosivos)

Corte de gradiente limita a magnitude dos gradientes antes do passo do otimizador. O mais comum é o corte por norma global (global norm clipping):

  • Calcule a norma global do gradiente ( |\nabla| )
  • Se exceder um limiar (c), escale todos os gradientes por (c / |\nabla|)

Isso é extremamente comum em:

  • treinamento de redes neurais recorrentes
  • regimes de treinamento com lotes grandes
  • treinamento em precisão mista, onde o risco de overflow é maior

Exemplo em PyTorch:

import torch.nn.utils as nn_utils

loss.backward()
nn_utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
optimizer.zero_grad(set_to_none=True)

O corte aborda principalmente gradientes explosivos, não gradientes que desaparecem.

7) Taxa de aprendizado e escolhas de otimizador

Gradientes explosivos frequentemente ficam muito piores com taxas de aprendizado agressivas demais. De forma semelhante, gradientes que desaparecem podem parecer “nada aprende” se o tamanho de passo efetivo ficar pequeno demais.

Alavancas práticas:

  • Reduza a taxa de aprendizado (ou use um agendamento).
  • Use aquecimento (warmup) para modelos grandes (comum no treinamento de arquiteturas Transformer).
  • Tente otimizadores estáveis e ajuste fino (Adam/AdamW são padrões comuns).

Veja:

Nuance importante: otimizadores adaptativos às vezes podem mascarar problemas de escala de gradiente, mas não “consertam” fluxo de gradiente ruim em profundidade. Eles principalmente mudam como as atualizações são escaladas por parâmetro.

8) Escalonamento da perda e considerações de precisão mista

Com precisão mista em float16 (float16 mixed precision), gradientes podem sofrer underflow (desaparecer para zero) ou overflow (Inf/NaN). Frameworks frequentemente usam escalonamento dinâmico da perda (dynamic loss scaling):

  • Multiplicar a perda por um fator de escala antes da retropropagação para evitar underflow
  • Remover a escala dos gradientes antes do passo do otimizador
  • Ajustar a escala se overflow for detectado

Se você vê NaNs apenas em precisão mista, verifique:

  • comportamento do escalonamento da perda
  • presença de operações numericamente instáveis (por exemplo, softmax sem estabilização)
  • corte de gradiente (frequentemente ajuda)

9) Ajustes de arquitetura e estratégia de treinamento

Dependendo do tipo de modelo:

  • Encurte a profundidade efetiva: menos camadas, ou use conexões residuais/de atalho.
  • Retropropagação truncada no tempo (truncated BPTT) para redes neurais recorrentes: limite o quanto os gradientes se propagam para trás no tempo (reduz riscos de desaparecimento/explosão, mas limita aprendizado de longo prazo).
  • Melhor condicionamento: normalização, caminhos residuais e inicialização apropriada.
  • Regularização (regularization): embora não seja uma correção direta, alguns regularizadores (por exemplo, decaimento de peso (weight decay)) podem ajudar a evitar pesos descontrolados que pioram gradientes explosivos; veja Regularização.

Exemplo prático: gradientes explosivos em uma rede neural recorrente (e como o corte ajuda)

Suponha que você treine uma rede neural recorrente simples em sequências longas. Um modo comum de falha são picos na perda e NaNs após alguns passos.

Um passo de treinamento mínimo no estilo PyTorch:

# forward
logits = model(x)              # x: (batch, time, features)
loss = criterion(logits, y)

# backward
optimizer.zero_grad(set_to_none=True)
loss.backward()

# diagnose before clipping
total_norm = 0.0
for p in model.parameters():
    if p.grad is not None:
        total_norm += p.grad.data.norm(2).item() ** 2
total_norm = total_norm ** 0.5
print("grad_norm_before:", total_norm)

# mitigate exploding gradients
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# step
optimizer.step()

O que você normalmente observa:

  • Sem corte: grad_norm_before pode saltar de ~1–10 para 1e3+; a perda pode virar NaN.
  • Com corte: o treinamento fica estável (embora você ainda possa precisar ajustar a taxa de aprendizado e considerar trocar para LSTM/GRU ou uma arquitetura Transformer para dependências longas).

Checklist prático (depuração de problemas de gradiente)

Quando o treinamento fica instável ou emperra, um checklist estruturado economiza tempo:

  1. Registre normas de gradiente (global + por camada).
  2. Se os gradientes explodem:
    • reduza a taxa de aprendizado,
    • habilite corte por norma global (comece com 0.5–1.0),
    • verifique overflow de precisão mista / habilite escalonamento da perda,
    • confirme que a inicialização não está grande demais.
  3. Se os gradientes desaparecem:
    • use unidade linear retificada/unidade linear de erro gaussiano/unidade linear sigmoide em vez de sigmoide/tangente hiperbólica em pilhas profundas,
    • adicione conexões residuais,
    • adicione/ajuste normalização (normalização em lote/normalização por camada),
    • confirme que você está usando inicialização Xavier/He apropriadamente,
    • considere mudanças de arquitetura (por exemplo, LSTM/GRU para sequências).
  4. Confirme o resto do pipeline de treinamento:

Resumo

Gradientes que desaparecem e gradientes explosivos são consequências fundamentais de aplicar a regra da cadeia ao longo de muitas camadas ou passos de tempo. Eles se manifestam como aprendizado travado (desaparecimento) ou instabilidade/NaNs (explosão). O aprendizado profundo moderno tem sucesso em grande parte porque usa uma caixa de ferramentas de técnicas de mitigação—inicialização cuidadosa, ativações não saturantes, camadas de normalização, conexões residuais, mecanismos de portas para modelos de sequência, configurações apropriadas de otimização e corte de gradiente—que, em conjunto, mantêm magnitudes de gradiente em uma faixa treinável.

Para um embasamento mais profundo sobre como os gradientes são calculados e propagados, veja Retropropagação e Diferenciação automática. Para os estabilizadores mais comuns na prática, veja Inicialização e Normalização e Otimizadores.