Redes Neurais e Deep Learning

Redes Neurais e Deep Learning

1. O Perceptron

O Perceptron (Frank Rosenblatt, 1957) e o modelo mais simples de neuronio artificial. Ele recebe entradas, aplica pesos, soma, e produz uma saida binaria.

Perceptron:

  x1 ─(w1)──┐

  x2 ─(w2)──┼──→ [ z = Σ(wi*xi) + b ] ──→ [ f(z) ] ──→ y

  x3 ─(w3)──┘

  z = w1*x1 + w2*x2 + w3*x3 + b    (combinacao linear)
  y = f(z)                            (funcao de ativacao)

  Para o Perceptron original:
  f(z) = 1  se z >= 0
  f(z) = 0  se z < 0                  (step function)

Implementacao

import numpy as np

class Perceptron:
    def __init__(self, n_features, lr=0.01):
        self.w = np.zeros(n_features)
        self.b = 0
        self.lr = lr

    def predict(self, x):
        z = np.dot(self.w, x) + self.b
        return 1 if z >= 0 else 0

    def train(self, X, y, epochs=100):
        for _ in range(epochs):
            for xi, yi in zip(X, y):
                pred = self.predict(xi)
                error = yi - pred
                # Regra de atualizacao do Perceptron
                self.w += self.lr * error * xi
                self.b += self.lr * error

# Exemplo: aprender AND logico
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([0, 0, 0, 1])

p = Perceptron(n_features=2)
p.train(X, y, epochs=10)

for xi in X:
    print(f"{xi} -> {p.predict(xi)}")
# [0, 0] -> 0
# [0, 1] -> 0
# [1, 0] -> 0
# [1, 1] -> 1

Limitacao Fundamental: o Problema XOR

O Perceptron so pode resolver problemas linearmente separaveis — onde existe uma reta (ou hiperplano) que separa as classes.

AND (linearmente separavel):     XOR (NAO linearmente separavel):

  y                                y
  1│     ●(1,1)                    1│ ●(0,1)     ●(1,1)
   │                                │  classe 1   classe 0
  0│ ●(0,0)  ●(1,0)               0│ ●(0,0)     ●(1,0)
   └──────────── x                  │  classe 0   classe 1
     Uma reta separa ● de ○          └──────────── x
                                     Nenhuma reta separa as classes!

Essa limitacao, demonstrada por Minsky e Papert em 1969, desencadeou o primeiro AI Winter. A solucao? Redes multicamada (MLP).


2. Funcoes de Ativacao

Funcoes de ativacao introduzem nao-linearidade na rede. Sem elas, empilhar multiplas camadas lineares seria equivalente a uma unica camada linear (composicao de funcoes lineares e linear).

Sigmoid

σ(z) = 1 / (1 + e^(-z))

Saida: (0, 1)
Derivada: σ'(z) = σ(z) * (1 - σ(z))

Vantagens: saida entre 0 e 1 (interpretavel como probabilidade)
Problemas: vanishing gradient (derivada → 0 para |z| grande),
           saida nao centrada em zero, exp() e lento

Tanh

tanh(z) = (e^z - e^(-z)) / (e^z + e^(-z))

Saida: (-1, 1)
Derivada: tanh'(z) = 1 - tanh²(z)

Vantagens: centrada em zero (melhor que sigmoid para camadas ocultas)
Problemas: ainda sofre vanishing gradient para |z| grande

ReLU (Rectified Linear Unit)

ReLU(z) = max(0, z)

Saida: [0, +∞)
Derivada: 1 se z > 0, 0 se z < 0

Vantagens: simples, rapido, sem vanishing gradient para z > 0,
           gera ativacoes esparsas (muitos zeros = eficiente)
Problemas: "dying ReLU" (neuronios com z < 0 param de aprender)

Variantes Modernas

# Leaky ReLU: resolve dying ReLU
def leaky_relu(z, alpha=0.01):
    return np.where(z > 0, z, alpha * z)

# GELU (Gaussian Error Linear Unit): usada em Transformers (BERT, GPT)
def gelu(z):
    return 0.5 * z * (1 + np.tanh(np.sqrt(2/np.pi) * (z + 0.044715 * z**3)))

# SiLU / Swish: usada em modelos modernos
def silu(z):
    return z * (1 / (1 + np.exp(-z)))  # z * sigmoid(z)

