Módulo 7 · Visión por computadora
CLIP, GAN y difusión
En esta páginaEl problema
El problema
Tienes imágenes sintéticas de figuras. Cada imagen llega con una descripción de dos palabras en lugar de una etiqueta de clase: una forma y un color. Hay tres formas y tres colores. Los pares de entrenamiento cubren ocho de las nueve combinaciones:
| forma | rojo | azul | verde |
|---|---|---|---|
| círculo | sí | sí | sí |
| cuadrado | sí | sí | sí |
| triángulo | sí | sí | no |
Un clasificador como el de Convoluciones y clasificación termina en una capa con una salida por clase. Entrenado con estos pares, tendría ocho salidas y ninguna para triángulo verde. La tarea pide además producir ejemplos nuevos que se parezcan a los datos reales. Un clasificador no produce ejemplos: asigna una clase a uno que ya existe.
Espacio compartido
CLIP (Contrastive Language-Image Pre-training) usa dos encoders. El encoder de imagen convierte una imagen en un vector. El encoder de texto convierte una descripción en un vector. Cada encoder termina en una proyección lineal a la misma dimensión, así que los dos vectores quedan en un espacio compartido. Ahí una imagen y un texto se comparan con similitud coseno.
En esta lección, texto_a_vector marca con 1 las palabras de la descripción dentro de un vocabulario de seis palabras. generar_imagenes produce vectores de 8 características: una mezcla fija de la forma y el color, más ruido. Esos vectores hacen el papel de la salida de una CNN. La celda plegada importa las bibliotecas, define las dos funciones, arma las listas de descripciones y genera un lote de 5 imágenes de cuadrado azul. La mezcla es una matriz fija de 6×8 con una fila por palabra, y el ruido tiene desviación estándar 0.3.
Mostrar códigoOcultar código
from collections import Counter
import numpy as np
import torch
import torch.nn.functional as F
from torch import nn
formas = ["círculo", "cuadrado", "triángulo"]
colores = ["rojo", "azul", "verde"]
vocabulario = formas + colores
def texto_a_vector(texto):
palabras = texto.split()
return torch.tensor([float(palabra in palabras) for palabra in vocabulario])
rng = np.random.default_rng(0)
# fila k: aporte de la palabra k del vocabulario a las 8 características
mezcla = torch.tensor(rng.normal(size=(6, 8)), dtype=torch.float32)
def generar_imagenes(texto, n):
ruido = torch.tensor(rng.normal(scale=0.3, size=(n, 8)), dtype=torch.float32)
return texto_a_vector(texto) @ mezcla + ruido
textos = [f"{forma} {color}" for forma in formas for color in colores]
textos_entrenamiento = [t for t in textos if t != "triángulo verde"]
forma_lote = generar_imagenes("cuadrado azul", 5).shape
El vocabulario lista primero las tres formas y luego los tres colores. textos reúne las nueve descripciones posibles y textos_entrenamiento deja fuera triángulo verde.
Pregunta. ¿Qué vector devuelve texto_a_vector("cuadrado azul")? ¿Qué forma tiene generar_imagenes("cuadrado azul", 5)?
Ver resultado
La columna «expresión» es el código que se evalúa y «resultado» es lo que devuelve. La fila de .shape dice que 5 imágenes dan una matriz de 5×8.
| expresión | resultado |
|---|---|
vocabulario | ['círculo', 'cuadrado', 'triángulo', 'rojo', 'azul', 'verde'] |
texto_a_vector("cuadrado azul") | tensor([0., 1., 0., 0., 1., 0.]) |
generar_imagenes("cuadrado azul", 5).shape | torch.Size([5, 8]) |
len(textos_entrenamiento) | 8 |
cuadrado y otro en azul. Cada imagen es un vector de 8 números. La similitud coseno necesita dos vectores de la misma longitud, así que un texto y una imagen no se comparan directamente. Los encoders resuelven eso: los dos proyectan a 4 dimensiones.Cada encoder es una capa nn.Linear sin sesgo. similitudes codifica un lote de imágenes y un lote de textos, normaliza cada vector a longitud 1 con F.normalize(..., dim=-1) y multiplica. El resultado es la matriz de similitud S.
torch.manual_seed(0)
encoder_imagen = nn.Linear(8, 4, bias=False)
encoder_texto = nn.Linear(6, 4, bias=False)
def similitudes(x_imagen, x_texto):
a = F.normalize(encoder_imagen(x_imagen), dim=-1)
b = F.normalize(encoder_texto(x_texto), dim=-1)
return a @ b.T # vectores de norma 1: cada producto es una similitud coseno
En un lote de pares, la imagen i va con el texto i. lote_texto apila los 8 textos de entrenamiento y lote_imagenes genera una imagen nueva para cada uno. torch.no_grad() calcula S sin registrar operaciones para gradientes, y argmax(dim=1) devuelve la columna con el valor mayor de cada fila.
Pregunta. Con un lote de 8 pares, ¿qué forma tiene S? ¿Qué representa S[i, j]? En un modelo que empareja bien, ¿en qué posición de cada fila está el máximo? ¿Esperas eso antes de entrenar?
lote_texto = torch.stack([texto_a_vector(t) for t in textos_entrenamiento])
def lote_imagenes():
return torch.cat([generar_imagenes(t, 1) for t in textos_entrenamiento])
with torch.no_grad():
S = similitudes(lote_imagenes(), lote_texto)
print(S.shape)
print(S.diagonal().round(decimals=2))
print(S.argmax(dim=1))
La salida esperada está oculta.
CLIP entrena los dos encoders con una pérdida contrastiva simétrica. Cada fila de S es un problema de clasificación: la imagen i debe elegir el texto i entre los 8 textos del lote. Cada columna es el problema inverso: el texto j debe elegir la imagen j. La pérdida promedia la entropía cruzada de las filas y la de las columnas. Antes de la entropía cruzada, S se divide entre una temperatura. CLIP aprende la temperatura; aquí queda fija en 0.1.
En perdida_clip, las etiquetas son torch.arange(N): la respuesta correcta de la fila i es la columna i. logits.T convierte las columnas en filas para la entropía cruzada del texto hacia la imagen.
def perdida_clip(S, temperatura=0.1):
etiquetas = torch.arange(S.shape[0])
logits = S / temperatura
perdida_filas = F.cross_entropy(logits, etiquetas)
perdida_columnas = F.cross_entropy(logits.T, etiquetas)
return (perdida_filas + perdida_columnas) / 2
Un solo optimizador Adam recibe los parámetros de los dos encoders, así que cada paso los actualiza juntos. Cada paso usa un lote nuevo de imágenes.
Pregunta. ¿Cuánto vale la entropía cruzada si el modelo da la misma probabilidad a los 8 textos? Después de 300 pasos, ¿la pérdida queda por encima o por debajo de ese valor?
parametros = list(encoder_imagen.parameters()) + list(encoder_texto.parameters())
optimizador = torch.optim.Adam(parametros, lr=0.05)
print("uniforme:", round(float(np.log(8)), 3))
for paso in range(300):
perdida = perdida_clip(similitudes(lote_imagenes(), lote_texto))
optimizador.zero_grad()
perdida.backward()
optimizador.step()
if paso % 100 == 0:
print(paso, round(perdida.item(), 3))
La salida esperada está oculta.
Con los encoders entrenados, se recalcula S con un lote nuevo de imágenes, igual que antes de entrenar.
with torch.no_grad():
S = similitudes(lote_imagenes(), lote_texto)
Pregunta. ¿Cambia argmax? ¿Qué valores esperas en la diagonal de S?
Ver resultado
Cada fila es una imagen del lote nuevo y cada columna numerada es un texto de entrenamiento, en el mismo orden. El valor es S[i, j] redondeado a un decimal, y la columna «argmax» es el texto que elige la imagen. La fila 2 dice que la imagen 2 tiene similitud 1.0 con el texto 2, −1.0 con el texto 3, y elige el texto 2.
| imagen | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | argmax |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.9 | 0.4 | -0.5 | 0.4 | 0.0 | -1.0 | 0.2 | -0.2 | 0 |
| 1 | 0.5 | 0.9 | 0.4 | -0.5 | 0.0 | -0.5 | -0.5 | 0.1 | 1 |
| 2 | -0.4 | 0.5 | 1.0 | -1.0 | -0.2 | 0.4 | -0.6 | 0.3 | 2 |
| 3 | 0.0 | -0.4 | -0.9 | 0.9 | 0.6 | 0.0 | 0.3 | -0.2 | 3 |
| 4 | -0.6 | -0.1 | -0.3 | 0.3 | 0.8 | 0.6 | -0.4 | 0.0 | 4 |
| 5 | -1.0 | -0.3 | 0.4 | -0.2 | 0.3 | 1.0 | -0.4 | 0.2 | 5 |
| 6 | 0.2 | -0.5 | -0.6 | 0.4 | -0.3 | -0.4 | 1.0 | 0.3 | 6 |
| 7 | -0.1 | 0.1 | 0.1 | -0.5 | -0.3 | -0.3 | 0.5 | 0.9 | 7 |
Con este lote, perdida_clip(S) vale 0.025.
argmax cambia: ahora devuelve [0, 1, …, 7]. La diagonal de S está entre 0.8 y 1, y la pérdida de este lote, 0.025, sigue cerca de 0. Cada imagen quedó cerca de su descripción y lejos de las otras siete.Clasificación zero-shot
En CLIP, una clase es una descripción. Para clasificar una imagen se escribe una descripción por clase candidata, se codifica cada una con el encoder de texto y se elige la de mayor similitud. Clasificar sin haber entrenado con ejemplos etiquetados de esas clases se llama clasificación zero-shot.
La celda compara dos listas de candidatas y cuenta con Counter cuántas veces se elige cada descripción.
Pregunta. Generas 50 imágenes nuevas de triángulo verde. Si las candidatas son solo las 8 descripciones de entrenamiento, ¿cuáles elegirá el modelo? Si agregas triángulo verde como novena candidata, ¿qué fracción de las 50 acierta?
imagenes_nuevas = generar_imagenes("triángulo verde", 50)
candidatas_8 = torch.stack([texto_a_vector(t) for t in textos_entrenamiento])
candidatas_9 = torch.stack([texto_a_vector(t) for t in textos])
with torch.no_grad():
elegidas_8 = similitudes(imagenes_nuevas, candidatas_8).argmax(dim=1)
elegidas_9 = similitudes(imagenes_nuevas, candidatas_9).argmax(dim=1)
print(Counter(textos_entrenamiento[i] for i in elegidas_8.tolist()))
print(Counter(textos[i] for i in elegidas_9.tolist()))
print((elegidas_9 == textos.index("triángulo verde")).float().mean().item())
La salida esperada está oculta.
El CLIP real usa un ResNet o un ViT como encoder de imagen y un transformer como encoder de texto. Se entrenó con 400 millones de pares imagen-texto tomados de internet. Para clasificar ImageNet sin usar sus imágenes de entrenamiento, escribe una descripción por clase con una plantilla como "a photo of a {clase}.". Esa plantilla es el punto de partida. Al promediar los vectores de texto de 80 plantillas distintas por clase, la clasificación zero-shot del mejor modelo CLIP iguala la exactitud de un ResNet-50 entrenado con las etiquetas de ImageNet.
Generador y discriminador
Una GAN (Generative Adversarial Network) tiene dos redes. El generador G recibe ruido z y produce una muestra. El discriminador D recibe una muestra y devuelve la probabilidad de que sea real. Se entrenan por turnos con entropía cruzada binaria:
- pérdida de
D:BCE(D(real), 1) + BCE(D(G(z)), 0) - pérdida de
G:BCE(D(G(z)), 1)
Una GAN de imágenes produce miles de píxeles. El mecanismo es el mismo con un solo número. Aquí los datos reales son 1000 valores con media 4 y desviación estándar 0.5. El generador es G(z) = a·z + b, con a = 1 y b = 0. El discriminador está fijo: D(x) = sigmoide(2·(x − 2)). Con requires_grad=True, PyTorch guarda en a.grad y b.grad el gradiente que calcula backward().
torch.manual_seed(0)
reales = 4 + 0.5 * torch.randn(1000, 1)
def discriminador_fijo(x):
return torch.sigmoid(2 * (x - 2))
a = torch.tensor(1.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
generadas = a * torch.randn(8, 1) + b
La celda siguiente toma 8 muestras reales y las 8 generadas. F.binary_cross_entropy recibe probabilidades y etiquetas, y torch.ones_like y torch.zeros_like crean etiquetas 1 y 0 con la misma forma que las probabilidades.
Pregunta. ¿Qué probabilidad media da D a las muestras reales y a las generadas? ¿Cuál de las dos pérdidas es grande? ¿Qué signo tiene el gradiente de la pérdida de G respecto a b, y hacia dónde mueve el descenso de gradiente las muestras generadas?
d_real = discriminador_fijo(reales[:8])
d_generadas = discriminador_fijo(generadas)
perdida_d = F.binary_cross_entropy(
d_real, torch.ones_like(d_real)
) + F.binary_cross_entropy(d_generadas, torch.zeros_like(d_generadas))
perdida_g = F.binary_cross_entropy(d_generadas, torch.ones_like(d_generadas))
perdida_g.backward()
print(round(d_real.mean().item(), 3), round(d_generadas.mean().item(), 3))
print(round(perdida_d.item(), 3), round(perdida_g.item(), 3))
print(round(b.grad.item(), 3))
La salida esperada está oculta.
Ahora los dos aprenden. D es un MLP 1 → 16 → 1 que devuelve un logit, así que se usa BCEWithLogitsLoss. G es nn.Linear(1, 1). Al entrenar D, las muestras generadas se pasan con detach(): ese paso no debe calcular gradientes para G.
paso_gan hace un turno de cada red con un lote de 64 reales y 64 generadas. Devuelve las dos pérdidas del turno.
bce = nn.BCEWithLogitsLoss()
unos, ceros = torch.ones(64, 1), torch.zeros(64, 1)
def paso_gan(generador, discriminador, opt_g, opt_d):
x_real = reales[torch.randint(0, 1000, (64,))]
x_generada = generador(torch.randn(64, 1))
logit_real = discriminador(x_real)
logit_generada = discriminador(x_generada.detach())
perdida_d = bce(logit_real, unos) + bce(logit_generada, ceros)
opt_d.zero_grad()
perdida_d.backward()
opt_d.step()
perdida_g = bce(discriminador(x_generada), unos) # G quiere que D diga «real»
opt_g.zero_grad()
perdida_g.backward()
opt_g.step()
return perdida_d.item(), perdida_g.item()
entrenar_gan crea las dos redes con una semilla, les da un optimizador Adam a cada una y repite paso_gan. Devuelve el generador y el historial de pérdidas, con una fila por paso.
def entrenar_gan(semilla, pasos=2000):
torch.manual_seed(semilla)
generador = nn.Linear(1, 1)
discriminador = nn.Sequential(nn.Linear(1, 16), nn.ReLU(), nn.Linear(16, 1))
# beta1 = 0.5 en lugar de 0.9: menos inercia, valor habitual en GAN
opt_g = torch.optim.Adam(generador.parameters(), lr=0.01, betas=(0.5, 0.999))
opt_d = torch.optim.Adam(discriminador.parameters(), lr=0.01, betas=(0.5, 0.999))
historial = [paso_gan(generador, discriminador, opt_g, opt_d) for _ in range(pasos)]
return generador, np.array(historial)
Para cada semilla, la celda siguiente genera 1000 muestras y promedia las pérdidas de los últimos 200 pasos.
Pregunta. Si al final D ya no distingue reales de generadas, ¿qué probabilidad da a cualquier muestra? ¿Cuánto valen entonces la pérdida de D y la de G? ¿Qué valores de peso y sesgo de G producen exactamente la distribución real? Con las semillas 3, 4 y 5, ¿llegan las tres a esos valores?
print("ln 4 =", round(float(np.log(4)), 3), " ln 2 =", round(float(np.log(2)), 3))
print("semilla peso sesgo media desviación [pérdida_D pérdida_G]")
for semilla in [3, 4, 5]:
generador, historial = entrenar_gan(semilla)
with torch.no_grad():
muestras = generador(torch.randn(1000, 1))
finales = historial[-200:].mean(axis=0).round(3)
peso_sesgo = [round(generador.weight.item(), 2), round(generador.bias.item(), 2)]
media_desv = [round(muestras.mean().item(), 2), round(muestras.std().item(), 2)]
print(semilla, *peso_sesgo, *media_desv, finales)
La salida esperada está oculta.
Las GAN de imágenes usan redes convolucionales en G y en D. El tutorial DCGAN de PyTorch entrena una así para generar caras con el mismo ciclo de dos pérdidas.
Ruido hacia adelante
Un modelo de difusión como DDPM (Denoising Diffusion Probabilistic Models) parte de dos procesos. El proceso hacia adelante agrega ruido gaussiano en T pasos pequeños hasta destruir la imagen. El proceso inverso es el que aprende la red: quitar ese ruido paso a paso.
En cada paso hacia adelante:
x_t = √(1 − β_t) · x_(t−1) + √β_t · ε, con ε ruido normal estándar.
DDPM usa T = 1000 y β_t crece en línea recta de 0.0001 a 0.02. Con ᾱ_t, el producto de (1 − β_s) para s de 1 a t, se llega a cualquier paso de un solo salto:
x_t = √ᾱ_t · x_0 + √(1 − ᾱ_t) · ε
torch.linspace produce los T valores de β_t y torch.cumprod calcula los productos acumulados ᾱ_t. ruido_directo aplica la fórmula de un salto.
T = 1000
betas = torch.linspace(1e-4, 0.02, T)
alfas_barra = torch.cumprod(1 - betas, dim=0)
def ruido_directo(x0, t, epsilon):
alfa_barra = alfas_barra[t - 1] # t va de 1 a T; el índice, de 0 a T − 1
return alfa_barra.sqrt() * x0 + (1 - alfa_barra).sqrt() * epsilon
El vector x0 es una imagen de 4×4 aplanada: un cuadrado de valor 1 sobre un fondo de valor −1.
cuadrado = torch.tensor(
[[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], dtype=torch.float32
)
x0 = (2 * cuadrado - 1).flatten() # 0 → −1 y 1 → 1, en un vector de 16
La celda siguiente imprime los dos coeficientes en tres pasos y una muestra de x_t, devuelta a la cuadrícula con reshape(4, 4).
Pregunta. ¿Cuánto vale √ᾱ_t, el coeficiente de la señal, en t = 1000? ¿En cuál de los pasos 10, 250 y 1000 ya no se reconoce el cuadrado?
torch.manual_seed(0)
print("t √ᾱ_t √(1 − ᾱ_t)")
for t in [10, 250, 1000]:
alfa_barra = alfas_barra[t - 1]
print(t, round(alfa_barra.sqrt().item(), 3), round((1 - alfa_barra).sqrt().item(), 3))
x_t = ruido_directo(x0, t, torch.randn(16))
print(x_t.reshape(4, 4).round(decimals=1) + 0.0)
La salida esperada está oculta.
La fórmula de un salto debe dar la misma distribución que los 1000 pasos pequeños. Se comprueba con 20000 copias de un mismo valor inicial, a las que se aplica cada paso con su propio β_t.
Pregunta. Partes de 20000 copias de x_0 = 1 y aplicas los pasos uno por uno. En t = 100, t = 500 y t = 1000, ¿qué media y qué desviación estándar esperas según la fórmula de un salto?
Ver resultado
Cada fila es un paso t. «media» y «desviación» se miden en las 20000 copias después de aplicar los t pasos uno por uno. Las dos últimas columnas son la media y la desviación estándar que predice la fórmula de un salto. La fila 500 dice que las copias tienen media 0.284 y desviación 0.96, y que la fórmula predice 0.28 y 0.96.
| t | media | desviación | √ᾱ_t | √(1 − ᾱ_t) |
|---|---|---|---|---|
| 100 | 0.944 | 0.323 | 0.947 | 0.321 |
| 500 | 0.284 | 0.96 | 0.28 | 0.96 |
| 1000 | 0.005 | 1.002 | 0.006 | 1.0 |
√ᾱ_t y la desviación estándar con √(1 − ᾱ_t), con diferencias en el tercer decimal. Para entrenar no hace falta recorrer los pasos anteriores: se elige un t al azar y se salta directo a x_t.Predicción del ruido
En DDPM, la red recibe x_t y t y predice el ruido ε que se sumó. La pérdida es el error cuadrático medio entre ε y la predicción ε̂. Si conoces ε, puedes despejar la imagen original:
x_0 = (x_t − √(1 − ᾱ_t) · ε) / √ᾱ_t
F.mse_loss calcula el error cuadrático medio entre dos tensores.
Pregunta. En t = 250, ¿cuánto vale la pérdida si la red predice ε exacto? ¿Y si predice un vector de ceros?
torch.manual_seed(1)
t = 250
epsilon = torch.randn(16)
x_t = ruido_directo(x0, t, epsilon)
print(round(F.mse_loss(epsilon, epsilon).item(), 3))
print(round(F.mse_loss(torch.zeros(16), epsilon).item(), 3))
La salida esperada está oculta.
Con ε exacto, el despeje devuelve x0: ninguna componente difiere en más de 1e-5. La red, en cambio, solo da una predicción ε̂ con error.
Pregunta. Si la predicción tiene un error de 0.1 en cada componente, ¿cuánto error queda en x_0 en t = 10 y en t = 1000?
Ver resultado
Cada fila es un paso t y el error que queda en cada componente de x_0 al despejar con un ε̂ que se equivoca en 0.1 en cada componente. La fila 500 dice que, en t = 500, ese error de 0.1 se convierte en 0.342 en x_0.
| t | error en x_0 |
|---|---|
| 10 | 0.004 |
| 250 | 0.095 |
| 500 | 0.342 |
| 1000 | 15.741 |
ε̂ se multiplica por √(1 − ᾱ_t) / √ᾱ_t al despejar. En t = 10 queda en 0.004. En t = 1000 crece a 15.7, más de siete veces la distancia entre el fondo y el cuadrado. Desde ruido puro, la imagen no se despeja de un salto. Para generar, DDPM empieza en ruido x_T y repite 1000 pasos inversos: en cada uno resta solo una parte del ruido predicho.Stable Diffusion aplica este proceso a vectores latentes de un autoencoder en lugar de píxeles. Guía cada paso con los vectores de la descripción que produce un encoder de texto de CLIP.
Práctica
Traza guiada
Con esta matriz de similitud de 3 imágenes y 3 textos, escribe antes de procesar: el texto que elige cada imagen, la imagen que elige cada texto y si perdida_clip(S_traza) queda por encima o por debajo de ln 3 ≈ 1.099.
S_traza = torch.tensor(
[
[0.9, 0.1, 0.0],
[0.2, 0.3, 0.8],
[0.1, 0.7, 0.6],
]
)
Comprueba con S_traza.argmax(dim=1), S_traza.argmax(dim=0) y perdida_clip. Las filas 1 y 2 eligen el texto de la otra, y la pérdida vale 2.079, por encima de ln 3. Si tu predicción no coincide, revisa qué eje recorre argmax en cada caso.
Temperatura
Repite el entrenamiento de CLIP con encoders nuevos y temperatura=1.0 dentro de perdida_clip. Antes de procesar, predice si la pérdida final baja de ln 8 y si la diagonal de S se acerca a 1. Compara con la versión de temperatura 0.1 y relaciona la diferencia con el rango de S / temperatura, que con temperatura 1 va solo de −1 a 1.
GAN de otra distribución
Cambia los datos reales a 1000 valores con media −2 y desviación estándar 1.5, y entrena entrenar_gan con las semillas 0, 1 y 2. Antes de procesar, escribe el peso y el sesgo que reproducen esa distribución y las pérdidas de equilibrio. Comprueba con assert que la media de las muestras de al menos una semilla está a menos de 0.2 de −2. Reporta qué semillas colapsan.
Reto opcional
Entrena un MLP que reciba x_t y t / T para los datos reales de la GAN y prediga ε. En cada paso, elige un t al azar para cada muestra y construye x_t con ruido_directo. Compara su pérdida de validación con la del predictor de ceros y reporta en qué rangos de t la red predice mejor el ruido.
Errores que revisar
- Si la diagonal de
Sno domina después de entrenar, revisa que la imagenidel lote vaya con el textoiy que las etiquetas seantorch.arange(N). - Si un lote contiene dos pares con la misma descripción, la pérdida de CLIP trata un par correcto como negativo. Revisa que las descripciones del lote sean distintas.
- Si aparece
RuntimeError: Trying to backward through the graph a second time, faltadetach()en las muestras generadas del paso deD. - Si las muestras generadas tienen desviación estándar cercana a 0 y el peso de
Gestá cerca de 0, el generador colapsó: la pérdida puede verse en equilibrio aunque la distribución no coincida. - Si
x_tya es ruido puro ent = 10, revisaalfas_barra: debe ser el producto acumulado de1 − β, no deβ.
Cierre
Explica qué sustituye a la capa con una salida por clase en CLIP y qué aprende a producir cada red en una GAN y en un modelo de difusión.
Lista de comprobación
- ¿Qué representa
S[i, j]y por qué la pérdida de CLIP usa las filas y las columnas? - ¿Por qué el modelo clasifica
triángulo verdesin ningún par de entrenamiento con esa descripción? - ¿Cuánto valen las pérdidas de
DyGcuandoDda 0.5 a todas las muestras, y por qué eso no garantiza queGcopie los datos? - ¿Qué fracción de la señal queda en
x_tsegúnᾱ_t, y por qué no se despejax_0de un salto desdet = 1000?
Documentación oficial
- CLIP: Learning Transferable Visual Models From Natural Language Supervision: los dos encoders, la pérdida contrastiva y la clasificación zero-shot.
- Generative Adversarial Networks: generador, discriminador y sus pérdidas.
- Tutorial DCGAN de PyTorch: una GAN convolucional de imágenes con el ciclo de entrenamiento completo.
- Denoising Diffusion Probabilistic Models: proceso hacia adelante, programa de
β_t, predicción del ruido y muestreo. - Curso de difusión de Hugging Face: modelos de difusión con código, desde el proceso de ruido hasta Stable Diffusion.