Faza 04 · lecția 14

Transformere vizuale (ViT)

Scopul lecției: Decupați imaginea în patch-uri, tratați fiecare patch ca pe un cuvânt, rulați un Transformer standard. Nu priviți înapoi.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
Viziune computerizată
Lectură
16 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. Fluxul de procesare
  5. Încorporarea patch-urilor
  6. Tokenul de clasă
  7. Încorporarea pozițională
  8. Blocul de codificator Transformer
  9. De ce pre-LN
  10. Compromisul dimensiunii patch-ului
  11. Rețeta DeiT pentru antrenarea ViT pe ImageNet-1k
  12. Swin vs. ConvNeXt
  13. Preantrenarea MAE
  14. Construiți
  15. Pasul 1: Încorporarea patch-urilor
  16. Pasul 2: Blocul Transformer
  17. Pasul 3: ViT-ul
  18. Pasul 4: Verificare de plauzibilitate — inferența unei singure imagini
  19. Utilizați
  20. Livrați
  21. Exerciții
  22. Termeni-cheie
  23. Lecturi suplimentare

Decupați imaginea în patch-uri, tratați fiecare patch ca pe un cuvânt, rulați un Transformer standard. Nu priviți înapoi.

Tip: Construiți Limbaje: Python Cerințe prealabile: Faza 7 Lecția 02 (Autoatenție), Faza 4 Lecția 04 (Clasificarea imaginilor) Timp: ~45 de minute

Obiective de învățare

  • Implementați de la zero încorporarea patch-urilor, încorporarea pozițională învățată, tokenul de clasă și blocurile de codificator Transformer pentru a construi un ViT minimal
  • Explicați de ce s-a considerat că ViT are nevoie de date masive de preantrenare până când DeiT și MAE au demonstrat altfel
  • Comparați ViT, Swin și ConvNeXt după părtinirile lor arhitecturale (niciuna, atenție locală pe ferestre, backbone convoluțional)
  • Ajustați fin un ViT preantrenat pe un set de date mic cu timm și rețeta standard linear-probe / fine-tune

Problema

Timp de un deceniu, convoluția a fost sinonimă cu viziunea computerizată. CNN-urile aveau părtiniri inductive puternice — localitate, echivarianță la translație — despre care nimeni nu credea că pot fi înlocuite. Apoi Dosovitskiy et al. (2020) au arătat că un Transformer simplu, aplicat patch-urilor de imagine aplatizate, fără niciun mecanism convoluțional, putea egala sau depăși cele mai bune CNN-uri la scară mare.

Problema era expresia „la scară mare”. ViT pe ImageNet-1k pierdea în fața ResNet. ViT preantrenat pe ImageNet-21k sau JFT-300M și apoi ajustat fin pe ImageNet-1k îl depășea. Concluzia era că Transformerele nu aveau părtiniri utile, însă le puteau învăța din suficiente date. Lucrările ulterioare (DeiT, MAE, DINO) au arătat că, prin rețetele de antrenare potrivite — augmentare puternică, preantrenare auto-supervizată, distilare — ViT-urile se antrenează bine și cu date puține.

Notă tehnică a traducerii: ViT-ul simplu nu are părtinirile convoluționale de localitate și echivarianță la translație, dar împărțirea regulată în patch-uri, proiecția partajată și codarea pozițională sunt tot alegeri structurale. Rezultatele ViT, DeiT și MAE sunt observații pentru datele și protocoalele lor de antrenare; nu demonstrează că toate părtinirile sunt învățate exclusiv din volum de date sau că orice set de date mic va produce același rezultat.

În 2026, CNN-urile pure rămân competitive pe dispozitive de margine (ConvNeXt este cel mai puternic), însă Transformerele domină în toate celelalte: segmentare (Mask2Former, SegFormer), detectare (DETR, RT-DETR), multimodal (CLIP, SigLIP), video (VideoMAE, VJEPA). Structura blocului ViT este cea care trebuie cunoscută.

Notă tehnică a traducerii: Nu există un câștigător universal pentru producție. Alegerea dintre CNN, ViT și un Transformer ierarhic depinde de sarcină, date, rezoluție, accelerator, precizie, latență, memorie, consum și constrângerile de implementare; afirmațiile despre un model „cel mai puternic” sau dominant trebuie citite ca instantanee dependente de benchmark, nu ca regulă generală.

Conceptul

Fluxul de procesare

Диаграмма к уроку «Transformere vizuale (ViT)»

Șapte pași. Patch-uri -> tokenuri -> atenție -> clasificator. Fiecare variantă (DeiT, Swin, ConvNeXt, preantrenarea MAE) modifică unul sau doi dintre cei șapte și lasă restul neschimbat.