Quando Usar Cada Funcao

┌─────────────────┬────────────────────────────────────────┐
│ Funcao          │ Quando usar                            │
├─────────────────┼────────────────────────────────────────┤
│ ReLU            │ Padrao para camadas ocultas de MLPs    │
│ Leaky ReLU      │ Quando muitos neuronios estao "morrendo"│
│ GELU            │ Transformers (BERT, GPT, etc.)         │
│ SiLU/Swish      │ Modelos de visao modernos (EfficientNet)│
│ Sigmoid         │ Camada de saida para classificacao     │
│                 │ binaria (saida 0-1)                    │
│ Softmax         │ Camada de saida para classificacao     │
│                 │ multiclasse (soma = 1)                 │
│ Tanh            │ Camadas ocultas de RNNs (historico)    │
│ Linear (nenhuma)│ Camada de saida para regressao         │
└─────────────────┴────────────────────────────────────────┘

3. MLP (Multi-Layer Perceptron)

Um MLP e uma rede neural com uma ou mais camadas ocultas. Cada camada e uma transformacao linear seguida de uma funcao de ativacao nao-linear.

Input     Hidden Layer 1    Hidden Layer 2    Output
(3)           (4)               (4)            (2)

x1 ─────●─────●─────●─────●──────●──────●──────●──── y1
        │╲   ╱│╲   ╱│╲   ╱│╲    ╱│╲    ╱│╲    ╱│
x2 ─────●──╲╱─●──╲╱─●──╲╱─●──╲╱──●──╲╱──●──╲╱──●── y2
        │  ╱╲ │  ╱╲ │  ╱╲ │  ╱╲  │  ╱╲  │  ╱╲  │
x3 ─────●╱───╲●╱───╲●╱───╲●╱────╲●╱────╲●╱────╲●

Cada aresta tem um peso (parametro aprendido).
Cada neuronio aplica: z = Σ(wi*xi) + b, a = f(z)

Total de parametros:
  Camada 1: 3*4 + 4 = 16    (pesos + biases)
  Camada 2: 4*4 + 4 = 20
  Camada 3: 4*2 + 2 = 10
  Total: 46 parametros

Universal Approximation Theorem

O Teorema da Aproximacao Universal (Cybenko, 1989; Hornik, 1991) estabelece que um MLP com uma unica camada oculta com numero suficiente de neuronios pode aproximar qualquer funcao continua em um dominio compacto com precisao arbitraria.

Isso nao significa que uma camada e suficiente na pratica. Redes profundas (muitas camadas) sao mais eficientes em parametros para representar funcoes complexas.


4. Forward Pass e Funcoes de Perda

Forward Pass

O forward pass e a computacao que transforma a entrada em saida, propagando dados atraves de todas as camadas.

import numpy as np

class MLP:
    def __init__(self, layer_sizes):
        self.weights = []
        self.biases = []

        for i in range(len(layer_sizes) - 1):
            # Inicializacao He (bom para ReLU)
            w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * np.sqrt(2.0 / layer_sizes[i])
            b = np.zeros((1, layer_sizes[i+1]))
            self.weights.append(w)
            self.biases.append(b)

    def relu(self, z):
        return np.maximum(0, z)

    def softmax(self, z):
        exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))
        return exp_z / exp_z.sum(axis=1, keepdims=True)

    def forward(self, X):
        self.activations = [X]
        self.z_values = []

        a = X
        for i in range(len(self.weights)):
            z = a @ self.weights[i] + self.biases[i]
            self.z_values.append(z)

            if i < len(self.weights) - 1:
                a = self.relu(z)        # ReLU nas camadas ocultas
            else:
                a = self.softmax(z)     # Softmax na saida

            self.activations.append(a)
        return a

Funcoes de Perda (Loss Functions)

A funcao de perda quantifica quao errada esta a predicao do modelo. O objetivo do treinamento e minimizar essa funcao.

MSE (regressao):
  MSE = (1/n) * Σ(y_pred - y_real)²
  Penaliza erros grandes mais que erros pequenos (quadratico)

Cross-Entropy (classificacao):
  Binaria:     L = -[y * log(p) + (1-y) * log(1-p)]
  Multiclasse: L = -Σ(y_i * log(p_i))

