Transformers de Visão (ViT)

O que é um Vision Transformer (ViT)?

Um Vision Transformer (ViT) é uma arquitetura de rede neural que adapta o codificador Transformer (Transformer encoder) — originalmente projetado para texto — para entendimento de imagens. Em vez de processar pixels com filtros convolucionais como as RNCs (CNNs), o ViT:

  1. Divide uma imagem em subimagens (patches) de tamanho fixo
  2. Incorpora (embeds) cada subimagem em um vetor (um “token (token)”)
  3. Alimenta a sequência resultante de tokens em um codificador Transformer padrão baseado em autoatenção (self-attention)

Essa ideia simples funciona surpreendentemente bem, especialmente quando ViTs são pré-treinados (pretrained) em grandes conjuntos de dados (datasets) e depois passam por ajuste fino (fine-tuning) para tarefas posteriores (classificação, detecção, segmentação).

O ViT faz parte da família mais ampla de Transformers e compartilha muitos dos mesmos conceitos de projeto: incorporações de tokens, codificações posicionais, autoatenção multi-cabeça, blocos MLP, conexões residuais e normalização de camada.

Por que substituir convoluções por atenção?

As convoluções incorporam fortes vieses indutivos (inductive biases): localidade (pixels próximos importam mais) e equivariância a translação (translation equivariance). Esses vieses ajudam as CNNs a aprender de forma eficiente com dados limitados.

Transformers, por outro lado, dependem de autoatenção, que pode conectar quaisquer dois tokens independentemente da distância. Isso dá aos ViTs:

  • Campos receptivos globais (global receptive fields) desde a primeira camada
  • Modelagem flexível de dependências de longo alcance (por exemplo, relações entre objetos distantes)
  • Excelente comportamento de escalonamento (scaling behavior) com o tamanho do modelo e do conjunto de dados

A contrapartida é que ViTs “puros” tipicamente precisam de mais dados e/ou pré-treinamento mais forte para igualar CNNs em escalas menores.

Transformando uma imagem em uma sequência: incorporações de subimagens

Patchificação

Dada uma imagem (x \in \mathbb{R}^{H \times W \times C}), o ViT a divide em subimagens sem sobreposição de tamanho (P \times P). O número de subimagens é:

[ N = \frac{H}{P} \cdot \frac{W}{P} ]

Cada subimagem é achatada em um vetor de comprimento (P \cdot P \cdot C).

Exemplo: para uma imagem RGB 224×224 e (P=16):

  • Subimagens por lado: 224 / 16 = 14
  • Total de subimagens: (14 \times 14 = 196)
  • Comprimento da sequência de tokens (sem extras): 196

Projeção linear (incorporação de subimagem)

Cada subimagem achatada é mapeada para uma incorporação (D)-dimensional com uma camada linear aprendida:

[ \mathbf{z}_0^i = \mathbf{E} \cdot \text{patch}_i + \mathbf{b} ]

Na prática, essa projeção é frequentemente implementada como uma Conv2d (Conv2d) com tamanho de kernel (P) e stride (P), o que é computacionalmente conveniente e equivalente a patchificar+linear.

O token [CLS] (token de classificação)

Para classificação de imagens, o ViT comumente antepõe um token especial aprendido, [CLS], à sequência de subimagens:

[ [\mathbf{x}_{cls}, \mathbf{x}_1, \dots, \mathbf{x}_N] ]

Após o codificador Transformer, o estado oculto final correspondente a [CLS] é usado como uma representação agregada da imagem para classificação (semelhante ao BERT).

Nem todos os transformers de visão usam CLS — alguns usam pooling por média global (global average pooling) sobre os tokens de subimagem —, mas o CLS continua sendo uma escolha central de projeto no ViT “clássico”.

Codificações posicionais: dizendo ao modelo “de onde” vieram as subimagens

Transformers são invariantes a permutações, a menos que injetemos informação de posição. O ViT adiciona incorporações posicionais (positional embeddings) às incorporações de tokens:

[ \mathbf{z}_0^i \leftarrow \mathbf{z}_0^i + \mathbf{p}^i ]

Escolhas comuns:

  • Incorporações posicionais absolutas aprendidas (learned absolute positional embeddings) (ViT original): uma tabela aprendida de tamanho ((N+1) \times D) (incluindo o CLS)
  • Incorporações posicionais cientes de 2D (2D-aware positional embeddings) (várias variantes): incorporam a estrutura de linha/coluna
  • Vieses posicionais relativos (relative positional biases) (populares em modelos posteriores): melhor transferência entre resoluções e frequentemente usados em transformers hierárquicos