Notă tehnică a traducerii: Aceasta este o schemă didactică, nu o clasificare arhitecturală completă. DeiT este în primul rând o rețetă de antrenare și de distilare pentru ViT; Swin este un Transformer ierarhic cu ferestre deplasate; ConvNeXt rămâne un CNN; iar MAE este un obiectiv de preantrenare cu codificator–decodor. Ele nu diferă doar prin una sau două substituții independente în același flux de procesare.

Încorporarea patch-urilor

Prima convoluție este secretul. Dimensiunea kernelului este 16, stride-ul este 16, astfel încât o imagine de 224x224 devine o grilă de 14x14 patch-uri de 16x16, fiecare proiectat într-o încorporare cu 768 de dimensiuni. Acea singură convoluție atât împarte în patch-uri, cât și proiectează liniar.

Intrare:  (3, 224, 224)
Conv (3 -> 768, k=16, s=16, fără padding):
Ieșire: (768, 14, 14)
Aplatizare spațială: (196, 768)

196 de patch-uri = 196 de tokenuri. Dimensiunea caracteristicilor fiecărui token este 768 (ViT-B), 1024 (ViT-L) sau 1280 (ViT-H).

Tokenul de clasă

Un singur vector învățat, adăugat la începutul secvenței:

tokens = [CLS; patch_1; patch_2; ...; patch_196]   formă (197, 768)

După N blocuri Transformer, ieșirea [CLS] este reprezentarea globală a imaginii. Capul de clasificare citește doar acest vector.

Încorporarea pozițională

Transformerele nu au o noțiune încorporată despre poziția spațială. Adăugați un vector învățat fiecărui token:

tokens = tokens + learned_pos_embedding   (tot forma (197, 768))

Încorporarea este un parametru al modelului; antrenarea bazată pe gradient o adaptează la structura 2D a imaginii. Există alternative sinusoidale 2D, însă sunt rareori utilizate în practică.

Notă tehnică a traducerii: Încorporările poziționale absolute învățate sunt legate de lungimea secvenței sau de grila din preantrenare; o schimbare de rezoluție poate necesita adaptarea lor, de exemplu prin interpolare. Există și codări relative ale poziției, iar Swin folosește un bias de poziție relativă; de aceea nu este corect să se considere că o singură variantă pozițională acoperă practica curentă.

Blocul de codificator Transformer

Standard. Autoatenție multi-head, MLP, conexiuni reziduale, pre-LayerNorm.

x = x + MSA(LN(x))
x = x + MLP(LN(x))

MLP are două straturi cu GELU: Linear(d -> 4d) -> GELU -> Linear(4d -> d)

ViT-B/16 suprapune 12 dintre aceste blocuri, fiecare cu 12 capete de atenție, însumând 86M de parametri.

Notă tehnică a traducerii: „ViT-B/16” desemnează de regulă configurația Base cu patch-uri de 16 pixeli; aproximarea de 86M depinde de configurație și de capul de clasificare. Rezoluția 224 este o alegere frecventă de preantrenare, nu o proprietate intrinsecă a blocului; ea schimbă numărul de patch-uri și compatibilitatea încorporărilor poziționale.

De ce pre-LN

Transformerele timpurii foloseau post-LN (x = LN(x + sublayer(x))) și se antrenau greu dincolo de 6–8 straturi fără warmup. Pre-LN (x = x + sublayer(LN(x))) antrenează stabil rețele mai adânci fără warmup. Fiecare ViT și fiecare LLM modern folosește pre-LN.

Notă tehnică a traducerii: Pre-LN poate îmbunătăți comportamentul gradientului la inițializare și poate elimina necesitatea warmup-ului în anumite configurații studiate; stabilitatea depinde însă de arhitectură și de rețeta de antrenare. De asemenea, modelele moderne folosesc mai multe variante de normalizare, inclusiv RMSNorm, astfel încât afirmația despre o singură convenție universală este prea amplă.

Compromisul dimensiunii patch-ului

  • Patch-uri 16x16 -> 196 de tokenuri, standard.
  • Patch-uri 32x32 -> 49 de tokenuri, mai rapide, dar cu rezoluție mai mică.
  • Patch-uri 8x8 -> 784 de tokenuri, mai fine, însă costul atenției O(n^2) crește nefavorabil.

Patch-uri mai mari = mai puține tokenuri = mai rapid, dar cu mai puține detalii spațiale. SwinV2 folosește patch-uri 4x4 în ferestre ierarhice.

Rețeta DeiT pentru antrenarea ViT pe ImageNet-1k