Intuicao: cross-entropy mede a "surpresa" do modelo.
Predicao 90% na classe correta → loss baixa.
Predicao 30% na classe correta → loss alta.

5. Backpropagation

Backpropagation (Rumelhart, Hinton & Williams, 1986) e o algoritmo que calcula o gradiente da funcao de perda em relacao a cada peso da rede. Ele usa a regra da cadeia do calculo, propagando o erro da saida de volta ate a entrada.

Forward pass: dados fluem da entrada para a saida
              X → h1 → h2 → y_pred → Loss

Backward pass: gradientes fluem da saida para a entrada
              ∂L/∂W1 ← ∂L/∂h1 ← ∂L/∂h2 ← ∂L/∂y_pred ← Loss

Cada peso e atualizado proporcionalmente a sua
contribuicao para o erro total.

Passo a Passo para uma Rede de 2 Camadas

Camada 1: z1 = X @ W1 + b1,  a1 = ReLU(z1)
Camada 2: z2 = a1 @ W2 + b2, a2 = softmax(z2) = y_pred
Loss:     L = cross_entropy(y_true, y_pred)

Backward pass:
  1. dz2 = y_pred - y_true
  2. dW2 = a1^T @ dz2,  db2 = sum(dz2)
  3. da1 = dz2 @ W2^T
  4. dz1 = da1 * (z1 > 0)    (derivada do ReLU)
  5. dW1 = X^T @ dz1,   db1 = sum(dz1)
  6. Atualizar: W = W - lr * dW (para todos os pesos)

Implementacao Completa

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size, lr=0.01):
        self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2.0 / input_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2.0 / hidden_size)
        self.b2 = np.zeros((1, output_size))
        self.lr = lr

    def relu(self, z):
        return np.maximum(0, z)

    def softmax(self, z):
        exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))
        return exp_z / exp_z.sum(axis=1, keepdims=True)

    def forward(self, X):
        self.z1 = X @ self.W1 + self.b1
        self.a1 = self.relu(self.z1)
        self.z2 = self.a1 @ self.W2 + self.b2
        self.a2 = self.softmax(self.z2)
        return self.a2

    def backward(self, X, y_true):
        m = X.shape[0]
        dz2 = self.a2 - y_true
        dW2 = (1/m) * self.a1.T @ dz2
        db2 = (1/m) * np.sum(dz2, axis=0, keepdims=True)
        da1 = dz2 @ self.W2.T
        dz1 = da1 * (self.z1 > 0)
        dW1 = (1/m) * X.T @ dz1
        db1 = (1/m) * np.sum(dz1, axis=0, keepdims=True)

        self.W2 -= self.lr * dW2
        self.b2 -= self.lr * db2
        self.W1 -= self.lr * dW1
        self.b1 -= self.lr * db1

    def train(self, X, y_true, epochs=1000):
        for epoch in range(epochs):
            y_pred = self.forward(X)
            loss = -np.mean(np.sum(y_true * np.log(y_pred + 1e-15), axis=1))
            self.backward(X, y_true)
            if epoch % 200 == 0:
                print(f"Epoch {epoch:4d}: loss = {loss:.4f}")

# Resolver XOR (que o Perceptron nao consegue!)
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[1,0], [0,1], [0,1], [1,0]])  # one-hot

nn = NeuralNetwork(input_size=2, hidden_size=8, output_size=2, lr=0.5)
nn.train(X, y, epochs=2000)

6. Inicializacao e Regularizacao

Inicializacao de Pesos

┌──────────────┬──────────────────────────────────────────────┐
│ Metodo       │ Descricao                                    │
├──────────────┼──────────────────────────────────────────────┤
│ Zeros        │ NUNCA use. Todos os neuronios aprendem a     │
│              │ mesma coisa (simetria nao e quebrada).        │
├──────────────┼──────────────────────────────────────────────┤
│ Xavier/Glorot│ w ~ N(0, 2/(n_in + n_out))                   │
│              │ Bom para sigmoid e tanh.                      │
├──────────────┼──────────────────────────────────────────────┤
│ He/Kaiming   │ w ~ N(0, 2/n_in)                             │
│              │ Bom para ReLU. Padrao em PyTorch.             │
└──────────────┴──────────────────────────────────────────────┘

Dropout

