Computer Vision - Ghid complet pentru lotul ONIA (pe baza programei de la IOAI)
Cuprins
- Cum se abordează o problemă de Computer Vision
- Convoluții și straturi CNN fundamentale
- Clasificarea imaginilor
- Detectarea obiectelor: YOLO, SSD, DETR
- Segmentarea imaginilor: U-Net și semantic segmentation
- Encodere vizuale pre-antrenate: ResNet și transfer learning
- Augmentarea imaginilor
- Modele generative: GAN-uri
- Self-Supervised Learning pentru imagini
- Vision-Language Models: CLIP
- Diffusion Models
- Rețetă practică pentru concursuri
- Exemplu complet PyTorch: definire, antrenare și validare CNN
0. Cum se abordează o problemă de Computer Vision
O problemă de Computer Vision se abordează metodic, nu începând direct cu cel mai mare model disponibil. Primul obiectiv este să înțelegi datele, etichetele și metrica, apoi să construiești un baseline simplu pe care îl poți verifica și îmbunătăți.
0.1 Înțelege exact task-ul
Înainte de model, formulează problema cât mai precis:
-
Ce primește modelul la intrare?
Imagine RGB, imagine grayscale, secvență video, pereche imagine-text, imagine medicală, imagine satelitară etc. -
Ce trebuie să producă la ieșire?
Clasă unică, clase multiple, bounding box-uri, mască pe pixeli, embedding, imagine generată sau scor de similaritate. -
Care este metrica de concurs?
Accuracy, F1, mAP, IoU, Dice, top-k accuracy sau alt scor. Loss-ul de antrenare trebuie ales astfel încât să ajute metrica, nu doar să scadă frumos pe train. -
Care sunt constrângerile practice?
Număr mic de imagini, clase dezechilibrate, timp limitat de antrenare, rezoluție mare, memorie GPU redusă sau diferență între train și test.
0.2 Verifică și vizualizează datele
În Computer Vision, vizualizarea datelor este esențială. Multe erori nu se văd în loss, dar se văd imediat în imagini:
- imagini corupte sau citite greșit;
- canale inversate, de exemplu BGR în loc de RGB;
- normalizare greșită;
- etichete incorecte;
- clase rare sau lipsă;
- bounding box-uri deplasate;
- măști segmentate cu valori greșite;
- train și validation care conțin imagini aproape identice.
Un obicei foarte bun este să vizualizezi exemple înainte și după augmentări. Pentru detecție și segmentare, trebuie vizualizate și adnotările, nu doar imaginile.
0.3 Construiește întâi un baseline simplu
Un baseline bun trebuie să fie ușor de rulat și ușor de verificat. De exemplu:
- pentru clasificare: ResNet-18 pre-antrenat sau un CNN mic;
- pentru detecție: YOLO/SSD/Faster R-CNN pre-antrenat;
- pentru segmentare: U-Net cu encoder simplu;
- pentru image-text: CLIP zero-shot sau cu un classifier peste embedding-uri;
- pentru generare: o variantă mică, pe rezoluții reduse.
Scopul baseline-ului nu este obținerea unui scor final bun, ci să demonstreze că pipeline-ul funcționează: datele intră corect, loss-ul scade, metrica se calculează corect, iar predicțiile arată rezonabil.
0.4 Fă diagnostic înainte de a complica modelul
Dacă rezultatele sunt slabe, verifică în ordine:
- Datele: sunt corecte imaginile, etichetele și split-ul train/validation?
- Pipeline-ul: transformările păstrează etichetele? augmentările nu distrug obiectul?
- Overfit pe un batch mic: poate modelul să memorizeze 16-32 de exemple? Dacă nu, există probabil un bug.
- Loss și metrică: optimizezi ceva compatibil cu scorul cerut?
- Erori vizuale: ce tipare au exemplele greșite? fundaluri similare, obiecte mici, clase confundate, blur, occlusion?
0.5 Exemplu rapid: inspecția unui batch de imagini
import torch
def show_batch(images, labels, class_names=None, n=8):
images = images[:n].detach().cpu()
labels = labels[:n].detach().cpu()
fig, axes = plt.subplots(1, n, figsize=(2 * n, 2))
for ax, img, label in zip(axes, images, labels):
# tensor C x H x W -> H x W x C
img = img.permute(1, 2, 0)
img = img.clamp(0, 1)
ax.imshow(img)
title = class_names[label] if class_names is not None else int(label)
ax.set_title(str(title))
ax.axis("off")
plt.tight_layout()
plt.show()
images, labels = next(iter(train_loader))
show_batch(images, labels, class_names=train_dataset.classes)
0.6 Regula de aur
Un sistem performant de Computer Vision este rareori rezultatul unei singure idei spectaculoase. De obicei este suma coerentă a mai multor decizii bune:
- date curate și bine împărțite;
- vizualizare atentă a imaginilor și adnotărilor;
- augmentări potrivite problemei;
- arhitectură adecvată dimensiunii datelor;
- funcție de loss compatibilă cu metrica;
- validare corectă;
- analiză atentă a greșelilor.
1. Convoluții și straturi CNN fundamentale
1.1 Ideea centrală
O imagine poate fi privită ca un tensor , unde este numărul de canale, iar și sunt înălțimea și lățimea. Pentru imagini RGB, .
Un strat convoluțional aplică filtre mici, numite kernel-uri, care se deplasează peste imagine și extrag tipare locale. În primele straturi, aceste tipare sunt simple: muchii, colțuri, variații de culoare. În straturile profunde, ele devin mai abstracte: texturi, părți de obiecte, forme complexe.
1.2 Formula convoluției 2D
Pentru o imagine și un kernel , convoluția discretă este:
În deep learning, operația implementată în biblioteci precum PyTorch este de obicei cross-correlation, nu convoluție matematică strictă, deoarece kernel-ul nu este inversat. Diferența este rareori importantă practic, deoarece kernel-ul este învățat.
1.3 Dimensiunea ieșirii
Pentru o dimensiune de intrare , kernel de dimensiune , padding și stride , dimensiunea ieșirii este:
Pentru imagini bidimensionale:
1.4 De ce funcționează CNN-urile?
CNN-urile exploatează trei proprietăți fundamentale ale imaginilor:
| Proprietate | Explicație | Beneficiu |
|---|---|---|
| Localitate | Pixeli apropiați sunt puternic corelați | filtre mici, eficiente |
| Partajarea parametrilor | Același kernel este folosit în toate pozițiile | mai puțini parametri |
| Echivarianță la translație | Dacă obiectul se deplasează, se deplasează și activarea | robustețe spațială |
1.5 Capcane frecvente în concursuri
- Folosirea unui model prea mare pe un dataset mic duce la overfitting.
- Padding-ul nepotrivit poate micșora prea repede hărțile de activare.
- Pooling-ul agresiv poate distruge informația spațială utilă.
- Normalizarea greșită a imaginilor poate încetini sau destabiliza antrenarea.
1.6 Resurse teoretice
- CS231n — CNN Fundamentals (Stanford)
- UVA Deep Learning — Convolutions
- MIT — CNN + Transfer Learning
- Sapienza — Convolutions Lecture
1.7 Practică
1.8 Exemplu PyTorch: bloc convoluțional minimal
import torch.nn as nn
class ConvBlock(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2),
)
def forward(self, x):
return self.net(x)
block = ConvBlock(3, 32)
x = torch.randn(8, 3, 64, 64)
y = block(x)
print(y.shape) # torch.Size([8, 32, 32, 32])
2. Clasificarea imaginilor
2.1 Obiectiv
În clasificare, modelul primește o imagine și produce o distribuție de probabilitate peste clase:
Aici produce logit-urile, adică scoruri reale ne-normalizate. Funcția softmax transformă aceste scoruri în probabilități:
unde este numărul de clase.
2.2 Cross-entropy loss
Pentru o etichetă one-hot și predicție , loss-ul este:
Dacă eticheta corectă este clasa , atunci formula devine:
Intuitiv, modelul este penalizat puternic atunci când atribuie probabilitate mică clasei corecte.
2.3 Metrici importante
| Metrică | Când este utilă | Observație |
|---|---|---|
| Accuracy | clase echilibrate | simplă, dar poate induce în eroare |
| Balanced accuracy | clase dezechilibrate | media recall-ului pe clase |
| F1-score | dezechilibru puternic | combină precision și recall |
| Top-k accuracy | multe clase | utilă în clasificări fine-grained |
2.4 Strategii de performanță
- Pornește cu un baseline simplu: ResNet-18 sau EfficientNet mic.
- Verifică distribuția claselor.
- Folosește augmentări moderate: crop, flip, color jitter.
- Aplică label smoothing dacă etichetele sunt zgomotoase.
- Monitorizează diferența dintre train loss și validation loss.
2.5 Resurse teoretice
2.6 Notebook-uri
- PyTorch Image Classification (bentrevett)
- Transfer Learning VGG16 CIFAR-10
- ResNet50 / AlexNet CIFAR-10
- Albumentations Classification Pipeline
2.7 Exemplu PyTorch: model simplu pentru clasificare
import torch.nn as nn
class TinyImageClassifier(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)),
)
self.classifier = nn.Linear(128, num_classes)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
return self.classifier(x) # logits, nu softmax
model = TinyImageClassifier(num_classes=10)
images = torch.randn(16, 3, 64, 64)
labels = torch.randint(0, 10, (16,))
criterion = nn.CrossEntropyLoss()
loss = criterion(model(images), labels)
loss.backward()
3. Detectarea obiectelor: YOLO, SSD, DETR
3.1 Obiectiv
În object detection, modelul trebuie să prezică simultan:
- clasa fiecărui obiect;
- poziția obiectului, de obicei ca bounding box;
- uneori și un scor de încredere, numit objectness.
Un bounding box este frecvent reprezentată ca:
sau alternativ:
unde este centrul, iar sunt lățimea și înălțimea.
3.2 Intersection over Union
Cea mai importantă metrică geometrică este IoU:
Un IoU mare înseamnă că predicția se suprapune bine peste obiectul real.
3.3 Funcția de loss în detecție
O formulă generică pentru detecție este:
unde:
- penalizează clasa greșită;
- penalizează localizarea greșită;
- penalizează predicțiile greșite de existență a obiectului;
- și controlează importanța termenilor.
3.4 Non-Maximum Suppression
Modelele de detecție produc adesea mai multe box-uri pentru același obiect. NMS păstrează box-ul cu scor mare și elimină box-urile foarte suprapuse.
Algoritm conceptual:
- Sortează predicțiile descrescător după scor.
- Alege predicția cu scor maxim.
- Elimină predicțiile cu peste un prag față de predicția aleasă.
- Repetă până nu mai rămân predicții.
3.5 YOLO vs DETR
| Model | Idee | Avantaje | Limitări |
|---|---|---|---|
| YOLO | detecție rapidă într-o singură trecere | foarte rapid, practic | poate necesita NMS și reglaje atente |
| SSD | predicții multi-scale cu anchor boxes | bun pentru obiecte de dimensiuni variate | sensibil la design-ul anchor-urilor |
| DETR | detecție ca problemă de set prediction | elegant, fără NMS clasic | antrenare mai dificilă, multe date |
3.6 Resurse teoretice
- CS231n — Detection and Segmentation
- YOLO — Explanation (CMU)
- Modern Object Detection Overview
- IITM — Detection Lecture
- UCSD — Advanced Detection
- UNC — Detection Lecture
3.7 Notebook-uri
- YOLO OpenCV Implementation
- YOLOv8 Training (Roboflow)
- DETR Tutorial
- DETR Factory (PyTorch)
- YOLOv8 + RT-DETR
- General Detection Repo
3.8 Exemplu PyTorch: folosirea unui model de detecție
import torchvision
num_classes = 4 # include clasa background pentru Faster R-CNN
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT")
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = torchvision.models.detection.faster_rcnn.FastRCNNPredictor(
in_features,
num_classes,
)
images = [torch.randn(3, 320, 320), torch.randn(3, 256, 256)]
targets = [
{
"boxes": torch.tensor([[30., 40., 180., 220.]]),
"labels": torch.tensor([1]),
},
{
"boxes": torch.tensor([[50., 60., 200., 210.]]),
"labels": torch.tensor([2]),
},
]
model.train()
loss_dict = model(images, targets)
loss = sum(loss_dict.values())
loss.backward()
4. Segmentarea imaginilor: U-Net și semantic segmentation
4.1 Obiectiv
În segmentare, modelul produce o etichetă pentru fiecare pixel. Dacă imaginea are dimensiunea , ieșirea pentru segmentare semantică are forma:
unde este numărul de clase.
4.2 U-Net: intuiție arhitecturală
U-Net combină două componente:
- encoder: reduce rezoluția și extrage context semantic;
- decoder: reconstruiește rezoluția spațială;
- skip connections: transferă detalii fine din encoder în decoder.
4.3 Dice coefficient
Pentru două măști și , coeficientul Dice este:
În practică, pentru predicții probabilistice și etichete binare :
unde previne împărțirea la zero.
4.4 Dice loss
Pentru optimizare, se folosește adesea:
Pentru segmentare cu clase dezechilibrate, Dice loss poate fi mai potrivită ca funcție de eroare decât cross-entropy simplă.
4.5 Recomandări practice
- Folosește U-Net pentru dataset-uri mici sau medii.
- Combină cu pentru stabilitate:
- Verifică vizual predicțiile, nu doar metricile.
- Pentru obiecte mici, ai grijă la downsampling excesiv.
4.6 Resurse teoretice
4.7 Notebook-uri
- Binary Segmentation Intro
- Cars Segmentation Dataset
- Multiclass Segmentation CamVid
- Kaggle Salt Example
- PyTorch Semantic Segmentation
4.8 Exemplu PyTorch: mini U-Net pentru segmentare binară
import torch.nn as nn
import torch.nn.functional as F
class MiniUNet(nn.Module):
def __init__(self):
super().__init__()
self.enc1 = nn.Sequential(nn.Conv2d(3, 32, 3, padding=1), nn.ReLU())
self.enc2 = nn.Sequential(nn.Conv2d(32, 64, 3, padding=1), nn.ReLU())
self.dec1 = nn.Sequential(nn.Conv2d(96, 32, 3, padding=1), nn.ReLU())
self.out = nn.Conv2d(32, 1, kernel_size=1)
def forward(self, x):
e1 = self.enc1(x)
e2 = self.enc2(F.max_pool2d(e1, 2))
up = F.interpolate(e2, scale_factor=2, mode="bilinear", align_corners=False)
x = torch.cat([up, e1], dim=1)
return self.out(self.dec1(x)) # logits: B x 1 x H x W
model = MiniUNet()
images = torch.randn(4, 3, 128, 128)
masks = torch.randint(0, 2, (4, 1, 128, 128)).float()
loss = nn.BCEWithLogitsLoss()(model(images), masks)
5. Encodere vizuale pre-antrenate: ResNet și transfer learning
5.1 De ce transfer learning?
Un encoder pre-antrenat a învățat deja reprezentări vizuale generale. În loc să antrenăm de la zero, putem reutiliza aceste reprezentări și adapta ultimul strat pentru problema noastră.
5.2 Residual connections
ResNet introduce conexiuni reziduale:
unde este transformarea învățată de câteva straturi convoluționale. Această formulă permite gradientului să circule mai ușor prin rețele profunde.
5.3 Strategii de fine-tuning
| Strategie | Ce faci | Când este utilă |
|---|---|---|
| Feature extraction | îngheți encoderul, antrenezi doar classifierul | dataset mic |
| Partial fine-tuning | dezgheți ultimele blocuri | dataset mediu |
| Full fine-tuning | antrenezi tot modelul | dataset mare sau domeniu diferit |
5.4 Formulare generală
Dacă este encoderul și este clasificatorul, modelul este:
În feature extraction, rămâne fix, iar în fine-tuning se actualizează și .
5.5 Notebook-uri
5.6 Exemplu PyTorch: fine-tuning ResNet
from torchvision.models import resnet18, ResNet18_Weights
num_classes = 5
model = resnet18(weights=ResNet18_Weights.DEFAULT)
# Feature extraction: înghețăm encoderul.
for param in model.parameters():
param.requires_grad = False
# Înlocuim ultimul strat pentru clasele problemei noastre.
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, num_classes)
# La început se antrenează doar model.fc. Pentru fine-tuning complet,
# se pot dezgheța ulterior ultimele blocuri sau întreg modelul.
trainable_params = [p for p in model.parameters() if p.requires_grad]
6. Augmentarea imaginilor
6.1 Ideea centrală
Augmentarea generează variații realiste ale imaginilor, astfel încât modelul să învețe caracteristici robuste, nu memorarea exactă a exemplelor din train.
Formal, putem scrie:
unde este o distribuție de transformări: crop, flip, rotații, blur, modificări de culoare etc.
6.2 Principiul corectitudinii semantice
O augmentare este bună doar dacă păstrează eticheta. De exemplu:
- flip orizontal este bun pentru pisici/câini;
- flip vertical poate fi greșit pentru cifre sau semne de circulație;
- rotații mari pot schimba clasa în probleme cu orientare importantă;
- crop agresiv poate elimina obiectul relevant.
6.3 Augmentări utile pe tipuri de probleme
| Problemă | Augmentări recomandate | Atenționări |
|---|---|---|
| Clasificare | random crop, flip, color jitter, mixup | evită transformări care schimbă clasa |
| Detecție | resize, flip, mosaic, affine | trebuie transformate și box-urile |
| Segmentare | crop, flip, elastic transform | trebuie transformate și măștile |
| Date medicale | rotații mici, contrast, blur moderat | nu introduce artefacte nerealiste |
6.4 Mixup și CutMix
Mixup combină două exemple:
CutMix decupează o regiune dintr-o imagine și o lipește în alta, ajustând etichetele proporțional cu aria regiunii.
6.5 Notebook-uri
6.6 Exemplu PyTorch: augmentări pentru clasificare
train_tfms = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.7, 1.0)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
transforms.ToTensor(),
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225)),
])
valid_tfms = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225)),
])
Pentru detecție și segmentare, aceeași transformare geometrică trebuie aplicată și pe box-uri sau măști.
7. Modele generative: GAN-uri
7.1 Ideea centrală
Un GAN are două rețele:
- Generatorul produce imagini false din zgomot ;
- Discriminatorul încearcă să distingă imaginile reale de cele generate.
Aceste două rețele joacă un joc adversarial.
7.2 Obiectivul GAN clasic
Generatorul încearcă să păcălească discriminatorul, iar discriminatorul încearcă să nu fie păcălit.
7.3 Probleme frecvente
| Problemă | Simptom | Soluții posibile |
|---|---|---|
| Mode collapse | generatorul produce imagini foarte asemănătoare | WGAN, regularizare, arhitectură mai stabilă |
| Instabilitate | loss-uri oscilante | learning rate mai mic, normalizare, batch size mai mare |
| Discriminator prea puternic | generatorul nu mai învață | actualizări echilibrate, label smoothing |
7.4 Resurse teoretice
- Toronto — GANs Lecture
- IITM — GAN Lecture
- Strasbourg — GAN Overview
- UNC GAN Part 1
- UNC GAN Part 2
- Cornell GAN Lecture
- Harvard GAN Part 1
- Harvard GAN Part 2
7.5 Practică
- FGSM Adversarial Tutorial
- DCGAN Faces Tutorial
- GAN Tutorial Colab
- PyTorch GAN Repository
- Lightning GAN Example
7.6 Exemplu PyTorch: schelet minimal GAN
import torch.nn as nn
latent_dim = 100
class Generator(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(latent_dim, 256), nn.ReLU(),
nn.Linear(256, 3 * 32 * 32), nn.Tanh(),
)
def forward(self, z):
return self.net(z).view(-1, 3, 32, 32)
class Discriminator(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Flatten(),
nn.Linear(3 * 32 * 32, 256), nn.LeakyReLU(0.2),
nn.Linear(256, 1),
)
def forward(self, x):
return self.net(x)
G, D = Generator(), Discriminator()
z = torch.randn(16, latent_dim)
fake_images = G(z)
fake_scores = D(fake_images)
8. Self-Supervised Learning pentru imagini
8.1 Obiectiv
Self-supervised learning folosește date fără etichete explicite. Modelul primește o sarcină artificială, numită pretext task, din care învață reprezentări utile.
Exemple:
- prezicerea rotației imaginii;
- reconstruirea părților mascate;
- apropierea reprezentărilor a două augmentări ale aceleiași imagini;
- contrastarea imaginilor diferite.
8.2 Contrastive learning
Avem două augmentări ale aceleiași imagini, și , considerate pereche pozitivă. Alte imagini din batch sunt negative.
O formă uzuală a loss-ului contrastiv este:
unde:
- este reprezentarea imaginii;
- este similaritatea cosinus;
- este temperatura.
8.3 De ce contează pentru olimpiadă?
În multe probleme reale, datele etichetate sunt puține. SSL permite folosirea unui volum mare de imagini neetichetate pentru a obține un encoder bun, apoi se aplică fine-tuning cu puține etichete.
8.4 Resurse
8.5 Exemplu PyTorch: encoder cu proiecție contrastivă
import torch.nn as nn
import torch.nn.functional as F
from torchvision.models import resnet18
class ContrastiveModel(nn.Module):
def __init__(self, embedding_dim=128):
super().__init__()
self.encoder = resnet18(weights=None)
in_features = self.encoder.fc.in_features
self.encoder.fc = nn.Identity()
self.projector = nn.Sequential(
nn.Linear(in_features, 256), nn.ReLU(),
nn.Linear(256, embedding_dim),
)
def forward(self, x):
h = self.encoder(x)
z = self.projector(h)
return F.normalize(z, dim=1)
model = ContrastiveModel()
x1 = torch.randn(32, 3, 224, 224) # augmentarea 1
x2 = torch.randn(32, 3, 224, 224) # augmentarea 2
z1, z2 = model(x1), model(x2)
similarity_matrix = z1 @ z2.T
9. Vision-Language Models: CLIP
9.1 Ideea centrală
CLIP aliniază imagini și texte într-un spațiu comun de reprezentare. O imagine și descrierea ei trebuie să aibă embedding-uri apropiate, iar perechile nepotrivite trebuie să fie depărtate.
9.2 Formulare
Avem un encoder de imagine și un encoder de text :
Scorul de compatibilitate este de obicei similaritatea cosinus:
9.3 Zero-shot classification
Pentru o imagine și clase textuale , alegem clasa cu similaritate maximă:
Exemplu de prompt-uri:
- „a photo of a cat”;
- „a photo of a dog”;
- „a satellite image of a river”.
9.4 Când este util CLIP?
- clasificare zero-shot;
- căutare imagine-text;
- filtrare semantică;
- etichetare automată preliminară;
- reranking al predicțiilor unui model specializat.
9.5 Resurse
9.6 Practică
9.7 Exemplu PyTorch: clasificare zero-shot cu CLIP
from PIL import Image
from transformers import CLIPModel, CLIPProcessor
model_name = "openai/clip-vit-base-patch32"
model = CLIPModel.from_pretrained(model_name)
processor = CLIPProcessor.from_pretrained(model_name)
image = Image.open("example.jpg").convert("RGB")
texts = ["a photo of a cat", "a photo of a dog", "a photo of a car"]
inputs = processor(text=texts, images=image, return_tensors="pt", padding=True)
with torch.no_grad():
outputs = model(**inputs)
probs = outputs.logits_per_image.softmax(dim=1)
predicted_idx = probs.argmax(dim=1).item()
print(texts[predicted_idx], probs[0, predicted_idx].item())
10. Diffusion Models
10.1 Ideea centrală
Modelele de difuzie învață să genereze imagini pornind de la zgomot și eliminând progresiv zgomotul. Procesul are două direcții:
- forward process: adaugă zgomot imaginii reale;
- reverse process: învață să elimine zgomotul.
10.2 Procesul forward
La fiecare pas se adaugă zgomot gaussian:
După mai mulți pași, imaginea devine aproape zgomot gaussian.
10.3 Procesul reverse
Modelul învață distribuția inversă:
În multe implementări, rețeaua prezice zgomotul adăugat:
10.4 De ce U-Net în diffusion?
U-Net este potrivit deoarece combină:
- context global, necesar pentru structura imaginii;
- detalii locale, necesare pentru texturi și margini;
- skip connections, utile pentru reconstrucție la rezoluție mare.
10.5 Resurse
10.6 Notebook-uri
- Vanilla Diffusion
- Diffusion Errors
- Classifier-Free Guidance
- EMNIST Diffusion
- Extra Diffusion Notebook
10.7 Exemplu PyTorch: rețea mică pentru prezicerea zgomotului
import torch.nn as nn
class TinyDenoiser(nn.Module):
def __init__(self, channels=3):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(channels + 1, 64, 3, padding=1), nn.SiLU(),
nn.Conv2d(64, 64, 3, padding=1), nn.SiLU(),
nn.Conv2d(64, channels, 3, padding=1),
)
def forward(self, x_t, t):
# t este scalat în [0, 1] și introdus ca hartă suplimentară.
t_map = t.view(-1, 1, 1, 1).expand(-1, 1, x_t.shape[2], x_t.shape[3])
return self.net(torch.cat([x_t, t_map], dim=1))
model = TinyDenoiser()
x_t = torch.randn(8, 3, 64, 64)
t = torch.rand(8)
true_noise = torch.randn_like(x_t)
pred_noise = model(x_t, t)
loss = nn.MSELoss()(pred_noise, true_noise)
11. Rețetă practică pentru concursuri
11.1 Checklist de început
Înainte de a antrena orice model:
- verifică dimensiunile imaginilor;
- vizualizează exemple din fiecare clasă;
- caută imagini corupte sau etichete suspecte;
- verifică distribuția claselor;
- stabilește o împărțire corectă train/validation;
- definește metrica exactă cerută de concurs.
11.2 Baseline recomandat
Pentru clasificare:
- ResNet-18 pre-antrenat;
- augmentări moderate;
- AdamW sau SGD cu momentum;
- scheduler de learning rate;
- early stopping pe validation score.
Pentru segmentare:
- U-Net cu encoder pre-antrenat;
- loss combinat ;
- validare vizuală pe predicții;
- post-procesare simplă dacă metrica o permite.
Pentru detecție:
- model YOLO pre-antrenat;
- verificare atentă a bounding box-urilor;
- augmentări compatibile cu box-urile;
- tuning pentru confidence threshold și NMS threshold.
11.3 Diagnostic rapid al problemelor
| Simptom | Cauză posibilă | Soluție |
|---|---|---|
| Train accuracy mare, validation mică | overfitting | augmentări, regularizare, model mai mic |
| Train accuracy mică | underfitting sau bug | verifică datele, learning rate, modelul |
| Loss devine NaN | instabilitate numerică | learning rate mai mic, verifică normalizarea |
| Modelul prezice o singură clasă | dezechilibru de clase | weighted loss, sampling, augmentări |
| Performanță slabă pe test | distribuție diferită | validare mai realistă, augmentări mai bune |
11.4 Exemplu PyTorch: testul de overfit pe un batch mic
import torch.nn as nn
import torch.optim as optim
def overfit_one_batch(model, loader, device="cuda", steps=100):
model.to(device)
model.train()
images, labels = next(iter(loader))
images = images.to(device)
labels = labels.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=1e-3)
for step in range(steps):
optimizer.zero_grad(set_to_none=True)
logits = model(images)
loss = criterion(logits, labels)
loss.backward()
optimizer.step()
if step % 10 == 0:
acc = (logits.argmax(dim=1) == labels).float().mean().item()
print(f"step={step:03d} loss={loss.item():.4f} acc={acc:.3f}")
# Dacă modelul nu poate ajunge aproape de 100% pe un batch mic,
# verifică datele, loss-ul, learning rate-ul și forma tensorilor.
12. Exemplu complet PyTorch: definire, antrenare și validare CNN
Exemplul următor definește o rețea CNN compactă, potrivită pentru imagini mici de tip CIFAR-10. Codul este gândit ca schelet clar pentru concursuri: ușor de modificat, ușor de depanat.
12.1 Importuri și configurare
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
BATCH_SIZE = 128
NUM_CLASSES = 10
EPOCHS = 10
LR = 3e-4
12.2 Transformări pentru date
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=(0.4914, 0.4822, 0.4465),
std=(0.2470, 0.2435, 0.2616)),
])
test_tfms = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=(0.4914, 0.4822, 0.4465),
std=(0.2470, 0.2435, 0.2616)),
])
12.3 Dataset și DataLoader
root="./data",
train=True,
download=True,
transform=train_tfms,
)
test_ds = datasets.CIFAR10(
root="./data",
train=False,
download=True,
transform=test_tfms,
)
train_loader = DataLoader(
train_ds,
batch_size=BATCH_SIZE,
shuffle=True,
num_workers=2,
pin_memory=True,
)
test_loader = DataLoader(
test_ds,
batch_size=BATCH_SIZE,
shuffle=False,
num_workers=2,
pin_memory=True,
)
12.4 Definirea rețelei CNN
def __init__(self, in_channels, out_channels):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
)
def forward(self, x):
return self.net(x)
class SmallCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.features = nn.Sequential(
ConvBlock(3, 64),
nn.MaxPool2d(2), # 32x32 -> 16x16
ConvBlock(64, 128),
nn.MaxPool2d(2), # 16x16 -> 8x8
ConvBlock(128, 256),
nn.MaxPool2d(2), # 8x8 -> 4x4
)
self.classifier = nn.Sequential(
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Dropout(p=0.2),
nn.Linear(256, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
model = SmallCNN(num_classes=NUM_CLASSES).to(DEVICE)
12.5 Funcția de antrenare
model.train()
total_loss = 0.0
correct = 0
total = 0
for images, labels in loader:
images = images.to(DEVICE, non_blocking=True)
labels = labels.to(DEVICE, non_blocking=True)
optimizer.zero_grad(set_to_none=True)
logits = model(images)
loss = criterion(logits, labels)
loss.backward()
optimizer.step()
total_loss += loss.item() * images.size(0)
preds = logits.argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
avg_loss = total_loss / total
accuracy = correct / total
return avg_loss, accuracy
12.6 Funcția de evaluare
def evaluate(model, loader, criterion):
model.eval()
total_loss = 0.0
correct = 0
total = 0
for images, labels in loader:
images = images.to(DEVICE, non_blocking=True)
labels = labels.to(DEVICE, non_blocking=True)
logits = model(images)
loss = criterion(logits, labels)
total_loss += loss.item() * images.size(0)
preds = logits.argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
avg_loss = total_loss / total
accuracy = correct / total
return avg_loss, accuracy
12.7 Bucla completă de antrenare
optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)
best_acc = 0.0
for epoch in range(1, EPOCHS + 1):
train_loss, train_acc = train_one_epoch(model, train_loader, optimizer, criterion)
val_loss, val_acc = evaluate(model, test_loader, criterion)
scheduler.step()
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), "best_small_cnn.pt")
print(
f"Epoch {epoch:02d} | "
f"train loss: {train_loss:.4f}, train acc: {train_acc:.4f} | "
f"val loss: {val_loss:.4f}, val acc: {val_acc:.4f}"
)
print(f"Best validation accuracy: {best_acc:.4f}")
12.8 Ce trebuie observat în acest exemplu
ConvBlocksepară clar logica unui bloc convoluțional.BatchNorm2dstabilizează distribuțiile activărilor.AdaptiveAvgPool2d((1, 1))permite clasificatorului să fie mai independent de dimensiunea spațială.AdamWinclude regularizare prin weight decay decuplat.CosineAnnealingLRscade learning rate-ul lin, util în antrenări scurte.label_smoothingpoate îmbunătăți generalizarea când etichetele sunt zgomotoase.