13. Vision Transformers
O capítulo anterior construiu um encoder para sequências de tokens de texto. Em 2021, Dosovitskiy et al. fizeram uma pergunta deliberadamente crua: e se você alimentar esse mesmo encoder, sem mudar nada, com uma imagem?1 A resposta — "An Image is Worth 16×16 Words" — foi que, com dados suficientes, um Transformer quase puro bate uma CNN em classificação de imagens, sem nenhuma convolução em lugar nenhum.
O resultado importou menos como número de classificação e mais como demonstração: a arquitetura não precisa saber que está olhando para uma imagem. É por isso que o ViT é o capítulo-ponte. Com ele na mão, os encoders de imagem dentro do CLIP, dentro de todo modelo de visão-linguagem, dentro do Stable Diffusion e do DiT deixam de ser coisas separadas a aprender.
Slides
Clique nos slides e use as setas ← → para navegar; F abre em tela cheia e P abre a visão do apresentador, com as notas. Os três simuladores e os três diagramas desta página rodam dentro dos slides. Abrir os slides numa aba nova
O trade-off de viés indutivo, com o preço certo
O capítulo 10 chamou a convolução de um prior forte, barato e fixo. Uma camada de auto-atenção não tem nada disso: cada patch pode atender a qualquer outro patch desde a primeira camada e qualquer noção de que pixels vizinhos têm relação precisa ser aprendida dos dados. Siga um patch pelos dois, camada a camada:
Isso é uma troca, não uma melhoria. O artigo original mediu os dois lados dela:
| Dados de pré-treino | Resultado |
|---|---|
| Só ImageNet-1k (~1,3M de imagens) | O ViT perde para uma ResNet comparável |
| ImageNet-21k (~14M) | Aproximadamente empatados |
| JFT-300M (~300M) | O ViT vence e transfere melhor |
O mesmo resultado modelo a modelo: cinco ViTs do apêndice do artigo e a maior ResNet do BiT13, cada um pré-treinado nos três conjuntos e ajustado no ImageNet.
Leia a tabela como uma única frase: o prior convolucional é um piso em escala pequena e um teto em escala grande. Abaixo de um certo limiar de dados, ele dá de graça o que o Transformer precisa comprar; acima dele, proíbe relações que o Transformer fica livre para descobrir.
Esse limiar não é lei da natureza e ele se moveu
O DeiT2 mostrou que boa parte da afirmação "o ViT precisa de 300M de imagens" era, na verdade, "o ViT precisa de uma receita de treino que ninguém tinha escrito ainda" — com a augmentation, a regularização e um token de destilação certos, um ViT treina de forma competitiva só com o ImageNet-1k; Steiner et al.5 mediram que a augmentation e a regularização certas, com mais computação de treino, podem valer quase tanto quanto dez vezes mais dados de pré-treino. O ConvNeXt8 depois fez o argumento simétrico do outro lado. A leitura moderna é que receita e escala explicam a maior parte do buraco que os artigos de 2021 atribuíram à arquitetura.
O pipeline
O único mecanismo genuinamente novo é o primeiro passo. Tudo depois dele é o encoder que você já conhece.
Uma imagem \(x \in \mathbb{R}^{H \times W \times C}\) (\(H \times W\) pixels, \(C\) canais: 3 no RGB) é cortada em patches \(P \times P\) sem sobreposição. Cada um é achatado em \(P^2 C\) números e projetado por uma única camada linear compartilhada \(E\) para \(d\) números, a largura do modelo:
Para \(224 \times 224\) e \(P = 16\), são \(N = 196\) tokens. O ViT de 2021 põe mais um token na frente, o [CLS]: um vetor aprendido, o mesmo para toda imagem, cuja linha na saída é a que a cabeça de classificação lê. Embeddings posicionais são somados, porque a atenção sozinha não distingue o patch do canto superior esquerdo do canto inferior direito. Depois a sequência atravessa \(L\) blocos de encoder pré-norm padrão4. Sem posições o encoder é equivariante a permutações: embaralhe os tokens de entrada e os de saída saem embaralhados do mesmo jeito; assim nem a média deles nem a linha do [CLS] enxergam a ordem (o quebra-cabeça do painel abaixo).
Siga o tensor: ViT-B/16 em 224 px
O nome se lê tamanho do modelo / tamanho do patch: ViT-B/16 é o modelo Base (\(d = 768\), \(L = 12\), 12 cabeças) com \(P = 16\). Uma imagem, formato a formato:
| Passo | Formato | Parâmetros |
|---|---|---|
| Imagem de entrada | \(3 \times 224 \times 224\) | — |
| Cortar em patches \(16 \times 16\), achatar | \(196 \times 768\) — cada linha são \(16 \cdot 16 \cdot 3\) pixels | — |
Projeção linear \(E\) (Conv2d(3, 768, 16, stride=16)) | \(196 \times 768\) | \(768 \cdot 768 + 768 = 590\,592\) |
Prefixar o [CLS], somar as posições | \(197 \times 768\) | \(768 + 197 \cdot 768 = 152\,064\) |
| 12 blocos de encoder (atenção + MLP \(768 \to 3072 \to 768\)) | \(197 \times 768\) | \(12 \times 7\,087\,872 \approx 85{,}1\text{M}\) |
LayerNorm final, ler a linha do [CLS] | \(768\) | \(1\,536\) |
| Cabeça linear | \(1000\) | \(769\,000\) |
| Total | ≈ 86,6M |
Duas coisas a notar. O comprimento da sequência nunca muda dentro do encoder: todo bloco leva \(197 \times 768\) a \(197 \times 768\). E quase todos os parâmetros estão nos blocos, que não sabem o tamanho da imagem — só a tabela de posições sabe. É por isso que mudar a resolução exige interpolá-la. Avance passo a passo para ver o que isso faz com o ViT-B/16 ajustado em 384 px e como a RoPE 2-D evita o problema:
O patch embedding é uma convolução
Uma projeção linear sobre patches \(P \times P\) sem sobreposição é exatamente Conv2d(C, d, kernel_size=P, stride=P) e é assim que toda implementação faz — uma operação que divide em patches e projeta ao mesmo tempo. Ou seja, a afirmação "sem convoluções" tem um asterisco: há exatamente uma, na entrada do modelo. Junto com a interpolação da tabela de posições quando a resolução muda, é um dos dois únicos lugares em que o arranjo 2-D da imagem é posto à mão no modelo.
O pipeline inteiro, como código, com números pequenos o bastante para conferir à mão: uma imagem 4 × 4 de um canal, \(P = 2\) (quatro patches de quatro números), \(d = 3\) e um passo de atenção do [CLS]. A barra vertical dá 0,436 para vertical; os mesmos patches como quebra-cabeça dão 0,393 e, sem as posições, os dois dão 0,414. O xadrez mostra o que a projeção \(4 \to 3\) joga fora.
O tamanho do patch é toda a decisão de projeto
\(P\) controla duas coisas ao mesmo tempo, em direções opostas. Não há mais nada num ViT com tanta alavancagem.
Tudo que está dentro de um patch vira um token antes de a atenção sequer rodar. A atenção move informação entre patches, nunca dentro de um: da primeira camada em diante o modelo trabalha numa grade \(\frac{H}{P} \times \frac{W}{P}\) e o que só existe abaixo dessa grade precisa sobreviver à projeção \(E\). Se consegue ou não depende de uma razão, \(P^2C\) números entrando e \(d\) saindo:
| Modelo | \(P^2 C \to d\) | O que \(E\) consegue manter |
|---|---|---|
| ViT-B/16 | \(768 \to 768\) | tudo, em princípio — \(E\) é quadrada |
| ViT-Ti/16 | \(768 \to 192\) | no máximo um quarto das dimensões |
| ViT-B/32 | \(3072 \to 768\) | no máximo um quarto |
O painel desenha cada patch com a sua cor média, o resumo mais grosseiro que um token poderia guardar. Arraste o tamanho do patch até 32 e veja o rosto encolher a um punhado de tokens; o laboratório reconstrói uma imagem a partir do patch embedding de um ViT-Ti/16 pré-treinado de verdade para mostrar o que uma projeção \(768 \to 192\) de fato mantém.
Puxe para o outro lado e a contagem explode: reduzir \(P\) pela metade quadruplica \(N\). Dobrar a resolução faz o mesmo. Por imagem, um ViT custa cerca de
então quadruplicar \(N\) quadruplica o primeiro termo e multiplica o segundo por 16. Em 224 px o primeiro termo domina: no ViT-B/16 a atenção é 4% das 17,4 G multiplicações-somas e passar para \(P = 8\) custa 4,5×, não 16×. O termo quadrático só passa a dominar quando \(N\) passa de \(6d\) — 4 608 tokens no ViT-B —, que é exatamente o regime de alta resolução: em 448 px com \(P = 8\), a atenção já é 40% da conta e a matriz de scores \(N \times N\) ainda precisa caber na memória. É por isso que:
- 14 e 16 são os tamanhos de patch quase universais desde 2020;
- modelos de visão-linguagem em alta resolução ladrilham uma imagem grande em vários recortes de 224 ou 336 pixels em vez de alimentar o encoder com uma grade enorme;
- o patch-and-pack no estilo NaViT9 existe — empacotar imagens de resolução variável numa única sequência, manter a proporção nativa e pular os tokens de que não se precisa.
O que mudou desde 2021
O ViT de 2021 ainda é reconhecível, mas um modelo que você baixaria hoje difere em quatro pontos. Os quatro valem a pena porque aparecem no encoder de visão de todo modelo multimodal.
-
O token
[CLS]praticamente saiu de cena
Ler a classe de um token especial era um hábito herdado do BERT. Muitos encoders atuais usam global average pooling ou uma pequena cabeça de attention-pooling (a do SigLIP: uma query aprendida que faz atenção sobre os tokens de patch) — tão bom ou melhor, sem um token estranho que precisa ser treinado para ser um resumo. Alguns o mantêm por compatibilidade (o DINOv2 ainda tem um), mas nada mais depende dele.
E num modelo de visão-linguagem a cabeça de classificação sumiu por completo: o que é passado ao modelo de linguagem são os tokens de patch, projetados no espaço de embedding do LLM.
-
Posições aprendidas → RoPE 2-D
Embeddings posicionais absolutos aprendidos precisam ser interpolados sempre que a resolução muda e essa interpolação custa acurácia. A RoPE 2-D — a rotação do capítulo 11, aplicada separadamente aos eixos de linha e coluna — é relativa por construção e não tem tabela para interpolar, então mudar a resolução não exige cirurgia. O Qwen2-VL e o DINOv3 a usam.
-
Tokens de registro
ViTs grandes e bem treinados colocam atenção enorme em alguns patches de fundo sem significado — usando-os como rascunho, o que destrói os mapas de atenção e prejudica tarefas densas (uma resposta por pixel, como segmentação ou profundidade). Acrescentar um punhado de tokens de registro10 sem uso dá ao modelo onde guardar esse estado. Os artefatos somem, os mapas de atenção voltam a ser interpretáveis e as métricas de predição densa melhoram. Poucas linhas de código para artefatos que os autores encontraram em ViTs supervisionados, supervisionados por texto e auto-supervisionados (DeiT-III, OpenCLIP, DINOv2).
-
Hierarquia, onde ela é necessária
Um ViT simples mantém uma resolução do começo ao fim, o que é desperdício para detecção e segmentação. O Swin3 reintroduziu atenção em janelas e uma pirâmide; o ViTDet mostrou que um ViT simples também pode ser adaptado com uma pirâmide de features por cima. Os dois estão vivos: o backbone simples venceu no pré-treino; o hierárquico, nas tarefas densas.
Para que serve um ViT, de fato, em 2026
Muito pouco do uso em produção é "classifique esta imagem em 1000 classes". O ViT venceu ao se tornar a forma padrão de transformar pixels em tokens para outra coisa:
| Uso | O que o ViT fornece |
|---|---|
| Modelos de visão-linguagem | O encoder de imagem. Tokens de patch são projetados e prefixados à sequência de texto — é assim que modelos abertos como LLaVA e Qwen2-VL enxergam; os proprietários não publicam os seus encoders. |
| Contrastivo imagem-texto | A torre de imagem do CLIP e do SigLIP (capítulo 19), treinada contra uma torre de texto — a mesma torre de texto que depois condiciona geradores de imagem como o Stable Diffusion. |
| Features auto-supervisionadas | O DINOv27 e o DINOv311 produzem features densas de propósito geral sem rótulos, que batem as supervisionadas em segmentação, profundidade e correspondência — usadas congeladas, como extrator, em robótica e imagem médica. |
| Backbones generativos | DiT e MMDiT são ViTs operando sobre patches latentes em vez de patches de pixel (capítulo 22). |
| Pré-treino mascarado | MAE6 — mascare 75% dos patches e reconstrua. Barato (o encoder só vê os 25% visíveis) e uma boa inicialização. |
A razão pela qual ele venceu não é acurácia
Um ViT e uma ConvNet moderna pontuam praticamente igual no ImageNet. O ViT está em toda parte mesmo assim, porque ele emite uma sequência de tokens no mesmo formato que o texto usa. Concatenar tokens de patch com tokens de palavra e rodar um único Transformer sobre os dois é trivial; fazer o equivalente com um mapa de features convolucional, não. O argumento é multimodalidade, não classificação.
CNN vs. ViT, atualizado
| CNN | Vision Transformer | |
|---|---|---|
| Operação central | Convolução (local, fixa) | Auto-atenção (global, aprendida) |
| Viés indutivo | Forte | Fraco — comprado com dados |
| Campo receptivo | Cresce com a profundidade | Global na camada 1 |
| Custo | \(O(\text{pixels})\) | \(O(N d^2)\), mais \(O(N^2 d)\) de atenção, que domina quando \(N > 6d\) |
| Poucos dados | Vence | Precisa de pré-treino ou de receita estilo DeiT |
| Dados em escala web | Acompanha, com a mesma receita e o mesmo compute12 | Onde acontece quase todo o pré-treino em escala web |
| Uso multimodal | Desajeitado | Nativo — ele já emite tokens |
| Ainda padrão em | Autoencoders latentes, segmentação médica, no próprio aparelho | Tudo que fica antes de um LLM |
Referência de implementação
import torch, torch.nn as nn
class PatchEmbed(nn.Module):
"""Imagem -> sequência de tokens de patch. Um Conv2d faz patchify E projeção."""
def __init__(self, patch=16, in_ch=3, dim=768):
super().__init__()
self.proj = nn.Conv2d(in_ch, dim, kernel_size=patch, stride=patch)
def forward(self, x): # (B, C, H, W)
x = self.proj(x) # (B, dim, H/p, W/p)
return x.flatten(2).transpose(1, 2) # (B, N, dim)
class ViT(nn.Module):
"""Registros no lugar do token CLS, mean pooling no lugar da cabeça no CLS."""
def __init__(self, dim=768, depth=12, heads=12, n_classes=1000,
n_patches=196, n_registers=4):
super().__init__()
self.patch_embed = PatchEmbed(dim=dim)
# init aleatória: registros idênticos receberiam gradientes idênticos e nunca se separariam
self.reg = nn.Parameter(torch.randn(1, n_registers, dim) * .02)
self.pos = nn.Parameter(torch.randn(1, n_patches, dim) * .02)
layer = nn.TransformerEncoderLayer(dim, heads, dim * 4, dropout=0.0, activation='gelu',
norm_first=True, batch_first=True)
self.encoder = nn.TransformerEncoder(layer, depth, enable_nested_tensor=False)
self.norm = nn.LayerNorm(dim)
self.head = nn.Linear(dim, n_classes)
self.n_reg = n_registers
def forward(self, x):
x = self.patch_embed(x) + self.pos
x = torch.cat([self.reg.expand(x.size(0), -1, -1), x], dim=1)
x = self.encoder(x)[:, self.n_reg:] # descarta os registros; eram rascunho
return self.head(self.norm(x).mean(dim=1))
import timm
# um ViT supervisionado, ajustado para suas classes
model = timm.create_model('vit_base_patch16_224', pretrained=True, num_classes=10)
# features do DINOv2, congeladas — em geral a opção mais forte quando há poucos rótulos
dino = torch.hub.load('facebookresearch/dinov2', 'dinov2_vitb14')
feats = dino.forward_features(img)['x_norm_patchtokens'] # (B, N, 768)
Laboratório: o prior fraco, medido
Cada afirmação desta página é medida num notebook no Colab com o CIFAR-10 (60 000 imagens coloridas, 32 × 32, dez classes). Ele roda no navegador numa GPU T4 gratuita — menos de quinze minutos de computação; não é preciso instalar nada. São nove seções, cada uma terminando num checkpoint (as oito primeiras com valor de verificação):
- a divisão em patches escrita à mão e conferida contra o
F.unfold; - o patch embedding como convolução com passo e o que a projeção \(768 \to 192\) de um ViT-Ti/16 pré-treinado mantém de cada patch;
- uma imagem embaralhada como quebra-cabeça que um ViT sem posições não distingue da original;
- o preço de um token, pela fórmula e pelo relógio;
- uma CNN pequena contra um TinyViT com 1 000, 5 000 e 20 000 imagens, com o mesmo orçamento;
- o mesmo com 5 000 imagens sem augmentation — a receita, em escala pequena;
- um ViT-Ti/16 pré-treinado e congelado com uma regressão logística por cima, contra a mesma arquitetura com pesos aleatórios;
- o que o ViT pré-treinado aprendeu no lugar do prior: a grade 2-D na tabela de posições e a distância de atenção de cada cabeça;
- um ViT seu para 5 000 imagens, com orçamento de 600 000 parâmetros.
Abrir o laboratório no Colab — salve uma cópia no seu Drive (Arquivo → Salvar uma cópia no Drive) para rodar e editar.
Pontos principais
- Um ViT é o encoder de texto aplicado a patches de imagem. A única parte nova é o patchify — e isso é uma convolução com passo.
- O prior convolucional é um piso em escala pequena e um teto em escala grande. É toda a troca.
- O limiar de dados de 2021 era em grande parte um problema de receita. DeiT e ConvNeXt fecharam o buraco pelos dois lados.
- O tamanho do patch é a decisão de projeto: um patch é um token antes de a atenção rodar, então nada abaixo da grade de patches é visto a não ser através da projeção; e \(N\) cresce quadraticamente quando \(P\) encolhe ou a resolução sobe. 14–16 é onde o meio-termo cai; o termo de atenção só domina o custo quando \(N > 6d\).
- Um encoder atual usa pooling no lugar do
[CLS], RoPE 2-D no lugar de posições aprendidas e tokens de registro para absorver os artefatos de atenção. - ViTs venceram porque emitem tokens, que é o que um modelo de linguagem come. A pontuação de classificação nunca foi o ponto.
- Em produção, um ViT costuma ser o encoder de visão de outra coisa: um VLM, CLIP/SigLIP, features do DINOv2, ou um backbone de difusão.
- Em escala pequena o prior fraco tem um preço real. No laboratório, com 1 000 a 20 000 imagens do CIFAR-10, uma CNN pequena vence um ViT de custo parecido por 15 a 20 pontos; um ViT congelado, pré-treinado em 14M de imagens, vence os dois com 1 000 rótulos e uma sonda linear (uma regressão logística sobre as features congeladas dele). O prior pode ser aprendido — com muito mais dados.
-
Dosovitskiy, A., et al. (2021). An Image is Worth 16×16 Words: Transformers for Image Recognition at Scale — ICLR. ↩
-
Touvron, H., et al. (2021). Training data-efficient image transformers & distillation through attention (DeiT) — ICML. A receita que removeu a exigência do JFT-300M. ↩
-
Liu, Z., et al. (2021). Swin Transformer: Hierarchical Vision Transformer using Shifted Windows — ICCV. ↩
-
Vaswani, A., et al. (2017). Attention Is All You Need — NeurIPS. (O bloco de encoder que o ViT reaproveita, com cada LayerNorm movida para antes da sua subcamada: pré-norm.) ↩
-
Steiner, A., et al. (2021). How to train your ViT? Data, Augmentation, and Regularization in Vision Transformers — TMLR. ↩
-
He, K., et al. (2022). Masked Autoencoders Are Scalable Vision Learners — CVPR. Mascare 75%, codifique só o que está visível. ↩
-
Oquab, M., et al. (2024). DINOv2: Learning Robust Visual Features without Supervision — TMLR. Features auto-supervisionadas boas o bastante para usar congeladas. ↩
-
Liu, Z., et al. (2022). A ConvNet for the 2020s (ConvNeXt) — CVPR. ↩
-
Dehghani, M., et al. (2023). Patch n' Pack: NaViT, a Vision Transformer for any Aspect Ratio and Resolution — NeurIPS. ↩
-
Darcet, T., Oquab, M., Mairal, J., & Bojanowski, P. (2024). Vision Transformers Need Registers — ICLR. Os tokens-artefato de norma alta, diagnosticados e corrigidos. ↩
-
Siméoni, O., et al. (2025). DINOv3 — arXiv. O DINOv2 em escala maior, com RoPE 2-D e features densas que continuam limpas em alta resolução. ↩
-
Smith, S. L., Brock, A., Berrada, L., & De, S. (2023). ConvNets Match Vision Transformers at Scale — arXiv. NFNets pré-treinadas no JFT-4B igualam ViTs com o mesmo orçamento de compute. ↩
-
Kolesnikov, A., et al. (2020). Big Transfer (BiT): General Visual Representation Learning — ECCV. ResNets pré-treinadas no ImageNet-1k, no ImageNet-21k e no JFT-300M: a CNN de referência do artigo do ViT. ↩