Treinamento (dropout = 0.5):         Inferencia:
Desativa 50% dos neuronios           Todos os neuronios ativos
aleatoriamente a cada batch.          (pesos * (1-dropout))

  ● ─── ● ─── ●                      ● ─── ● ─── ●
  ● ─── X ─── ●  (desativado)        ● ─── ● ─── ●
  ● ─── ● ─── X  (desativado)        ● ─── ● ─── ●
  ● ─── ● ─── ●                      ● ─── ● ─── ●

Efeito: forca cada neuronio a ser util independentemente,
evitando co-adaptacao. Funciona como um ensemble implicito.

Batch Normalization

Normaliza as ativacoes de cada camada para ter media 0 e variancia 1. Beneficios: treinamento mais estavel, permite learning rates maiores, funciona como regularizador leve.

# Em PyTorch:
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.BatchNorm1d(256),
    nn.ReLU(),
    nn.Dropout(0.3),
    nn.Linear(256, 128),
    nn.BatchNorm1d(128),
    nn.ReLU(),
    nn.Dropout(0.3),
    nn.Linear(128, 10),
)

7. CNNs (Convolutional Neural Networks)

CNNs sao projetadas para processar dados com estrutura espacial — imagens, audio (espectrogramas) e ate texto (1D). A ideia central: em vez de conectar cada neuronio a todos os pixels, usamos filtros (kernels) que escaneiam a imagem detectando padroes locais.

Operacao de Convolucao

Imagem de entrada (5x5):        Filtro/Kernel (3x3):

┌─────────────────────┐         ┌───────────┐
│  1  0  1  0  1      │         │  1  0  1  │
│  0  1  0  1  0      │         │  0  1  0  │
│  1  0  1  0  1      │         │  1  0  1  │
│  0  1  0  1  0      │         └───────────┘
│  1  0  1  0  1      │
└─────────────────────┘

Convolucao: deslizar o filtro sobre a imagem,
calculando o dot product em cada posicao.

Parametros:
  Kernel size: tamanho do filtro (3x3 e o padrao)
  Stride: quantos pixels o filtro avanca (1 = padrao)
  Padding: adicionar zeros nas bordas
  Numero de filtros: cada filtro detecta um padrao diferente

Pooling e Hierarquia de Features

Pooling reduz a dimensionalidade espacial, mantendo features importantes. Max Pooling (2x2, stride 2) substitui cada regiao 2x2 pelo valor maximo, reduzindo dimensao pela metade.

Camadas iniciais         Camadas medias           Camadas finais
(features simples)       (features compostas)     (conceitos)

┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│  ─  │  \      │       │  Olho    Orelha│       │    Gato       │
│  |  /         │       │  Boca    Nariz │       │    Cachorro   │
│  Bordas       │       │  Partes        │       │    Objetos    │
│  Texturas     │       │  de objetos    │       │    completos  │
└───────────────┘       └───────────────┘       └───────────────┘

Residual Connections (ResNet)

O problema de redes muito profundas: o gradiente se torna tao pequeno que camadas iniciais mal aprendem (vanishing gradient). ResNet resolve isso com skip connections:

Bloco residual:

  x ─────────────────────────────┐
  │                              │ (skip/identity)
  ▼                              │
  [Conv → BN → ReLU]            │
  │                              │
  ▼                              │
  [Conv → BN]                   │
  │                              │
  ▼                              │
  [   +   ] ← soma x + F(x)    │
  │       ←──────────────────────┘

  [ReLU]


  saida = ReLU(F(x) + x)

O modelo aprende a funcao RESIDUAL F(x) = saida_desejada - x.
Se a camada ideal fosse identidade, F(x) = 0 e facil de aprender.
import torch
import torch.nn as nn

class ResidualBlock(nn.Module):
    def __init__(self, channels):
        super().__init__()
        self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
        self.bn1 = nn.BatchNorm2d(channels)
        self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(channels)
        self.relu = nn.ReLU()

    def forward(self, x):
        residual = x
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out = out + residual            # skip connection
        out = self.relu(out)
        return out

Arquiteturas Classicas

