← Blog

Backpropagation em linguagem humana

Treinar uma rede é mudar pesos para a perda cair. A pergunta prática é: mudar para que lado, e de quanto? Backpropagation responde isso. Não é um ritual de deep learning. É a regra da cadeia aplicada a um programa que se lembra de como foi calculado.

Se uma rede é um aproximador de função feito de camadas, o passo para trás é o relatório de quem, nessa composição, empurrou a perda para cima.

Derivada como sensibilidade

Esqueça, por um momento, a fórmula decorada. Pegue um número x e uma função f. Empurre x um pouquinho. Veja o quanto f se mexeu. A razão entre o empurrão da saída e o empurrão da entrada é a derivada naquele ponto.

Se a derivada é 3, um empurrão positivo em x empurra f três vezes mais, na mesma direção. Se é −2, empurrar x para cima puxa f para baixo. Se é zero, aquele parâmetro, naquele ponto, não está ajudando nem atrapalhando.

Isso é sensibilidade. Em uma rede, queremos a sensibilidade da perda a cada peso. Com ela, sabemos o sentido do ajuste: se aumentar o peso aumenta a perda, diminuímos o peso. O tamanho do passo é outra decisão — a taxa de aprendizado. A direção vem do sinal da derivada.

Quando há vários números envolvidos, falamos de derivadas parciais: a sensibilidade a um peso, mantendo os outros como estão. O vetor de todas essas sensibilidades é o gradiente. Descida de gradiente é andar um pouco contra esse vetor.

flowchart TD
  A[Entrada] --> B[Operação]
  B --> C[Operação]
  C --> D[Perda]
  D --> E[Crédito]
  E --> C
  E --> B
  E --> A
  classDef ouro fill:#FBF5E4,stroke:#C9A227,color:#0B1F18,stroke-width:1.4px
  classDef verde fill:#F3FAF7,stroke:#1E7A57,color:#0B1F18,stroke-width:1.4px
  classDef fim fill:#14513B,stroke:#0F3328,color:#FBFAF6,stroke-width:1.4px
  class A ouro
  class B,C,E verde
  class D fim

O grafo é a memória da conta

Uma rede profunda é uma sequência de operações: multiplicar, somar, ativar, normalizar. No caminho de ida — o forward — cada operação produz um valor. Se o programa esquecer de quem veio aquele valor, não há como voltar.

O grafo computacional é essa memória. Cada nó guarda o resultado, os pais e a operação. Uma soma sabe que veio de dois termos. Um produto sabe que veio de dois fatores. Uma tangente hiperbólica sabe que veio de um único argumento.

Nada disso exige um framework. Numa folha, e = a × b, d = e + c, L = d × f já é um grafo. O valor de L depende de a, b, c e f através de contas intermediárias. Para saber como L reage a a, não basta olhar a. É preciso atravessar o caminho.

Frameworks como PyTorch constroem esse grafo enquanto o tensor flui. Por isso um loss.backward() parece magia na primeira vez. Não é. É o grafo sendo percorrido na ordem inversa, com a regra da cadeia em cada aresta. Autograd é backpropagation industrializado: o mesmo princípio, para tensores, com kernels rápidos.

Para trás como atribuição de crédito

No último nó está a perda. A perda em relação a si mesma tem derivada 1: se a perda sobe um pouco, ela sobe um pouco.

Dali, cada operação local sabe a própria derivada. Uma soma espalha o crédito igualmente para os dois termos — somar não escala ninguém. Um produto transfere ao fator a o crédito multiplicado por b, e vice-versa: se b era grande, mexer em a mexe mais no produto. Uma ativação multiplica pelo declive da curva naquele ponto. Onde a curva está plana, o crédito some. O neurônio saturado não recebe recado.

A regra da cadeia é a composição desses recados. O crédito que chega a um nó do meio é o crédito da perda em relação àquele nó. Ele se multiplica pela derivada local e segue para os pais. Quando um peso participa de vários caminhos, os créditos se somam. Essa soma é a responsabilidade total daquele peso pelo erro daquele exemplo.

