- Retropropagação (backpropagation) é a base do treinamento de redes neurais, mas, se você não entender seu funcionamento interno, podem surgir erros inesperados em uma estrutura de “abstração com vazamento (leaky abstraction)”
- Funções de ativação sigmoid e tanh podem fazer o treinamento parar por causa do desvanecimento do gradiente (vanishing gradient) se a inicialização dos pesos estiver errada
- ReLU pode causar o fenômeno de ReLU morta (dead ReLU), em que neurônios ficam permanentemente desativados quando a entrada é menor ou igual a 0
- Em RNNs, multiplicações repetidas de matrizes podem causar explosão do gradiente (exploding gradient), e para evitar isso é necessário usar gradient clipping ou LSTM
- Se você não entender como a retropropagação funciona, mesmo que o framework cuide disso automaticamente, sua capacidade de depuração e de melhorar o modelo cai bastante
Por que é necessário entender a retropropagação
- No curso CS231n de Stanford, as tarefas são projetadas para que os alunos implementem manualmente a propagação direta e a retropropagação
- Alguns alunos reclamam que isso é desnecessário, já que frameworks como TensorFlow calculam a retropropagação automaticamente
- Porém, a retropropagação é uma abstração com vazamento, não uma abstração completa, e sem conhecer seu funcionamento interno fica difícil identificar a causa de falhas no treinamento
- A postura de simplesmente pensar “o framework resolve isso sozinho” acaba levando à queda na capacidade de projetar modelos e depurar problemas
Desvanecimento do gradiente em sigmoid
- Funções não lineares como sigmoid e tanh entram em estado de saturação (saturation) quando os valores de entrada são grandes, fazendo a saída se aproximar de 0 ou 1
- Nessa situação, o gradiente local z(1-z)* se torna 0, e a propagação do gradiente é bloqueada durante a retropropagação
- O gradiente máximo da sigmoid é 0,25, então a cada passagem o sinal encolhe para 1/4 ou menos
- Como resultado, a velocidade de aprendizado das camadas inferiores fica significativamente mais lenta do que a das camadas superiores
- Por isso, ao usar camadas sigmoid, é preciso ter atenção especial à inicialização dos pesos e ao pré-processamento dos dados
O problema da ReLU morta
- ReLU é uma função que transforma a saída em 0 quando a entrada é menor ou igual a 0
- Se a saída do neurônio for 0 na propagação direta, então na retropropagação o gradiente também será 0, e esse neurônio pode ficar permanentemente desativado
- Durante o treinamento, grandes atualizações de peso ou uma taxa de aprendizado alta podem fixar o neurônio em um “estado morto”
- Há casos em que, após o treinamento, uma parte considerável de todos os neurônios continua produzindo 0
- Portanto, ao usar ReLU, o ajuste da taxa de aprendizado e a estratégia de inicialização são importantes
Explosão do gradiente em RNNs
- Em RNNs simples, a mesma matriz de estado oculto (Whh) é multiplicada repetidamente a cada passo de tempo
- Na retropropagação, o gradiente converge para 0 ou cresce sem limite dependendo do tamanho do autovalor (eigenvalue) dessa matriz
- Se |b| < 1, ocorre desvanecimento do gradiente; se |b| > 1, ocorre explosão do gradiente
- Para evitar isso, é comum aplicar gradient clipping ou usar a arquitetura LSTM
Exemplo incorreto de clipping em código de DQN
- Em uma implementação de DQN baseada em TensorFlow, foi encontrado um código que aplicava
tf.clip_by_value diretamente ao delta (erro de Q)
- Dessa forma, quando o delta saía da faixa definida, o gradiente se tornava 0 e o treinamento parava
- Como a intenção era fazer clipping do gradiente, o correto seria usar Huber loss
- No código de exemplo, a perda de Huber é implementada condicionalmente combinando
tf.square e tf.abs
- Esse problema foi relatado como issue no GitHub e corrigido imediatamente
Conclusão
- Retropropagação não é apenas uma ferramenta automatizada, mas um sistema de atribuição de crédito (credit assignment) que produz consequências complexas
- Sem entender seu funcionamento interno, você acaba enfrentando instabilidade do modelo, falhas de treinamento e limites na depuração
- Retropropagação não é matematicamente tão difícil, e pode ser aprendida de forma intuitiva com o curso e as tarefas de CS231n
- Entender retropropagação melhora bastante a capacidade de projetar redes neurais e resolver problemas
- Em vez de depender apenas da diferenciação automática do framework, é importante compreender o fluxo real da retropropagação
1 comentários
Comentários no Hacker News
Parece que backpropagation está ganhando uma má reputação injustamente aqui
Na verdade, esta discussão é mais sobre o quão imperfeitas são a gradient e as variantes de gradient descent como abstrações do processo de otimização do que sobre backpropagation em si
Backpropagation é apenas um algoritmo para calcular a derivada de uma função composta, e problemas como o desaparecimento do gradiente ao empilhar várias sigmoides não são problemas do backpropagation, mas sim características da própria função
O motivo de fazer as pessoas implementarem o backward pass manualmente é fazê-las calcular a derivada por conta própria e sentir na prática como os termos exponenciais atuam
O ponto principal é que, em certas situações, não dá para abstrair os detalhes do backpropagation, inclusive o cálculo do gradiente
Isso vale especialmente ao usar gradient descent, e pode ser menos problemático em outros algoritmos de otimização global
Como, na prática de hoje, o único jeito de calcular gradients em deep learning é via backpropagation, esse vazamento de abstração é algo real
Respeito a contribuição do Karpathy, mas os textos e palestras dele muitas vezes embaralham distinções conceituais, o que acaba gerando mal-entendidos
Pelo nível dele, espera-se uma precisão maior
A contribuição do Karpathy para o ensino de deep learning é realmente enorme
De textos curtos até o artigo clássico sobre RNNs, além das aulas no YouTube e projetos no GitHub, tudo é excelente
O nanochat, publicado recentemente, também é um ótimo exemplo: um exemplo completo, pequeno e claro ajuda muito quem está aprendendo
Depois percebi que eles estavam mais interessados em confiar e usar LLMs do que em entendê-los
Na prática, pareciam mais fascinados por discussões especulativas do tipo “uma sociedade em que máquinas inteligentes fazem tudo” do que pelo funcionamento dos LLMs
Em vez de simplesmente “perguntar ao ChatGPT”, ele pesquisa por conta própria, lê código e encontra bugs
Esse tipo de abordagem investigativa é o verdadeiro aprendizado
No mestrado, fiz uma tarefa de implementar backpropagation manualmente com base em um artigo
Era para escrever o forward e o backward pass usando apenas operações matemáticas, e foi a melhor experiência de aprendizado daquele ano
É o tipo de tarefa que a gente dificilmente faria por conta própria, mas que ajuda demais quando se é obrigado a fazer
Criei uma UI para visualizar como pesos e vieses mudavam durante o treinamento
Eu tinha curiosidade sobre a relação entre backpropagation e optimizer
O SGD simplesmente anda na direção do gradiente, mas optimizers mais sofisticados, como Adam, não usam o gradiente de forma direta: aplicam normalização, momentum, clipping etc.
Nesse caso, será que calcular o gradiente com exatidão é realmente necessário, ou basta saber a direção aproximada?
Há trabalhos relacionados, como artigos sobre o ruído no SGD e estudos de visualização
Mas seguir uma direção “mais ou menos certa” é arriscado — a curvatura da função de loss (Hessian) muda abruptamente
Na prática, ideias assim levaram ao stochastic gradient descent e, mais adiante, a abordagens como Direct Feedback Alignment
Também é interessante o texto do Ben Recht sobre a relação entre otimização e aprendizado por reforço
O que importa não é tanto o valor da função de loss, mas a forma do gradiente e da curvatura
Optimizers como Adam ajustam o gradiente estimando, por aproximação de primeira ordem, a sensibilidade de escala de cada parâmetro
Otimização de ordem mais alta é inviável com funções não lineares como ReLU
É uma medida essencial para lidar com o problema de vanishing gradient
Em espaços de alta dimensão, mesmo pequenos erros podem ter grande impacto, então calcular o gradiente com precisão é muito importante
O problema começa já em como calcular essa “direção aproximada”
Mesmo optimizers avançados, como AdamW, ainda dependem fortemente do gradiente
Por volta de 2016, parece que truques como gradient clipping eram usados com muito mais frequência
Por exemplo, no artigo de 2013 do Alex Graves, Sequence Generation with RNNs, ele diz que usou clipping para evitar explosão de gradiente em LSTMs
Eu mesmo senti a importância do tema a ponto de fazer uma disciplina inteira focada só no autograd do PyTorch
Hoje os frameworks já dão suporte a clipping, e a compreensão dos problemas de treinamento melhorou bastante
Mas o problema em si não desapareceu — ReLU e GELU continuam sendo ativações padrão, e o treinamento de LLMs ainda se parece bastante com uma “arte obscura”
O Smol Training Playbook da Hugging Face é prova disso
No longo prazo, talvez seja vantajoso treinar modelos robustos à diversidade de funções de ativação
Por exemplo, alternar aleatoriamente entre ReLU, Swish, GELU etc. durante o treinamento poderia produzir um efeito de regularização semelhante ao dropout
Isso também permitiria, na inferência, trocar por uma função computacionalmente mais barata
O título original, “Yes you should understand backprop”, é bem mais claro e melhor
Quando o código faz coisas demais por você, fica fácil cair na ilusão de que “funciona por mágica”
Na pós-graduação, ter calculado convolução e backpropagation manualmente foi muito útil para mim
A pergunta “se o framework calcula o backward pass automaticamente, por que eu deveria escrever isso?”
segue a mesma lógica preocupante de “se existe calculadora, por que aprender soma?”
Assim como é útil entender compiladores, algoritmos de ordenação e o funcionamento de transistores, aprender backpropagation também tem valor
Mas, como o tempo de estudo é limitado, a questão central é se isso é mais útil do que estudar outros tópicos
Backpropagation vale a pena justamente porque, sem entender seu funcionamento interno, fica difícil perceber modos de falha ocultos
Eu mesmo já implementei várias vezes, e é uma complexidade ideal para avaliar uma linguagem nova
Quando comecei a aprender deep learning, backpropagation parecia quase mágica
Mas, ao implementar por conta própria, vi que era apenas uma sequência de cálculos simples, e isso me deu muito mais confiança para depurar problemas e descobrir por que a loss parava de cair
Para quem está aprendendo deep learning, eu recomendo implementar isso manualmente ao menos uma vez
Também existe a posição contrária
Acho que estudantes não precisam necessariamente implementar backpropagation em NumPy
O problema de vazamento do BackProp pode ser resolvido por pesquisadores com novos optimizers, e o desenvolvedor só precisa achar bons hiperparâmetros
Parte do problema surge no design do modelo ou no loop de treinamento
Por exemplo, gradient clipping não é o padrão na maioria dos frameworks
Este texto é voltado a leitores com perfil de pesquisador ou interesse acadêmico
Se você não entender como o gradiente se comporta em funções como sigmoid ou ReLU, não vai conseguir lidar com problemas de explosão ou desaparecimento
Para criar novas arquiteturas de modelo, é preciso entender como backpropagation funciona; caso contrário, o treinamento pode falhar ou perder desempenho
Só ao atravessar a abstração é que você consegue descobrir as verdadeiras áreas desconhecidas (unknown unknowns)