Uma implicação prática: se você fizer ajuste fino de um ViT em uma resolução diferente da do pré-treinamento, muitas vezes precisa de interpolação de incorporação posicional (positional embedding interpolation) (mais sobre isso adiante).

Arquitetura central do ViT (codificador Transformer sobre tokens de subimagem)

Um ViT padrão é essencialmente:

  • Incorporação de subimagem + codificação posicional
  • Uma pilha de blocos de codificador Transformer
  • Uma cabeça de classificação (MLP)

Cada bloco do codificador tipicamente segue uma estrutura de pré-normalização de camada (Pre-LayerNorm):

  1. Normalização de camada
  2. Autoatenção multi-cabeça (Multi-Head Self-Attention, MHSA)
  3. Conexão residual
  4. Normalização de camada
  5. MLP (MLP) (rede feed-forward)
  6. Conexão residual

Autoatenção multi-cabeça sobre subimagens

A autoatenção calcula interações entre todos os tokens. Para a matriz de tokens (X \in \mathbb{R}^{(N+1)\times D}):

[ Q = XW_Q,\quad K = XW_K,\quad V = XW_V ] [ \text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d}}\right)V ]

A atenção multi-cabeça executa isso em paralelo em múltiplas cabeças e concatena os resultados.

Propriedade-chave: qualquer subimagem pode atender a qualquer outra subimagem em uma única camada, permitindo raciocínio global cedo.

Bloco MLP (rede feed-forward)

O bloco MLP é tipicamente uma rede feed-forward de duas camadas aplicada independentemente por token:

  • Linear (D \rightarrow rD) (frequentemente (r=4))
  • Ativação GELU
  • Linear (rD \rightarrow D)

Tamanhos típicos de modelo

Modelos ViT são comumente descritos com nomes como ViT-Ti/S/B/L/H:

  • ViT-B/16: modelo Base, tamanho de subimagem 16, frequentemente (D=768), 12 camadas, 12 cabeças
  • ViT-L/16: maior profundidade/largura (por exemplo, (D=1024), 24 camadas)

O tamanho da subimagem importa muito:

  • Subimagens menores (por exemplo, 8×8) → mais tokens → melhor detalhe fino, porém maior custo computacional (a atenção é quadrática no número de tokens).
  • Subimagens maiores (por exemplo, 32×32) → menos tokens → mais barato, mas pode perder detalhe.

Complexidade e o problema da “atenção quadrática”

A autoatenção é (O(T^2)) no comprimento da sequência (T). Para ViT, (T = N+1), onde (N) é o número de subimagens.

Se você aumenta a resolução da imagem, a contagem de tokens cresce quadraticamente com a resolução (para tamanho de subimagem fixo), e o custo de atenção cresce aproximadamente de forma quártica com o aumento da resolução linear:

  • 224×224 com P=16 → 196 tokens
  • 384×384 com P=16 → (24×24)=576 tokens
    O tamanho da matriz de atenção salta de ~196² para ~576².

Por isso muitos transformers de visão práticos introduzem projetos hierárquicos (por exemplo, atenção em janelas), embora o “ViT puro” ainda seja amplamente usado, especialmente com resoluções moderadas ou implementações eficientes de atenção.

Pré-treinamento de ViTs: o que faz com que funcionem bem?

Pré-treinamento supervisionado em escala (receita original do ViT)

Os resultados originais do ViT foram fortes quando os modelos foram pré-treinados em conjuntos de dados rotulados muito grandes (por exemplo, ImageNet-21k, JFT-300M) e depois ajustados finamente em conjuntos menores (por exemplo, ImageNet-1k). Isso destacou um ponto-chave:

  • ViTs escalam extremamente bem com dados
  • Com poucos dados, ViTs puros podem ter desempenho inferior ao de CNNs, a menos que sejam regularizados cuidadosamente

Aumento de dados e detalhes de otimização importam

Treinamento eficaz de ViT frequentemente depende de:

  • Otimizador AdamW (AdamW) (decaimento de peso desacoplado)
  • Aquecimento de taxa de aprendizado (warmup) e agendas de decaimento cosseno
  • Aumentos fortes: RandAugment, Mixup, CutMix
  • Regularização: dropout, profundidade estocástica (DropPath), suavização de rótulo (label smoothing)

Essas escolhas podem ser tão importantes quanto a própria arquitetura.

Destilação e “treinar ViTs com menos dados” (por exemplo, ideias no estilo DeiT)

Um desenvolvimento prático importante foi mostrar que ViTs podem ser treinados de forma eficaz em conjuntos como ImageNet-1k com a receita certa, às vezes usando:

  • Um modelo professor (teacher model) (frequentemente uma CNN) para fornecer alvos suaves
  • Tokens de destilação ou perda de destilação