“Atribuição de crédito” é o nome antigo e ainda o melhor. O erro aconteceu no fim. Alguém, no meio da conta, contribuiu. Backpropagation distribui a culpa — e o mérito — na proporção da sensibilidade.

Não é uma interpretação psicológica. É aritmética. Mas a imagem ajuda a depurar. Se um peso nunca muda, talvez o crédito não esteja chegando: ativação saturada, taxa pequena demais, ou um caminho que não depende daquele peso. Se explode, talvez um produto de termos grandes esteja amplificando o recado.

O que você precisa entender para usar PyTorch

Você não precisa reimplementar autograd para treinar. Precisa não desligar o grafo sem querer.

Tensores que exigem gradiente carregam o histórico. Operações sobre eles estendem o histórico. No fim, chama-se backward na perda. Os .grad dos parâmetros recebem o crédito. O otimizador dá o passo e zera o acumulador.

Três hábitos evitam a maior parte da confusão:

  1. O forward é o programa. Tudo que você escreve na ida — inclusive um if que escolhe um ramo — define o grafo daquele exemplo. O caminho de volta só existe para o que foi executado.
  2. O grafo é do lote atual. Depois do passo, descarta-se. Guardar grafos antigos enche memória. Por isso se usa zero_grad e se evita reter tensores intermediários sem necessidade.
  3. Algumas contas não devem receber crédito. Congelar uma camada, usar um valor só como número, calcular uma métrica de log: detach e torch.no_grad existem para isso. Métrica de avaliação não é perda de treino.

A intuição da derivada como empurrão continua válida em tensores. Só que o empurrão agora é em muitas direções ao mesmo tempo. O framework trata o livro-caixa. Você trata a pergunta: esta operação deveria influenciar os pesos?

Um exemplo pequeno cabe na cabeça

Suponha três números e uma perda que é o produto de uma soma. Você consegue, no papel, marcar a seta de volta em cada operação. O exercício não é originalidade matemática. É sentir que não há caixa-preta entre a perda e o peso.

Quando a rede cresce, o papel acaba. A disciplina permanece: cada camada, na ida, é uma conta; na volta, uma derivada local. Convolução, atenção, normalização — todas precisam de uma volta bem definida. Quem implementa um bloco novo em PyTorch, no fundo, declara como o crédito atravessa aquele bloco.

Há uma verificação antiga e ainda saudável: comparar o gradiente analítico com um empurrão numérico pequeno. Se os dois discordam, a volta está errada. Frameworks maduros já testaram as operações comuns. O bug, hoje, costuma estar no que você adicionou em volta — a perda errada, o alvo no formato invertido, a máscara que zera o crédito sem querer.

O que backpropagation não faz

Não escolhe a função de perda. Não limpa o rótulo. Não impede memorização. Só informa, dado o programa e o exemplo, a sensibilidade da perda aos pesos.

Também não garante um mínimo global. Descer a ladeira local é o que o gradiente oferece. Em dimensões altas, isso costuma ser suficiente para achar um lugar útil. “Suficiente” não é “ótimo do mundo”. É engenharia.

Finalmente, não explica a rede. O crédito diz quanto mexer. Não diz, em linguagem humana, o que o peso representa. Interpretar representações é outro ofício, posterior e incerto.

Backpropagation é a ponte entre “a perda está alta” e “este peso deve descer um pouco”. Derivada como sensibilidade, grafo como memória, volta como crédito. Com isso, loss.backward() deixa de ser um feitiço e volta a ser o que sempre foi: a regra da cadeia, aplicada a um programa que teve o cuidado de não esquecer as contas.

Se quiser conferir cada derivada no papel antes do framework, o volume 4 da AI One faz o caminho do perceptron ao PyTorch, uma derivada de cada vez.

Leia também: Redes neurais: o que está sendo aprendido.

#Backpropagation#Redes neurais#Cálculo#PyTorch

Thiago Nogueira

Autor de A Caixa Mágica e das séries Programming One e AI One na Editora Pensar Bem.

Capa de AI One

AI One · Volume 4

Do perceptron ao PyTorch, uma derivada de cada vez.