Modelos de difusión DDPM¶

Modelos de difusión probabilística de eliminación de ruido¶

Autor: Sergio Quiroga Sandoval.

Universidad Nacional de Colombia, squirogas@unal.edu.co

Coautores: Manuel Fernando Valle Amortegui, Saúl Álvarez Lázaro, Helena Perilla Vargas, Laura Victoria Torres Pinilla

Contenido¶

  • Librerías y paquetes

Conceptos básicos de modelos generativos

  • Modelo Generativo.
  • Variable Latente.
  • Tratabilidad y flexibilidad.

Modelo de difusión de juguete una distribución 2 dimensional

  • Proceso de Difusión Directa (Forward).
  • Proceso de Difusión Inversa (Reverse).

Librerías y paquetes¶

In [ ]:
import Pkg
Pkg.add("Distributions");
Pkg.add("Plots");
Pkg.add("GaussianMixtures");
Pkg.add("Flux");
Pkg.add("ProgressMeter");
Pkg.add("MLUtils");
Pkg.add("Statistics");
Pkg.add("Optimisers");
Pkg.add("Zygote");

using GaussianMixtures;
using Random, LinearAlgebra;
using Distributions, Plots;
using Flux, ProgressMeter, MLUtils, Statistics;
using Flux: onehotbatch, crossentropy, logitcrossentropy;
using MLUtils: DataLoader;
  • Random: para controlar la semilla y reproducibilidad.
  • Distributions: para definir y muestrear distribuciones gaussianas multivariadas.
  • GaussianMixtures: Para generar modelos generativos de Mezcla Gaussiana.

Conceptos básicos de modelos generativos¶

Dado un conjunto de datos con etiquetas $D =\{\vec{x}_i, y_i\}_{i=0}^{n}$, un modelo generativo busca aproximar la distribución conjunta sobre todas las observaciones y etiquetas $p(x,y)$ o, si el dataset no tiene etiquetas $D =\{\vec{x}_i\}_{i=0}^{n}$, la distribución $p(\vec{x_1},\ldots, \vec{x_n})$ de los datos observados.

Una vez que tenemos una buena aproximación de esa distribución, podemos muestrear nuevas observaciones $x$ plausibles, es decir, generar datos nuevos que "parezcan" parte del mismo fenómeno que el conjunto de entrenamiento $x$.

Un modelo discriminativo se enfoca en la probabilidad condicional $p(y\mid x)$ o directamente en una frontera de decisión. Su objetivo es clasificar o predecir una etiqueta a partir de una observación: no necesita saber cómo se generan todos los detalles de $x$, sólo necesita la información suficiente para distinguir clases.

En cambio, un generativo debe capturar la estructura completa de los datos (correlaciones, modos, forma de la densidad) para poder reconstruir o producir muestras nuevas.


  • Qué aprenden

    • Generativos: la distribución conjunta $p(x,y)$ (o $p(x)$ si no hay etiquetas).
    • Discriminativos: la probabilidad condicional $p(y\mid x)$ o la regla de clasificación directamente.
  • Para qué sirven

    • Generar nuevos ejemplos (data augmentation, síntesis de imágenes, audio, texto).
    • Estimación de densidad (detectar anomalías, compresión, modelado probabilístico).
    • Inferencia con variables latentes: escribir $p(x)=\int p(x\mid z)\,p(z)\,dz$ y aprender un espacio latente $z$ que explica la variabilidad de los datos.
  • Tipos importantes

    • Autoregresivos (PixelCNN, Transformer-based): densidad explícita, muestreo secuencial.
    • Variational Autoencoders (VAEs): modelo explícito con latente y función de evidencia (ELBO).
    • Generative Adversarial Networks (GANs): modelo implícito que produce buenos samples, pero sin probabilidad tractable.
    • Normalizing flows: transformaciones invertibles con densidad exacta y tractable.
    • Modelos de difusión / score-based: aprenden gradientes de la log-densidad $\nabla_x \log p(x)$ y muestrean invirtiendo un proceso de ruido.
  • Ventajas / desventajas

    • Ventaja: permiten generar y hacer inferencia completa sobre la distribución de los datos.
    • Desventaja: aprender $p(x)$ puede ser más difícil (costoso y con trade-offs entre calidad de sample, tractabilidad y evaluación). Los discriminativos suelen ser más simples y eficientes para tareas de clasificación.