Mesmo que você não use destilação, a lição mais ampla permanece: ViTs se beneficiam de pipelines de treinamento cuidadosos.

Pré-treinamento auto-supervisionado: MAE, DINO, aprendizado contrastivo e modelagem mascarada

Pipelines modernos de ViT frequentemente usam Aprendizado Auto-Supervisionado para reduzir a dependência de rótulos e melhorar a transferência:

  • Modelagem de imagem mascarada (masked image modeling) (por exemplo, estilo MAE): mascara muitas subimagens e treina o modelo (codificador/decodificador) para reconstruir o conteúdo ausente. Isso é análogo, em espírito, à modelagem de linguagem mascarada.
  • Autodestilação / métodos de agrupamento (self-distillation / clustering methods) (por exemplo, estilo DINO): aprendem representações ao casar incorporações sob diferentes aumentos sem rótulos explícitos.
  • Aprendizado contrastivo (contrastive learning) (por exemplo, estilo CLIP): alinha incorporações de imagem com incorporações de texto (multimodal), o que pode produzir backbones de ViT muito transferíveis.

Esses métodos são populares porque podem gerar representações que ajustam finamente bem para classificação e para tarefas de predição densa.

Ajuste fino de ViTs para classificação de imagens

Um fluxo de trabalho comum é:

  1. Pré-treinar um backbone de ViT (supervisionado ou auto-supervisionado)
  2. Substituir a cabeça de classificação para o seu conjunto de dados
  3. Fazer ajuste fino de algumas ou de todas as camadas

Escolhas práticas de ajuste fino

  • Sondagem linear (linear probing): congelar o backbone e treinar apenas um classificador linear em cima do CLS ou de características agregadas
    • Bom para avaliar rapidamente a qualidade das representações
  • Ajuste fino completo (full fine-tuning): atualizar todos os pesos
    • Em geral, melhor acurácia se você tiver dados suficientes
  • Ajuste fino parcial (partial fine-tuning): congelar camadas iniciais e ajustar finamente camadas finais
    • Pode ajudar quando os dados são limitados
  • Decaimento de taxa de aprendizado por camada (layer-wise learning-rate decay): LR menor para camadas iniciais, maior para camadas finais e para a cabeça
    • Comum para ajuste fino estável

Interpolação de incorporação posicional (quando a resolução muda)

Se um ViT é pré-treinado em 224×224 (subimagens 14×14), mas ajustado finamente em 384×384 (subimagens 24×24), a tabela de incorporação posicional absoluta aprendida não corresponde mais à contagem de tokens.

Uma correção padrão:

  • Remodelar as incorporações posicionais das subimagens em uma grade 2D
  • Interpolar (geralmente bicúbica) para o novo tamanho de grade
  • Achatar de volta e concatenar com a incorporação posicional do CLS

Muitas bibliotecas lidam com isso automaticamente, mas é útil saber por que é necessário.

Usando ViTs além da classificação: detecção e segmentação

Tarefas de predição densa exigem saídas espacialmente estruturadas, não apenas um único rótulo. Há duas formas comuns de adaptar ViTs:

1) Usar um ViT como backbone que produz mapas de características

Você pode remodelar tokens de subimagem de volta para uma grade 2D para formar um “mapa de características”:

  • Tokens (excluindo CLS): ((H/P \cdot W/P) \times D)
  • Remodelar para: ((H/P) \times (W/P) \times D)

Depois, anexar uma cabeça específica da tarefa:

  • Cabeças de detecção (detection heads) (frequentemente usando pirâmides de características / características multi-escala)
  • Decodificadores de segmentação (segmentation decoders) (upsampling + conexões de atalho ou decodificadores Transformer)

Essa abordagem espelha como backbones de CNN são usados, exceto que as características se originam de blocos de atenção.

2) Projetos de detecção/segmentação com transformers de ponta a ponta

Algumas arquiteturas incorporam transformers de forma mais profunda no pipeline de detecção/segmentação (por exemplo, predição baseada em consultas, em que um conjunto de consultas aprendidas atende a características da imagem). Mesmo quando a abordagem original usava características de CNN, muitas versões modernas usam backbones no estilo ViT.

Na prática, ao transferir um ViT para detecção/segmentação, você frequentemente verá:

  • Características multi-escala (seja via transformers hierárquicos, seja extraindo camadas intermediárias do ViT)
  • Vieses posicionais relativos (melhor para tamanhos de entrada variáveis)
  • Resoluções de entrada maiores e tamanhos de subimagem menores (melhor localização)

Exemplo prático: classificar imagens com um ViT pré-treinado (PyTorch + Hugging Face)

