Phase 04 - Lesson 14
Vision Transformers (ViT)
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Corte a imagem em patches, trate cada patch como uma palavra, rode um transformer padrao. Nao olhe para tras.
Tipo: Build Linguagens: Python Pre-requisitos: Fase 7 Licao 02 (Self-Attention), Fase 4 Licao 04 (Classificacao de Imagens) Tempo: ~45 minutos
Objetivos de Aprendizado
- Implementar patch embedding, positional embedding aprendido, class token e blocos de encoder transformer do zero para construir um ViT minimo
- Explicar por que se acreditava que o ViT precisava de pretreinamento massivo de dados ate que DeiT e MAE provaram o contrario
- Comparar ViT, Swin e ConvNeXt em seus priors arquiteturais (nenhum, atencao em janela local, backbone convolucional)
- Fazer fine-tune de um ViT pre-treinado em um dataset pequeno usando
timme a receita padrao de linear-probe / fine-tune
O Problema
Por uma decada, convolucao foi sinonimo de visao computacional. CNNs tinham fortes vieses indutivos — localidade, equivariancia a translacao — que ninguem achava que dava para substituir. Entao Dosovitskiy et al. (2020) mostraram que um transformer puro aplicado a patches de imagem achatados, sem nenhuma maquinaria convolucional, podia igualar ou superar as melhores CNNs em escala.
O detalhe era "em escala". ViT no ImageNet-1k perdia para o ResNet. ViT pre-treinado no ImageNet-21k ou JFT-300M e depois fine-tunado no ImageNet-1k o superava. A conclusao foi que transformers careciam de priors uteis, mas podiam aprende-los com dados suficientes. Trabalhos subsequentes (DeiT, MAE, DINO) mostraram que, com as receitas de treinamento certas — augmentation forte, pretreinamento auto-supervisionado, destilacao — ViTs treinam bem em dados pequenos tambem.
Em 2026, CNNs puras ainda sao competitivas em dispositivos de borda (ConvNeXt e a mais forte), mas transformers dominam todo o resto: segmentacao (Mask2Former, SegFormer), deteccao (DETR, RT-DETR), multimodal (CLIP, SigLIP), video (VideoMAE, VJEPA). A estrutura do bloco ViT e a que vale conhecer.
O Conceito
O pipeline
flowchart LR
IMG["Imagem<br/>(3, 224, 224)"] --> PATCH["Patch embedding<br/>conv 16x16 s=16<br/>-> (768, 14, 14)"]
PATCH --> FLAT["Achata para<br/>(196, 768) tokens"]
FLAT --> CAT["Adiciona no inicio<br/>token [CLS]"]
CAT --> POS["Soma positional<br/>embed aprendido"]
POS --> ENC["N blocos de<br/>encoder transformer"]
ENC --> CLS["Pega a saida do<br/>token [CLS]"]
CLS --> HEAD["Classificador MLP"]
style PATCH fill:#dbeafe,stroke:#2563eb
style ENC fill:#fef3c7,stroke:#d97706
style HEAD fill:#dcfce7,stroke:#16a34a
Sete passos. Patches -> tokens -> atencao -> classificador. Cada variante (DeiT, Swin, ConvNeXt, pretreinamento MAE) muda um ou dois dos sete e deixa o resto intacto.
Patch embedding
A primeira conv e o segredo. Tamanho de kernel 16, stride 16, entao uma imagem 224x224 vira uma grade 14x14 de patches 16x16, cada um projetado para um embedding de 768 dimensoes. Aquela unica conv tanto patchifica quanto projeta linearmente.
Input: (3, 224, 224)
Conv (3 -> 768, k=16, s=16, no padding):
Output: (768, 14, 14)
Flatten spatial: (196, 768)
196 patches = 196 tokens. A dimensao de feature de cada token e 768 (ViT-B), 1024 (ViT-L) ou 1280 (ViT-H).
Class token
Um unico vetor aprendido adicionado no inicio da sequencia:
tokens = [CLS; patch_1; patch_2; ...; patch_196] shape (197, 768)
Apos N blocos transformer, a saida do [CLS] e a representacao global da imagem. A cabeca de classificacao le apenas esse unico vetor.
Positional embedding
Transformers nao tem nocao embutida de posicao espacial. Some um vetor aprendido a cada token:
tokens = tokens + learned_pos_embedding (also shape (197, 768))
O embedding e um parametro do modelo; o treinamento baseado em gradiente o adapta a estrutura 2D da imagem. Existem alternativas sinusoidais 2D, mas raramente sao usadas na pratica.
Bloco de encoder transformer
Padrao. Multi-head self-attention, MLP, conexoes residuais, pre-LayerNorm.
x = x + MSA(LN(x))
x = x + MLP(LN(x))
MLP is two-layer with GELU: Linear(d -> 4d) -> GELU -> Linear(4d -> d)
O ViT-B/16 empilha 12 desses blocos, cada um com 12 cabecas de atencao, totalizando 86M de parametros.
Por que pre-LN
Os primeiros transformers usavam post-LN (x = LN(x + sublayer(x))) e tinham dificuldade de treinar alem de 6-8 camadas sem warmup. Pre-LN (x = x + sublayer(LN(x))) treina redes mais profundas de forma estavel sem warmup. Todo ViT e todo LLM moderno usam pre-LN.
Trade-off do tamanho de patch
- Patches 16x16 -> 196 tokens, padrao.
- Patches 32x32 -> 49 tokens, mais rapido mas resolucao menor.
- Patches 8x8 -> 784 tokens, mais fino mas o custo O(n^2) da atencao escala mal.
Patches maiores = menos tokens = mais rapido mas menos detalhe espacial. O SwinV2 usa patches 4x4 em janelas hierarquicas.
A receita do DeiT para treinar ViT no ImageNet-1k
O ViT original precisava do JFT-300M para superar CNNs. O DeiT (Touvron et al., 2020) treinou o ViT-B ate 81,8% top-1 no ImageNet-1k sozinho com quatro mudancas:
- Augmentation pesada: RandAugment, Mixup, CutMix, Random Erasing.
- Stochastic depth (descartar blocos inteiros aleatoriamente durante o treinamento).
- Repeated augmentation (mesma imagem amostrada 3 vezes por batch).
- Destilacao de um professor CNN (opcional, eleva ainda mais a acuracia).
Toda receita moderna de treinamento de ViT descende do DeiT.
Swin vs ConvNeXt
- Swin (Liu et al., 2021) — atencao baseada em janela. Cada bloco atende dentro de uma janela local; blocos alternados deslocam a janela para misturar informacao entre janelas. Traz de volta um prior de localidade no estilo CNN mantendo o operador de atencao.
- ConvNeXt (Liu et al., 2022) — uma CNN redesenhada que iguala as escolhas arquiteturais do Swin (convs depthwise, LayerNorm, GELU, bottleneck invertido). Mostrou que a diferenca nao e "atencao vs convolucao", mas "receita de treinamento moderna + arquitetura".
Em 2026, ConvNeXt-V2 e Swin-V2 sao ambos de nivel de producao; a escolha certa depende da sua stack de inferencia (ConvNeXt compila melhor para borda) e do corpus de pretreinamento.
Pretreinamento MAE
Masked Autoencoder (He et al., 2022): mascare 75% dos patches aleatoriamente, treine o encoder para processar apenas os 25% visiveis, treine um pequeno decoder para reconstruir os patches mascarados a partir da saida do encoder. Apos o pretreinamento, descarte o decoder e faca fine-tune do encoder.
O MAE torna o ViT treinavel no ImageNet-1k sozinho, atinge SOTA e e a receita auto-supervisionada padrao atual.
Construa
Passo 1: Patch embedding
import torch
import torch.nn as nn
class PatchEmbedding(nn.Module):
def __init__(self, in_channels=3, patch_size=16, dim=192, image_size=64):
super().__init__()
assert image_size % patch_size == 0
self.proj = nn.Conv2d(in_channels, dim, kernel_size=patch_size, stride=patch_size)
num_patches = (image_size // patch_size) ** 2
self.num_patches = num_patches
def forward(self, x):
x = self.proj(x)
return x.flatten(2).transpose(1, 2)
Uma conv, um flatten, um transpose. Esse e o passo inteiro de imagem-para-tokens.
Passo 2: Bloco transformer
Pre-LN, multi-head self-attention, MLP com GELU, conexoes residuais.
class Block(nn.Module):
def __init__(self, dim, num_heads, mlp_ratio=4, dropout=0.0):
super().__init__()
self.ln1 = nn.LayerNorm(dim)
self.attn = nn.MultiheadAttention(dim, num_heads, dropout=dropout, batch_first=True)
self.ln2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, dim * mlp_ratio),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(dim * mlp_ratio, dim),
nn.Dropout(dropout),
)
def forward(self, x):
a, _ = self.attn(self.ln1(x), self.ln1(x), self.ln1(x), need_weights=False)
x = x + a
x = x + self.mlp(self.ln2(x))
return x
O nn.MultiheadAttention cuida da divisao em cabecas, do produto escalar escalonado e da projecao de saida. batch_first=True para que os shapes sejam (N, seq, dim).
Passo 3: O ViT
class ViT(nn.Module):
def __init__(self, image_size=64, patch_size=16, in_channels=3,
num_classes=10, dim=192, depth=6, num_heads=3, mlp_ratio=4):
super().__init__()
self.patch = PatchEmbedding(in_channels, patch_size, dim, image_size)
num_patches = self.patch.num_patches
self.cls_token = nn.Parameter(torch.zeros(1, 1, dim))
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, dim))
self.blocks = nn.ModuleList([
Block(dim, num_heads, mlp_ratio) for _ in range(depth)
])
self.ln = nn.LayerNorm(dim)
self.head = nn.Linear(dim, num_classes)
nn.init.trunc_normal_(self.pos_embed, std=0.02)
nn.init.trunc_normal_(self.cls_token, std=0.02)
def forward(self, x):
x = self.patch(x)
cls = self.cls_token.expand(x.size(0), -1, -1)
x = torch.cat([cls, x], dim=1)
x = x + self.pos_embed
for blk in self.blocks:
x = blk(x)
x = self.ln(x[:, 0])
return self.head(x)
vit = ViT(image_size=64, patch_size=16, num_classes=10, dim=192, depth=6, num_heads=3)
x = torch.randn(2, 3, 64, 64)
print(f"output: {vit(x).shape}")
print(f"params: {sum(p.numel() for p in vit.parameters()):,}")
Cerca de 2,8M de parametros — um ViT minusculo tratavel em CPU. O ViT-B real tem 86M; mesma definicao de classe com dim=768, depth=12, num_heads=12.
Passo 4: Sanity check — inferencia de imagem unica
logits = vit(torch.randn(1, 3, 64, 64))
print(f"logits: {logits}")
print(f"probs: {logits.softmax(-1)}")
Deve rodar sem erro. As probabilidades somam 1.
Use
O timm traz cada variante de ViT com pesos pre-treinados no ImageNet. Uma linha:
import timm
model = timm.create_model("vit_base_patch16_224", pretrained=True, num_classes=10)
O timm e o padrao de producao para vision transformers em 2026. Suporta ViT, DeiT, Swin, Swin-V2, ConvNeXt, ConvNeXt-V2, MaxViT, MViT, EfficientFormer e dezenas de outros sob a mesma API.
Para trabalho multimodal (imagem + texto), o transformers traz CLIP, SigLIP, BLIP-2, LLaVA. O encoder de imagem em todos esses e uma variante de ViT.
Entregue
Esta licao produz:
outputs/prompt-vit-vs-cnn-picker.md— um prompt que escolhe entre um ViT, um ConvNeXt ou um Swin com base no tamanho do dataset, no compute e na stack de inferencia.outputs/skill-vit-patch-and-pos-embed-inspector.md— uma skill que verifica se os shapes de patch embedding e positional embedding de um ViT correspondem ao comprimento de sequencia esperado pelo modelo, capturando os bugs de portabilidade mais comuns.
Exercicios
- (Facil) Imprima os shapes de cada tensor intermediario para um forward pass pelo ViT minusculo acima. Confirme: input
(N, 3, 64, 64)-> patches(N, 16, 192)-> com CLS(N, 17, 192)-> input do classificador(N, 192)-> output(N, num_classes). - (Medio) Faca fine-tune de um ViT-S/16 pre-treinado do
timmno dataset CIFAR sintetico da Licao 4. Compare com o fine-tune de um ResNet-18 nos mesmos dados. Reporte o tempo de treinamento e a acuracia final. - (Dificil) Implemente o pretreinamento MAE para o ViT minusculo: mascare 75% dos patches, treine o encoder + um pequeno decoder para reconstruir os patches mascarados. Avalie a acuracia de linear-probe nos dados sinteticos antes e depois do pretreinamento.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Patch embedding | "A primeira conv" | Uma conv com tamanho de kernel = stride = tamanho do patch; transforma a imagem em uma grade de token embeddings |
| Class token | "[CLS]" | Um vetor aprendido adicionado no inicio da sequencia de tokens; sua saida final e a representacao global da imagem |
| Positional embedding | "Pos aprendido" | Um vetor aprendido somado a cada token para que o transformer saiba de onde veio cada patch |
| Pre-LN | "LayerNorm antes do sublayer" | A variante estavel do transformer: x + sublayer(LN(x)) em vez de LN(x + sublayer(x)) |
| Multi-head attention | "Atencao paralela" | A atencao padrao do transformer dividida em num_heads subespacos independentes, concatenados depois |
| ViT-B/16 | "Base, patch 16" | O tamanho canonico: dim=768, depth=12, heads=12, patch_size=16, image=224; ~86M params |
| DeiT | "ViT eficiente em dados" | ViT treinado apenas no ImageNet-1k com augmentation forte; provou que datasets grandes de pretreinamento nao sao estritamente necessarios |
| MAE | "Masked autoencoder" | Pretreinamento auto-supervisionado: mascare 75% dos patches, reconstrua; a receita dominante de pretreinamento de ViT |
Leitura Adicional
- An Image is Worth 16x16 Words (Dosovitskiy et al., 2020) — o paper do ViT
- DeiT: Data-efficient Image Transformers (Touvron et al., 2020) — como treinar ViT apenas no ImageNet-1k
- Masked Autoencoders are Scalable Vision Learners (He et al., 2022) — pretreinamento MAE
- timm documentation — a referencia para cada vision transformer que voce usara em producao