Faza 04 · lecția 09

Generarea imaginilor — GAN-uri

Scopul lecției: Un GAN este format din două rețele neuronale într-un joc cu reguli fixe. Una desenează, cealaltă critică. Ambele devin mai bune împreună până când desenele o păcălesc pe critică.

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

Curs
AI Engineering from Scratch
Fază
Viziune computerizată
Lectură
17 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. Cele două rețele
  5. Jocul
  6. Pierderea nesaturantă
  7. Regulile de arhitectură DCGAN
  8. Moduri de eșec și semnăturile lor
  9. Evaluarea
  10. Construiți-l
  11. Pasul 1: Generatorul
  12. Pasul 2: Discriminatorul
  13. Pasul 3: Pasul de antrenare
  14. Pasul 4: Bucla completă de antrenare pentru forme sintetice
  15. Pasul 5: Eșantionarea
  16. Pasul 6: Normalizarea spectrală
  17. Folosiți-l
  18. Livrați-l
  19. Exerciții
  20. Termeni-cheie
  21. Lecturi suplimentare

Un GAN este format din două rețele neuronale într-un joc cu reguli fixe. Una desenează, cealaltă critică. Ambele devin mai bune împreună până când desenele o păcălesc pe critică.

Tip: Construcție Limbaje: Python Cerințe prealabile: Faza 4 Lecția 03 (CNN-uri), Faza 3 Lecția 06 (Optimizatori), Faza 3 Lecția 07 (Regularizare) Timp: ~75 de minute

Obiective de învățare

  • Explicați jocul minimax dintre generator și discriminator și de ce echilibrul corespunde lui p_model = p_data
  • Implementați un DCGAN în PyTorch și faceți-l să genereze imagini sintetice coerente de 32x32 în mai puțin de 60 de linii
  • Stabilizați antrenarea GAN cu cele trei tehnici standard: pierderea nesaturantă, normalizarea spectrală, TTUR (regula de actualizare cu două scale temporale)
  • Citiți curbele de antrenare care disting convergența sănătoasă de colapsul modurilor, oscilație și situația în care discriminatorul câștigă complet

Problema

Clasificarea învață o rețea să mapeze imagini la etichete. Generarea inversează problema: eșantionează imagini noi care par să provină din aceeași distribuție. Nu există o ieșire „corectă” cu care să puteți face diff; există doar o distribuție pe care vreți să o imitați.

Funcțiile de pierdere standard (MSE, entropie încrucișată) nu pot măsura „a provenit acest eșantion din distribuția reală?”. Minimizarea erorii pentru fiecare pixel produce medii neclare, nu eșantioane realiste. Descoperirea a fost să învățați funcția de pierdere: antrenați o a doua rețea a cărei sarcină este să distingă imaginile reale de cele false și folosiți aprecierea ei pentru a ghida generatorul.

GAN-urile (Goodfellow et al., 2014) au definit acest cadru. Până în 2018, StyleGAN producea fețe de 1024x1024 imposibil de deosebit de fotografii. De atunci, modelele de difuzie au preluat supremația în calitate și controlabilitate, dar fiecare tehnică ce face difuzia practică — alegerile de normalizare, spațiile latente, pierderile pe caracteristici — a fost înțeleasă mai întâi pe GAN-uri.

Notă tehnică a traducerii: Afirmația despre „fiecare” tehnică este prea largă. Normalizarea pe loturi, modelele latente variaționale și pierderile perceptuale au și dezvoltări independente de GAN-uri; GAN-urile au avut un rol important în folosirea lor pentru sinteză, nu în originea tuturor acestor idei. Consultați Batch Normalization, Auto-Encoding Variational Bayes și Perceptual Losses.

Conceptul

Cele două rețele

Диаграмма к уроку «Generarea imaginilor — GAN-uri»

Generatorul G primește un vector de zgomot z și produce o imagine. Discriminatorul D primește o imagine și produce un singur scalar: probabilitatea ca imaginea să fie reală.

Notă tehnică a traducerii: În formularea probabilistică, D(x) desemnează probabilitatea ca exemplul să fie real. Implementarea de mai jos nu aplică sigmoid în Discriminator, deci întoarce logit-uri; F.binary_cross_entropy_with_logits aplică intern sigmoida într-o formulare stabilă numeric. Nu adăugați o sigmoidă înaintea acestei funcții de pierdere.

