Aula 9: Funções Compostas, Redes Neurais e Diferenciação Automática
Otimização e Álgebra Linear para Machine Learning
1 Revisão e Introdução
1.1 Revisão Cuidadosa: O Que a Aula 8 Deixou Pronto
Na Aula 8, formalizamos a otimização contínua irrestrita através do algoritmo de Descida do Gradiente (Gradient Descent - GD). Sob a hipótese de diferenciabilidade e \(L\)-suavidade (Lema da Descida), provamos que avançar na direção oposta ao gradiente \(-\nabla f(\mathbf{w})\) garante descida monotônica do custo quando o passo satisfaz \(\alpha < 2/L\). Aplicamos o algoritmo à Regressão Linear e à Regressão Logística, onde a função de perda de Entropia Cruzada é estritamente convexa e o gradiente possui uma expressão analítica compacta:
\[\nabla_{\mathbf{w}} \mathcal{L} = \frac{1}{N} X^T (\hat{\mathbf{y}} - \mathbf{y})\]
Contudo, tanto a regressão linear quanto a logística compartilham uma limitação estrutural profunda: são modelos lineares nos parâmetros. O preditor depende diretamente do hiperplano \(z = \mathbf{w}^T \mathbf{x} + b\). No California Housing, onde a relação entre renda média (MedInc), idade da casa (HouseAge) e o valor do imóvel (MedHouseVal) é fortemente não-linear, modelos lineares subajustam os dados.
1.2 O Pesadelo da Diferenciação Simbólica
Para motivar por que precisamos de algoritmos especializados, examine a função escalar univariada a seguir:
\[f(x) = \sqrt{x^2 + \exp(x^2)} + \cos(x^2 + \exp(x^2))\]
Se tentarmos calcular a derivada \(\frac{df}{dx}\) aplicando as regras usuais de diferenciação simbólica (regra da cadeia, da soma e do produto) diretamente no papel, obtemos:
\[\frac{df}{dx} = \frac{2x + 2x\exp(x^2)}{2\sqrt{x^2 + \exp(x^2)}} - \sin\big(x^2 + \exp(x^2)\big)\cdot\big(2x + 2x\exp(x^2)\big)\]
Fatorando e simplificando:
\[\frac{df}{dx} = 2x\big(1 + \exp(x^2)\big)\left[ \frac{1}{2\sqrt{x^2 + \exp(x^2)}} - \sin\big(x^2 + \exp(x^2)\big) \right]\]
Observe o que aconteceu: 1. O termo \(x^2 + \exp(x^2)\) e sua derivada \(2x(1 + \exp(x^2))\) foram recalculados e duplicados múltiplas vezes. 2. Em código computacional, avaliar essa fórmula expandida é redundante e numericamente ineficiente. 3. Se uma função com apenas 5 operações elementares já sofre dessa duplicação, imagine uma rede neural com dezenas de camadas compostas: tentar escrever a expressão simbólica fechada do gradiente gera uma explosão combinatória de termos (expression swell).
1.3 Ideia Central
Em vez de expandir expressões simbólicas globais, os frameworks modernos de Inteligência Artificial baseiam-se em dois princípios fundamentais: 1. Reutilização de Computações no Backpropagation: Em redes neurais em camadas, quase toda a computação intermediária necessária para calcular o gradiente da camada \(i\) já foi calculada ao avaliar o gradiente da camada \(i+1\). 2. Diferenciação Automática em Grafos: Decompomos qualquer programa em um Grafo Acíclico Direcionado (DAG) de operações atômicas simples, acumulando derivadas numéricas exatas na ordem reversa da execução.
1.4 Roteiro da Aula
Nesta aula, responderemos a quatro perguntas centrais:
- Como a Regra da Cadeia explora o reuso de computações no Backpropagation analítico através das 3 equações canônicas?
- Por que os paradigmas manual, numérico e simbólico falham em modelos profundos, e como a Diferenciação Automática resolve isso via Grafos Acíclicos Direcionados (DAGs) e variáveis intermediárias?
- Como a regra da cadeia multivariada se propaga em grafos com bifurcações através da soma sobre múltiplos caminhos direcionados?
- Qual a dualidade matemática entre os modos Forward e Reverse pela associatividade da regra da cadeia, e por que o Teorema de Baur-Strassen torna o modo reverso o alicerce do Machine Learning?
1.5 Problema Motivador
Considere o dataset California Housing padronizado com \(d = 4\) atributos (MedInc, HouseAge, AveRooms, AveBedrms). Em vez de um modelo linear, construímos um MLP com uma camada oculta de 3 neurônios e função de ativação sigmoide:
\[\mathbf{z}^{(1)} = W^{(1)}\mathbf{x} + \mathbf{b}^{(1)} \in \mathbb{R}^3, \quad \mathbf{a}^{(1)} = \sigma(\mathbf{z}^{(1)}) \in \mathbb{R}^3, \quad \hat{y} = \mathbf{w}^{(2)T}\mathbf{a}^{(1)} + b^{(2)} \in \mathbb{R}\]
com função de perda quadrática \(\mathcal{L} = \frac{1}{2}(\hat{y} - y)^2\).
Enquanto derivar a perda em relação a \(\mathbf{w}^{(2)}\) é trivial (pois ele multiplica diretamente \(\mathbf{a}^{(1)}\)), derivar em relação à matriz de pesos oculta \(W^{(1)} \in \mathbb{R}^{3 \times 4}\) exige rastrear como cada componente \(W^{(1)}_{jk}\) afeta a pré-ativação \(z^{(1)}_j\), que afeta a ativação \(a^{(1)}_j\), que por sua vez altera \(\hat{y}\) e, finalmente, a perda \(\mathcal{L}\).
1.6 Pausa Ativa 1 — O Desafio da Profundidade
Por que não podemos simplesmente aplicar o algoritmo de Descida do Gradiente da Aula 8 em um MLP calculando derivadas analíticas individuais para cada um dos parâmetros de forma independente, tratando cada peso como uma função isolada?
Julgue V ou F — a questão só conta se acertar os 4 itens:
- □ Em um MLP com ativações não-lineares, a superfície de perda em relação aos parâmetros é em geral estritamente convexa, herdando as garantias de mínimo global da Aula 7.
- □ A inclusão de ativações não-lineares é indispensável: compor múltiplas camadas puramente afins \(W^{(2)}(W^{(1)}\mathbf{x} + \mathbf{b}^{(1)}) + \mathbf{b}^{(2)}\) equivale estritamente a um único modelo afim \(\tilde{W}\mathbf{x} + \tilde{\mathbf{b}}\).
- □ O gradiente da perda em relação a um peso de uma camada interna independe dos pesos das camadas posteriores, dependendo exclusivamente dos dados de entrada.
- □ O gradiente descendente pode ser aplicado a superfícies não-convexas, mas a convergência passa a ser garantida apenas para pontos críticos (onde \(\nabla \mathcal{L} = \mathbf{0}\)), sem garantia de mínimo global.
2 Intuição — A Correnteza e o Eco
Imagine um sistema fluvial complexo. A água das nascentes (os dados de entrada \(\mathbf{x}\) e os parâmetros \(W, \mathbf{b}\)) desce corredeiras e represas (somas ponderadas e ativações não-lineares), unindo-se gradativamente até desaguar em uma única lagoa na foz (a função de perda escalar \(\mathcal{L}\)). Esse fluxo direto é a Passagem Direta (Forward Pass).
Quando lançamos uma pedra na lagoa, a perturbação no nível da água gera uma onda acústica que viaja de volta rio acima, ecoando por todos os canais e afluentes até atingir cada nascente individual. Esse retorno do sinal de erro é a Passagem Reversa (Backward Pass).
O ponto pedagógico central é a modularidade atômica: nenhuma gota d’água no meio do rio precisa conhecer a geografia inteira da bacia hidrográfica; cada represa só precisa saber como transmitir o fluxo para frente e como retransmitir o eco que chega da jusante para seus afluentes imediatos a montante.
3 Bloco 1: Redes Neurais e a Reutilização de Computações
3.1 Recordando: Regras da Soma e da Cadeia (Escalar e Vetorial)
Antes de derivarmos redes neurais profundas, convém recapitular os dois pilares operacionais do cálculo diferencial da Aula 6:
3.1.1 1. Regra da Soma (Linearidade do Operador Diferencial)
A diferenciação distribui-se aditivamente: - Escalar Univariada: Se \(f(x) = u(x) + v(x)\), então: \[\frac{df}{dx} = \frac{du}{dx} + \frac{dv}{dx}\] - Multivariada / Gradiente Vetorial: Se \(f(\mathbf{x}) = u(\mathbf{x}) + v(\mathbf{x})\) para \(\mathbf{x} \in \mathbb{R}^n\), o vetor gradiente satisfaz: \[\nabla_{\mathbf{x}} (u + v) = \nabla_{\mathbf{x}} u + \nabla_{\mathbf{x}} v\] - Campos Vetoriais (Jacobianas): Se \(\mathbf{f}(\mathbf{x}) = \mathbf{u}(\mathbf{x}) + \mathbf{v}(\mathbf{x}) \in \mathbb{R}^m\), a matriz Jacobiana é a soma das Jacobianas: \[J_{\mathbf{u} + \mathbf{v}}(\mathbf{x}) = J_{\mathbf{u}}(\mathbf{x}) + J_{\mathbf{v}}(\mathbf{x})\]
3.1.2 2. Regra da Cadeia (Composição de Funções)
- Escalar Univariada: Se \(y = g(u)\) e \(u = f(x)\), a taxa de variação é o produto direto das derivadas locais: \[\frac{dy}{dx} = \frac{dy}{du} \frac{du}{dx}\]
- Escalar Multivariada com Múltiplos Canais (Soma de Caminhos): Se \(y = g(u_1, \dots, u_m)\) e cada \(u_j = f_j(x)\), a perturbação em \(x\) propaga-se por todos os canais intermediários: \[\frac{dy}{dx} = \sum_{j=1}^m \frac{\partial y}{\partial u_j} \frac{du_j}{dx}\]
- Vetorial com Saída Escalar (Denominator Layout): Se \(\mathbf{u} = \mathbf{f}(\mathbf{x}) \in \mathbb{R}^m\) e \(y = g(\mathbf{u}) \in \mathbb{R}\) para \(\mathbf{x} \in \mathbb{R}^n\): \[\nabla_{\mathbf{x}} (g \circ \mathbf{f}) = J_{\mathbf{f}}(\mathbf{x})\, \nabla_{\mathbf{u}} g \in \mathbb{R}^{n \times 1}\] onde \(J_{\mathbf{f}}(\mathbf{x}) \in \mathbb{R}^{n \times m}\) possui as derivadas nas colunas (\([J_{\mathbf{f}}]_{ij} = \frac{\partial f_j}{\partial x_i}\)) e \(\nabla_{\mathbf{u}} g \in \mathbb{R}^{m \times 1}\) é o gradiente da função externa.
- Composição Geral entre Campos Vetoriais: Se \(\mathbf{y} = \mathbf{g}(\mathbf{f}(\mathbf{x})) \in \mathbb{R}^p\), a matriz Jacobiana total é o produto matricial encadeado: \[J_{\mathbf{g} \circ \mathbf{f}}(\mathbf{x}) = J_{\mathbf{f}}(\mathbf{x})\, J_{\mathbf{g}}(\mathbf{u}) \in \mathbb{R}^{n \times p}\]
3.2 Redes Neurais Profundas como Composição de Funções
Em aprendizado profundo, a saída predita \(\hat{\mathbf{y}}\) é calculada como uma composição multinível de funções:
\[\hat{\mathbf{y}} = (f_K \circ f_{K-1} \circ \dots \circ f_1)(\mathbf{x}) = f_K\big(f_{K-1}(\dots(f_1(\mathbf{x}))\dots)\big)\]
onde \(\mathbf{x}\) denota as entradas (ex: atributos do California Housing), \(\hat{\mathbf{y}}\) denota as predições, e cada camada \(f_i\) possui seu próprio conjunto de parâmetros \(\boldsymbol{\theta}_i = \{A_{i-1}, \mathbf{b}_{i-1}\}\):
\[f_i(\mathbf{x}_{i-1}) = \sigma(A_{i-1} \mathbf{x}_{i-1} + \mathbf{b}_{i-1})\]
com \(\mathbf{x}_0 := \mathbf{x}\) e \(\sigma\) uma função de ativação não-linear (sigmoide, tanh, ReLU). O objetivo do treinamento consiste em encontrar os parâmetros \(\boldsymbol{\theta} = \{A_0, \mathbf{b}_0, \dots, A_{K-1}, \mathbf{b}_{K-1}\}\) que minimizam uma perda empírica, por exemplo, a perda quadrática \(\mathcal{L}(\boldsymbol{\theta}) = \frac{1}{2}\|\mathbf{y} - f_K(\boldsymbol{\theta}, \mathbf{x})\|^2\).
3.3 A Obtenção dos Gradientes pela Regra da Cadeia
Para atualizar os parâmetros por Descida do Gradiente, precisamos das derivadas parciais da perda \(\mathcal{L}\) em relação aos parâmetros \(\boldsymbol{\theta}_j = \{A_j, \mathbf{b}_j\}\) de cada camada \(j = 0, \dots, K-1\).
A regra da cadeia multivariada nos permite expressar essas derivadas como uma cascata de multiplicações:
\[\begin{aligned} \frac{\partial \mathcal{L}}{\partial \boldsymbol{\theta}_{K-1}} &= \frac{\partial \mathcal{L}}{\partial f_K} \frac{\partial f_K}{\partial \boldsymbol{\theta}_{K-1}} \\ \frac{\partial \mathcal{L}}{\partial \boldsymbol{\theta}_{K-2}} &= \frac{\partial \mathcal{L}}{\partial f_K} \frac{\partial f_K}{\partial f_{K-1}} \frac{\partial f_{K-1}}{\partial \boldsymbol{\theta}_{K-2}} \\ \frac{\partial \mathcal{L}}{\partial \boldsymbol{\theta}_{K-3}} &= \frac{\partial \mathcal{L}}{\partial f_K} \frac{\partial f_K}{\partial f_{K-1}} \frac{\partial f_{K-1}}{\partial f_{K-2}} \frac{\partial f_{K-2}}{\partial \boldsymbol{\theta}_{K-3}} \\ &\;\;\vdots \\ \frac{\partial \mathcal{L}}{\partial \boldsymbol{\theta}_i} &= \left( \frac{\partial \mathcal{L}}{\partial f_K} \frac{\partial f_K}{\partial f_{K-1}} \dots \frac{\partial f_{i+2}}{\partial f_{i+1}} \right) \frac{\partial f_{i+1}}{\partial \boldsymbol{\theta}_i} \end{aligned}\]
3.4 Reutilização de Computações: O Coração do Backpropagation
O segredo que torna o treinamento de redes neurais tratável reside na seguinte observação:
Para obter \(\frac{\partial \mathcal{L}}{\partial \boldsymbol{\theta}_i}\), basta: 1. Multiplicar o sinal acumulado \(\frac{\partial \mathcal{L}}{\partial f_{i+1}}\) pela derivada de transição entre camadas \(\frac{\partial f_{i+1}}{\partial f_i}\); 2. Multiplicar pela derivada local da camada com relação aos seus próprios parâmetros \(\frac{\partial f_i}{\partial \boldsymbol{\theta}_i}\).
Essa estrutura recursiva é precisamente o algoritmo de Backpropagation: o sinal de erro é propagado de trás para frente, de modo que cada camada calcula seu gradiente local aproveitando o eco acumulado das camadas anteriores.
3.5 Formalização Matricial com Jacobianos e Sensibilidades
Traduzindo essa mecânica para a notação matricial do curso com gradientes-coluna e denominator layout: - Se \(\mathbf{f}: \mathbb{R}^n \to \mathbb{R}^m\), a matriz Jacobiana é \(J_{\mathbf{f}}(\mathbf{x}) \in \mathbb{R}^{n \times m}\) com colunas \(\nabla_{\mathbf{x}} f_j\). - Para a composição \(g(\mathbf{f}(\mathbf{x}))\), temos a regra da cadeia: \[\nabla_{\mathbf{x}} (g \circ \mathbf{f}) = J_{\mathbf{f}}(\mathbf{x})\, \nabla_{\mathbf{u}} g\]
Definindo o vetor de sensibilidade pré-sináptica \(\boldsymbol{\delta}^{(l)} := \nabla_{\mathbf{z}^{(l)}} \mathcal{L} \in \mathbb{R}^{d_l}\) para cada camada \(l\), obtemos as 3 equações canônicas do backpropagation:
3.5.1 1. Sensibilidade da Camada de Saída (\(L\))
Na camada final \(L\), a perda \(\mathcal{L}\) depende diretamente das ativações pós-sinápticas \(\mathbf{a}^{(L)} = \sigma(\mathbf{z}^{(L)})\). Aplicando a regra da cadeia escalar para cada componente \(j = 1, \dots, d_L\): \[\frac{\partial \mathcal{L}}{\partial z_j^{(L)}} = \frac{\partial \mathcal{L}}{\partial a_j^{(L)}} \frac{\partial a_j^{(L)}}{\partial z_j^{(L)}} = \frac{\partial \mathcal{L}}{\partial a_j^{(L)}} \, \sigma'(z_j^{(L)})\] Em notação vetorial, agrupando pelo produto elemento a elemento (Hadamard \(\odot\)): \[\boldsymbol{\delta}^{(L)} := \nabla_{\mathbf{z}^{(L)}} \mathcal{L} = \nabla_{\mathbf{a}^{(L)}}\mathcal{L} \odot \sigma'(\mathbf{z}^{(L)}) \in \mathbb{R}^{d_L}\] Para a função de perda quadrática \(\mathcal{L} = \frac{1}{2}\|\mathbf{a}^{(L)} - \mathbf{y}\|^2\), temos \(\nabla_{\mathbf{a}^{(L)}}\mathcal{L} = \mathbf{a}^{(L)} - \mathbf{y}\) (o vetor de resíduos preditivos diretos), de modo que \(\boldsymbol{\delta}^{(L)} = (\mathbf{a}^{(L)} - \mathbf{y}) \odot \sigma'(\mathbf{z}^{(L)})\).
3.5.2 2. Propagação Retrógrada da Sensibilidade (\(l \to l-1\))
Para qualquer camada oculta \(l < L\), as ativações \(\mathbf{a}^{(l)}\) afetam a perda unicamente através das pré-ativações da camada seguinte \(\mathbf{z}^{(l+1)} = W^{(l+1)}\mathbf{a}^{(l)} + \mathbf{b}^{(l+1)}\). Pela regra da cadeia multivariada, somamos as contribuições de todas as unidades receptoras \(k = 1, \dots, d_{l+1}\): \[\frac{\partial \mathcal{L}}{\partial a_j^{(l)}} = \sum_{k=1}^{d_{l+1}} \frac{\partial \mathcal{L}}{\partial z_k^{(l+1)}} \frac{\partial z_k^{(l+1)}}{\partial a_j^{(l)}} = \sum_{k=1}^{d_{l+1}} \delta_k^{(l+1)} W_{kj}^{(l+1)} = \left((W^{(l+1)})^T \boldsymbol{\delta}^{(l+1)}\right)_j\] Isso estabelece que o gradiente em relação à ativação pós-sináptica é \(\nabla_{\mathbf{a}^{(l)}}\mathcal{L} = (W^{(l+1)})^T \boldsymbol{\delta}^{(l+1)}\). Retrocedendo em seguida através da função de ativação pontual \(\mathbf{a}^{(l)} = \sigma(\mathbf{z}^{(l)})\), obtemos a sensibilidade pré-sináptica da camada \(l\): \[\boldsymbol{\delta}^{(l)} := \nabla_{\mathbf{z}^{(l)}}\mathcal{L} = \nabla_{\mathbf{a}^{(l)}}\mathcal{L} \odot \sigma'(\mathbf{z}^{(l)}) = \left( (W^{(l+1)})^T \boldsymbol{\delta}^{(l+1)} \right) \odot \sigma'(\mathbf{z}^{(l)}) \in \mathbb{R}^{d_l}\] A matriz transposta \((W^{(l+1)})^T \in \mathbb{R}^{d_l \times d_{l+1}}\) funciona aqui como o operador adjunto linear que mapeia o vetor de sinal de erro da dimensão \(d_{l+1}\) de volta para a dimensão \(d_l\).
3.5.3 3. Gradientes dos Parâmetros (\(W^{(l)}\) e \(\mathbf{b}^{(l)}\))
Com as sensibilidades \(\boldsymbol{\delta}^{(l)}\) em mãos, o cálculo das derivadas em relação aos parâmetros a serem atualizados pelo otimizador é imediato. Como \(z_j^{(l)} = \sum_{k=1}^{d_{l-1}} W_{jk}^{(l)} a_k^{(l-1)} + b_j^{(l)}\), vemos que o peso \(W_{jk}^{(l)}\) afeta unicamente a \(j\)-ésima componente \(z_j^{(l)}\): \[\frac{\partial \mathcal{L}}{\partial W_{jk}^{(l)}} = \frac{\partial \mathcal{L}}{\partial z_j^{(l)}} \frac{\partial z_j^{(l)}}{\partial W_{jk}^{(l)}} = \delta_j^{(l)} \, a_k^{(l-1)}\] Agrupando todos os elementos \((j, k)\) em uma matriz, obtemos elegantemente um produto diádico (produto externo de matrizes de posto 1): \[\nabla_{W^{(l)}} \mathcal{L} = \boldsymbol{\delta}^{(l)} (\mathbf{a}^{(l-1)})^T \in \mathbb{R}^{d_l \times d_{l-1}}\] Para o vetor de vieses \(\mathbf{b}^{(l)}\), como \(\frac{\partial z_j^{(l)}}{\partial b_j^{(l)}} = 1\), temos: \[\nabla_{\mathbf{b}^{(l)}} \mathcal{L} = \boldsymbol{\delta}^{(l)} \in \mathbb{R}^{d_l}\] Com isso, a atualização pela Descida do Gradiente (\(\eta > 0\)) para a camada \(l\) é: \[W^{(l)} \leftarrow W^{(l)} - \eta \, \boldsymbol{\delta}^{(l)}(\mathbf{a}^{(l-1)})^T, \quad \mathbf{b}^{(l)} \leftarrow \mathbf{b}^{(l)} - \eta \, \boldsymbol{\delta}^{(l)}\]
3.5.4 A Instabilidade Numérica das Diferenças Finitas
A diferenciação numérica por diferenças finitas ilustra com clareza por que métodos aproximados falham em otimização. Ao calcular \(f'(x) \approx \frac{f(x+h) - f(x)}{h}\), enfrentamos um dilema insolúvel de ponto flutuante (IEEE 754): - Se \(h\) for grande (\(h > 10^{-5}\)), o erro de truncamento da Série de Taylor domina (\(\mathcal{O}(h)\) na progressiva, \(\mathcal{O}(h^2)\) na central); - Se \(h\) for excessivamente pequeno (\(h < 10^{-8}\)), \(f(x+h)\) e \(f(x)\) tornam-se quase idênticos em ponto flutuante, gerando cancelamento subtrativo catastrófico e amplificação do erro de arredondamento por \(\frac{\epsilon_{\text{mach}}}{h}\).
O gráfico abaixo evidencia a clássica curva em “V”: o menor erro alcançável com precisão dupla (float64) ainda é de cerca de \(10^{-8}\), enquanto a diferenciação automática atinge precisão perfeita de máquina (\(< 10^{-16}\)).
3.6 O Catálogo de Operações Atômicas e Suas Regras Simbólicas Pré-Computadas
Como a Diferenciação Automática consegue ser perfeitamente exata sem expandir expressões no papel?
Qualquer cálculo computacional arbitrário pode ser reduzido a um grafo de funções univariadas e bivariadas elementares. Para cada uma dessas primitivas, a derivada simbólica já foi pré-programada no sistema:
| Operação Primitiva | Função Direta (\(z\)) | Derivadas Parciais Locais Pré-Computadas | Comportamento no Backward Pass |
|---|---|---|---|
| Adição | \(z = x + y\) | \(\frac{\partial z}{\partial x} = 1, \quad \frac{\partial z}{\partial y} = 1\) | Distribuidor/Roteador: entrega o sinal de erro intacto a ambas as entradas: \(\bar{x} = \bar{z}, \; \bar{y} = \bar{z}\). |
| Subtração | \(z = x - y\) | \(\frac{\partial z}{\partial x} = 1, \quad \frac{\partial z}{\partial y} = -1\) | Inversor parcial: \(\bar{x} = \bar{z}, \; \bar{y} = -\bar{z}\). |
| Multiplicação | \(z = x \cdot y\) | \(\frac{\partial z}{\partial x} = y, \quad \frac{\partial z}{\partial y} = x\) | Troca cruzada ponderada: \(\bar{x} = y \cdot \bar{z}, \; \bar{y} = x \cdot \bar{z}\). |
| Divisão | \(z = \frac{x}{y}\) | \(\frac{\partial z}{\partial x} = \frac{1}{y}, \quad \frac{\partial z}{\partial y} = -\frac{x}{y^2}\) | Escala pela recíproca e quadrado inverso. |
| Potência | \(z = x^k\) | \(\frac{\partial z}{\partial x} = k x^{k-1}\) | Regra do tombo clássica. |
| Exponencial | \(z = \exp(x)\) | \(\frac{\partial z}{\partial x} = \exp(x) = z\) | Auto-reprodução pelo valor de saída. |
| Logaritmo | \(z = \ln(x)\) | \(\frac{\partial z}{\partial x} = \frac{1}{x}\) | Escala pelo inverso da entrada. |
| Cosseno | \(z = \cos(x)\) | \(\frac{\partial z}{\partial x} = -\sin(x)\) | Modulação senoidal negativa. |
| Seno | \(z = \sin(x)\) | \(\frac{\partial z}{\partial x} = \cos(x)\) | Modulação cossenoidal. |
| Raiz Quadrada | \(z = \sqrt{x}\) | \(\frac{\partial z}{\partial x} = \frac{1}{2\sqrt{x}} = \frac{1}{2z}\) | Inverso do dobro da saída. |
| Sigmoide | \(z = \sigma(x)\) | \(\frac{\partial z}{\partial x} = \sigma(x)(1 - \sigma(x)) = z(1-z)\) | Aproveita diretamente o valor ativado \(z\). |
| ReLU | \(z = \max(0, x)\) | \(\frac{\partial z}{\partial x} = \mathbf{1}(x > 0)\) | Válvula liga/desliga: gradiente passa se \(x>0\), zera se \(x \le 0\). |
3.6.1 O Papel Singular da Regra da Adição
Note com atenção especial a Regra da Adição: \[z = x + y \implies \frac{\partial z}{\partial x} = 1, \quad \frac{\partial z}{\partial y} = 1\]
Quando o sinal de erro \(\bar{z} := \frac{\partial \mathcal{L}}{\partial z}\) chega ao nó de adição pela passagem reversa, a regra da cadeia produz: \[\bar{x} = \bar{z} \cdot \frac{\partial z}{\partial x} = \bar{z} \cdot 1 = \bar{z}, \qquad \bar{y} = \bar{z} \cdot \frac{\partial z}{\partial y} = \bar{z} \cdot 1 = \bar{z}\]
A adição age como um divisor perfeito de sinal: ela não escala, não atenua e não distorce o gradiente; ela simplesmente bifurca o eco \(\bar{z}\) de forma idêntica e simultânea para todos os seus afluentes.
3.7 Decomposição em Variáveis Intermediárias e Grafos Acíclicos Direcionados (DAGs)
Munidos desse catálogo de primitivas, qualquer função ou programa pode ser decomposto em uma sequência estrita de atribuições a variáveis intermediárias.
Retomemos a função que motivou nosso dilema do inchaço simbólico: \[f(x) = \sqrt{x^2 + \exp(x^2)} + \cos(x^2 + \exp(x^2))\]
Em vez de escrever a derivada inteira de uma só vez, decompomos a função em operações do nosso catálogo: \[\begin{aligned} a &= x^2 && \text{(Potência: } k=2\text{)} \\ b &= \exp(a) && \text{(Exponencial)} \\ c &= a + b && \text{(Adição)} \\ d &= \sqrt{c} && \text{(Raiz Quadrada)} \\ e &= \cos(c) && \text{(Cosseno)} \\ f &= d + e && \text{(Adição Final)} \end{aligned}\]
Essa sequência de variáveis intermediárias define a topologia de um Grafo Acíclico Direcionado (DAG), onde os nós são as variáveis numéricas e as arestas representam a aplicação das primitivas atômicas:
3.8 A Regra da Cadeia Multivariada em Grafos: Princípio da Acumulação em Bifurcações
Para retropropagar a derivada da saída \(f\) até a entrada \(x\), consultamos as derivadas locais do nosso catálogo e aplicamos a regra da cadeia no sentido inverso.
As derivadas locais imediatas retiradas diretamente da tabela são: \[\begin{aligned} \frac{\partial a}{\partial x} &= 2x, \quad &\frac{\partial b}{\partial a} &= \exp(a), \quad &\frac{\partial c}{\partial a} &= 1, \quad &\frac{\partial c}{\partial b} &= 1 \\ \frac{\partial d}{\partial c} &= \frac{1}{2\sqrt{c}}, \quad &\frac{\partial e}{\partial c} &= -\sin(c), \quad &\frac{\partial f}{\partial d} &= 1, \quad &\frac{\partial f}{\partial e} &= 1 \end{aligned}\]
Agora acompanhe o recuo reverso passo a passo:
- Inicialização: A sensibilidade da saída em relação a si mesma é \(\frac{\partial f}{\partial f} = 1\).
- Propagação pela adição final (\(f = d + e\)): Pela regra da adição, o sinal 1 é replicado para \(d\) e \(e\): \[\frac{\partial f}{\partial d} = \frac{\partial f}{\partial f}\frac{\partial f}{\partial d} = 1 \cdot 1 = 1, \qquad \frac{\partial f}{\partial e} = \frac{\partial f}{\partial f}\frac{\partial f}{\partial e} = 1 \cdot 1 = 1\]
- Bifurcação no nó \(c\) (Soma de Caminhos Incidentes): A variável \(c\) alimenta dois nós distintos (\(d\) e \(e\)). Pela regra da cadeia multivariada, a sensibilidade total de \(c\) é a soma de todas as rotas direcionadas que partem de \(c\) até a saída: \[\frac{\partial f}{\partial c} = \frac{\partial f}{\partial d}\frac{\partial d}{\partial c} + \frac{\partial f}{\partial e}\frac{\partial e}{\partial c} = 1 \cdot \frac{1}{2\sqrt{c}} + 1 \cdot (-\sin(c))\]
- Propagação para \(b\) (\(c = a + b\)): \[\frac{\partial f}{\partial b} = \frac{\partial f}{\partial c}\frac{\partial c}{\partial b} = \frac{\partial f}{\partial c} \cdot 1\]
- Bifurcação no nó \(a\) (Soma de Caminhos Incidentes): A variável \(a\) afeta a saída por dois canais: alimenta \(b\) (que alimenta \(c\)) e alimenta \(c\) diretamente. Devemos somar as duas contribuições: \[\frac{\partial f}{\partial a} = \frac{\partial f}{\partial b}\frac{\partial b}{\partial a} + \frac{\partial f}{\partial c}\frac{\partial c}{\partial a} = \frac{\partial f}{\partial b}\exp(a) + \frac{\partial f}{\partial c} \cdot 1\]
- Propagação Final para a Entrada \(x\) (\(a = x^2\)): \[\frac{\partial f}{\partial x} = \frac{\partial f}{\partial a}\frac{\partial a}{\partial x} = \frac{\partial f}{\partial a} \cdot (2x)\]
3.9 Pausa Ativa 3 — Grafos de Computação
Se um nó intermediário em um grafo de computação possui 3 arestas de saída (alimenta 3 variáveis subsequentes), por que a sua sensibilidade total acumulada é estritamente a soma das sensibilidades parciais que retornam de cada aresta, e o que aconteceria com o cálculo se uma biblioteca esquecesse de somar e simplesmente sobrescrevesse o valor do gradiente?
Julgue V ou F — a questão só conta se acertar os 4 itens:
- □ O motor de diferenciação automática não precisa aplicar regras de cálculo simbólico em tempo de execução para cada programa: ele apenas avalia numericamente as derivadas de um catálogo fechado de operações atômicas pré-programadas.
- □ A regra da adição \(z = x + y\) atua no backward pass como um divisor idêntico de fluxo, propagando o gradiente \(\bar{z}\) integralmente para ambas as variáveis \(x\) e \(y\) (\(\bar{x} = \bar{z}\) e \(\bar{y} = \bar{z}\)).
- □ Em um nó com múltiplas arestas de saída (bifurcação), substituir o operador de acúmulo
+=por atribuição simples=preserva a correção do gradiente desde que as variáveis sejam processadas em ordem alfabética. - □ A diferenciação automática evita o inchaço de expressões (expression swell) porque nunca constrói a fórmula algébrica simbólica expandida global da derivada no papel.
4 Bloco 3: Modos de Diferenciação Automática: Forward vs. Reverse
4.1 A Dualidade Pela Associatividade da Multiplicação de Matrizes
Agora que dominamos a representação em grafos e o catálogo de operações atômicas, enfrentamos a pergunta fundamental:
Em qual direção devemos propagar a regra da cadeia no grafo? Da entrada para a saída (Forward) ou da saída para a entrada (Reverse)?
A resposta revela uma das dualidades mais profundas da álgebra linear computacional: ambos os modos calculam rigorosamente a mesma Jacobiana total, diferindo unicamente na associatividade da multiplicação matricial.
Considere uma cadeia de \(K\) transformações multivariadas consecutivas entre espaços euclidianos: \[\mathbf{x}_0 \in \mathbb{R}^{n_0} \xrightarrow{\mathbf{f}_1} \mathbf{x}_1 \in \mathbb{R}^{n_1} \xrightarrow{\mathbf{f}_2} \mathbf{x}_2 \in \mathbb{R}^{n_2} \dots \xrightarrow{\mathbf{f}_K} \mathbf{x}_K \in \mathbb{R}^{n_K}\]
Pela regra da cadeia multivariada, a matriz Jacobiana total da composição \(J \in \mathbb{R}^{n_K \times n_0}\) é o produto encadeado das matrizes Jacobianas locais: \[J = J_K \cdot J_{K-1} \cdot J_{K-2} \cdots J_2 \cdot J_1\] onde cada matriz intermediária \(J_i := \frac{\partial \mathbf{x}_i}{\partial \mathbf{x}_{i-1}} \in \mathbb{R}^{n_i \times n_{i-1}}\).
Como a multiplicação de matrizes é associativa, \((A B) C = A (B C)\), o resultado matemático final de \(J\) independe da ordem dos parênteses. Entretanto, o custo computacional e o consumo de memória de diferentes parentizações divergem de forma colossal!
4.2 O Modo Forward: Tangentes e Produtos Jacobiana-Vetor (JVP)
No Modo Forward (também denominado modo tangente ou pushforward), agrupamos os parênteses da direita para a esquerda: \[J \cdot \mathbf{v} = J_K \Big( J_{K-1} \Big( \dots \Big( J_2 \big( J_1 \mathbf{v} \big) \Big) \dots \Big) \Big)\] onde \(\mathbf{v} \in \mathbb{R}^{n_0}\) é um vetor de perturbação direcional inicial na entrada.
Observe o que acontece em cada passo: 1. Multiplicamos a matriz \(J_1 \in \mathbb{R}^{n_1 \times n_0}\) pelo vetor \(\mathbf{v} \in \mathbb{R}^{n_0}\), obtendo um novo vetor \(\dot{\mathbf{x}}_1 \in \mathbb{R}^{n_1}\); 2. Multiplicamos a matriz \(J_2 \in \mathbb{R}^{n_2 \times n_1}\) pelo vetor \(\dot{\mathbf{x}}_1\), gerando outro vetor \(\dot{\mathbf{x}}_2 \in \mathbb{R}^{n_2}\); 3. Em nenhum momento realizamos uma multiplicação cara de Matriz \(\times\) Matriz: cada etapa é um produto Matriz \(\times\) Vetor, conhecido na literatura como JVP (Jacobian-Vector Product).
As variáveis \(\dot{\mathbf{x}}_i := \frac{\partial \mathbf{x}_i}{\partial \mathbf{x}_0} \mathbf{v}\) representam a derivada direcional de cada estado intermediário na direção da perturbação de entrada \(\mathbf{v}\). Elas fluem para a frente, simultaneamente à passagem direta dos dados!
4.2.1 Execução Passo a Passo do Modo Forward no DAG Canônico
Para entender concretamente como o modo forward opera sem matrizes abstratas, apliquemos o algoritmo exatamente sobre o grafo de variáveis intermediárias que construímos no Bloco 2: \[f(x) = \sqrt{x^2 + \exp(x^2)} + \cos(x^2 + \exp(x^2))\]
No modo forward, cada nó \(v\) calcula e propaga simultaneamente seu valor numérico primal e sua derivada direcional tangente \(\dot{v} := \frac{\partial v}{\partial x} \dot{x}\).
A regra de atualização direta para qualquer nó \(v_i\) em um DAG é: \[\dot{v}_i = \sum_{v_p \in \text{Pais}(v_i)} \frac{\partial v_i}{\partial v_p} \, \dot{v}_p\] onde a soma percorre todos os nós que alimentam \(v_i\) (seus pais imediatos).
Acompanhe a propagação para a frente passo a passo a partir da semente \(\dot{x} = 1\):
Semente de Entrada (\(x\)): Definimos a perturbação unitária \(\dot{x} = 1\) (derivada de \(x\) em relação a si mesmo).
Nó \(a = x^2\):
- Primal: \(a = x^2\)
- Tangente: \(\dot{a} = \frac{\partial a}{\partial x} \dot{x} = 2x \cdot \dot{x} = 2x\)
Nó \(b = \exp(a)\):
- Primal: \(b = \exp(a) = \exp(x^2)\)
- Tangente: \(\dot{b} = \frac{\partial b}{\partial a} \dot{a} = \exp(a) \cdot \dot{a} = \exp(x^2) \cdot 2x\)
Nó \(c = a + b\) (Junção de Múltiplos Pais): Aqui o nó \(c\) recebe duas variáveis de entrada (\(a\) e \(b\)). Pela regra de propagação forward:
- Primal: \(c = a + b = x^2 + \exp(x^2)\)
- Tangente: \[\dot{c} = \frac{\partial c}{\partial a} \dot{a} + \frac{\partial c}{\partial b} \dot{b} = 1 \cdot \dot{a} + 1 \cdot \dot{b} = 2x + 2x \exp(x^2) = 2x\big(1 + \exp(x^2)\big)\]
Nós \(d = \sqrt{c}\) e \(e = \cos(c)\): Ambos dependem apenas de \(c\):
- Primal de \(d\): \(d = \sqrt{c}\)
- Tangente de \(d\): \(\dot{d} = \frac{\partial d}{\partial c} \dot{c} = \frac{1}{2\sqrt{c}} \dot{c}\)
- Primal de \(e\): \(e = \cos(c)\)
- Tangente de \(e\): \(\dot{e} = \frac{\partial e}{\partial c} \dot{c} = -\sin(c) \dot{c}\)
Nó Final de Saída \(f = d + e\) (Junção Final):
- Primal: \(f = d + e = \sqrt{c} + \cos(c)\)
- Tangente: \[\dot{f} = \frac{\partial f}{\partial d} \dot{d} + \frac{\partial f}{\partial e} \dot{e} = 1 \cdot \dot{d} + 1 \cdot \dot{e} = \dot{d} + \dot{e} = \left(\frac{1}{2\sqrt{c}} - \sin(c)\right) \dot{c}\]
Ao substituir \(c\) e \(\dot{c}\), obtemos exatamente a derivada clássica \(\frac{\mathrm{d}f}{\mathrm{d}x}\)!
No modo forward, como o par \((\text{primal}, \text{tangente}) = (v_i, \dot{v}_i)\) é calculado de maneira estritamente síncrona junto com o fluxo de execução, não é necessário registrar o grafo em memória (fita/tape) nem preservar valores intermediários antigos. Assim que \(c\) e \(\dot{c}\) são calculados, as variáveis \(a, b, \dot{a}, \dot{b}\) podem ser descartadas da RAM. A complexidade de memória é \(\mathcal{O}(1)\) ou proporcional apenas à profundidade da pilha!
Cada passagem direta do modo forward avalia a projeção de \(J\) sobre uma única direção de entrada \(\mathbf{v}\). Para obter a matriz Jacobiana completa \(J \in \mathbb{R}^{m \times n}\), precisamos semear o algoritmo com os \(n\) vetores da base canônica \(\mathbf{e}_1, \dots, \mathbf{e}_n\), exigindo \(n\) passagens completas para frente: \[\text{Custo do Modo Forward} = \mathcal{O}(d_{\text{in}} \cdot C_{\text{eval}})\] onde \(C_{\text{eval}}\) é o custo de uma única passagem direta. Se a rede possuir 1 milhão de parâmetros (\(d_{\text{in}} = 10^6\)), o modo forward exigirá 1 milhão de avaliações completas da rede!
4.3 O Modo Reverse: Co-vetores e Produtos Vetor-Jacobiana (VJP)
No Modo Reverse (também denominado modo adjunto, pullback ou backpropagation generalizado), agrupamos os parênteses da esquerda para a direita: \[\mathbf{u}^T \cdot J = \Big( \dots \Big( \big( \mathbf{u}^T J_K \big) J_{K-1} \Big) \dots \Big) J_1\] onde \(\mathbf{u} \in \mathbb{R}^{n_K}\) é um vetor de sensibilidade inicial na saída.
A mecânica agora inverte-se: 1. Multiplicamos o vetor-linha \(\mathbf{u}^T \in \mathbb{R}^{1 \times n_K}\) pela matriz \(J_K \in \mathbb{R}^{n_K \times n_{K-1}}\), resultando em um novo vetor-linha \(\bar{\mathbf{x}}_{K-1}^T \in \mathbb{R}^{1 \times n_{K-1}}\); 2. Multiplicamos esse vetor-linha pela matriz anterior \(J_{K-1}\), produzindo \(\bar{\mathbf{x}}_{K-2}^T\); 3. Cada etapa intermediária é um produto Vetor \(\times\) Matriz, conhecido como VJP (Vector-Jacobian Product).
As variáveis \(\bar{\mathbf{x}}_i := \Big(\frac{\partial \mathbf{x}_K}{\partial \mathbf{x}_i}\Big)^T \mathbf{u}\) são chamadas de adjuntos ou co-vetores. Elas propagam o sinal de sensibilidade para trás, na direção contrária ao fluxo de dados!
Em Machine Learning supervisionado, a função de perda \(\mathcal{L}\) é um escalar (\(d_{\text{out}} = n_K = 1\)). Ao escolher a semente escalar trivial \(u = 1\) (\(\frac{\partial \mathcal{L}}{\partial \mathcal{L}} = 1\)), uma única passagem reversa propaga os co-vetores por toda a cadeia, retornando simultaneamente o gradiente completo de todos os milhões ou bilhões de parâmetros de entrada: \[\text{Custo do Modo Reverse} = \mathcal{O}(d_{\text{out}} \cdot C_{\text{eval}}) = \mathcal{O}(1 \cdot C_{\text{eval}})\] O custo independe inteiramente do número de parâmetros de entrada \(d_{\text{in}}\)!
4.4 O Teorema de Baur-Strassen / Speelpenning (1983)
Essa assimetria assombrosa entre os modos forward e reverse é consagrada por um dos teoremas mais célebres da Ciência da Computação teórica:
4.4.1 De Onde Vem o Fator 5? A Intuição da Demonstração
Por que a constante limitante superior é no máximo \(5\), e não 50 ou 500?
A demonstração clássica de Baur & Strassen (1983) e Griewank & Walther (2008) baseia-se em uma contagem atômica de operações aritméticas para cada nó do circuito:
Passagem Direta (\(1 \cdot C_{\text{eval}}\)): Avaliamos a função para a frente, calculando cada variável intermediária \(v_i = \phi(v_j, v_k)\). Cada operação atômica consome \(1\) unidade de custo. O custo total do forward pass é \(C_{\text{forward}} = C_{\text{eval}} = L\) operações.
Passagem Reversa (no máximo \(4 \cdot C_{\text{eval}}\)): Na volta, para cada operação direta \(v_i = \phi(v_j, v_k)\), precisamos retropropagar o sinal de sensibilidade \(\bar{v}_i := \frac{\partial f}{\partial v_i}\) acumulando nos seus dois nós de entrada: \[\bar{v}_j \mathrel{+}= \bar{v}_i \cdot \frac{\partial \phi}{\partial v_j}, \qquad \bar{v}_k \mathrel{+}= \bar{v}_i \cdot \frac{\partial \phi}{\partial v_k}\] Analisemos o pior caso de uma operação bivariada, a multiplicação (\(v_i = v_j \cdot v_k\)):
- Derivadas locais: \(\frac{\partial v_i}{\partial v_j} = v_k\) e \(\frac{\partial v_i}{\partial v_k} = v_j\).
- Para atualizar \(\bar{v}_j\): calculamos o produto \(\bar{v}_i \cdot v_k\) (1 mult) e somamos em \(\bar{v}_j\) (1 soma) \(\to \mathbf{2}\) operações.
- Para atualizar \(\bar{v}_k\): calculamos o produto \(\bar{v}_i \cdot v_j\) (1 mult) e somamos em \(\bar{v}_k\) (1 soma) \(\to \mathbf{2}\) operações.
- Total no backward por nó: \(2 + 2 = \mathbf{4}\) operações aritméticas elementares para desfazer cada multiplicação direta!
- (Para adições \(v_i = v_j + v_k\), o custo é ainda menor: como as derivadas locais valem \(1\), gastamos apenas \(2\) somas no backward).
Custo Combinado Total do Gradiente: Como o modo reverso executa a passagem direta para armazenar os valores intermediários e em seguida executa a passagem reversa: \[C_{\text{grad}} = C_{\text{forward}} + C_{\text{backward}} \le C_{\text{eval}} + 4 \cdot C_{\text{eval}} = 5 \cdot C_{\text{eval}}\]
Essa contagem desmistifica o teorema: cada operação direta gera no retorno no máximo duas multiplicações e duas somas para transferir os erros aos operandos!
4.5 Critérios de Decisão em Machine Learning e o Trade-Off de Memória
Compreendida a teoria da associatividade, como decidir qual modo utilizar em problemas práticos?
| Critério | Modo Forward (Tangente / JVP) | Modo Reverse (Adjunto / VJP) |
|---|---|---|
| Dimensões Favoráveis | \(d_{\text{in}} \ll d_{\text{out}}\) (poucas entradas, muitas saídas) | \(d_{\text{in}} \gg d_{\text{out}}\) (muitas entradas, poucas saídas) |
| Aplicações Típicas | Modelos generativos com espaço latente pequeno (\(z \in \mathbb{R}^{10} \to y \in \mathbb{R}^{10^6}\)); cinemática e robótica; derivadas temporais em EDOs neurais. | Treinamento de Redes Neurais e LLMs (perda escalar \(\mathcal{L} \in \mathbb{R}\) com milhões de pesos \(W\)); sensibilidade paramétrica. |
| Operação Central | Produto Jacobiana-Vetor (\(J \mathbf{v}\)) | Produto Vetor-Jacobiana (\(\mathbf{u}^T J\)) |
| Consumo de Memória | \(\mathcal{O}(1)\) na profundidade: valores intermediários podem ser descartados imediatamente após o uso. | \(\mathcal{O}(L)\) na profundidade: exige congelar todas as ativações intermediárias na fita (tape) para a passagem reversa. |
4.5.1 O Custo Oculto da Memória no Modo Reverso
Se o Teorema de Baur-Strassen garante que o tempo do modo reverso é linear e barato, qual é a contrapartida?
A contrapartida é o consumo de memória!
- No modo forward, as perturbações \(\dot{x}\) caminham junto com os dados. Logo que um nó termina de calcular seus filhos, seu valor pode ser imediatamente desalocado da memória RAM/VRAM. O consumo de memória é constante em relação ao número de camadas (\(\mathcal{O}(1)\)).
- No modo reverso, para calcular a derivada local \(\frac{\partial x_j}{\partial x_i}\) na volta (por exemplo, a derivada da multiplicação \(\frac{\partial (xy)}{\partial x} = y\) ou da ativação \(\sigma'(z) = \sigma(z)(1-\sigma(z))\)), o sistema precisa conhecer os valores numéricos exatos assumidos na passagem direta.
- Portanto, o modo reverso é obrigado a reter na VRAM da GPU todas as ativações intermediárias de todas as camadas até que o sinal reverso passe por elas. Para redes gigantescas com centenas de camadas (Transformers/LLMs), essa fita de ativações consome muito mais memória do que os próprios parâmetros do modelo, sendo a causa primária dos temidos erros de Out Of Memory (OOM).
4.6 Pausa Ativa 4 — Modos Forward vs. Reverse
Imagine que você está projetando o sistema de controle de um braço robótico que recebe 3 ângulos de junta motora \(\boldsymbol{\theta} \in \mathbb{R}^3\) e calcula a trajetória espacial de 50 pontos da estrutura \(\mathbf{p} \in \mathbb{R}^{150}\). Você precisa da matriz Jacobiana completa \(\frac{\partial \mathbf{p}}{\partial \boldsymbol{\theta}} \in \mathbb{R}^{150 \times 3}\) a cada ciclo de controle de 1 milissegundo. Qual modo de autodiff você deve adotar e por quantas passagens pelo grafo?
Julgue V ou F — a questão só conta se acertar os 4 itens:
- □ Ambos os modos Forward e Reverse produzem exatamente a mesma matriz Jacobiana numérica, decorrendo da propriedade de associatividade da multiplicação matricial \((AB)C = A(BC)\).
- □ O modo forward requer menos memória de execução que o modo reverso, pois pode descartar variáveis intermediárias da memória assim que seus nós dependentes imediatos forem avaliados.
- □ Para treinar uma rede neural profunda com 1 milhão de pesos e função de perda escalar, o modo forward e o modo reverso demandam assintoticamente o mesmo número de operações de ponto flutuante.
- □ O Teorema de Baur-Strassen garante que, para qualquer função escalar, o vetor gradiente completo pode ser calculado em no máximo cerca de 5 vezes o tempo de uma única passagem direta.
5 Fechamento
5.1 Retomando as Quatro Perguntas
- Como a Regra da Cadeia explora o reuso de computações no Backpropagation analítico através das 3 equações canônicas? Através de equações de recorrência matricial que propagam o vetor de sensibilidade pré-sináptica \(\boldsymbol{\delta}^{(l)} = ((W^{(l+1)})^T \boldsymbol{\delta}^{(l+1)}) \odot \sigma'(\mathbf{z}^{(l)})\). Ao retroceder camada a camada, reaproveitamos o prefixo já computado, expressando os gradientes dos pesos como o produto diádico \(\nabla_{W^{(l)}}\mathcal{L} = \boldsymbol{\delta}^{(l)}(\mathbf{a}^{(l-1)})^T\).
- Por que os paradigmas manual, numérico e simbólico falham em modelos profundos, e como a Diferenciação Automática resolve isso via DAGs e variáveis intermediárias? O cálculo manual é intransferível; o numérico sofre com instabilidade e cancelamento subtrativo em ponto flutuante; e o simbólico sofre inchaço exponencial de termos (\(\mathcal{O}(2^d)\)). A autodiff resolve isso avaliando numericamente a derivada exata em tempo de execução via catálogo pré-programado de primitivas atômicas sobre um DAG de variáveis intermediárias.
- Como a regra da cadeia multivariada se propaga em grafos com bifurcações através da soma sobre múltiplos caminhos direcionados? Em nós que alimentam múltiplos destinos (bifurcações), a sensibilidade total é a soma das contribuições de todas as arestas de saída: \(\bar{x}_i = \sum_{x_j \in \text{Filhos}(x_i)} \bar{x}_j \frac{\partial x_j}{\partial x_i}\). A regra da adição \(z = x+y\) atua como um divisor idêntico de fluxo (\(\bar{x}=\bar{z}, \bar{y}=\bar{z}\)).
- Qual a dualidade matemática entre os modos Forward e Reverse pela associatividade da regra da cadeia, e por que o Teorema de Baur-Strassen torna o modo reverso o alicerce do Machine Learning? Ambos multiplicam as mesmas matrizes Jacobianas locais, mas associam à direita (JVP: matriz-vetor da entrada para a saída, custo \(\mathcal{O}(d_{\text{in}} \cdot C_{\text{eval}})\)) ou à esquerda (VJP: vetor-matriz da saída para a entrada, custo \(\mathcal{O}(d_{\text{out}} \cdot C_{\text{eval}})\)). Como a perda em ML é escalar (\(d_{\text{out}} = 1\)), o Teorema de Baur-Strassen garante que todos os gradientes são obtidos em tempo linear (\(\le 5 C_{\text{eval}}\)) em uma única passagem reversa.
5.2 O Que Fica em Aberto e Encerramento do Bloco 2
Com esta aula, concluímos o Bloco 2 do curso (O Cálculo da Otimização Diferenciável). Saímos do conceito de derivadas parciais e gradientes (Aula 6), passamos pela análise de convexidade e matriz Hessiana (Aula 7), dominamos a dinâmica do Gradient Descent em modelos convexos (Aula 8) e construímos a máquina universal de diferenciação exata em grafos de computação para modelos profundos (Aula 9).
Agora possuímos um motor capaz de calcular gradientes de qualquer rede neural. Contudo, há um gargalo monumental que ainda não enfrentamos: - No California Housing, somamos os gradientes sobre todas as \(N = 20.640\) residências para dar um único passo de descida. - O que acontece quando nosso dataset possui 100 milhões de imagens ou 1 trilhão de tokens de texto? Avaliar o grafo inteiro para dar um único passo é computacionalmente proibitivo!
Além disso, como se comporta a dinâmica de descida quando a superfície de perda possui vales estreitos (ravines) e curvaturas heterogêneas?
5.3 Ponte para a Aula 10
Na próxima aula, inauguramos o Bloco 3 (Dinâmica Avançada e Escala) com o Gradiente Descendente Estocástico (SGD) e Mini-Batching (Aula 10). Analisaremos o gradiente sob a ótica de um estimador amostral ruidoso, provando que mini-batches reduzem dramaticamente o custo por iteração enquanto preservam a direção esperada de descida.
Mais adiante, resgataremos as Séries de Taylor Multivariadas e a análise de curvatura de segunda ordem para responder: por que o gradiente oscila violentamente em vales estreitos e como os aceleradores de Momento (Polyak e Nesterov) e os métodos de segunda ordem (Newton) superam essas limitações!