ViT-ul original avea nevoie de JFT-300M pentru a depăși CNN-urile. DeiT (Touvron et al., 2020) a antrenat ViT-B până la 81,8% top-1 numai pe ImageNet-1k cu patru schimbări:

  1. Augmentare intensă: RandAugment, Mixup, CutMix, Random Erasing.
  2. Adâncime stocastică (eliminarea aleatoare a unor blocuri întregi în timpul antrenării).
  3. Augmentare repetată (aceeași imagine este eșantionată de 3 ori per lot).
  4. Distilare de la un profesor CNN (opțională, crește în continuare acuratețea).

Fiecare rețetă modernă de antrenare ViT descinde din DeiT.

Notă tehnică a traducerii: Valoarea de 81,8% este legată de varianta DeiT, rezoluție, augmentări și protocolul de evaluare; lucrarea DeiT raportează mai multe rezultate, inclusiv rezultate diferite pentru distilare. Ea nu este o acuratețe garantată pentru orice ViT-B sau set de date. DeiT a influențat multe rețete, dar nu este originea exclusivă a tuturor tehnicilor moderne de antrenare ViT.

Swin vs. ConvNeXt

  • Swin (Liu et al., 2021) — atenție bazată pe ferestre. Fiecare bloc acordă atenție într-o fereastră locală; blocurile alternative deplasează fereastra pentru a amesteca informația între ferestre. Readuce o părtinire de localitate asemănătoare CNN-urilor, păstrând operatorul de atenție.
  • ConvNeXt (Liu et al., 2022) — CNN reproiectat care egalează alegerile arhitecturale ale Swin (convoluții depthwise, LayerNorm, GELU, bottleneck inversat). A arătat că diferența nu este „atenție versus convoluție”, ci „rețetă modernă de antrenare + arhitectură”.

În 2026, ConvNeXt-V2 și Swin-V2 sunt ambele pregătite pentru producție; alegerea corectă depinde de fluxul de inferență (ConvNeXt se compilează mai bine pentru margine) și de corpusul de preantrenare.

Notă tehnică a traducerii: Lucrarea Swin demonstrează eficiența atenției locale pe ferestre și a ierarhiei la sarcinile evaluate, iar lucrarea ConvNeXt compară o familie CNN modernizată cu Transformere în propriul protocol. Aceste rezultate nu justifică o regulă independentă de hardware că unul dintre ele se compilează sau rulează mai bine pe orice dispozitiv de margine.

Preantrenarea MAE

Masked Autoencoder (He et al., 2022): mascați aleatoriu 75% dintre patch-uri, antrenați codificatorul să proceseze numai 25% vizibile, antrenați un decodor mic să reconstruiască patch-urile mascate din ieșirea codificatorului. După preantrenare, eliminați decodorul și ajustați fin codificatorul.

MAE face ViT antrenabil numai pe ImageNet-1k, atinge SOTA și este rețeta auto-supervizată implicită actuală.

Notă tehnică a traducerii: În MAE, 75% este o proporție exemplificată de lucrare, nu definiția obligatorie a metodei. Codificatorul operează pe patch-urile vizibile, iar decodorul ușor reconstruiește imaginea din reprezentarea latentă și tokenurile de mască. Lucrarea raportează rezultate competitive în protocolul evaluat, însă nu există o rețetă implicită universală: alegerea metodei și performanța depind de arhitectură, date, obiectiv și protocolul de evaluare.

Construiți

Pasul 1: Încorporarea patch-urilor

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)

O convoluție, o aplatizare, o transpunere. Aceasta este întreaga etapă imagine-la-tokenuri.

Pasul 2: Blocul Transformer

Pre-LN, autoatenție multi-head, MLP cu GELU, conexiuni reziduale.

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

nn.MultiheadAttention gestionează împărțirea în capete, produsul scalar scalat și proiecția ieșirii. batch_first=True, deci formele sunt (N, seq, dim).

Notă tehnică a traducerii: Pentru intrări batched, batch_first=True stabilește forma (N, seq, dim); pentru o intrare neîmpachetată parametrul este ignorat. Cu need_weights=False, apelul nu solicită ponderile atenției și poate folosi implementări optimizate; acest bloc este o implementare demonstrativă, nu o garanție că reproduce toate detaliile unui checkpoint ViT preantrenat.

Pasul 3: ViT-ul

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()):,}")

Aproximativ 2,8M de parametri — un ViT mic, fezabil pe CPU. ViT-B real are 86M; aceeași definiție de clasă cu dim=768, depth=12, num_heads=12.

Pasul 4: Verificare de plauzibilitate — inferența unei singure imagini

logits = vit(torch.randn(1, 3, 64, 64))
print(f"logits: {logits}")
print(f"probs:  {logits.softmax(-1)}")

Ar trebui să ruleze fără eroare. Probabilitățile însumează 1.

Utilizați

timm oferă fiecare variantă ViT cu ponderi preantrenate pe ImageNet. O singură linie:

import timm

model = timm.create_model("vit_base_patch16_224", pretrained=True, num_classes=10)