Jocul

G vrea ca D să greșească. D vrea să aibă dreptate. Formal:

min_G max_D  E_x[log D(x)] + E_z[log(1 - D(G(z)))]

Citiți de la dreapta la stânga: D maximizează acuratețea pentru imaginile reale (log D(real)) și false (log (1 - D(fake))). G minimizează acuratețea lui D pentru imaginile false — vrea ca D(G(z)) să fie mare.

Goodfellow a demonstrat că acest minimax are un echilibru global în care p_G = p_data, D produce 0,5 peste tot, iar divergența Jensen–Shannon dintre distribuțiile generate și reale este zero. Partea dificilă este atingerea lui.

Notă tehnică a traducerii: Acesta este un rezultat idealizat: presupune distribuții de populație, capacitate suficientă și discriminatorul optim pentru generatorul curent. Rețelele finite, seturile de date finite și optimizarea alternantă nu garantează atingerea acestui echilibru.

Pierderea nesaturantă

Forma de mai sus este instabilă numeric. La începutul antrenării, D(G(z)) este aproape de zero pentru fiecare imagine falsă, astfel încât log(1 - D(G(z))) are gradienți care dispar față de G. Corecția: inversați pierderea lui G.

L_D = -E_x[log D(x)] - E_z[log(1 - D(G(z)))]
L_G = -E_z[log D(G(z))]                          # nesaturantă

Acum, când D(G(z)) este aproape de zero, pierderea lui G este mare, iar gradientul său este informativ. Fiecare GAN modern se antrenează cu această variantă.

Notă tehnică a traducerii: Problema principală a obiectivului minimax original este saturația gradientului generatorului la începutul antrenării, nu o proprietate generală de instabilitate a aritmeticii în virgulă mobilă. Obiectivul nesaturant este euristica propusă în lucrarea originală pentru a oferi gradienți mai puternici generatorului; este foarte folosit, dar nu universal, deoarece Wasserstein GAN și variantele sale folosesc obiectivul Wasserstein.

Regulile de arhitectură DCGAN

Radford, Metz, Chintala (2015) au condensat ani de experimente eșuate în cinci reguli care fac antrenarea GAN stabilă:

  1. Înlocuiți pooling-ul cu convoluții cu stride (în ambele rețele).
  2. Folosiți batch norm atât în generator, cât și în discriminator, cu excepția ieșirii lui G și a intrării lui D.
  3. Eliminați straturile complet conectate din arhitecturile mai profunde.
  4. G folosește ReLU în toate straturile cu excepția ieșirii (tanh pentru ieșirea din [-1, 1]).
  5. D folosește LeakyReLU (negative_slope=0.2) în toate straturile.

Fiecare GAN modern bazat pe convoluții (StyleGAN, BigGAN, GigaGAN) pornește în continuare de la aceste reguli și înlocuiește componentele una câte una.

Notă tehnică a traducerii: Regulile DCGAN sunt recomandări empirice pentru configurația arhitecturală și protocolul studiate de Radford et al., nu condiții universale de stabilitate. Arhitecturile ulterioare, inclusiv StyleGAN și BigGAN, modifică substanțial normalizarea, generatorul și discriminatorul.

Moduri de eșec și semnăturile lor

Диаграмма к уроку «Generarea imaginilor — GAN-uri»

  • Colapsul modurilor: G găsește o imagine care păcălește D și o produce numai pe aceasta. Corecție: adăugați discriminare pe minibatch, normalizare spectrală sau condiționare pe etichete.
  • Discriminatorul câștigă: D devine prea puternic prea repede, iar gradienții lui G dispar. Corecție: un D mai mic, o rată de învățare mai mică pentru D sau netezirea etichetelor reale.
  • Oscilația: cele două rețele alternează victorii fără să se apropie vreodată de echilibru. Corecție: TTUR (D învață mai repede decât G cu un factor de 2–4) sau treceți la pierderea Wasserstein.

Notă tehnică a traducerii: Curbele pierderilor sunt indicii, nu diagnostice univoce: scara lor depinde de obiectiv, arhitectură, ratele de actualizare și definiția pierderii. Verificați simultan diversitatea eșantioanelor și metrici de calitate/acoperire, nu aplicați automat o singură corecție pornind doar de la două curbe.