In [14]:
# 1. Definimos un modelo generativo (Mezcla de 2 Gaussianas)
# Componente 1: Media -2, Desviación Estándar 0.5
# Componente 2: Media 3, Desviación Estándar 1.0
# La mezcla está ponderada 40% a la componente 1 y 60% a la 2.
gmm = MixtureModel(
    [Normal(-2, 0.5), Normal(3, 1.0)], # Las dos componentes Gaussianas
    [0.4, 0.6]                         # Sus pesos
)

# 2. Generamos 1000 nuevas muestras a partir del modelo
nuevas_muestras = rand(gmm, 1000)

# 3. Visualizamos los datos generados
histogram(nuevas_muestras, bins=50, normalize=:pdf, label="Datos Generados",
          title="Muestras generadas por un GMM", legend=:topleft,
          color=:cornflowerblue)

plot!(x -> pdf(gmm, x), -5, 6, label="Densidad Real del Modelo", lw=3, color=:red)
Out[14]:
No description has been provided for this image

Variable latente¶

Una variable latente$(z)$ es una variable oculta o no observada que el modelo utiliza para capturar alguna estructura o caracteristica subyacente en los datos. Los modelos generativos a menudo usan variables latentes para simplificar la descripción de datos complejos. Por ejemplo, en GMM, la variable latente podria ser de cual de las compotentes gaussianas proviene este punto de datos o en un autoencoder variacional(VAE) para caras, una variable latente podria controlar el angulo de la sonrisa o el color del pelo.

Tratabilidad y Flexibilidad¶

Estos dos conceptos estan fuertemente relacionados y en conflicto.

  • Tratabilidad: Se refiere a la viabilidad computacional de las operaciones clave de un modelo. Un modelo es tratable si podemos calcular de forma eficiente la probabilidad de un dato $(p(x))$, entrenar el modelo y generar nuevas muestras. Si estas operaciones son demasiado lentas o requieren una cantidad de recursos prohibitiva, el modelo es intratable.

  • Flexibilidad: Es la capacidad del modelo para capturar distribuciones de datos muy complejas y variadas. Un modelo de una sola gaussiana es poco flexible; solo puede aprender datos con forma de campana. Un VAE o un GAN son muy flexibles y pueden aprender distribuciones complejas como las de imágenes de caras.

  • Modelos simples (Gaussian, Laplace):

    • Pros: $ p(x) $ es analítico, entrenar y muestrear es rápido.
    • Contras: estructura limitada (poca capacidad para datos complejos).
  • Modelos flexibles $ \frac{\phi(x)}{Z} $:

    • Pros: teóricamente pueden representar cualquier forma.
    • Contras: $ Z = \int \phi(x)\,dx $ es intratable → muestreo y evaluación costosos.

El reto en el modelado generativo es encontrar un buen equilibrio: queremos un modelo lo suficientemente flexible para capturar los datos reales, pero que siga siendo tratable para poder entrenarlo y usarlo, y que no exceda en flexivilidad porque puede causar overfitting. Aqui mostramos un ejemplo de un modelo simple el cual no captura los datos tan bien y un modelo mas riguroso que si lo hace

In [3]:
gmm = MixtureModel([Normal(-2, 0.5), Normal(3, 1.0)], [0.4, 0.6])

# Proceso de generación explícito con la variable latente
function generar_muestra_con_latente(modelo)
    # 1. Muestrear la variable latente (z): ¿Qué componente usamos? (1 o 2)
    # La probabilidad de elegir la componente 1 es 0.4, y la 2 es 0.6.
    z_latente = rand(Categorical(modelo.prior.p))

    # 2. Muestrear el dato (x) desde la componente elegida
    x_dato = rand(modelo.components[z_latente])

    return (dato=x_dato, componente_origen=z_latente)
