Retropropagação (Backpropagation)

Visão geral

Retropropagação (backpropagation) (“backprop”) é o algoritmo mais comumente usado para treinar Redes Neurais (Neural Networks). Ela computa de forma eficiente gradientes (gradients) de uma perda escalar (scalar loss) com respeito a muitos parâmetros (parameters) ao aplicar a regra da cadeia (chain rule) através de um grafo computacional (computation graph) no sentido inverso. Esses gradientes são então usados por um otimizador (optimizer) (por exemplo, descida do gradiente estocástica (stochastic gradient descent, SGD) ou Adam) para atualizar parâmetros; veja Descida do Gradiente e Otimizadores.

Um modelo mental útil é:

  • A passagem direta (forward pass) computa as saídas e a perda, enquanto registra valores intermediários.
  • A passagem reversa (backward pass) envia sinais de gradiente (gradient signals) da perda de volta através do grafo, acumulando como cada parâmetro influenciou a perda.

A retropropagação é essencialmente diferenciação automática em modo reverso (reverse-mode automatic differentiation); frameworks modernos a implementam como parte da Diferenciação Automática (Autodiff).

Grafos computacionais: onde os gradientes “fluem”

Um grafo computacional (computation graph) é um grafo acíclico direcionado (directed acyclic graph, DAG) cujos nós são operações (somar, multiplicar, multiplicação de matrizes, funções de ativação, etc.) e cujas arestas carregam tensores (tensors).

Exemplo de grafo (escalar):

  • (a = xw)
  • (y = a + b)
  • (L = (y - t)^2)

A passagem direta avalia os nós em ordem topológica (topological order). A passagem reversa computa derivadas na ordem inversa.

A regra da cadeia como base

Para uma composição (L = f(g(h(\theta)))), a regra da cadeia diz:

[ \frac{dL}{d\theta} = \frac{dL}{df}\frac{df}{dg}\frac{dg}{dh}\frac{dh}{d\theta} ]

A retropropagação é uma forma sistemática de aplicar essa regra em grafos grandes, reutilizando resultados intermediários.

Gradientes locais e gradientes a montante

Cada operação computa uma derivada local (local derivative) (como sua saída muda em função de suas entradas). Durante a passagem reversa, cada nó recebe um gradiente a montante (upstream gradient) (gradiente da perda em relação à saída do nó) e produz gradientes a jusante (downstream gradients) (gradientes em relação às entradas do nó).

Se um nó computa (z = u \cdot v) (multiplicação escalar), e sabemos (\frac{\partial L}{\partial z}), então:

  • (\frac{\partial L}{\partial u} = \frac{\partial L}{\partial z}\cdot \frac{\partial z}{\partial u} = \frac{\partial L}{\partial z}\cdot v)
  • (\frac{\partial L}{\partial v} = \frac{\partial L}{\partial z}\cdot u)

Grafos com ramificações e acumulação de gradientes

Se um valor é usado em múltiplos lugares (ramificação / fan-out), os gradientes somam devido à linearidade da diferenciação.

Se (z = f(x) + g(x)), então:

[ \frac{\partial L}{\partial x} = \frac{\partial L}{\partial z}\left(\frac{\partial f}{\partial x} + \frac{\partial g}{\partial x}\right) ]

Frameworks implementam isso acumulando gradientes em .grad de cada tensor.

Retropropagação em redes neurais em camadas

Considere uma rede de propagação direta (feedforward network) com (L) camadas:

  • Ativações (activations): (a^{(0)} = x)
  • Pré-ativações (pre-activations): (z^{(\ell)} = W^{(\ell)} a^{(\ell-1)} + b^{(\ell)})
  • Ativações: (a^{(\ell)} = \phi^{(\ell)}(z^{(\ell)}))
  • Perda (loss): (\mathcal{L}(a^{(L)}, y))

A retropropagação computa gradientes (\nabla_{W^{(\ell)}}\mathcal{L}) e (\nabla_{b^{(\ell)}}\mathcal{L}).

Sinais de erro (“deltas”)

Defina o erro da camada:

[ \delta^{(\ell)} \equiv \frac{\partial \mathcal{L}}{\partial z^{(\ell)}} ]

