Backpropagation
A retropropagação aplica a regra da cadeia no sentido inverso através de uma rede neural para obter o gradiente de cada peso numa única passagem. Aborda a matemática, o custo e o código.
A retropropagação, abreviação de «propagação reversa de erros», é o algoritmo que calcula quanto cada peso de uma rede neural contribuiu para o erro de uma previsão. Partindo da função de perda, aplica a regra da cadeia camada por camada em direção à entrada e reutiliza cada resultado intermediário, de modo que uma única passagem reversa produz o gradiente de cada peso, quer o modelo tenha milhares de parâmetros, quer tenha bilhões. Em seguida, um otimizador usa esses gradientes para atualizar os pesos, fazendo da retropropagação a etapa de aprendizado por trás de praticamente todos os modelos de aprendizado profundo. Rumelhart, Hinton e Williams popularizaram seu uso em redes neurais no artigo de 1986 da Nature, Learning representations by back-propagating errors.
Como funciona a retropropagação#
O treinamento de um lote exige duas passagens. A passagem direta encaminha as entradas por cada camada, armazena as ativações intermediárias e termina com a perda. A passagem reversa começa na perda e percorre as camadas na ordem inversa. Cada camada recebe o gradiente da perda em relação à sua saída, chamado gradiente ascendente, e o multiplica por suas próprias derivadas locais para produzir dois resultados: o gradiente dos pesos da camada, que é guardado para o otimizador, e o gradiente em relação à entrada da camada, que se torna o gradiente ascendente da camada abaixo.
Como cada camada precisa apenas de seu gradiente ascendente e das ativações armazenadas, nenhuma derivada é calculada duas vezes. Em termos de diferenciação automática, a retropropagação é a diferenciação em modo reverso do grafo computacional da rede, e o PyTorch constrói esse grafo durante a passagem direta e o percorre de trás para a frente quando loss.backward() é chamado. Em uma rede com dois pesos, o resultado do autograd do PyTorch pode ser verificado com a regra da cadeia desenvolvida à mão:
import torch
# A two-weight network: prediction = w2 * tanh(w1 * x)
x, target = torch.tensor(1.5), torch.tensor(0.5)
w1 = torch.tensor(0.8, requires_grad=True)
w2 = torch.tensor(-1.2, requires_grad=True)
# Forward pass, keeping the hidden activation h for the backward pass
h = torch.tanh(w1 * x)
loss = (w2 * h - target) ** 2
# Backward pass: autograd applies the chain rule from the loss back to each weight
loss.backward()
# The same chain rule by hand, reusing the upstream gradient dL/dy for both weights
dL_dy = 2 * (w2 * h - target)
dL_dw2 = dL_dy * h
dL_dw1 = dL_dy * w2 * (1 - h**2) * x
print(f"autograd: dL/dw1 = {w1.grad:.4f}, dL/dw2 = {w2.grad:.4f}")
print(f"by hand: dL/dw1 = {dL_dw1:.4f}, dL/dw2 = {dL_dw2:.4f}")As duas linhas imprimem dL/dw1 = 1.6475, dL/dw2 = -2.5016. A versão escrita à mão calcula dL_dy uma vez e reutiliza o resultado para os dois pesos, a mesma reutilização que permite ao autograd lidar com redes com bilhões de pesos. A retropropagação ao longo do tempo (BPTT) é o mesmo algoritmo aplicado a uma rede neural recorrente desenrolada ao longo de suas etapas temporais.
Retropropagação vs. otimizador#
A retropropagação apenas calcula gradientes e não altera nenhum peso. Um algoritmo de otimização, como o gradiente descendente estocástico (SGD) ou o Adam, transforma esses gradientes em atualizações de pesos, escaladas pela taxa de aprendizado. O PyTorch soma o resultado de cada passagem reversa aos gradientes já armazenados nos pesos, permitindo que uma etapa do otimizador combine vários lotes:
O treinamento do Ultralytics YOLO usa essa acumulação de gradientes para atingir um lote nominal de 64 imagens (nbs=64): depois do aquecimento, uma execução batch=16 calcula a retropropagação em cada lote, mas executa o otimizador a cada quatro lotes, após limitar o gradiente combinado a uma norma de 10.
Gradientes que desaparecem e explodem#
A passagem para trás multiplica as derivadas locais camada após camada, então a magnitude do gradiente pode diminuir ou crescer geometricamente com a profundidade. Quando a maioria dos fatores é menor que 1, como acontece com unidades sigmoid saturadas, as primeiras camadas recebem quase nenhum sinal, o que constitui o problema do gradiente que desaparece. Quando a maioria dos fatores é maior que 1, o problema do gradiente explosivo produz atualizações enormes e perdas NaN. Ativações da família ReLU, inicialização de pesos escalada, normalização em lote e as conexões de atalho das redes residuais mantêm estáveis as magnitudes dos gradientes ao longo de centenas de camadas.
Custo computacional e de memória#
Cada ativação necessária para uma derivada local precisa permanecer na memória desde a passagem para frente até a passagem para trás alcançá-la; por isso, a memória de treinamento aumenta com o tamanho do lote, a resolução da entrada e a profundidade, e o treinamento precisa de muito mais memória que a inferência. O checkpointing de gradientes armazena apenas algumas ativações e recalcula as demais durante a passagem para trás, trocando mais computação por menos memória, e a precisão mista mantém a maioria das ativações em formatos de 16 bits que ocupam metade do espaço dos números de ponto flutuante de 32 bits. Em termos computacionais, a passagem para trás custa cerca do dobro da passagem para frente, pois cada camada calcula gradientes tanto para seus pesos quanto para suas entradas; por isso, as estimativas das leis de escala contam cerca de 6 operações de ponto flutuante por parâmetro por token de treinamento: 2 na passagem para frente e 4 na passagem para trás.










