Faza 04 · lecția 10
Generarea imaginilor — modele de difuzie
Scopul lecției: Un model de difuzie învață să elimine zgomotul. Antrenați-l să elimine o cantitate mică de zgomot dintr-o imagine zgomotoasă, repetați procesul invers de o mie de ori și obțineți un generator de imagini.
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Obiective de învățare
- Problema
- Conceptul
- Procesul direct
- Saltul în formă închisă
- Procesul invers
- Pierderea de antrenare
- Eșantionatorul (DDPM)
- De ce 1000 de pași
- DDIM: eșantionare de 20× mai rapidă
- Condiționarea de timp
- Construiți
- Pasul 1: Schema de zgomot
- Pasul 2: Difuzia directă (q_sample)
- Pasul 3: Un U-Net mic, condiționat de timp
- Pasul 4: Bucla de antrenare
- Pasul 5: Eșantionatorul (DDPM)
- Pasul 6: Eșantionatorul DDIM (determinist, ~20× mai rapid)
- Utilizați-l
- Livrați-l
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
Un model de difuzie învață să elimine zgomotul. Antrenați-l să elimine o cantitate mică de zgomot dintr-o imagine zgomotoasă, repetați procesul invers de o mie de ori și obțineți un generator de imagini.
Tip: Construcție Limbaje: Python Cerințe prealabile: Faza 4 Lecția 07 (U-Net), Faza 1 Lecția 06 (Probabilități), Faza 3 Lecția 06 (Optimizatori) Timp: ~75 de minute
Obiective de învățare
- Derivați procesul direct de adăugare a zgomotului
x_0 -> x_1 -> ... -> x_Tși explicați de ce forma închisăq(x_t | x_0)este valabilă pentru orice t - Implementați un obiectiv de antrenare în stil DDPM care face regresie asupra zgomotului adăugat la fiecare pas și un eșantionator care parcurge drumul înapoi de la zgomot pur la o imagine
- Construiți un U-Net condiționat de timp (suficient de mic pentru a fi antrenat pe CPU) care prezice zgomotul pentru orice pas temporal
- Explicați diferența dintre eșantionarea DDPM și DDIM și când este potrivită fiecare (Lecția 23 tratează în profunzime flow matching și rectified flow)
Problema
GAN-urile generează dintr-un singur pas: zgomot la intrare, imagine la ieșire, o singură trecere înainte. Sunt rapide și dificil de antrenat. Modelele de difuzie generează iterativ: pornesc de la zgomot pur, elimină zgomotul în pași mici, iar imaginea apare treptat. Sunt lente și ușor de antrenat. În ultimii cinci ani, această din urmă proprietate a dominat: orice echipă mică poate antrena un model de difuzie și poate obține eșantioane rezonabile; antrenarea GAN-urilor este o meserie învățată de-a lungul anilor de rulări eșuate.
Dincolo de stabilitatea antrenării, structura iterativă a difuziei deblochează tot ce face generarea modernă de imagini: condiționare pe text, inpainting, editarea imaginilor, super-rezoluție, stil controlabil. Fiecare pas al buclei de eșantionare este un loc unde poate fi injectată o constrângere nouă. Acest punct de inserție explică de ce Stable Diffusion, Imagen, DALL-E 3, Midjourney și fiecare model de imagini controlabil pe care îl veți utiliza se bazează pe difuzie.
Notă tehnică a traducerii: Comparația dintre GAN-uri și difuzie este o orientare didactică, nu o regulă universală. Costul de antrenare și de inferență, stabilitatea, latența și calitatea depind de arhitectură, rezoluție, condiționare, date, hardware și scheduler; există modele generative controlabile care nu sunt modele de difuzie.
Această lecție construiește DDPM-ul minimal: adăugarea directă a zgomotului, eliminarea inversă a zgomotului, bucla de antrenare. Lecția următoare (Stable Diffusion) îl conectează la un sistem de producție cu un VAE, un codificator de text și classifier-free guidance.
Conceptul
Procesul direct
Luați o imagine x_0. Adăugați o cantitate mică de zgomot gaussian pentru a obține x_1. Adăugați puțin mai mult zgomot pentru a obține x_2. Continuați timp de T pași până când x_T devine aproape imposibil de distins de zgomotul gaussian pur.
q(x_t | x_{t-1}) = N(x_t; sqrt(1 - beta_t) * x_{t-1}, beta_t * I)
beta_t este o schemă de varianțe mici, de obicei liniară de la 0.0001 la 0.02 pe parcursul a T=1000 de pași. Fiecare pas micșorează puțin semnalul și injectează zgomot proaspăt.
Saltul în formă închisă
Adăugarea zgomotului pas cu pas este un lanț Markov, însă matematica se compactează: puteți eșantiona x_t direct din x_0 într-un singur pas.
Definim alpha_t = 1 - beta_t
Definim alpha_bar_t = prod_{s=1..t} alpha_s
Apoi:
q(x_t | x_0) = N(x_t; sqrt(alpha_bar_t) * x_0, (1 - alpha_bar_t) * I)
Echivalent:
x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * epsilon
unde epsilon ~ N(0, I)
Această singură ecuație este motivul pentru care difuzia este practică. În timpul antrenării alegeți un t aleator, eșantionați x_t direct din x_0 și antrenați într-un singur pas — nu este necesară simularea întregului lanț Markov.
Procesul invers
Procesul direct este fix. Procesul invers p(x_{t-1} | x_t) este ceea ce învață rețeaua neuronală. Modelele de difuzie nu prezic direct x_{t-1}; ele prezic zgomotul epsilon adăugat la pasul t, iar matematica deduce x_{t-1} din acesta.
Pierderea de antrenare
Pentru fiecare pas de antrenare:
- Eșantionați o imagine reală
x_0. - Eșantionați uniform un pas temporal
tdin [1, T]. - Eșantionați zgomot
epsilon ~ N(0, I). - Calculați
x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * epsilon. - Preziceți
epsilon_theta(x_t, t)cu rețeaua. - Minimizați
|| epsilon - epsilon_theta(x_t, t) ||^2.
Atât. Rețeaua neuronală învață să prezică zgomotul la orice pas temporal. Pierderea este MSE. Nu există joc adversarial, colaps sau oscilație.
Notă tehnică a traducerii: MSE este forma simplificată uzuală pentru parametrizarea prin predicția zgomotului, însă obiectivul DDPM provine dintr-o bornă variațională. Există și ponderări ale pierderii, predicția lui
x_0sau parametrizarea v; nu rezultă că orice model de difuzie folosește exact acest unic apel MSE.
Eșantionatorul (DDPM)
Pentru a genera: porniți de la x_T ~ N(0, I) și parcurgeți înapoi câte un pas.
pentru t = T, T-1, ..., 1:
eps = model(x_t, t)
x_{t-1} = (1 / sqrt(alpha_t)) * (x_t - (beta_t / sqrt(1 - alpha_bar_t)) * eps) + sqrt(beta_t) * z
unde z ~ N(0, I) dacă t > 1, altfel 0
returnează x_0
Ideea-cheie este că, deși condiționala inversă nu este cunoscută în formă închisă în general, ea este cunoscută pentru acest proces direct gaussian specific. Coeficienții cu aspect complicat sunt cei obținuți prin regula lui Bayes.
Notă tehnică a traducerii: Condiționala posterioară exactă a procesului direct,
q(x_{t-1} | x_t, x_0), are varianțatilde_beta_t = beta_t * (1 - alpha_bar_{t-1}) / (1 - alpha_bar_t). Procesul invers învățatp_theta(x_{t-1} | x_t)nu cunoaștex_0: îi estimează media din predicțiaepsilon, iar DDPM poate alege o varianță fixă, inclusivbeta_tca în acest fragment, sautilde_beta_t. Prin urmare,sqrt(beta_t)nu este universal greșit; alegerea varianței trebuie să corespundă parametrizării și schedulerului folosite.
De ce 1000 de pași
Schema de zgomot direct este aleasă astfel încât fiecare pas să adauge suficient de puțin zgomot încât pasul invers să fie aproape gaussian. Prea puțini pași și pasul invers este departe de gaussian, iar rețeaua nu îl poate modela bine. Prea mulți pași și eșantionarea devine costisitoare, cu câștiguri descrescătoare. T=1000 cu o schemă liniară este valoarea implicită DDPM.
Notă tehnică a traducerii: T=1000 și schema liniară sunt alegerea de referință a DDPM original, nu o cerință teoretică. Schemele, parametrizările și solvere moderne pot folosi alte numere de pași; compromisul trebuie măsurat pentru modelul și obiectivul concret.
DDIM: eșantionare de 20× mai rapidă
Antrenarea este aceeași. Eșantionarea se schimbă. DDIM (Song et al., 2020) definește un proces invers determinist care sare peste pași temporali fără reantrenare. Eșantionarea în 50 de pași cu DDIM oferă o calitate apropiată de DDPM cu 1000 de pași. Fiecare sistem de producție utilizează DDIM sau o variantă și mai rapidă (DPM-Solver, Euler ancestral).
Notă tehnică a traducerii: „20×” este o valoare ilustrativă; lucrarea DDIM raportează experimental intervalul 10–50× în anumite configurații. Câștigul de viteză și apropierea calității depind de model, de schema de zgomot, de discretizare și de metrică; 50 de pași nu garantează calitate comparabilă pentru orice model și nici toate sistemele de producție nu utilizează DDIM. DDIM este o familie de procese implicite ne-Markoviene;
etacontrolează stocasticitatea, iar afirmația că o anumită valoare „recuperează DDPM” trebuie evaluată pentru parametrizarea și planificarea concretă.
Condiționarea de timp
Rețeaua epsilon_theta(x_t, t) trebuie să știe ce pas temporal elimină zgomotul. Modelele moderne de difuzie injectează t prin reprezentări temporale sinusoidale (aceeași idee ca poziționarea în transformere), care se adaugă la hărțile de caracteristici la fiecare nivel U-Net.
t_embedding = sinusoidal(t)
feature_map += MLP(t_embedding)
Fără condiționarea de timp, rețeaua trebuie să ghicească nivelul zgomotului din imaginea însăși, ceea ce funcționează, dar este mult mai puțin eficient din punctul de vedere al eșantioanelor.
Construiți
Pasul 1: Schema de zgomot
import torch
def linear_beta_schedule(T=1000, beta_start=1e-4, beta_end=2e-2):
return torch.linspace(beta_start, beta_end, T)
def precompute_schedule(betas):
alphas = 1.0 - betas
alphas_cumprod = torch.cumprod(alphas, dim=0)
return {
"betas": betas,
"alphas": alphas,
"alphas_cumprod": alphas_cumprod,
"sqrt_alphas_cumprod": torch.sqrt(alphas_cumprod),
"sqrt_one_minus_alphas_cumprod": torch.sqrt(1.0 - alphas_cumprod),
"sqrt_recip_alphas": torch.sqrt(1.0 / alphas),
}
schedule = precompute_schedule(linear_beta_schedule(T=1000))
Precalculați o singură dată, apoi selectați după indice în timpul antrenării și al eșantionării.
Pasul 2: Difuzia directă (q_sample)
def q_sample(x0, t, noise, schedule):
sqrt_a = schedule["sqrt_alphas_cumprod"][t].view(-1, 1, 1, 1)
sqrt_one_minus_a = schedule["sqrt_one_minus_alphas_cumprod"][t].view(-1, 1, 1, 1)
return sqrt_a * x0 + sqrt_one_minus_a * noise
Formă închisă într-o singură linie. t este un lot de pași temporali, câte unul pentru fiecare imagine din lot.
Notă tehnică a traducerii: Dacă antrenarea se face pe un accelerator, tensorii din
schedule, indiciitși imaginile trebuie să fie pe dispozitive compatibile; fragmentul creează inițial schema pe dispozitivul implicit. Într-o implementare de producție, înregistrați schema ca buffer al modulului sau mutați-o explicit împreună cu modelul.
Pasul 3: Un U-Net mic, condiționat de timp
import torch.nn as nn
import torch.nn.functional as F
import math
def timestep_embedding(t, dim=64):
half = dim // 2
freqs = torch.exp(-math.log(10000) * torch.arange(half, device=t.device) / half)
args = t[:, None].float() * freqs[None]
emb = torch.cat([args.sin(), args.cos()], dim=-1)
return emb
class TinyUNet(nn.Module):
def __init__(self, img_channels=3, base=32, t_dim=64):
super().__init__()
self.t_mlp = nn.Sequential(
nn.Linear(t_dim, base * 4),
nn.SiLU(),
nn.Linear(base * 4, base * 4),
)
self.t_dim = t_dim
self.enc1 = nn.Conv2d(img_channels, base, 3, padding=1)
self.enc2 = nn.Conv2d(base, base * 2, 4, stride=2, padding=1)
self.mid = nn.Conv2d(base * 2, base * 2, 3, padding=1)
self.dec1 = nn.ConvTranspose2d(base * 2, base, 4, stride=2, padding=1)
self.dec2 = nn.Conv2d(base * 2, img_channels, 3, padding=1)
self.time_proj = nn.Linear(base * 4, base * 2)
def forward(self, x, t):
t_emb = timestep_embedding(t, self.t_dim)
t_emb = self.t_mlp(t_emb)
t_proj = self.time_proj(t_emb)[:, :, None, None]
h1 = F.silu(self.enc1(x))
h2 = F.silu(self.enc2(h1)) + t_proj
h3 = F.silu(self.mid(h2))
d1 = F.silu(self.dec1(h3))
d2 = torch.cat([d1, h1], dim=1)
return self.dec2(d2)
U-Net cu două niveluri, cu condiționarea de timp injectată la gâtul de sticlă. Măriți adâncimea și lățimea pentru imagini reale.
Notă tehnică a traducerii: În
TinyUNetdin fragment, condiționarea de timp este injectată numai dupăenc2, la gâtul de sticlă, nu la fiecare nivel U-Net cum afirmă explicația generală. Arhitecturile de producție pot injecta reprezentarea temporală în mai multe blocuri, dar poziția și mecanismul sunt alegeri de arhitectură.
Pasul 4: Bucla de antrenare
def train_step(model, x0, schedule, optimizer, device, T=1000):
model.train()
x0 = x0.to(device)
bs = x0.size(0)
t = torch.randint(0, T, (bs,), device=device)
noise = torch.randn_like(x0)
x_t = q_sample(x0, t, noise, schedule)
pred = model(x_t, t)
loss = F.mse_loss(pred, noise)
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
Aceasta este întreaga buclă de antrenare. Fără joc GAN, fără pierdere specializată, un singur apel MSE.
Pasul 5: Eșantionatorul (DDPM)
@torch.no_grad()
def sample(model, schedule, shape, T=1000, device="cpu"):
model.eval()
x = torch.randn(shape, device=device)
betas = schedule["betas"].to(device)
sqrt_one_minus_a = schedule["sqrt_one_minus_alphas_cumprod"].to(device)
sqrt_recip_alphas = schedule["sqrt_recip_alphas"].to(device)
for t in reversed(range(T)):
t_batch = torch.full((shape[0],), t, dtype=torch.long, device=device)
eps = model(x, t_batch)
coef = betas[t] / sqrt_one_minus_a[t]
mean = sqrt_recip_alphas[t] * (x - coef * eps)
if t > 0:
x = mean + torch.sqrt(betas[t]) * torch.randn_like(x)
else:
x = mean
return x
1000 de treceri înainte pentru a produce un lot de eșantioane. În cod real, ați înlocui acest lucru cu un eșantionator DDIM în 50 de pași.
Pasul 6: Eșantionatorul DDIM (determinist, ~20× mai rapid)
@torch.no_grad()
def sample_ddim(model, schedule, shape, steps=50, T=1000, device="cpu", eta=0.0):
model.eval()
x = torch.randn(shape, device=device)
alphas_cumprod = schedule["alphas_cumprod"].to(device)
ts = torch.linspace(T - 1, 0, steps + 1).long()
for i in range(steps):
t = ts[i]
t_prev = ts[i + 1]
t_batch = torch.full((shape[0],), t, dtype=torch.long, device=device)
eps = model(x, t_batch)
a_t = alphas_cumprod[t]
a_prev = alphas_cumprod[t_prev] if t_prev >= 0 else torch.tensor(1.0, device=device)
x0_pred = (x - torch.sqrt(1 - a_t) * eps) / torch.sqrt(a_t)
sigma = eta * torch.sqrt((1 - a_prev) / (1 - a_t) * (1 - a_t / a_prev))
dir_xt = torch.sqrt(1 - a_prev - sigma ** 2) * eps
noise = sigma * torch.randn_like(x) if eta > 0 else 0
x = torch.sqrt(a_prev) * x0_pred + dir_xt + noise
return x
eta=0 este complet determinist (același zgomot de intrare produce mereu aceeași ieșire). eta=1 recuperează DDPM.
Notă tehnică a traducerii: În fragment,
eta=1introduce zgomot în traiectoria DDIM, însă nu trebuie interpretat drept echivalență automată cu orice eșantionator DDPM. Mai mult,tsse termină la0, astfel că perechile din buclă nu produc niciodatăt_prev=-1, iar ramura cua_prev=1nu este atinsă. Cu indexare de la zero,alphas_cumprod[0]este încă mai mic decât 1; pentru a parcurge explicit pasul final până la imaginea curată, programați tranzițiat=0 -> -1cua_prev=1sau definiți consecvent o convenție cu un pas inițial fără zgomot, fără a dubla pasul zero. Echivalența depinde de variația aleasă, de pașii selectați și de parametrizarea modelului; comparați cu formularea DDIM și cu schedulerul concret utilizat.
Utilizați-l
Pentru lucru în producție, utilizați diffusers:
from diffusers import DDPMScheduler, UNet2DModel
unet = UNet2DModel(sample_size=32, in_channels=3, out_channels=3, layers_per_block=2)
scheduler = DDPMScheduler(num_train_timesteps=1000)
Biblioteca oferă schedulere gata pregătite (DDPM, DDIM, DPM-Solver, Euler, Heun), U-Net-uri configurabile, pipeline-uri pentru text-la-imagine și imagine-la-imagine, precum și ajutoare pentru ajustare fină LoRA.
Pentru cercetare, k-diffusion (Katherine Crowson) are implementările de referință cele mai fidele și cele mai bune variante de eșantionare.
Livrați-l
Această lecție produce:
outputs/prompt-diffusion-sampler-picker.md— un prompt care alege DDPM / DDIM / DPM-Solver / Euler pe baza țintei de calitate, a bugetului de latență și a tipului de condiționare.outputs/skill-noise-schedule-designer.md— o abilitate care produce o schemă beta liniară, cosinusoidală sau sigmoidă pentru T și nivelul-țintă de corupere, plus grafice de diagnostic ale raportului semnal-zgomot în timp.
Exerciții
- (Ușor) Vizualizați procesul direct: luați o imagine și reprezentați
x_tpentrut in [0, 100, 250, 500, 750, 1000]. Verificați căx_1000arată ca zgomot gaussian pur. - (Mediu) Antrenați TinyUNet pe setul de date synthetic-circles timp de 20 de epoci și eșantionați 16 cercuri. Comparați eșantionarea DDPM (1000 de pași) cu DDIM (50 de pași) — produc imagini similare cu aceeași sămânță de zgomot?
- (Dificil) Implementați o schemă cosinusoidală de zgomot (Nichol & Dhariwal, 2021):
alpha_bar_t = cos^2((t/T + s) / (1 + s) * pi / 2). Antrenați același model cu scheme liniare și cosinusoidale și arătați că schema cosinusoidală oferă eșantioane mai bune la număr mic de pași.
Notă tehnică a traducerii: Cu
linear_beta_schedule(T=1000), indicii valizi ai tensorilor programați sunt de la 0 la 999. Pentru codul din lecție, înlocuițit=1000din primul exercițiu cut=999; altfel indexarea depășește lungimea schemei. Formula cosinusoidală dată pentrualpha_bar_tnecesită și normalizare la valoarea inițială, derivarea raporturilor succesive pentrubeta_tși, de regulă, limitarea valorilor; rezultatul „mai bun” la puțini pași este o ipoteză experimentală, nu o garanție.
Termeni-cheie
| Termen | Ce se spune de obicei | Ce înseamnă de fapt |
|---|---|---|
| Proces direct | „Adăugați zgomot în timp” | Lanț Markov fix care corupe o imagine în zgomot gaussian în T pași |
| Proces invers | „Eliminați zgomotul pas cu pas” | Distribuție învățată care parcurge drumul înapoi de la zgomot la imagine |
| Predicția epsilon | „Preziceți zgomotul” | Ținta de antrenare: epsilon_theta(x_t, t) prezice zgomotul adăugat la pasul t |
| Schema beta | „Cantitățile de zgomot” | Secvență de T varianțe mici care definesc cât zgomot intră la fiecare pas |
| alpha_bar_t | „Factor cumulativ de păstrare” | Produsul lui (1 - beta_s) până la timpul t; t mai mare înseamnă mai puțin semnal rămas |
| Eșantionator DDPM | „Ancestral, stocastic” | Eșantionează fiecare x_{t-1} din condiționala sa gaussiană; 1000 de pași |
| Eșantionator DDIM | „Determinist, rapid” | Rescrie eșantionarea ca ODE deterministă; 20–100 de pași cu o calitate similară |
| Condiționarea de timp | „Spuneți modelului ce t este” | Reprezentare sinusoidală a lui t injectată în U-Net pentru ca acesta să cunoască nivelul zgomotului |
Lecturi suplimentare
- Denoising Diffusion Probabilistic Models (Ho et al., 2020) — lucrarea care a făcut difuzia practică și a depășit GAN-urile la FID
- Improved DDPM (Nichol & Dhariwal, 2021) — schema cosinusoidală și parametrizarea v
- DDIM (Song, Meng, Ermon, 2020) — eșantionatorul determinist care a făcut posibilă inferența în timp real
- Elucidating the Design Space of Diffusion (Karras et al., 2022) — o perspectivă unificată asupra fiecărei alegeri de proiectare a difuziei; referința actuală cea mai bună
Notă tehnică a traducerii: Afirmațiile din descrierile lecturilor despre „depășirea GAN-urilor la FID”, „inferență în timp real” și „cea mai bună referință actuală” sunt dependente de moment, metrică, set de date și protocol. Ele trebuie citite ca descrieri istorice sau editoriale ale lucrărilor, nu ca rezultate universale și permanente.
Sursă: Originalul în limba engleză
Navigare: ← Lecția 04.09 — Generarea imaginilor — GAN-uri · Faza 4 — Viziune computerizată · Lecția 04.11 — Stable Diffusion — arhitectură și ajustare fină → · Catalog complet