┌────────────┬──────┬────────────┬──────────────────────────────┐
│ Arquitetura│ Ano  │ Camadas    │ Inovacao                      │
├────────────┼──────┼────────────┼──────────────────────────────┤
│ LeNet-5    │ 1998 │ 5          │ CNN para digitos (MNIST)      │
│ AlexNet    │ 2012 │ 8          │ ReLU, Dropout, GPU training   │
│ VGGNet     │ 2014 │ 16-19      │ Filtros 3x3 empilhados       │
│ GoogLeNet  │ 2014 │ 22         │ Inception modules (paralelos) │
│ ResNet     │ 2015 │ 50-152     │ Residual connections (skip)   │
│ EfficientNet│2019 │ variavel   │ Scaling balanceado (NAS)      │
│ ViT        │ 2020 │ Transformer│ Vision Transformer (patches)  │
└────────────┴──────┴────────────┴──────────────────────────────┘

8. RNNs (Recurrent Neural Networks)

RNNs processam sequencias mantendo um estado oculto (hidden state) que funciona como memoria do que ja foi processado.

RNN desdobrada no tempo:

  x1          x2          x3          x4
  │           │           │           │
  ▼           ▼           ▼           ▼
┌─────┐    ┌─────┐    ┌─────┐    ┌─────┐
│     │───→│     │───→│     │───→│     │
│ h1  │    │ h2  │    │ h3  │    │ h4  │
└─────┘    └─────┘    └─────┘    └─────┘
  │           │           │           │
  ▼           ▼           ▼           ▼
  y1          y2          y3          y4

A cada passo t:
  h_t = tanh(W_hh * h_(t-1) + W_xh * x_t + b_h)
  y_t = W_hy * h_t + b_y

O Problema do Vanishing Gradient

Quando a sequencia e longa, os gradientes que fluem de volta no tempo (backpropagation through time - BPTT) sao multiplicados repetidamente pela mesma matriz de pesos. Se os valores proprios forem < 1, os gradientes encolhem exponencialmente — o modelo esquece informacoes distantes.

Gradiente fluindo de h_100 ate h_1:
  ∂L/∂h_1 = ∂L/∂h_100 * ∂h_100/∂h_99 * ... * ∂h_2/∂h_1

Se cada fator < 1:  0.9^99 ≈ 0.00003 (gradiente praticamente zero)
Se cada fator > 1:  1.1^99 ≈ 12527   (gradiente explode)

9. LSTM (Long Short-Term Memory)

LSTM (Hochreiter & Schmidhuber, 1997) resolve o vanishing gradient com um mecanismo de gates que controlam o fluxo de informacao.

Os Tres Gates

import numpy as np

def lstm_cell(x_t, h_prev, c_prev, Wf, Wi, Wc, Wo, bf, bi, bc, bo):
    combined = np.concatenate([h_prev, x_t])

    # Forget gate: decide o que ESQUECER do cell state
    f_t = sigmoid(Wf @ combined + bf)

    # Input gate: decide o que ADICIONAR ao cell state
    i_t = sigmoid(Wi @ combined + bi)
    c_candidate = np.tanh(Wc @ combined + bc)

    # Atualizar cell state
    c_t = f_t * c_prev + i_t * c_candidate

    # Output gate: decide o que EMITIR do cell state
    o_t = sigmoid(Wo @ combined + bo)
    h_t = o_t * np.tanh(c_t)

    return h_t, c_t

Por que LSTM funciona: o cell state C_t e uma “esteira transportadora” que flui ao longo de toda a sequencia. O forget gate pode manter informacoes inalteradas (multiplicando por 1), eliminando o problema de vanishing gradient. A informacao pode persistir por centenas de passos.


10. Transformers

O Paper que Mudou Tudo

“Attention Is All You Need” (Vaswani et al., 2017) eliminou recorrencia e convolucao, usando apenas mecanismos de self-attention. O resultado: paralelizacao total e desempenho superior em NLP.

Self-Attention (Scaled Dot-Product Attention)

A ideia central: para cada token na sequencia, calcular quao relevantes sao todos os outros tokens para entende-lo.

Entrada: "O gato sentou no tapete"

Para o token "sentou":
  - "gato" e muito relevante (quem sentou?)       → peso alto
  - "tapete" e relevante (onde sentou?)            → peso medio
  - "O" e pouco relevante                         → peso baixo
  - "no" e pouco relevante                        → peso baixo

Mecanismo Matematico

Cada token gera tres vetores a partir de seus embeddings:

Query (Q): "O que estou procurando?"
Key (K):   "O que tenho a oferecer?"
Value (V): "Qual informacao carrego?"

Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V

