Autocodificadores variacionales (VAE): imágenes nuevas a partir de un espacio latente continuo
Modelo generativo con variable latente, un VAE escrito desde cero con el truco de reparametrización, un espacio latente ordenado, generación de dígitos a partir de ruido frente a un autocodificador corriente, el efecto de β y el colapso, la interpolación y dónde se usan los VAE.
Serie Fundamentos de IA Parte 44 de 52
- Mapa de la inteligencia artificial: qué es cada cosa y cómo encaja todo
- Breve historia de la inteligencia artificial: de Turing a los modelos de lenguaje
- Datos, modelo, entrenamiento, inferencia: los conceptos básicos en un solo ejemplo
- Cómo leer la notación matemática: símbolos, subíndices, sumatorios y cuantificadores
- Conjuntos y relaciones: las piezas con las que se construyen las definiciones en IA
- Funciones y aplicaciones: por qué todo modelo de IA es una función
- Lógica y demostraciones: las reglas con las que se razona en IA
- Grafos y árboles: la estructura sobre la que se apoyan la búsqueda y las redes neuronales
- Álgebra lineal para aprendizaje automático: vectores, matrices y por qué la IA sobre todo multiplica
- Derivadas, gradiente y regla de la cadena: cómo sabe un modelo en qué dirección aprender
- Descenso de gradiente: cómo aprende un modelo
- Qué es la probabilidad: del lanzamiento de un dado a los axiomas de Kolmogórov
- Probabilidad condicionada y teorema de Bayes: cómo cambiar una estimación cuando te enteras de algo nuevo
- Variable aleatoria y función de distribución: una sola función que lo dice todo sobre la distribución
- Función de densidad y función de probabilidad: por qué una variable continua tiene probabilidad cero en cada punto
- Esperanza, varianza y covarianza: dónde está una distribución, qué anchura tiene y qué se relaciona con qué
- Resumen de las distribuciones de probabilidad: Bernoulli, binomial, Poisson, uniforme, normal y exponencial
- Ley de los grandes números y teorema central del límite: por qué las medias se comportan de forma tan predecible
- Estadística y estimación de parámetros: máxima verosimilitud, MAP e intervalos de confianza
- Teoría de la información: entropía, entropía cruzada y divergencia KL, el origen de las funciones de pérdida
- Búsqueda en el espacio de estados: BFS, DFS, UCS y A* con animaciones
- Juegos y adversarios: minimax, poda alfa-beta y Monte Carlo tree search
- Satisfacción de restricciones: backtracking, propagación de restricciones y AC-3 con un sudoku y un mapa de regiones checas
- Lógica y sistemas basados en conocimiento: encadenamiento hacia delante y hacia atrás, resolución y sistemas expertos
- Planificación: STRIPS, el mundo de bloques, heurísticas a partir de la relajación y la planificación en los agentes de IA
- Búsqueda local y recocido simulado: un viajante por las capitales regionales checas
- Algoritmos genéticos: población, cruce, mutación y selección en una mochila para la excursión
- Estrategias evolutivas y programación genética: de la regla de un quinto a la ley de Kepler
- Optimización por colonia de hormigas: cómo un rastro de feromona encuentra un camino corto
- Optimización por enjambre de partículas: inercia, experiencia propia y experiencia del enjambre
- Neuroevolución: evolución de los pesos y la forma de las redes neuronales, y el algoritmo NEAT
- Conjuntos difusos y funciones de pertenencia: cómo escribir «algo cálido»
- Lógica difusa y probabilidad: por qué «algo cálido» no significa «probablemente cálido»
- Lógica difusa: t-normas, implicaciones y reglas SI–ENTONCES
- Control difuso: Mamdani y Sugeno, del ventilador al péndulo invertido sobre un carro
- Agrupamiento difuso: el algoritmo fuzzy c-means y los puntos entre clusters
- Sistemas neuro-difusos (ANFIS): reglas difusas que aprenden de los datos
- Cadenas de Markov: matriz de transición, distribución estacionaria y PageRank
- Modelos ocultos de Márkov: el algoritmo de Viterbi y el casino deshonesto
- Redes bayesianas: por qué está mojada la hierba y qué significa eso para el aspersor
- El filtro de Kalman: cómo estimar a partir de mediciones imprecisas dónde está un coche y a qué velocidad va
- Tipos de aprendizaje automático: supervisado, no supervisado, semisupervisado y más
- Prompts, contexto y RAG: cómo darle a un modelo la información correcta
- Autocodificadores variacionales (VAE): imágenes nuevas a partir de un espacio latente continuo
- Redes generativas adversarias (GAN): el falsificador contra el detective
- Modelos de difusión: cómo el ruido puro se convierte en una imagen
- Procesos de decisión de Markov: estados, acciones, recompensas y ecuaciones de Bellman
- Q-learning y DQN: aprender el valor de las acciones, de la tabla a la red neuronal
- Gradiente de la política y PPO: aprender directamente la política
- Cómo funciona un agente de IA: un modelo de lenguaje en un bucle con herramientas
- Programación con agentes de IA: encargo, pruebas y revisión
- Evaluación y seguridad de los sistemas de IA: cómo medir y cómo vigilar
Temario completo de la serie 52 de 83 temas publicados
- Por qué con un autocodificador corriente no se puede generar bien.
- Cómo funciona el VAE: una distribución en lugar de un punto, la penalización KL y el truco de reparametrización.
- Qué cuesta β, por qué las imágenes del VAE son borrosas y dónde se usan hoy los VAE.
Conviene conocer antes: Teoría de la información: entropía, entropía cruzada y divergencia KL, el origen de las funciones de pérdida
El autocodificador de la parte sobre representaciones sabe comprimir una imagen en unos pocos números y volver a componerla a partir de ellos. Surge una pregunta: si el decodificador sabe convertir un código en un dígito, ¿no podríamos darle un código que nos hemos inventado y obtener un dígito nuevo que nadie ha dibujado? Con un autocodificador corriente eso casi no funciona. El autocodificador variacional es una modificación suya que permite justo eso, y es el primero de los modelos generativos a los que nos dedicaremos en esta parte de la serie.
Continúa las partes Autocodificadores y representaciones y Teoría de la información (divergencia KL).
1. De la compresión a la generación
Definición — modelo generativo
Un modelo generativo aprende de los datos x1, …, xn una distribución p(x) de la que se pueda muestrear, es decir, producir nuevos x que se parecen a los datos pero no son copias de ellos. Los modelos con variable latente lo hacen en dos pasos: sortean un código z de una distribución sencilla p(z) (por ejemplo, la normal) y el decodificador calcula x a partir de él.
Un clasificador responde «qué es esto». Un modelo generativo sabe responder «enséñame algo que podría estar en los datos». La receta: tira los dados para obtener unos cuantos números, dáselos a un pintor (el decodificador) y él pintará una imagen a partir de ellos.
p(x) = ∫ p(x | z) · p(z) dz
- p(z)
- distribución del código (prior): la elegimos nosotros y sencilla, aquí N(0, I), porque luego, al generar, sabemos sortear de ella
- p(x | z)
- decodificador: qué probabilidad tiene la imagen x si recibimos el código z; aquí una red que devuelve para cada píxel la probabilidad de que esté encendido
- ∫ … dz
- suma sobre todos los códigos posibles: la imagen pudo surgir de cualquier código; pero la integral sobre todo el espacio latente no sabemos calcularla, y por eso el VAE la sustituye por una estimación (más abajo)
El problema del autocodificador corriente: no sabemos de dónde sortear el código. El codificador coloca los dígitos en el espacio latente donde le place. En nuestra red con código bidimensional, los códigos de los dígitos de prueba se sitúan en torno al punto (-3,9; -0,9) con desviación típica 4,3 y 6,0, y entre los grupos hay huecos en los que el decodificador nunca ha visto nada.
2. Autocodificador variacional
Definición — autocodificador variacional (VAE)
El VAE (Diederik Kingma y Max Welling, 2013) tiene un codificador que para la imagen x no devuelve un solo punto, sino una distribución normal q(z | x) = N(μ(x), diag σ²(x)). Aprende minimizando la pérdida
L = −Ez ~ q[log p(x | z)] + β · KL(q(z | x) ‖ N(0, I)),
donde el primer término es el error de reconstrucción (aquí la entropía cruzada binaria de los píxeles) y el segundo, una penalización por cuánto se aleja la distribución del código de la normal estándar. Para la distribución normal, KL = ½ ∑j (μj² + σj² − log σj² − 1). Con β = 1, −L es una cota inferior de la log-verosimilitud de los datos (ELBO).
El codificador no puede decir «este siete vive exactamente aquí», sino solo «en algún lugar de esta nubecita». El decodificador tiene que saber dibujar entonces un siete desde cualquier punto de la nubecita. Y la penalización KL empuja todas las nubecitas hacia el origen y no les deja ser demasiado pequeñas, así que se solapan, llenan el espacio y entre ellas no quedan huecos.
L(x) = −Ez ~ q(z | x)[ log p(x | z) ] + β · KL( q(z | x) ‖ N(0, I) )
- −Ez ~ q[log p(x | z)]
- error de reconstrucción: lo mal que dibuja el decodificador x a partir de un código sorteado de la nubecita. La esperanza sobre toda la nubecita (no solo su centro) obliga al decodificador a dibujar bien en todo el entorno del código; el signo menos se debe a que el logaritmo de la probabilidad lo queremos lo más grande posible y la pérdida lo más pequeña posible
- β
- peso de la penalización: fija el compromiso entre precisión y orden en el espacio; β = 1 es exactamente el ELBO, otros valores los probamos en la sección sobre β
- KL(q(z | x) ‖ N(0, I))
- distancia de la nubecita a la distribución de la que vamos a sortear: sin ella, el codificador reduciría las nubecitas a puntos y los dispersaría lejos unos de otros (la reconstrucción sería mejor) y volveríamos a tener un autocodificador corriente
KL = ½ ∑j=1k ( μj² + σj² − log σj² − 1 )
- KL
- la penalización de la fórmula anterior, calculada de forma exacta para la distribución normal; las coordenadas del código son independientes, por eso es una suma sobre j
- μj²
- desplazamiento del centro de la nubecita respecto al origen: crece cuadráticamente, así que los códigos lejanos cuestan mucho y el codificador los atrae hacia el cero, donde vamos a sortear
- σj² − log σj²
- anchura de la nubecita: la función s − log s tiene su mínimo 1 justo en s = 1; penaliza una nubecita demasiado ancha, pero sobre todo una demasiado estrecha (−log σ² → ∞ para σ → 0), para que la nubecita no se convierta en un punto
Teorema — el ELBO es una cota inferior de la log-verosimilitud
Para cualquier distribución q(z | x) se cumple
log p(x) = Eq[log p(x | z)] − KL(q(z | x) ‖ p(z)) + KL(q(z | x) ‖ p(z | x)) ≥ Eq[log p(x | z)] − KL(q(z | x) ‖ p(z)).
Demostración. log p(x) no depende de z, así que es igual a su esperanza respecto a q. Por el teorema de Bayes, p(x) = p(x | z) p(z) / p(z | x); multiplicamos la fracción por q(z | x) / q(z | x):
log p(x) = Eq[log p(x | z)] + Eq[log (p(z) / q(z | x))] + Eq[log (q(z | x) / p(z | x))].
El segundo término es −KL(q ‖ p(z)), el tercero KL(q ‖ p(z | x)). La divergencia KL es siempre no negativa (desigualdad de Gibbs de la parte sobre teoría de la información), así que al omitir el tercer término obtenemos la desigualdad. ∎
El lado derecho es el ELBO (evidence lower bound) y con β = 1 es exactamente −L. Así que minimizando L elevamos la cota inferior de log p(x) sin tener que calcular la integral. La igualdad se da cuando q(z | x) coincide con la verdadera distribución a posteriori p(z | x).
Definición — entropía cruzada binaria de los píxeles
El decodificador devuelve para cada uno de los 64 píxeles un número yj ∈ (0, 1) y lo toma como la probabilidad de que el píxel esté encendido. Para una imagen con brillos xj ∈ [0, 1] es
−log p(x | z) = −∑j=164 [ xj log yj + (1 − xj) log(1 − yj) ].
Ejemplo para un píxel: está encendido (x = 1) y el decodificador dice 0,9 → −ln 0,9 ≈ 0,105; dice 0,5 → ln 2 ≈ 0,693; está apagado (x = 0) y el decodificador dice 0,9 → −ln 0,1 ≈ 2,303. El error cometido con seguridad es el que más cuesta. Como la salida es una sigmoide, la derivada respecto a la entrada de la sigmoide es simplemente yj − xj, igual que en la regresión logística.
Definición — truco de reparametrización (reparametrization trick)
El sorteo z ~ N(μ, σ²) no se puede derivar respecto a μ y σ. Por eso se escribe como z = μ + σ · ε, donde ε ~ N(0, 1) es un ruido independiente de los parámetros. Entonces ∂z/∂μ = 1 y ∂z/∂σ = ε, y el gradiente llega mediante retropropagación hasta el codificador.
Sacamos el azar «fuera»: en lugar de «sortea un punto de la nubecita» decimos «sortea un ruido genérico y desplázalo y estíralo según el codificador». Desplazar y estirar son cuentas corrientes, y esas sí se pueden derivar.
zj = μj(x) + σj(x) · εj, σj = exp(½ · log σj²), εj ~ N(0, 1)
- μj(x)
- centro de la nubecita, salida del codificador: ∂z/∂μ = 1, el gradiente del decodificador pasa al codificador sin cambios
- σj(x)
- anchura de la nubecita: el codificador no devuelve σ, sino log σ², que puede ser cualquier número real; la exponencial siempre lo convierte en una anchura positiva, así que la red no tiene que vigilar ninguna condición. ∂z/∂σ = ε
- εj ~ N(0, 1)
- todo el azar: no depende de ningún parámetro, así que no hace falta derivar nada a través de él; al generar omitimos el codificador y sorteamos z directamente de N(0, I)
Ejemplo — un dígito de prueba (0) en el VAE entrenado con k = 2
codificador: μ = (0,285; 1,587), log σ² = (−2,695; −1,931) → σ = (0,260; 0,381)
KL1 = ½ (0,081 + 0,068 + 2,695 − 1) ≈ 0,922
KL2 = ½ (2,518 + 0,145 + 1,931 − 1) ≈ 1,797 → KL ≈ 2,72 nats
reparametrización con ε = (0,5; −1,0): z = (0,285 + 0,260 · 0,5; 1,587 − 0,381 · 1,0) = (0,415; 1,206)
La mayor parte de la penalización no la aporta aquí la distancia al origen, sino la estrechez de la nubecita: el término −log σ² contribuye con 2,695 y 1,931. El codificador querría un código lo más preciso posible; la KL lo mantiene más ancho. La KL media sobre los dígitos de prueba es 3,42 nats. El error de reconstrucción de este dígito (desde el centro μ) es 34,6 nats en 64 píxeles.
def krok_vae(davka, beta=1.0):
for x in davka:
mu, logvar = koder(x) # k + k números
sigma = exp(0.5 * logvar)
eps = normal(0, 1, size=len(mu)) # azar desde fuera
z = mu + sigma * eps # truco de reparametrización
y = dekoder(z) # probabilidades de los píxeles
rek = -sum(x * log(y) + (1 - x) * log(1 - y))
kl = 0.5 * sum(mu**2 + exp(logvar) - logvar - 1)
zpetne_sireni(rek + beta * kl) # el gradiente pasa z → mu, sigma → codificador
adam_krok() # gradiente medio del lote
Nuestra red: 64 píxeles → 32 neuronas (tanh) → μ y log σ² (k números cada uno) → 32 neuronas → 64 píxeles (sigmoide), entrenada con Adam durante 150 épocas con 600 dígitos sintéticos. La misma red, solo que sin el ruido ε y sin la penalización KL, es un autocodificador corriente, así que la comparación es justa.
3. Un espacio latente ordenado
Ambas redes agrupan los dígitos iguales. Pero el autocodificador los dispersa por una superficie grande: las dos circunferencias de la distribución normal son pequeñas a su lado. Los códigos del VAE se sitúan en torno al origen con desviación típica 1,14 y 1,04, es decir, justo allí de donde vamos a sortear.
Cómo es todo el espacio lo muestra una cuadrícula: puntos del espacio latente distribuidos regularmente, cada uno pasado por el decodificador.
En el VAE los dígitos se transforman suavemente unos en otros y casi cada casilla es un dígito legible. Seamos honestos: en el plano, también el decodificador del autocodificador es bastante suave y su cuadrícula también contiene sobre todo dígitos. La diferencia solo se ve plenamente con un código de más dimensiones.
4. Generación: dígitos nuevos a partir de ruido
Sorteamos 200 códigos y los decodificamos: en el VAE de N(0, I), en el autocodificador (para darle una oportunidad justa) de una distribución normal ajustada a sus códigos reales. La calidad la medimos con la distancia (el error cuadrático medio) de la muestra al dígito de entrenamiento más cercano. Los dígitos de prueba reales tienen hasta el de entrenamiento más cercano una media de 0,017; el ruido aleatorio, 0,159.
Definición — distancia al dígito de entrenamiento más cercano y distribución ajustada
Para una muestra y ∈ [0, 1]64 y los dígitos de entrenamiento x(1), …, x(600) es
d(y) = mini (1/64) ∑j=164 (yj − x(i)j)²,
es decir, el error cuadrático medio (MSE) respecto al dígito más parecido; la calidad del generador es la media de d sobre 200 muestras. Un d pequeño significa «se parece a algún dígito real»; ojo, un cero lo obtendría también un generador que solo copiara los datos de entrenamiento.
El autocodificador sortea de zj = mj + sj · εj, donde mj y sj son la media y la desviación típica de la j-ésima coordenada de los códigos de los dígitos de prueba (para k = 2: m = (−3,9; −0,9), s = (4,3; 6,0)). Las coordenadas se sortean de forma independiente: una posible dependencia entre ellas la distribución ajustada no la capta.
| tamaño del código k | reconstrucción AE (MSE) | reconstrucción VAE (MSE) | muestras AE | muestras VAE |
|---|---|---|---|---|
| 2 | 0,051 | 0,063 | 0,042 | 0,044 |
| 8 | 0,025 | 0,036 | 0,090 | 0,054 |
| 16 | 0,017 | 0,039 | 0,112 | 0,055 |
Con un código bidimensional, ambas redes están parecidas (0,042 frente a 0,044). Con ocho dimensiones, el autocodificador se desmorona: sus muestras tienen una distancia de 0,090, más de medio camino hacia el ruido, mientras que el VAE se mantiene en 0,054. Con dieciséis es 0,112 frente a 0,055. Los códigos del autocodificador en muchas dimensiones se sitúan sobre una superficie delgada y retorcida, y un punto aleatorio de la distribución normal ajustada prácticamente nunca cae en ella.
El precio: el VAE reconstruye peor. Con k = 16 su error de reconstrucción es 0,039 frente a 0,017 del autocodificador, y sus imágenes son más borrosas. El código tiene ruido, así que el decodificador aprende a dibujar la «media» de lo que podría haber en el entorno del código. La borrosidad es un punto débil conocido del VAE.
5. Reconstrucción frente a orden: β
El peso β determina con qué fuerza la penalización KL empuja los códigos hacia la distribución normal. Cinco VAE con código k = 2 y distintos β:
| β | KL (nats por imagen) | reconstrucción (MSE) | muestras (distancia) |
|---|---|---|---|
| 0,1 | 8,18 | 0,053 | 0,044 |
| 0,5 | 4,71 | 0,057 | 0,044 |
| 1 | 3,42 | 0,063 | 0,044 |
| 2 | 2,07 | 0,076 | 0,052 |
| 5 | 0,05 | 0,131 | 0,075 |
Un β pequeño deja los códigos más libres (KL 8,2) y la reconstrucción es más precisa. Un β grande los comprime hacia el cero. Con β = 5 la KL baja a 0,05: el codificador ha dejado de guardar nada en el código y el decodificador dibuja siempre más o menos lo mismo, reciba el código que reciba. A esto se le llama colapso del posterior (posterior collapse).
Definición — el nat y la KL como precio del código
El nat es la unidad de información con logaritmo natural: 1 nat = 1 / ln 2 ≈ 1,443 bits. La KL media sobre los datos acota por arriba la información mutua entre la imagen y el código:
Ex KL(q(z | x) ‖ p(z)) = I(x; z) + KL(q(z) ‖ p(z)) ≥ I(x; z),
donde q(z) = Ex q(z | x) es la distribución de los códigos de todas las imágenes juntas. La KL es, por tanto, el «precio» que paga el codificador por lo que guarda en el código sobre la imagen.
Con β = 0,1 es como mucho 8,18 nats ≈ 11,8 bits por imagen; con β = 5, solo 0,05 nats ≈ 0,07 bits, menos de una décima de bit, así que el código no dice casi nada sobre la imagen y al decodificador no le queda más remedio que dibujar siempre lo mismo.
Por el contrario, un β mayor que 1 lo usa a propósito el β-VAE (Irina Higgins y otros, 2017): una presión más fuerte hacia coordenadas independientes del código hace que las dimensiones individuales correspondan a menudo a propiedades comprensibles (inclinación, grosor del trazo).
6. Transiciones suaves
Tomamos los códigos de dos dígitos de prueba (de las redes con código de ocho dimensiones; de cada cifra, la imagen que el VAE reconstruye mejor) y recorremos la recta entre ellos en ocho pasos: z(t) = (1 − t) · za + t · zb para t = 0, 1/7, 2/7, …, 1, donde za, zb son los centros μ de ambos dígitos. Decodificamos cada punto intermedio.
La transición es suave en ambas redes: los pasos intermedios son formas verosímiles entre los dos dígitos. Por la recta entre dos códigos reales no nos alejamos mucho de los datos, así que aquí el autocodificador corriente no se queda atrás: su punto débil es solo el sorteo a ciegas. El mismo principio permite en los modelos grandes cambiar de forma suave, por ejemplo, la expresión de una cara o el estilo de una imagen.
7. Dónde se usan los VAE
- Compresión para otros generadores. Stable Diffusion (difusión latente, Robin Rombach y otros, 2022) no trabaja con píxeles, sino con el código de un autocodificador con una penalización KL pequeña: comprime una imagen de 512 × 512 × 3 en 64 × 64 × 4 y la difusión se ejecuta en ese espacio más pequeño. De la difusión tratará una de las próximas partes.
- Códigos discretos. El VQ-VAE (Aäron van den Oord y otros, 2017), en lugar de un código continuo, elige entradas de un diccionario aprendido. La imagen se convierte así en una secuencia de «tokens» que un transformer sabe generar.
- Datos científicos. Moléculas, datos celulares, sonido: en cualquier sitio donde sea útil un espacio suave en el que se pueda buscar e interpolar.
En nitidez de las imágenes, los VAE pierden frente a las GAN y los modelos de difusión. Pero tienen un aprendizaje estable, una verdadera interpretación probabilística y un codificador de regalo.
8. Resumen
- Un modelo generativo aprende la distribución de los datos para poder muestrear de ella; con variable latente, sortea un código y lo decodifica.
- El VAE codifica en una distribución N(μ, σ²) y la pérdida = reconstrucción + β · KL respecto a N(0, I). El truco z = μ + σε permite derivar a través del sorteo.
- Los códigos del VAE se sitúan en torno al origen y llenan el espacio; los del autocodificador están dispersos y con huecos.
- Con 8 dimensiones de código: muestras del autocodificador 0,090, del VAE 0,054 (dígitos reales 0,017, ruido 0,159).
- El precio es una reconstrucción peor y la borrosidad; un β demasiado grande lleva al colapso (KL 0,05).
¿Quieres probarlo por ti mismo? Pide a un agente de IA que entrene un VAE con código bidimensional en el conjunto de datos MNIST y que dibuje la cuadrícula de imágenes decodificadas: verás todo el «mapa» de los dígitos escritos a mano.
¿Lo ves de otra manera? Escribe a support@coden-app.com.