end

muestra = generar_muestra_con_latente(gmm)
println("Dato generado: $(round(muestra.dato, digits=2)), Origen (variable latente z): Componente $(muestra.componente_origen)")
Dato generado: -1.59, Origen (variable latente z): Componente 1
In [4]:
datos_reales = vcat(rand(Normal(-3, 1.0), 200), rand(Normal(4, 1.5), 300))

k = 2
gmm_ajustado = GMM(k, datos_reales)

componentes_del_modelo = Vector{Normal{Float64}}()
for i in 1:k
    media = gmm_ajustado.μ[i]
    varianza = gmm_ajustado.Σ[i][1]
    desviacion_estandar = sqrt(varianza)
    componente_actual = Normal(media, desviacion_estandar)
    push!(componentes_del_modelo, componente_actual)
end
modelo_flexible = MixtureModel(componentes_del_modelo, gmm_ajustado.w)

modelo_simple = fit(Normal, datos_reales)

histogram(datos_reales, bins=60, normalize=:pdf, label="Datos Reales", color=:gray,
          legend=:topleft)
plot!(x -> pdf(modelo_simple, x), -8, 9, label="Modelo Poco Flexible (Normal)", lw=3, color=:orange)
plot!(x -> pdf(modelo_flexible, x), -8, 9, label="Modelo Flexible (GMM k=2)", lw=3, color=:green,
      title="Comparando Flexibilidad de Modelos")