Notă tehnică a traducerii: TTUR înseamnă scale temporale de actualizare diferite; nu fixează o direcție sau un raport universal de 2–4 între ratele de învățare. Raportul adecvat depinde de obiectiv, optimizator, date și arhitectură.

Evaluarea

GAN-urile nu au adevăr de referință, așadar cum știți că funcționează?

  • Inspectarea eșantioanelor — priviți pur și simplu 64 de eșantioane la sfârșitul fiecărei epoci. Nu este negociabil.
  • FID (Fréchet Inception Distance) — distanța dintre distribuțiile caracteristicilor Inception-v3 ale seturilor reale și generate. Mai mic este mai bine. Standardul comunității.
  • Inception Score — mai vechi, mai fragil; preferați FID.
  • Precizie/recall pentru modele generative — măsoară separat calitatea (precizia) și acoperirea (recall). Mai informativ decât FID singur.

Pentru o rulare mică pe date sintetice, inspectarea eșantioanelor este suficientă.

Notă tehnică a traducerii: FID estimează distanța Fréchet dintre aproximări gaussiene ale caracteristicilor Inception și depinde de modelul de caracteristici, preprocesare, setul de referință și numărul de eșantioane. Un FID mic singur nu demonstrează absența colapsului modurilor sau calitatea pentru toate clasele; completați-l cu inspectare și metrici de precizie/recall generativ.

Construiți-l

Pasul 1: Generatorul

Un generator DCGAN mic care primește zgomot cu 64 de dimensiuni și produce o imagine de 32x32.

import torch
import torch.nn as nn