Onde d_k e a dimensao dos vetores (para estabilidade numerica).
import numpy as np

def scaled_dot_product_attention(Q, K, V, mask=None):
    d_k = Q.shape[-1]
    scores = Q @ K.T / np.sqrt(d_k)

    if mask is not None:
        scores = scores + mask  # mask com -inf bloqueia posicoes futuras

    weights = np.exp(scores - scores.max(axis=-1, keepdims=True))
    weights = weights / weights.sum(axis=-1, keepdims=True)

    output = weights @ V
    return output, weights

Multi-Head Attention

Em vez de um unico mecanismo de atencao, o Transformer usa multiplas “cabecas” em paralelo, cada uma aprendendo a prestar atencao a aspectos diferentes da sequencia.

Multi-Head Attention (h = 8 cabecas):

                    ┌──→ Head 1: Attention(Q1, K1, V1) ──→ O1 ──┐
                    │                                             │
  X ──→ [Projecoes] ──→ Head 2: Attention(Q2, K2, V2) ──→ O2 ──┼──→ [Concat] ──→ [Linear] ──→ Saida
                    │                                             │
                    └──→ Head 8: Attention(Q8, K8, V8) ──→ O8 ──┘

  Cada cabeca opera em uma sub-dimensao: d_k = d_model / h
  Se d_model = 512 e h = 8, cada cabeca tem d_k = 64

  Uma cabeca pode aprender relacoes sintaticas (sujeito-verbo),
  outra relacoes semanticas (sinonimos), outra posicionais, etc.

Positional Encoding

Self-attention e invariante a ordem — nao sabe se “gato comeu rato” e diferente de “rato comeu gato”. Positional Encoding adiciona informacao de posicao:

PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

Cada posicao recebe um vetor unico baseado em senos e cossenos
de diferentes frequencias.

Arquitetura Completa do Transformer

┌─────────────────────────────────────────────────────────────┐
│                     TRANSFORMER                              │
│                                                              │
│  ┌────────────────────┐      ┌──────────────────────────┐   │
│  │      ENCODER       │      │        DECODER            │   │
│  │                    │      │                           │   │
│  │  Input Embedding   │      │  Output Embedding         │   │
│  │  + Positional Enc  │      │  + Positional Enc         │   │
│  │        │           │      │        │                  │   │
│  │        ▼           │      │        ▼                  │   │
│  │  ┌───────────────┐│      │  ┌──────────────────┐     │   │
│  │  │ Multi-Head    ││      │  │ Masked Multi-Head│     │   │
│  │  │ Self-Attention││      │  │ Self-Attention   │     │   │
│  │  └───────┬───────┘│      │  └──────┬───────────┘     │   │
│  │  [Add & Norm]     │      │  [Add & Norm]              │   │
│  │        │           │      │        │                  │   │
│  │  ┌───────────────┐│      │  ┌──────────────────┐     │   │
│  │  │ Feed Forward  ││  ──→ │  │ Cross-Attention  │     │   │
│  │  │ Network       ││      │  │ (Q=decoder,      │     │   │
│  │  └───────┬───────┘│      │  │  K,V=encoder)    │     │   │
│  │  [Add & Norm]     │      │  └──────┬───────────┘     │   │
│  │        │           │      │  [Add & Norm]              │   │
│  │     (Nx layers)   │      │        │                  │   │
│  └────────────────────┘      │  ┌──────────────────┐     │   │
│                              │  │ Feed Forward     │     │   │
│                              │  │ Network          │     │   │
│                              │  └──────┬───────────┘     │   │
│                              │  [Add & Norm]              │   │
│                              │     (Nx layers)           │   │
│                              │        │                  │   │
│                              │  [Linear + Softmax]       │   │
│                              │        │                  │   │
│                              │    Predicao              │   │
│                              └──────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Componentes-chave:
  Add & Norm: residual connection + layer normalization
  Feed Forward Network: MLP com 2 camadas (expansao 4x + projecao)
  Masked Self-Attention: mascara tokens futuros (autoregressive)
  Cross-Attention: decoder "olha" para a saida do encoder

11. Por que Transformers Venceram