[ Info: Initializing GMM, 2 Gaussians diag covariance 1 dimensions using 500 data points
K-means converged with 4 iterations (objv = 750.0533255631107)
┌ Info: K-means with 500 data points using 4 iterations
└ 125.0 data points per parameter
Out[4]:
No description has been provided for this image

Falta: comparar el costo de implementar un GMM vs una distribución Normal a partir de los datos

Modelo de difusión para distribuciones 2D¶

Este experimento en Julia implementa un toy dataset de dos cúmulos gaussianos en 2D y simula un proceso de difusión hacia adelante,difusión inversa y difusión inversa con guía condicional generando un GIFs animados que muestran la degradación progresiva de la estructura original así como la reconstrucción a partir del ruido en la difusión inversa.

In [5]:
# -------------------------------------
# 1. Definición del dataset
# -------------------------------------
Random.seed!(1234)
μ1, μ2 = [-2.0, -2.0], [2.0, 2.0]
σ = 0.5
N = 500
gauss1 = MvNormal(μ1, σ^2 * I(2))
gauss2 = MvNormal(μ2, σ^2 * I(2))
data = hcat(rand(gauss1, N), rand(gauss2, N))
labels = vcat(fill(0, N), fill(1, N));
  • Establecemos semilla 1234 para reproducibilidad.
  • Creamos dos gaussianas 2D centradas en μ1 y μ2 con desviación σ.
  • data es un arreglo de tamaño 2×1000 con puntos de ambos cúmulos.
In [6]:
# -------------------------------------
# 2. Parámetros de difusión
# -------------------------------------
T = 500;
β = range(1e-4, 0.02, length=T);
αs = 1 .- β;
αc = cumprod(αs);
sqrt_αc = sqrt.(αc);
sqrt_1m_αc = sqrt.(1 .- αc);
  • T: número total de pasos de difusión.
  • β: lista lineal de varianzas de ruido entre 1e-4 y 0.02.
  • α: producto acumulado de (1 − β_t), útil para fórmulas teóricas.

Se usa la lista de varianzas β para añadir cada vez más ruido, de forma progresiva en las iteraciones, de manera que en los primeros pasos hay relativamente poco ruido añadiendose.

Difusión hacia adelante¶

El proceso directo es el encargado de corromper progresivamente los datos originales con ruido gaussiano. Este proceso se modela como una cadena de Markov: en cada paso de tiempo se pasa de $x_{t-1}$ a $x_t$ mediante una transición probabilística que depende sólo de $x_{t-1}$.

$$ q(x^{(t)}\mid x^{(t-1)}) = \mathcal{N}\bigl(x^{(t)};\,\sqrt{1-\beta_t}\,x^{(t-1)},\,\beta_t\,I \bigr) $$

donde $\beta_t$ es un parámetro de ruido en el paso $t$. En otras palabras, en cada paso se desplaza ligeramente la imagen añadiendo ruido: la media de $x_t$ es $\sqrt{1-\beta_t}x_{t-1}$ y su covarianza es $\beta_t I$.

No se entrena nada en el proceso directo: los parámetros $\beta_t$ suelen elegirse a mano tal que para algún $T$, $x_T$ sea prácticamente ruido blanco gaussiano. El modelo de difusión entrenará en cambio el proceso inverso necesario para deshacer esta difusión.

In [7]:
# -------------------------------------
# 3. Forward diffusion snapshots
# -------------------------------------
X = copy(data)
snapshots = [copy(X)]
t_interval = 50
for t in 1:T
noise = randn(size(X))
X .= sqrt(αs[t]) .* X .+ sqrt(1-αs[t]) .* noise
if t % t_interval == 0
push!(snapshots, copy(X))
end
end
  • Copiamos data en X para preservar los datos originales.
  • Guardamos el estado inicial (t=0) en snapshots.
  • Cada iteración añade ruido gaussiano de varianza β[t].
  • Cada 10 pasos (cuando t % interval == 0) almacenamos un snapshot.
  1. $\beta_t$ = varianza de ruido en paso t
    Kernel: $$ q\bigl(x^{(t)}\mid x^{(t-1)}\bigr) =\mathcal{N}\bigl(\sqrt{1-\beta_t}\,x^{(t-1)},\,\beta_t I\bigr). $$

    • √(1−βₜ): atenua la señal.
    • √βₜ: escala el ruido nuevo.
  2. $\alpha_t$ = $\prod_{i=1}^{t}$(1−βᵢ)

    • Fracción de señal que sobrevive tras t pasos.
    • Permite derivar en closed‐form: $$ q\bigl(x^{(t)}\mid x^{(0)}\bigr) =\mathcal{N}\bigl(\sqrt{\alpha_t}\,x^{(0)},\,(1-\alpha_t)I\bigr). $$
  3. lista de incremento lineal de βₜ

    • βₜ crece suavemente de ~0 a ~0.02.
      • primeros pasos → poco ruido
      • últimos pasos → mucho ruido
In [8]:
# Animación snapshots
anim = @animate for (i, Xsnap) in enumerate(snapshots)
t = (i-1) * t_interval
scatter(data[1,:], data[2,:], ms=2, color=:blue, label="Original",
title="Forward t=$t", xlim=(-6,6), ylim=(-6,6), legend=:topright)
scatter!(Xsnap[1,:], Xsnap[2,:], ms=2, color=:red, alpha=0.5, label="Difuso")
end
gif(anim, "forward_diffusion.gif", fps=7)
[ Info: Saved animation to /content/forward_diffusion.gif
Out[8]:
No description has been provided for this image
  • @animate recorre cada snapshot.
  • En cada frame superpone los datos originales (azul) y los difusos (rojo).
  • El título indica el paso t correspondiente.
In [9]:
# -------------------------------------
# 4. Modelo de denoising ε_θ
# -------------------------------------
denoise_model = Chain(
    Dense(3, 128, relu),
    Dense(128, 128, relu),
    Dense(128, 2)
) |> f32
function forward_diffusion(x0, t_idxs)
    noise = randn(Float32, size(x0))
    a = sqrt_αc[t_idxs]'
    b = sqrt_1m_αc[t_idxs]'
    xt = a .* x0 .+ b .* noise
    return xt, noise
end
# -------------------------------------
# 5. Entrenamiento de ε_θ
# -------------------------------------
opt = Flux.Adam(1e-3)
state_den = Flux.setup(opt, denoise_model)

x0_train = Float32.(data)
epochs = 2000; batch = 128

println("Entrenando denoise_model...")
@showprogress for epoch in 1:epochs
    idx = rand(1:size(x0_train, 2), batch)
    x_batch = x0_train[:, idx]
    t_idxs = rand(1:T, batch)
    xt, real_noise = forward_diffusion(x_batch, t_idxs)
    t_norm = Float32.(t_idxs' / T)
    input = vcat(xt, t_norm)

    loss, grads = Flux.withgradient(denoise_model) do m
        pred_noise = m(input)
        Flux.mse(pred_noise, real_noise)
    end

    Flux.update!(state_den, denoise_model, grads[1])
end
println("denoise_model entrenado.")
Entrenando denoise_model...
┌ Warning: Layer with Float32 parameters got Float64 input.
│   The input will be converted, but any earlier layers may be very slow.
│   layer = Dense(3 => 128, relu)  # 512 parameters
│   summary(x) = "3×128 Matrix{Float64}"
└ @ Flux ~/.julia/packages/Flux/uRn8o/src/layers/stateless.jl:60
Progress: 100%|█████████████████████████████████████████| Time: 0:00:32
denoise_model entrenado.
In [10]:
# -------------------------------------
# 6. Entrenamiento del clasificador
# -------------------------------------
emb_clf = Flux.Embedding(T, 32)
clf_head = Chain(
    Dense(34, 64, relu), # 2 (datos) + 32 (embedding)
    Dense(64, 32, relu),
    Dense(32, 2) # Logits, sin softmax
)
model_clf = (embedding=emb_clf, head=clf_head)
opt_c = Flux.Adam(1e-3)
state_clf = Flux.setup(opt_c, model_clf)

println("Entrenando clasificador...")
@showprogress for epoch in 1:1000
    loader = DataLoader((data, labels), batchsize=128, shuffle=true)
    for (x0b, yb) in loader
        x0b = Float32.(x0b)
        t_idxs = rand(1:T, length(yb))
        xt, _ = forward_diffusion(x0b, t_idxs)
        yoh = onehotbatch(yb, [0, 1])
        loss, grads = Flux.withgradient(model_clf) do m
            te = m.embedding(t_idxs)
            inp = vcat(xt, te)
            logits = m.head(inp)
            Flux.logitcrossentropy(logits, yoh)
        end

        Flux.update!(state_clf, model_clf, grads[1])
    end
end
println("Clasificador entrenado.")
Entrenando clasificador...
Progress: 100%|█████████████████████████████████████████| Time: 0:00:13
Clasificador entrenado.
In [23]:
# -------------------------------------
# 8. Muestreo inverso con guía
# -------------------------------------
using Zygote
using ProgressMeter
using Plots

# Ajustes: reduce num_samples o aumenta snapshot_interval si es muy lento
num_samples = 500           # mismo número para todos los procesos para facilitar la comparación
snapshot_interval = 10      # cada cuántos timesteps guardamos un snapshot
s_guidance = 30.0f0         # fuerza de la guía (s)
gif_fps = 4

# Función que devuelve un arreglo de snapshots (cada snapshot es 2 × num_samples)
# Ahora acepta un argumento opcional `classifier` para evitar referenciar una variable global `model_clf`.
# `classifier` debe ser un NamedTuple o tupla con campos/índices: (embedding=..., head=...)
function sample_reverse_history(model;
        guided=false,
        classifier=nothing,   # <-- pasar model_clf aquí cuando guided=true
        s=30.0f0,
        num_samples=500,
        target_class=0,
        snapshot_interval=10)

    if guided && classifier === nothing
        error("guided=true pero no se pasó `classifier`. Pasa classifier=model_clf (ej. (embedding=..., head=...)).")
    end

    xt = randn(Float32, 2, num_samples)
    histories = Vector{Array{Float32,2}}()
    push!(histories, copy(xt))  # t = T (estado inicial ruidoso)

    class_idx = target_class + 1

    @showprogress for ti in T:-1:1
        # normalización temporal en la forma (1, num_samples)
        t_norm = fill(Float32(ti / T), 1, num_samples)
        model_input = vcat(xt, t_norm)   # (2 + 1) × num_samples si ese es el formato esperado
        ε_pred = model(model_input)      # 2 × num_samples

        αt = αs[ti]
        αc_t = αc[ti]
        βt = β[ti]

        term1 = (1.0f0 / sqrt(αt)) .* (xt .- (βt / sqrt(1.0f0 - αc_t)) .* ε_pred)

        if guided
            # usar el classifier pasado como argumento en lugar de `model_clf` global
            emb_layer = classifier.embedding
            head_layer = classifier.head

            # embeddings del timestep: (emb_dim, num_samples)
            te = emb_layer(fill(ti, num_samples))

            # sanity check del índice de clase (opcional, solo en la primera iteración)
            if ti == T
                logits_dummy = head_layer(vcat(xt[:,1:1], te[:,1:1]))
                n_classes = size(logits_dummy, 1)
                if class_idx < 1 || class_idx > n_classes
                    error("target_class fuera de rango. Debe ser 0..$(n_classes-1). Recibido: $target_class")
                end
            end

            # inicializar ∇logp con la misma forma que xt
            ∇logp = zeros(Float32, size(xt))

            # gradiente por ejemplo (puede ser lento para num_samples grandes)
            for i in 1:num_samples
                x_sample = xt[:, i:i]   # 2×1
                te_i = te[:, i:i]       # emb_dim × 1

                f = x -> begin
                    logits = head_layer(vcat(x, te_i))        # 2×1
                    logp = Flux.logsoftmax(logits; dims=1)   # 2×1
                    return logp[class_idx]                   # escalar 1×1
                end

                g_tuple = Zygote.gradient(f, x_sample)
                g = g_tuple[1]   # 2×1
                ∇logp[:, i] = vec(g)
            end

            # aplicar término de guía (como en tu pseudocódigo)
            term1 .+= s .* βt .* ∇logp
        end

        # ruido de transición si no es el último paso
        if ti > 1
            xt = term1 .+ sqrt(βt) .* randn(Float32, size(xt))
        else
            xt = term1
        end

        # guardar snapshot en los intervalos deseados (y guardar también el final)
        if (ti % snapshot_interval == 0) || (ti == 1)
            push!(histories, copy(xt))
        end
    end

    return histories
end

# --- Generar historiales: sin guía y con guía (clase 0 y 1) ---
println("Generando historial SIN guía...")
hist_ng = sample_reverse_history(denoise_model; guided=false, s=s_guidance,
                                 num_samples=num_samples, target_class=0,
                                 snapshot_interval=snapshot_interval)

# Para las corridas guiadas, PASAMOS explícitamente el classifier (model_clf) como argumento.
# Asegúrate de que `model_clf` exista en el scope (p. ej. (embedding=emb_clf, head=clf_head)).
# Si tu classifier está guardado en una variable distinta, pásala aquí.
if !isdefined(Main, :model_clf)
    error("No se encontró `model_clf` en el scope. Define model_clf = (embedding=..., head=...) antes de llamar a las versiones guiadas.")
end

println("Generando historial GUIADA (clase 0)...")
hist_g0 = sample_reverse_history(denoise_model; guided=true, classifier=model_clf,
                                 s=s_guidance, num_samples=num_samples, target_class=0,
                                 snapshot_interval=snapshot_interval)

println("Generando historial GUIADA (clase 1)...")
hist_g1 = sample_reverse_history(denoise_model; guided=true, classifier=model_clf,
                                 s=s_guidance, num_samples=num_samples, target_class=1,
                                 snapshot_interval=snapshot_interval)

# Asegurar que las longitudes coincidan para la animación (usar el mínimo)
n_frames = min(length(hist_ng), length(hist_g0), length(hist_g1))

# Crear animación con 2 subplots por frame:
anim = @animate for k in 1:n_frames
    t_current = T - ((k-1) * snapshot_interval)  # aproximación del timestep mostrado

    # Subplot izquierdo: sin guía
    p_left = scatter(data[1,:], data[2,:], ms=2, color=:blue, alpha=0.25, label="Original")
    scatter!(p_left, hist_ng[k][1,:], hist_ng[k][2,:], ms=3, color=:gray, alpha=0.5, label="Sin guía")
    title!(p_left, "Sin guía   t≈$t_current")
    xlims!(p_left, -6, 6); ylims!(p_left, -6, 6)
    xlabel!(p_left, ""); ylabel!(p_left, "")
    plot!(p_left, legend=:topright)

    # Subplot derecho: ambas guiadas superpuestas (rojo y verde)
    p_right = scatter(data[1,:], data[2,:], ms=2, color=:blue, alpha=0.25, label="Original")
    scatter!(p_right, hist_g0[k][1,:], hist_g0[k][2,:], ms=3, color=:red, alpha=0.6, label="Guiada c=0")
    scatter!(p_right, hist_g1[k][1,:], hist_g1[k][2,:], ms=3, color=:green, alpha=0.6, label="Guiada c=1")
    title!(p_right, "Guiadas   t≈$t_current")
    xlims!(p_right, -6, 6); ylims!(p_right, -6, 6)
    xlabel!(p_right, ""); ylabel!(p_right, "")
    plot!(p_right, legend=:topright)

    # Combinar los dos subplots en una sola figura (layout 1x2)
    plot(p_left, p_right, layout=(1,2), size=(1050,600))
end

# Guardar GIF
gif(anim, "guided_vs_unguided_generation.gif", fps=gif_fps)
println("GIF guardado como guided_vs_unguided_generation.gif")
Generando historial SIN guía...
Progress: 100%|█████████████████████████████████████████| Time: 0:00:00
Generando historial GUIADA (clase 0)...
Progress: 100%|█████████████████████████████████████████| Time: 0:00:11
Generando historial GUIADA (clase 1)...
Progress: 100%|█████████████████████████████████████████| Time: 0:00:11
GIF guardado como guided_vs_unguided_generation.gif
[ Info: Saved animation to /content/guided_vs_unguided_generation.gif
In [24]:
gif(anim, "guided_vs_unguided_generation.gif", fps=gif_fps)
[ Info: Saved animation to /content/guided_vs_unguided_generation.gif
Out[24]:
No description has been provided for this image
In [20]:
# -------------------------------------
# 9. Evaluación del clasificador
# -------------------------------------
test_t = 250
test_samples = 100
test_data = Float32.(hcat(rand(gauss1, test_samples), rand(gauss2, test_samples)))
test_labels = vcat(fill(0, test_samples), fill(1, test_samples))
t_idxs = fill(test_t, size(test_data, 2))
test_xt, _ = forward_diffusion(test_data, t_idxs)
te = model_clf.embedding(t_idxs)
clf_input = vcat(test_xt, te)

logits = model_clf.head(clf_input)
# Corrección final: Conversión correcta de logits a clases
preds = Flux.onecold(logits, [1, 2]) .- 1 # Convertir a 0 o 1
accuracy = sum(preds .== test_labels) / length(test_labels)
println("Precisión del clasificador en t=$test_t: $(round(accuracy*100, digits=2))%")
Precisión del clasificador en t=250: 96.5%

Referencias¶

  • J. Ho, A. Jain, and P. Abbeel, Denoising diffusion probabilistic models, arXiv preprint arxiv:2006.11239, (2020).
  • A. J. Jonathan Ho, Denoising diffusion probabilistic models. https://github.com/hojonathanho/diffusion, 2020.
  • J. Sohl-Dickstein, E. A. Weiss, N. Maheswaranathan, and S. Ganguli, Deep un- supervised learning using nonequilibrium thermodynamics, CoRR, abs/1503.03585 (2015).