Curso / fast.ai / Parte 2 · Unidad 3
● piloto de formato · condensado

fast.ai · Parte 2 · Unidad 3 de 5

Diffusion models desde cero: DDPM, DDIM y Karras

El núcleo matemático de todo lo que hoy llamamos "difusión" — desde el paper original de 2020 hasta las mejoras que hicieron viable generar una imagen en segundos en vez de minutos.

1. DDPM: noising hacia adelante, denoising hacia atrás

Denoising Diffusion Probabilistic Models (Ho et al., 2020) simplificó una idea que técnicamente existía desde 2015 pero era difícil de entrenar. El proceso hacia adelante (forward) agrega ruido gaussiano a una imagen en pasos sucesivos, según un schedule de varianza creciente:

β = torch.linspace(β_min, β_max, n_steps)  # variance schedule, lineal
α = 1. - β
ᾱ = torch.cumprod(α, dim=0)                # producto acumulado

# noisificar la imagen x0 hasta el timestep t, en un solo paso:
xt = ᾱ[t].sqrt()*x0 + (1-ᾱ[t]).sqrt()*ruido

La genialidad matemática del paper es que (el producto acumulado de todos los pasos hasta t) permite saltar directo a cualquier nivel de ruido en un solo cálculo, sin simular los t pasos intermedios uno por uno — esto es lo que hace viable entrenar en la práctica: durante el entrenamiento, se elige un timestep al azar por ejemplo del batch, se noisifica directo a ese nivel, y se le pide a una red neuronal que prediga qué ruido se agregó.

2. Entrenar un DDPM es sorprendentemente simple

Todo el entrenamiento se reduce a una MSELoss entre el ruido real agregado y el ruido que predice un U-Net (arquitectura que se cubre en la próxima unidad):

class DDPMCB(TrainCB):
    def before_batch(self, learn):
        ε = torch.randn(x0.shape)               # ruido real, x_T
        t = torch.randint(0, self.n_steps, (n,))     # timestep al azar por ejemplo
        ᾱ_t = self.ᾱ[t].reshape(-1,1,1,1)
        xt = ᾱ_t.sqrt()*x0 + (1-ᾱ_t).sqrt()*ε   # noisificar en un paso
        learn.batch = ((xt, t), ε)              # input: (imagen ruidosa, t) — target: el ruido

learn = Learner(model, dls, nn.MSELoss(), cbs=[DDPMCB(n_steps=1000, ...)])
learn.fit(epochs)

La generación (sampling) es el proceso inverso: arrancar de ruido puro (x_T = torch.randn(...)) y, para cada timestep de 999 a 0, pedirle al modelo que prediga el ruido presente, restarlo con la fórmula del paper, y repetir — mil pasos, cada uno una pasada por la red completa. En el notebook, generar imágenes reconocibles de Fashion-MNIST toma exactamente esos 1000 pasos secuenciales; no hay forma de paralelizarlos porque cada paso depende del resultado del anterior.

3. DDIM: el mismo modelo, 10× menos pasos

Mil pasos secuenciales por imagen es exactamente el motivo por el que la generación con difusión temprana era lenta. Denoising Diffusion Implicit Models (Song et al., 2020) resuelve esto sin reentrenar nada: reformula el proceso de sampling para que sea determinista (dado el mismo ruido inicial, siempre da la misma imagen) y permite saltar pasos:

def ddim_step(x_t, t, noise, abar_t, abar_t1, bbar_t, bbar_t1, eta):
    x_0_hat = ((x_t - bbar_t.sqrt()*noise) / abar_t.sqrt())
    x_t = abar_t1.sqrt()*x_0_hat + (bbar_t1-sig**2).sqrt()*noise
    return x_t

El parámetro eta interpola entre DDPM puro (eta=1, estocástico) y DDIM puro (eta=0, completamente determinista). El notebook mide el efecto real de reducir pasos usando FID (Fréchet Inception Distance) y KID (Kernel Inception Distance) — métricas estándar que comparan la distribución estadística de imágenes generadas contra imágenes reales, más bajo es mejor:

Pasos de samplingCalidad
1000 (DDPM completo)Referencia
333 → 200 → 100FID/KID prácticamente estables
50Degradación notoria pero aceptable
25Calidad claramente peor

La conclusión práctica, medida directamente: se puede bajar de 1000 a ~100 pasos sin pérdida de calidad perceptible — una mejora de 10× en velocidad de generación sin reentrenar el modelo, solo cambiando cómo se samplea. Esto es, literalmente, la diferencia entre Stable Diffusion tardando minutos o segundos en generar una imagen.

4. Karras: preconditioning en vez de un schedule fijo

El paper de Karras et al. (2022), "Elucidating the Design Space of Diffusion-Based Generative Models", replantea el problema desde otro ángulo: en vez de un schedule de ruido fijo e indexado por timestep entero, muestrea directamente el nivel de ruido (sigma) de una distribución log-normal continua, y reescala el input/output del modelo con tres coeficientes derivados analíticamente (c_skip, c_out, c_in) para que la red siempre vea activaciones de escala consistente sin importar cuánto ruido tenga la imagen:

def scalings(sig):
    totvar = sig**2 + sig_data**2
    return sig_data**2/totvar, sig*sig_data/totvar.sqrt(), 1/totvar.sqrt()

def noisify(x0):
    sig = (torch.randn([len(x0)])*1.2-1.2).exp()   # log-normal, no lineal
    c_skip,c_out,c_in = scalings(sig)
    noised_input = x0 + torch.randn_like(x0)*sig
    target = (x0 - c_skip*noised_input)/c_out
    return (noised_input*c_in, sig), target

La idea central que sobrevive el detalle matemático: en vez de tratar "cuánto ruido hay" como un índice discreto en una tabla fija (como hacía DDPM con sus 1000 timesteps), tratarlo como una cantidad continua que se puede escalar analíticamente da un espacio de diseño mucho más flexible — es la base conceptual de los samplers modernos (Euler, Heun, LMS) que reemplazaron por completo al sampler original de DDPM en herramientas como Stable Diffusion y ComfyUI.

5. Resumen

  1. DDPM noisifica una imagen hacia adelante con un schedule fijo, y entrena una red para predecir el ruido agregado en cualquier paso.
  2. El truco matemático de (producto acumulado) permite noisificar a cualquier nivel en un solo cálculo, sin simular pasos intermedios.
  3. DDIM reformula el sampling para ser determinista y saltar pasos — de 1000 a ~100 sin pérdida perceptible de calidad, medido con FID/KID.
  4. Karras reemplaza el schedule discreto por un nivel de ruido continuo con preconditioning analítico — la base de los samplers modernos.
  5. Nada de esto requirió cambiar la arquitectura de la red — son todas mejoras al proceso matemático de noising/denoising alrededor de un modelo que sigue prediciendo ruido.