class Generator(nn.Module):
    def __init__(self, z_dim=64, img_channels=3, feat=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.ConvTranspose2d(z_dim, feat * 4, kernel_size=4, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(feat * 4),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(feat * 4, feat * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(feat * 2),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(feat * 2, feat, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(feat),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(feat, img_channels, kernel_size=4, stride=2, padding=1, bias=False),
            nn.Tanh(),
        )

    def forward(self, z):
        return self.net(z.view(z.size(0), -1, 1, 1))

Patru convoluții transpuse, fiecare cu kernel_size=4, stride=2, padding=1, astfel încât dublează curat dimensiunea spațială. Activările de ieșire sunt în [-1, 1] prin tanh.

Pasul 2: Discriminatorul

Imaginea în oglindă a generatorului. LeakyReLU, convoluții cu stride, iar la final un logit scalar.

class Discriminator(nn.Module):
    def __init__(self, img_channels=3, feat=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(img_channels, feat, kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(feat, feat * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(feat * 2),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(feat * 2, feat * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(feat * 4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(feat * 4, 1, kernel_size=4, stride=1, padding=0),
        )

    def forward(self, x):
        return self.net(x).view(-1)

Ultima convoluție reduce o hartă de caracteristici 4x4 la 1x1. Ieșirea este un singur logit pentru fiecare imagine; aplicați sigmoid numai în timpul calculării pierderii.

Pasul 3: Pasul de antrenare

Alternați: actualizați D o dată, apoi G o dată, la fiecare lot.

import torch.nn.functional as F

def train_step(G, D, real, z, opt_g, opt_d, device):
    real = real.to(device)
    bs = real.size(0)

    # D step
    opt_d.zero_grad()
    d_real = D(real)
    d_fake = D(G(z).detach())
    loss_d = (F.binary_cross_entropy_with_logits(d_real, torch.ones_like(d_real))
              + F.binary_cross_entropy_with_logits(d_fake, torch.zeros_like(d_fake)))
    loss_d.backward()
    opt_d.step()

    # G step
    opt_g.zero_grad()
    d_fake = D(G(z))
    loss_g = F.binary_cross_entropy_with_logits(d_fake, torch.ones_like(d_fake))
    loss_g.backward()
    opt_g.step()

    return loss_d.item(), loss_g.item()

G(z).detach() în pasul lui D este critic: nu vrem ca gradientul să ajungă în G în timpul actualizării lui D. Uitarea acestei operații este eroarea clasică a începătorului.

Pasul 4: Bucla completă de antrenare pentru forme sintetice

from torch.utils.data import DataLoader, TensorDataset
import numpy as np

def synthetic_images(num=2000, size=32, seed=0):
    rng = np.random.default_rng(seed)
    imgs = np.zeros((num, 3, size, size), dtype=np.float32) - 1.0
    for i in range(num):
        r = rng.uniform(6, 12)
        cx, cy = rng.uniform(r, size - r, size=2)
        yy, xx = np.meshgrid(np.arange(size), np.arange(size), indexing="ij")
        mask = (xx - cx) ** 2 + (yy - cy) ** 2 < r ** 2
        color = rng.uniform(-0.5, 1.0, size=3)
        for c in range(3):
            imgs[i, c][mask] = color[c]
    return torch.from_numpy(imgs)

device = "cuda" if torch.cuda.is_available() else "cpu"
data = synthetic_images()
loader = DataLoader(TensorDataset(data), batch_size=64, shuffle=True)

G = Generator(z_dim=64, img_channels=3, feat=32).to(device)
D = Discriminator(img_channels=3, feat=32).to(device)
opt_g = torch.optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999))
opt_d = torch.optim.Adam(D.parameters(), lr=2e-4, betas=(0.5, 0.999))

for epoch in range(10):
    for (batch,) in loader:
        z = torch.randn(batch.size(0), 64, device=device)
        ld, lg = train_step(G, D, batch, z, opt_g, opt_d, device)
    print(f"epoch {epoch}  D {ld:.3f}  G {lg:.3f}")

Adam(lr=2e-4, betas=(0.5, 0.999)) este configurația implicită DCGAN — beta1 scăzut împiedică termenul de momentum să stabilizeze prea mult jocul adversarial.

Pasul 5: Eșantionarea

@torch.no_grad()
def sample(G, n=16, z_dim=64, device="cpu"):
    G.eval()
    z = torch.randn(n, z_dim, device=device)
    imgs = G(z)
    imgs = (imgs + 1) / 2
    return imgs.clamp(0, 1)

Treceți întotdeauna în modul de evaluare înainte de eșantionare. Pentru DCGAN, acest lucru contează fiindcă sunt folosite statisticile mobile ale batch norm în locul statisticilor lotului curent.

Notă tehnică a traducerii: G.eval() schimbă persistent modul modulului. Dacă reluați antrenarea după apelul sample, apelați G.train() înainte de următorul pas de antrenare; eval() nu dezactivează singur urmărirea gradientului.

Pasul 6: Normalizarea spectrală

Un înlocuitor direct pentru BN în discriminator, care garantează că rețeaua este 1-Lipschitz. Corectează majoritatea eșecurilor de tipul „D câștigă prea categoric”.

from torch.nn.utils import spectral_norm

def build_sn_discriminator(img_channels=3, feat=64):
    return nn.Sequential(
        spectral_norm(nn.Conv2d(img_channels, feat, 4, 2, 1)),
        nn.LeakyReLU(0.2, inplace=True),
        spectral_norm(nn.Conv2d(feat, feat * 2, 4, 2, 1)),
        nn.LeakyReLU(0.2, inplace=True),
        spectral_norm(nn.Conv2d(feat * 2, feat * 4, 4, 2, 1)),
        nn.LeakyReLU(0.2, inplace=True),
        spectral_norm(nn.Conv2d(feat * 4, 1, 4, 1, 0)),
    )

Înlocuiți Discriminator cu build_sn_discriminator() și adesea nu mai aveți nevoie de tehnica TTUR. Normalizarea spectrală este cea mai ușor de aplicat îmbunătățire individuală a robusteții.

Notă tehnică a traducerii: Normalizarea spectrală constrânge norma spectrală estimată a fiecărui strat, nu garantează necondiționat că orice rețea care o folosește este exact 1-Lipschitz. Pentru acest lanț de straturi, o limită globală de 1 necesită norme constrânse exact și activări cu constanta Lipschitz cel mult 1; implementarea practică estimează norma prin iterație de putere. În plus, build_sn_discriminator() întoarce forma (N, 1, 1, 1), spre deosebire de forma (N,) a lui Discriminator; codul afișat funcționează fiindcă ones_like și zeros_like au aceeași formă, însă alți consumatori pot necesita aplatizare. torch.nn.utils.spectral_norm este API-ul păstrat de original; documentația PyTorch recomandă varianta din torch.nn.utils.parametrizations pentru cod nou. Consultați Miyato et al. și documentația PyTorch.

Folosiți-l

Pentru generare serioasă, folosiți ponderi preantrenate sau treceți la difuzie. Două biblioteci standard:

  • torch_fidelity calculează FID / IS pentru generatorul dumneavoastră fără să scrieți cod personalizat de evaluare.
  • pytorch-gan-zoo (legacy) și StudioGAN oferă implementări testate de DCGAN, WGAN-GP, SN-GAN, StyleGAN și BigGAN.

În 2026, GAN-urile sunt în continuare cea mai bună alegere pentru: generarea de imagini în timp real (latență <10 ms), transferul de stil, traducerea imagine-la-imagine cu control precis (Pix2Pix, CycleGAN). Difuzia câștigă la fotorealism și condiționare prin text.

Notă tehnică a traducerii: Alegerea „cea mai bună”, ținta de latență, fotorealismul și condiționarea prin text depind de arhitectură, hardware, rezoluție, optimizare, date, metrică și constrângerile produsului; nu există o garanție generală de <10 ms pentru GAN-uri și nici un câștigător permanent între familiile de modele. Afirmația din textul-sursă trebuie citită ca o regulă orientativă dependentă de moment și de sarcină, nu ca reper universal.

Livrați-l

Această lecție produce:

  • outputs/prompt-gan-training-triage.md — un prompt care citește descrierea unei curbe de antrenare și alege modul de eșec (colapsul modurilor, D-câștigă, oscilație), plus singura corecție recomandată.
  • outputs/skill-dcgan-scaffold.md — o abilitate care scrie un schelet DCGAN din z_dim, image_size țintă și num_channels, incluzând bucla de antrenare și salvarea eșantioanelor.

Exerciții

  1. (Ușor) Antrenați DCGAN-ul de mai sus pe setul de date sintetic cu cercuri și salvați o grilă de 16 eșantioane la sfârșitul fiecărei epoci. În ce epocă cercurile generate devin clar circulare?
  2. (Mediu) Înlocuiți batch norm-ul discriminatorului cu normalizarea spectrală. Antrenați ambele versiuni în paralel. Care converge mai repede? Care are varianță mai mică între trei seed-uri?
  3. (Dificil) Implementați un DCGAN condițional: furnizați eticheta clasei atât lui G, cât și lui D (concatenați codificarea one-hot cu zgomotul în G, concatenați un canal de embedding al clasei în D). Antrenați-l pe setul de date sintetic „cercuri versus pătrate” din lecția 7 și arătați că condiționarea pe clasă funcționează eșantionând cu etichete specifice.

Termeni-cheie

Termen Ce spun oamenii Ce înseamnă de fapt
Generator (G) „Rețeaua care desenează” Mapează zgomotul la imagini; este antrenat să păcălească discriminatorul
Discriminator (D) „Critica” Clasificator binar; este antrenat să distingă imaginile reale de cele generate
Minimax „Jocul” min peste G, max peste D ale unei pierderi adversariale; echilibrul este p_G = p_data
Pierderea nesaturantă „Versiunea numeric sănătoasă” Pierderea lui G este -log(D(G(z))) în loc de log(1 - D(G(z))) pentru a evita gradienții care dispar la începutul antrenării
Colapsul modurilor „Generatorul produce un singur lucru” G produce numai un subset mic al distribuției datelor; corectați cu SN, discriminare pe minibatch sau un lot mai mare
TTUR „Două rate de învățare” D învață mai repede decât G, de obicei cu un factor de 2–4; stabilizează antrenarea
Normalizarea spectrală „Strat 1-Lipschitz” O normalizare a ponderilor care limitează constanta Lipschitz a fiecărui strat; împiedică D să devină arbitrar de abrupt
FID „Fréchet Inception Distance” Distanța dintre distribuțiile caracteristicilor Inception-v3 ale seturilor reale și generate; metrica standard de evaluare

Lecturi suplimentare

Notă tehnică a traducerii: „SOTA” desemnează un rezultat raportat într-un moment, pe benchmark-uri și metrici determinate; nu este un titlu permanent al unui model. Comparați lucrările în protocolul de evaluare relevant pentru sarcina curentă.

Sursă: Originalul în limba engleză

Navigare: ← Lecția 04.08 — Segmentarea instanțelor — Mask R-CNN · Faza 4 — Viziune computerizată · Lecția 04.10 — Generarea imaginilor — modele de difuzie → · Catalog complet