┌─────────────────┬──────────────────┬──────────────────────┐
│                 │ RNN/LSTM          │ Transformer           │
├─────────────────┼──────────────────┼──────────────────────┤
│ Paralelizacao   │ Sequencial (lento)│ Totalmente paralelo  │
│                 │ Token por token   │ Todos tokens de uma  │
│                 │                  │ vez na GPU            │
├─────────────────┼──────────────────┼──────────────────────┤
│ Dependencias    │ Dificuldade com  │ Acesso direto a      │
│ de longo alcance│ sequencias longas│ qualquer posicao     │
│                 │ (vanishing grad) │ via attention         │
├─────────────────┼──────────────────┼──────────────────────┤
│ Escalabilidade  │ Dificil escalar  │ Escala com mais      │
│                 │ alem de ~1000    │ dados, parametros e  │
│                 │ tokens           │ compute (scaling laws)│
├─────────────────┼──────────────────┼──────────────────────┤
│ Complexidade    │ O(n) por camada  │ O(n²) por camada     │
│ computacional   │                  │ (custo de attention)  │
│                 │                  │ Mas paralelizavel!    │
└─────────────────┴──────────────────┴──────────────────────┘

O custo O(n^2) do self-attention e o principal limitador — e por isso que modelos tem “context window”. Pesquisa ativa busca atencao sub-quadratica (Sparse Attention, Linear Attention, Flash Attention para eficiencia de memoria).

Variantes de Transformers

Encoder-only:   BERT, RoBERTa
                Bom para: classificacao, NER, similarity
                Pre-treino: masked language model (bidirecional)

Decoder-only:   GPT-1/2/3/4, Claude, LLaMA, Mistral
                Bom para: geracao de texto, conversacao
                Pre-treino: next token prediction (autoregressive)

Encoder-Decoder: T5, BART, modelo original
                 Bom para: traducao, sumarizacao
                 Pre-treino: reconstrucao de texto corrompido

12. Vision Transformers (ViT)

Em 2020, An Image is Worth 16x16 Words mostrou que Transformers podiam ser aplicados diretamente a imagens, dividindo-as em patches tratados como tokens.

Imagem (224x224) → dividir em patches de 16x16 → 196 patches

[CLS] [p1] [p2] ... [p196] + Positional Embeddings


Transformer Encoder (12 camadas)


Classificacao (usando o token [CLS])

Isso unificou visao e linguagem sob a mesma arquitetura, levando a modelos multimodais como GPT-4V e Gemini.


Resumo: Evolucao das Arquiteturas

Perceptron (1957) → Classificador linear, limitado a XOR
MLP (1986+)       → Camadas ocultas + backpropagation
CNN (1998-2015)   → Convolucao + pooling para visao computacional
RNN/LSTM (1997)   → Sequencias com memoria, vanishing gradient
Transformer (2017)→ Self-attention pura, paralelizavel
ViT (2020)        → Transformers para visao
LLMs (2020+)      → Transformers massivos, bilhoes de parametros

A tendencia e clara: Transformers se tornaram a arquitetura
universal para praticamente toda tarefa de deep learning.

Referencias e Fontes

  • Deep Learning — Ian Goodfellow, Yoshua Bengio & Aaron Courville (2016). Cobertura completa de redes feedforward, CNNs, RNNs, regularizacao e otimizacao. deeplearningbook.org
  • Attention Is All You Need — Vaswani et al. (2017). O paper que introduziu a arquitetura Transformer. arxiv.org/abs/1706.03762
  • Neural Networks and Deep Learning — Michael Nielsen. Livro online gratuito com explicacoes visuais e interativas sobre perceptrons e backpropagation. neuralnetworksanddeeplearning.com
  • CS231n: Convolutional Neural Networks for Visual Recognition — Stanford University. cs231n.stanford.edu. Curso completo cobrindo CNNs, arquiteturas e Vision Transformers
  • Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow — Aurelien Geron (3a edicao, 2022). Guia pratico com implementacoes de CNNs, RNNs e Transformers
  • Anthropic Documentationdocs.anthropic.com. Research papers sobre a arquitetura e treinamento de modelos Claude
  • OpenAI Cookbookcookbook.openai.com. Exemplos praticos de uso de Transformers e modelos de linguagem
  • Andrej Karpathy’s Neural Networks: Zero to Heroyoutube.com/@andrejkarpathy. Lectures cobrindo backpropagation, CNNs e GPT do zero
  • fast.aifast.ai. Curso pratico de deep learning com abordagem top-down