timm este opțiunea implicită pentru producție pentru Transformere vizuale în 2026. Oferă ViT, DeiT, Swin, Swin-V2, ConvNeXt, ConvNeXt-V2, MaxViT, MViT, EfficientFormer și zeci de altele prin același API.

Notă tehnică a traducerii: În API-ul documentat, pretrained=True încarcă ponderile ImageNet-1k asociate configurației disponibile. Argumentul num_classes=10 creează un cap de clasificare pentru cele 10 clase, care trebuie antrenat pentru sarcina nouă; disponibilitatea modelelor și checkpoint-urilor depinde de versiunea timm, configurație și licență.

Pentru lucru multimodal (imagine + text), transformers oferă CLIP, SigLIP, BLIP-2, LLaVA. Codificatorul de imagine din toate acestea este o variantă ViT.

Notă tehnică a traducerii: Această generalizare este prea largă. CLIP este un model cu codificator de imagine și codificator de text, iar lucrarea originală studiază atât codificatoare vizuale ResNet, cât și ViT. Familiile BLIP-2, LLaVA și SigLIP au configurații și checkpoint-uri distincte; înainte de utilizare verificați modelul, procesorul de imagine și rezoluția cerută de checkpoint-ul ales.

Livrați

Această lecție produce:

  • outputs/prompt-vit-vs-cnn-picker.md — un prompt care alege între un ViT, un ConvNeXt sau un Swin pe baza dimensiunii setului de date, a calculului disponibil și a fluxului de inferență.
  • outputs/skill-vit-patch-and-pos-embed-inspector.md — o aptitudine care verifică dacă formele încorporărilor patch și poziționale ale unui ViT se potrivesc cu lungimea de secvență așteptată de model, detectând cele mai frecvente erori de portare.

Exerciții

  1. (Ușor) Afișați formele fiecărui tensor intermediar pentru o trecere înainte prin ViT-ul mic de mai sus. Confirmați: intrare (N, 3, 64, 64) -> patch-uri (N, 16, 192) -> cu CLS (N, 17, 192) -> intrarea clasificatorului (N, 192) -> ieșire (N, num_classes).
  2. (Mediu) Ajustați fin un timm ViT-S/16 preantrenat pe setul de date synthetic-CIFAR din Lecția 4. Comparați cu ajustarea fină a ResNet-18 pe aceleași date. Raportați timpul de antrenare și acuratețea finală.
  3. (Dificil) Implementați preantrenarea MAE pentru ViT-ul mic: mascați 75% dintre patch-uri, antrenați codificatorul + un decodor mic să reconstruiască patch-urile mascate. Evaluați acuratețea linear-probe pe datele sintetice înainte și după preantrenare.

Notă tehnică a traducerii: În mod uzual, linear probing îngheață codificatorul preantrenat și antrenează numai capul liniar, iar fine-tuning actualizează o parte sau toate ponderile. Pentru o comparație validă între ViT și ResNet, păstrați aceeași împărțire de date, preprocesare, augmentare, număr de epoci și buget de căutare a hiperparametrilor.

Termeni-cheie

Termen Cum este numit în practică Ce înseamnă de fapt
Încorporare patch-uri „Prima convoluție” O convoluție cu dimensiunea kernelului = stride = dimensiunea patch-ului; transformă imaginea într-o grilă de încorporări de tokenuri
Token de clasă „[CLS]” Un vector învățat adăugat la începutul secvenței de tokenuri; ieșirea lui finală este reprezentarea globală a imaginii
Încorporare pozițională „Poziție învățată” Un vector învățat adăugat fiecărui token, astfel încât Transformerul să știe de unde provine fiecare patch
Pre-LN „LayerNorm înainte de sub-strat” Varianta stabilă de Transformer: x + sublayer(LN(x)) în loc de LN(x + sublayer(x))
Atenție multi-head „Atenție paralelă” Atenție Transformer standard împărțită în subspații independente num_heads, concatenate ulterior
ViT-B/16 „Base, patch 16” Dimensiunea canonică: dim=768, depth=12, heads=12, patch_size=16, image=224; ~86M parametri
DeiT „ViT eficient în date” ViT antrenat numai pe ImageNet-1k cu augmentare puternică; a arătat că seturile masive de date pentru preantrenare nu sunt strict necesare
MAE „Autoencoder mascat” Preantrenare auto-supervizată: mascați 75% dintre patch-uri, reconstruiți; rețeta dominantă de preantrenare ViT

Lecturi suplimentare

Sursă: Originalul în limba engleză

Navigare: ← Lecția 04.13 — Viziune 3D — nori de puncte și NeRF-uri · Faza 4 — Viziune computerizată · Catalog complet · Lecția 04.15 — Viziune în timp real — implementare la marginea rețelei →