Abaixo está um exemplo mínimo de como executar inferência com um ViT pré-treinado em uma imagem. (Para treinamento/ajuste fino, você adicionaria um conjunto de dados, perda, otimizador e loop de treinamento.)

import torch
from PIL import Image
from transformers import AutoImageProcessor, AutoModelForImageClassification

device = "cuda" if torch.cuda.is_available() else "cpu"

model_name = "google/vit-base-patch16-224"
processor = AutoImageProcessor.from_pretrained(model_name)
model = AutoModelForImageClassification.from_pretrained(model_name).to(device)
model.eval()

img = Image.open("my_image.jpg").convert("RGB")
inputs = processor(images=img, return_tensors="pt").to(device)

with torch.no_grad():
    logits = model(**inputs).logits
    pred_id = logits.argmax(dim=-1).item()

print("Predicted class:", model.config.id2label[pred_id])

Esboço de ajuste fino (o que muda?)

Para ajuste fino no seu conjunto de dados:

  • Substituir/redimensionar a cabeça de classificação para corresponder ao seu número de classes
  • Treinar com AdamW, uma agenda warmup+cosine e aumentos padrão
  • Considerar:
    • congelar o backbone por algumas épocas (opcional)
    • decaimento de taxa de aprendizado por camada (frequentemente útil)

Se você aumentar a resolução de treinamento, garanta que as incorporações posicionais sejam interpoladas (muitos frameworks fazem isso por você).

Exemplo prático: implementar incorporação de subimagem (trecho conceitual)

Isso demonstra o truque “Conv2d como patchifier” usado por muitas implementações de ViT:

import torch
import torch.nn as nn

class PatchEmbed(nn.Module):
    def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
        super().__init__()
        self.patch_size = patch_size
        self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)

    def forward(self, x):
        # x: [B, C, H, W]
        x = self.proj(x)              # [B, D, H/P, W/P]
        x = x.flatten(2).transpose(1, 2)  # [B, N, D], where N = (H/P)*(W/P)
        return x

Um ViT típico então:

  • antepõe um token CLS aprendido
  • adiciona incorporações posicionais
  • alimenta os tokens por blocos de codificador Transformer

Pontos fortes e limitações dos ViTs

Pontos fortes

  • Contexto global via atenção desde o início
  • Forte aprendizado por transferência (transfer learning) quando bem pré-treinado
  • A arquitetura é simples e modular (fácil de escalar profundidade/largura)
  • Funciona naturalmente com configurações multimodais (tokens de imagem se integram bem com tokens de texto)

Limitações

  • O custo computacional (compute cost) cresce rapidamente com a resolução devido à atenção quadrática
  • Viés indutivo embutido mais fraco do que em CNNs → pode exigir:
    • mais dados
    • regularização/aumento mais fortes
    • otimização cuidadosa
  • O ViT puro produz uma representação de escala única, a menos que seja adaptado; muitas tarefas densas se beneficiam de características hierárquicas/multi-escala

Variações comuns de projeto e “o que você verá na prática”

Mesmo que uma arquitetura seja rotulada como “semelhante a ViT”, ela pode incluir modificações como:

  • Transformers hierárquicos (hierarchical transformers): reduzem gradualmente a resolução dos tokens para criar características multi-escala (bom para detecção/segmentação)
  • Atenção em janelas / local (windowed / local attention): reduz o custo de atenção em alta resolução
  • Híbrido CNN+Transformer (hybrid CNN+Transformer): stem convolucional + codificador Transformer
  • Codificações posicionais relativas (relative positional encodings): melhor generalização para resoluções não vistas
  • Pooling em vez de CLS: média dos tokens de subimagem para classificação

Entender o “ViT clássico” (patchificar + codificador + CLS + posições absolutas) torna essas variantes muito mais fáceis de acompanhar.

Quando você deve usar um ViT?

Use um ViT (ou um backbone derivado de ViT) quando:

  • Você pode começar a partir de um checkpoint (checkpoint) pré-treinado forte
  • Você se importa com a transferência para muitas tarefas (classificação + detecção/segmentação)
  • Você quer uma representação escalável e nativa de transformers (incluindo extensões multimodais)

Considere alternativas (CNNs ou transformers hierárquicos/em janelas) quando:

  • Você precisa treinar do zero em um conjunto de dados pequeno
  • Você precisa de predições densas em resolução muito alta sob limites rígidos de computação
  • Você precisa de forte viés de localidade e eficiência

Conexões com conceitos relacionados

Se você quiser, posso adicionar uma seção focada em um destes tópicos: detalhes de interpolação de incorporação posicional, um loop completo de treinamento para ajuste fino, ou como backbones de ViT são conectados, na prática, a um detector/segmentador.