Então:

  1. Camada de saída [ \delta^{(L)} = \frac{\partial \mathcal{L}}{\partial a^{(L)}} \odot \phi'^{(L)}(z^{(L)}) ]

  2. Camadas ocultas [ \delta^{(\ell)} = \left(W^{(\ell+1)}\right)^{\top} \delta^{(\ell+1)} \odot \phi'^{(\ell)}(z^{(\ell)}) ]

  3. Gradientes dos parâmetros [ \frac{\partial \mathcal{L}}{\partial W^{(\ell)}} = \delta^{(\ell)} \left(a^{(\ell-1)}\right)^{\top} \qquad \frac{\partial \mathcal{L}}{\partial b^{(\ell)}} = \delta^{(\ell)} ]

Essas equações são as recorrências clássicas de “backprop” para perceptrons multicamadas (multilayer perceptrons, MLPs).

Um exemplo concreto resolvido (pequeno perceptron multicamadas)

Suponha:

  • Entrada (x \in \mathbb{R}^d)
  • Oculta: (h = \mathrm{ReLU}(W_1 x + b_1)) (unidade linear retificada (rectified linear unit, ReLU))
  • Saída: (\hat{y} = W_2 h + b_2)
  • Perda: (\mathcal{L} = \frac{1}{2}|\hat{y} - y|^2)

Passagem reversa:

  1. (\frac{\partial \mathcal{L}}{\partial \hat{y}} = \hat{y} - y)
  2. (\nabla_{W_2}\mathcal{L} = (\hat{y} - y), h^\top)
  3. (\nabla_{b_2}\mathcal{L} = \hat{y} - y)
  4. Propagar para a camada oculta: (\frac{\partial \mathcal{L}}{\partial h} = W_2^\top(\hat{y} - y))
  5. Através da unidade linear retificada: (\delta_1 = \frac{\partial \mathcal{L}}{\partial z_1} = \frac{\partial \mathcal{L}}{\partial h} \odot \mathbf{1}[z_1 > 0])
  6. (\nabla_{W_1}\mathcal{L} = \delta_1 x^\top), (\nabla_{b_1}\mathcal{L} = \delta_1)

Isso ilustra o padrão recorrente: transposição de matriz (matrix transpose) + multiplicação elemento a elemento (elementwise multiply) com a derivada da ativação + produto externo (outer product) para gradientes dos pesos.

Retropropagação através de operações comuns

Na prática, redes são construídas a partir de um pequeno conjunto de primitivas diferenciáveis (differentiable primitives). Conhecer seus gradientes ajuda a depurar e projetar modelos estáveis.

Camadas afins (affine layers: multiplicação de matrizes + viés)

Se (z = Wx + b) (viés (bias)):

  • (\frac{\partial \mathcal{L}}{\partial x} = W^\top \frac{\partial \mathcal{L}}{\partial z})
  • (\frac{\partial \mathcal{L}}{\partial W} = \left(\frac{\partial \mathcal{L}}{\partial z}\right) x^\top)
  • (\frac{\partial \mathcal{L}}{\partial b} = \frac{\partial \mathcal{L}}{\partial z})

Para minilotes (minibatches), isso vira multiplicações de matrizes em lote e reduções ao longo da dimensão do lote.

Unidade linear retificada (rectified linear unit, ReLU)

[ \mathrm{ReLU}(z) = \max(0, z), \quad \frac{d}{dz}\mathrm{ReLU}(z) = \mathbf{1}[z>0] ]

A unidade linear retificada é linear por partes; os gradientes ou passam adiante ou são bloqueados. Isso se conecta a Problemas de Gradiente (por exemplo, “ReLUs mortas”).

Sigmoide e tangente hiperbólica (ativações saturantes)

[ \sigma(z)=\frac{1}{1+e^{-z}},\quad \sigma'(z)=\sigma(z)(1-\sigma(z)) ]

A saturação (valores próximos de 0 ou 1) produz derivadas pequenas, contribuindo para gradientes que desaparecem; veja Problemas de Gradiente.

Softmax + entropia cruzada (simplificação prática importante)

Para classificação multiclasse (multi-class classification), frequentemente computamos:

  • valores logit (logits) (s)
  • probabilidades (p = \mathrm{softmax}(s)) (função softmax (softmax))
  • perda (\mathcal{L} = -\log p_{y}) (entropia cruzada (cross-entropy))

Um resultado-chave:

[ \frac{\partial \mathcal{L}}{\partial s} = p - \mathrm{onehot}(y) ]

Isso é uma das razões pelas quais a combinação “softmax + entropia cruzada” é ao mesmo tempo comum e numericamente estável quando implementada em conjunto. Para mais sobre objetivos, veja Ativações e Perdas.

Retropropagação vs. diferenciação automática em frameworks modernos

Historicamente, “retropropagação” se referia às recorrências derivadas à mão para redes em camadas. Hoje, o aprendizado profundo (deep learning) depende de diferenciação automática (automatic differentiation):

  • O usuário escreve o cálculo da passagem direta.
  • O framework constrói (explicitamente ou implicitamente) um grafo computacional.
  • Um algoritmo genérico em modo reverso gera o cálculo da passagem reversa.

É por isso que você pode retropropagar através de convoluções, atenção, camadas de normalização e código customizado — desde que seja composto por operações diferenciáveis.

A diferenciação automática em modo reverso é especialmente eficiente quando:

  • a saída é escalar (perda),
  • o número de parâmetros é grande.

Esse é exatamente o cenário de treinamento para redes profundas.

Exemplo prático: passo de treinamento com o PyTorch autograd

Abaixo há um passo mínimo de treinamento mostrando onde a retropropagação acontece (loss.backward()), e onde os parâmetros são atualizados (optimizer.step()).

import torch
import torch.nn as nn
import torch.optim as optim

model = nn.Sequential(
    nn.Linear(10, 64),
    nn.ReLU(),
    nn.Linear(64, 3),
)

optimizer = optim.AdamW(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()

x = torch.randn(32, 10)          # batch of 32
y = torch.randint(0, 3, (32,))   # class labels

logits = model(x)                # forward pass builds a graph
loss = criterion(logits, y)

optimizer.zero_grad()            # clear old gradients
loss.backward()                  # backprop: compute dloss/dparams
optimizer.step()                 # update parameters using gradients

Pontos-chave:

  • Gradientes se acumulam por padrão no PyTorch; por isso zero_grad().
  • loss.backward() realiza a travessia reversa do grafo e preenche .grad para cada parâmetro.
  • O otimizador aplica uma regra de atualização; veja Otimizadores.

Retropropagação manual (exemplo didático) para construir intuição

A diferenciação automática é conveniente, mas a diferenciação manual torna o “fluxo de gradientes (gradient flow)” concreto.

Exemplo: regressão linear com erro quadrático médio (mean squared error, MSE).

  • Predição: (\hat{y} = wx + b)
  • Perda: (\mathcal{L} = \frac{1}{2}(\hat{y} - y)^2)

Gradientes:

  • (\frac{\partial \mathcal{L}}{\partial w} = (\hat{y} - y),x)
  • (\frac{\partial \mathcal{L}}{\partial b} = (\hat{y} - y))

Um único passo de SGD:

def step(w, b, x, y, lr=0.1):
    yhat = w * x + b
    loss = 0.5 * (yhat - y) ** 2

    dloss_dyhat = (yhat - y)
    dloss_dw = dloss_dyhat * x
    dloss_db = dloss_dyhat * 1.0

    w = w - lr * dloss_dw
    b = b - lr * dloss_db
    return w, b, loss

w, b = 0.0, 0.0
for _ in range(5):
    w, b, loss = step(w, b, x=2.0, y=5.0)
    print(w, b, loss)

Isso é retropropagação em um grafo minúsculo: computar um gradiente a montante na perda e então propagá-lo através das operações até os parâmetros.

Eficiência: por que a retropropagação é rápida

Se você computasse (\frac{\partial \mathcal{L}}{\partial \theta_i}) separadamente para cada parâmetro (\theta_i), isso seria proibitivamente caro. A principal vantagem da retropropagação é que ela computa todos os gradientes de parâmetros em aproximadamente a mesma ordem de tempo de uma única avaliação de passagem direta (frequentemente um pequeno fator constante a mais).

Considerações práticas:

  • Tempo: a passagem reversa normalmente custa ~1–3× o custo da passagem direta, dependendo das operações.
  • Memória: a retropropagação precisa de ativações intermediárias salvas da passagem direta.

Compromissos de memória e checkpointing de gradiente

Armazenar cada intermediário pode ser caro para modelos profundos (especialmente blocos da Arquitetura Transformer (Transformer Architecture)). Duas estratégias comuns:

  • Checkpointing de ativações (activation checkpointing): salvar apenas algumas ativações; recomputar outras durante a passagem reversa para reduzir memória.
  • Treinamento em precisão mista (mixed precision training): armazenar alguns tensores em FP16/BF16; manter estabilidade com escalonamento da perda (loss scaling).

Essas escolhas afetam a estabilidade e o throughput do treinamento, mas não mudam a matemática subjacente da retropropagação.

Armadilhas comuns e dicas de depuração

Gradientes que desaparecem e que explodem

Gradientes podem encolher ou crescer exponencialmente com a profundidade, especialmente com certas ativações e inicializações. Mitigações incluem:

  • melhor inicialização e normalização (veja Inicialização e Normalização)
  • conexões residuais (residual connections)
  • clipping de gradiente (gradient clipping) (especialmente em redes neurais recorrentes (RNNs))
  • escolhas cuidadosas de ativação (família ReLU, unidade linear de erro gaussiano (Gaussian Error Linear Unit, GELU))

Veja Problemas de Gradiente para um tratamento mais aprofundado.

Operações não diferenciáveis e caminhos de gradiente “quebrados”

Algumas operações têm gradientes zero/indefinidos ou intencionalmente interrompem gradientes:

  • limiarização rígida / argmax (não diferenciável)
  • .detach() / stop_gradient (quebra explicitamente o grafo)
  • modificações in-place podem invalidar tensores salvos em alguns frameworks

Se o gradiente de um parâmetro é None ou sempre zero, verifique se o grafo computacional de fato conecta esse parâmetro à perda.

Estabilidade numérica

A retropropagação assume que derivadas são numericamente significativas. Problemas surgem com:

  • exponenciais/logaritmos (use implementações estáveis: truque log-sum-exp (log-sum-exp trick))
  • softmax computado de forma ingênua (prefira softmax-entropia-cruzada fundida (fused softmax-cross-entropy))
  • ativações extremamente grandes/pequenas (use normalização, clipping ou formulações de perda estáveis)

Retropropagação além de redes de propagação direta

A retropropagação generaliza para muitas arquiteturas:

  • Redes convolucionais (convolutional networks): convoluções são operadores lineares diferenciáveis; a passagem reversa computa gradientes em relação a kernels e entradas de forma eficiente.
  • Redes recorrentes (recurrent networks): o treinamento usa retropropagação através do tempo (backpropagation through time, BPTT), que retropropaga através da sequência desenrolada.
  • Atenção/Transformers (attention/Transformers): a retropropagação flui através de pesos de atenção, projeções e camadas de normalização; a memória costuma ser a principal restrição.
  • Múltiplas perdas / cabeças auxiliares (auxiliary heads): gradientes de múltiplas perdas somam nos parâmetros compartilhados.

Em todos os casos, ainda é o mesmo princípio: acumulação reversa de gradientes através de um grafo computacional.

Como a retropropagação se conecta a atualizações de parâmetros

A retropropagação em si não atualiza parâmetros; ela apenas computa gradientes. As atualizações são feitas por um otimizador:

[ \theta \leftarrow \theta - \eta \nabla_\theta \mathcal{L} ]

onde (\eta) é a taxa de aprendizado (learning rate) (e pode variar ao longo do tempo; veja Agendamentos de Taxa de Aprendizado). Otimizadores mais avançados usam momento (momentum), escalonamento adaptativo (adaptive scaling) e decaimento de peso (weight decay) — veja Otimizadores e Regularização.

Resumo

  • Retropropagação computa de forma eficiente (\nabla_\theta \mathcal{L}) ao aplicar a regra da cadeia para trás através de um grafo computacional.
  • Gradientes “fluem” como sensibilidades a montante (upstream sensitivities) multiplicadas por derivadas locais, com acumulação em ramificações.
  • No aprendizado profundo moderno, a retropropagação é implementada via diferenciação automática em modo reverso; você escreve a passagem direta e o framework gera a passagem reversa.
  • O treinamento prático combina: passagem direta → perda → backward() → passo do otimizador, com atenção à estabilidade, memória e correção dos caminhos de gradiente.

Para conceitos intimamente relacionados, veja Diferenciação Automática, Problemas de Gradiente e Otimizadores.