¡Claro!
Este código implementa un Autoencoder Variacional (VAE), un tipo de red
neuronal que aprende una representación comprimida de los datos (espacio latente) y
luego puede generar nuevos datos similares.
Aquí te explico cada línea:
1. Importaciones de Librerías
Python
import numpy as np
import tensorflow as tf
import [Link] as plt
from tensorflow import keras
from [Link] import layers
import numpy as np: Importa la librería NumPy, que es fundamental para operaciones
numéricas, especialmente con arrays y matrices. Se usa para manejar los datos de
las imágenes.
import tensorflow as tf: Importa la librería TensorFlow, un framework de código
abierto para aprendizaje automático. Es el motor principal detrás de la
construcción y el entrenamiento de la red neuronal.
import [Link] as plt: Importa la librería Matplotlib, que se usa para
crear gráficos y visualizaciones. En este caso, para mostrar las imágenes
generadas.
from tensorflow import keras: Importa el módulo Keras de TensorFlow. Keras es una
API de alto nivel para construir y entrenar modelos de aprendizaje profundo de
forma más sencilla.
from [Link] import layers: Importa el submódulo layers de Keras, que
contiene las diferentes capas predefinidas que se usan para construir las redes
neuronales (como capas convolucionales, densas, etc.).
2. Parámetros del VAE
Python
latent_dim = 2
latent_dim = 2: Define la dimensión del espacio latente. Esto significa que cada
imagen original (de 28x28 píxeles) se comprimirá en un vector de 2 números. Un
espacio latente de baja dimensión es bueno para visualizar y manipular las
representaciones aprendidas.
3. El Codificador (Encoder)
El codificador toma una imagen y la convierte en dos vectores que describen su
distribución en el espacio latente: la media y la varianza logarítmica.
Python
encoder_inputs = [Link](shape=(28, 28, 1))
x = layers.Conv2D(32, 3, activation="relu", strides=2, padding="same")
(encoder_inputs)
x = layers.Conv2D(64, 3, activation="relu", strides=2, padding="same")(x)
x = [Link]()(x)
x = [Link](16, activation="relu")(x)
z_mean = [Link](latent_dim, name="z_mean")(x)
z_log_var = [Link](latent_dim, name="z_log_var")(x)
encoder = [Link](encoder_inputs, [z_mean, z_log_var], name="encoder")
encoder_inputs = [Link](shape=(28, 28, 1)): Define la capa de entrada del
codificador. Espera imágenes de 28x28 píxeles con 1 canal (escala de grises).
x = layers.Conv2D(32, 3, activation="relu", strides=2, padding="same")
(encoder_inputs): Primera capa convolucional (Conv2D). Aplica 32 filtros de 3x3,
usa la función de activación ReLU, reduce la resolución a la mitad (strides=2) y
añade relleno para mantener el tamaño (padding="same").
x = layers.Conv2D(64, 3, activation="relu", strides=2, padding="same")(x): Segunda
capa convolucional similar, pero con 64 filtros, reduciendo la resolución de nuevo.
x = [Link]()(x): Convierte la salida 3D de las capas convolucionales en un
vector 1D plano. Esto es necesario para conectarlo a las capas densas.
x = [Link](16, activation="relu")(x): Una capa densa (totalmente conectada)
con 16 neuronas y activación ReLU. Actúa como una capa intermedia para procesar las
características extraídas.
z_mean = [Link](latent_dim, name="z_mean")(x): Capa densa que genera el
vector de medias (z_mean) para la distribución latente. Su tamaño es igual a
latent_dim.
z_log_var = [Link](latent_dim, name="z_log_var")(x): Capa densa que genera el
vector de logaritmo de las varianzas (z_log_var) para la distribución latente.
También de tamaño latent_dim. Se usa el logaritmo de la varianza para asegurar que
la varianza sea siempre positiva y por estabilidad numérica.
encoder = [Link](encoder_inputs, [z_mean, z_log_var], name="encoder"): Crea el
modelo del codificador. Toma encoder_inputs como entrada y devuelve los dos
vectores: z_mean y z_log_var.
4. El Decodificador (Decoder)
El decodificador toma un vector del espacio latente y lo convierte de nuevo en una
imagen.
Python
latent_inputs = [Link](shape=(latent_dim,))
x = [Link](7 * 7 * 64, activation="relu")(latent_inputs)
x = [Link]((7, 7, 64))(x)
x = layers.Conv2DTranspose(64, 3, activation="relu", strides=2, padding="same")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu", strides=2, padding="same")(x)
decoder_outputs = layers.Conv2DTranspose(1, 3, activation="sigmoid",
padding="same")(x)
decoder = [Link](latent_inputs, decoder_outputs, name="decoder")
latent_inputs = [Link](shape=(latent_dim,)): Define la capa de entrada del
decodificador. Espera un vector del tamaño latent_dim.
x = [Link](7 * 7 * 64, activation="relu")(latent_inputs): Una capa densa que
expande el vector latente a un tamaño que luego se puede reformar en una cuadrícula
(7x7x64).
x = [Link]((7, 7, 64))(x): Reforma el vector 1D en una cuadrícula 3D de 7x7
con 64 canales. Este es el tamaño al que las capas convolucionales inversas pueden
empezar a reconstruir la imagen.
x = layers.Conv2DTranspose(64, 3, activation="relu", strides=2, padding="same")(x):
Primera capa convolucional transpuesta (Conv2DTranspose). Es el inverso de una
convolución, aumentando la resolución (desconvolución). Usa 64 filtros, tamaño de
filtro 3x3, strides=2 para duplicar la resolución y padding="same".
x = layers.Conv2DTranspose(32, 3, activation="relu", strides=2, padding="same")(x):
Segunda capa convolucional transpuesta similar, pero con 32 filtros, aumentando la
resolución de nuevo.
decoder_outputs = layers.Conv2DTranspose(1, 3, activation="sigmoid",
padding="same")(x): Última capa convolucional transpuesta. Genera la imagen final
de 28x28x1 (1 canal para escala de grises). La activación sigmoid se usa porque las
imágenes están normalizadas entre 0 y 1.
decoder = [Link](latent_inputs, decoder_outputs, name="decoder"): Crea el
modelo del decodificador. Toma latent_inputs como entrada y devuelve la
decoder_outputs (la imagen reconstruida).
5. El Modelo VAE Completo
Esta clase combina el codificador y el decodificador, y define cómo se entrena el
modelo.
Python
class VAE([Link]):
def __init__(self, encoder, decoder, **kwargs):
super(VAE, self).__init__(**kwargs)
[Link] = encoder
[Link] = decoder
def train_step(self, data):
if isinstance(data, tuple):
data = data[0]
with [Link]() as tape:
z_mean, z_log_var = [Link](data)
z = [Link]((z_mean, z_log_var))
reconstruction = [Link](z)
reconstruction_loss = tf.reduce_mean(
[Link].binary_crossentropy(data, reconstruction)
)
reconstruction_loss *= 28 * 28
kl_loss = 1 + z_log_var - [Link](z_mean) - [Link](z_log_var)
kl_loss = tf.reduce_mean(kl_loss)
kl_loss *= -0.5
total_loss = reconstruction_loss + kl_loss
grads = [Link](total_loss, self.trainable_weights)
[Link].apply_gradients(zip(grads, self.trainable_weights))
return {
"loss": total_loss,
"reconstruction_loss": reconstruction_loss,
"kl_loss": kl_loss,
}
def call(self, data):
z_mean, z_log_var = [Link](data)
z = [Link]((z_mean, z_log_var))
return [Link](z)
def sampling(self, args):
z_mean, z_log_var = args
batch = [Link](z_mean)[0]
dim = [Link](z_mean)[1]
epsilon = [Link].random_normal(shape=(batch, dim))
return z_mean + [Link](0.5 * z_log_var) * epsilon
class VAE([Link]):: Define una nueva clase VAE que hereda de [Link], lo
que permite que se comporte como un modelo de Keras estándar.
def __init__(self, encoder, decoder, **kwargs):: El constructor de la clase. Recibe
los modelos encoder y decoder que definimos antes y los guarda como atributos.
def train_step(self, data):: Este método sobrescribe el train_step predeterminado
de Keras para definir el proceso de entrenamiento personalizado del VAE.
if isinstance(data, tuple): data = data[0]: Maneja el caso en que data podría ser
una tupla (común en datasets de Keras donde data y labels se pasan juntas).
with [Link]() as tape:: Inicia una cinta de gradientes. TensorFlow
registra todas las operaciones dentro de este bloque para calcular los gradientes
más tarde.
z_mean, z_log_var = [Link](data): Pasa los datos (imágenes) a través del
codificador para obtener la media y la varianza logarítmica.
z = [Link]((z_mean, z_log_var)): Llama al método sampling (explicado a
continuación) para obtener una muestra aleatoria del espacio latente. Esto es
crucial para la parte "variacional" del VAE.
reconstruction = [Link](z): Pasa la muestra latente a través del
decodificador para reconstruir la imagen.
reconstruction_loss = tf.reduce_mean(...): Calcula la pérdida de reconstrucción.
Compara la imagen original (data) con la imagen reconstruida (reconstruction)
usando la entropía cruzada binaria (adecuada para imágenes de 0 a 1).
reconstruction_loss *= 28 * 28: Escala la pérdida de reconstrucción por el número
de píxeles para que la pérdida sea por imagen, no por píxel.
kl_loss = 1 + z_log_var - [Link](z_mean) - [Link](z_log_var): Calcula la pérdida
KL (Kullback-Leibler). Esta es la parte "variacional". Mide qué tan cerca está la
distribución latente aprendida de una distribución normal estándar. Anima al
codificador a producir distribuciones latentes que sean fáciles de muestrear.
kl_loss = tf.reduce_mean(kl_loss): Calcula la media de la pérdida KL.
kl_loss *= -0.5: Ajusta la pérdida KL con un factor de -0.5 (parte de la
formulación de la pérdida ELBO en VAEs).
total_loss = reconstruction_loss + kl_loss: La pérdida total es la suma de la
pérdida de reconstrucción y la pérdida KL.
grads = [Link](total_loss, self.trainable_weights): Calcula los gradientes
de la pérdida total con respecto a todos los pesos entrenables del modelo.
[Link].apply_gradients(zip(grads, self.trainable_weights)): Aplica los
gradientes para actualizar los pesos del modelo, lo que lo entrena.
return {...}: Devuelve un diccionario con las diferentes pérdidas, útil para
monitorear el progreso del entrenamiento.
def call(self, data):: Este método define el comportamiento de "inferencia" del VAE
cuando se usa model(input_data). No se usa directamente durante el entrenamiento si
se sobrescribe train_step, pero es útil para la generación de imágenes.
Es similar a los primeros pasos de train_step: codifica la imagen, muestrea del
espacio latente y decodifica.
def sampling(self, args):: Este método implementa el "truco de reparametrización".
z_mean, z_log_var = args: Recibe la media y la varianza logarítmica del
codificador.
batch = [Link](z_mean)[0]: Obtiene el tamaño del lote (número de imágenes).
dim = [Link](z_mean)[1]: Obtiene la dimensión del espacio latente.
epsilon = [Link].random_normal(shape=(batch, dim)): Genera ruido
aleatorio (epsilon) de una distribución normal estándar. Esto permite que el modelo
aprenda a muestrear del espacio latente mientras los gradientes pueden fluir a
través de esta operación aleatoria.
return z_mean + [Link](0.5 * z_log_var) * epsilon: Aplica la fórmula del truco de
reparametrización para obtener una muestra z de la distribución gaussiana definida
por z_mean y z_log_var.
6. Carga y Preparación de Datos
Python
(x_train, _), (x_test, _) = [Link].load_data()
x_train = np.expand_dims(x_train, -1).astype("float32") / 255.0
x_test = np.expand_dims(x_test, -1).astype("float32") / 255.0
(x_train, _), (x_test, _) = [Link].load_data(): Carga el dataset
MNIST (imágenes de dígitos escritos a mano). Solo se necesitan las imágenes de
entrenamiento y prueba (x_train, x_test), por eso se usa _ para ignorar las
etiquetas.
x_train = np.expand_dims(x_train, -1).astype("float32") / 255.0:
np.expand_dims(x_train, -1): Añade una dimensión al final de los datos de la imagen
(de 28x28 a 28x28x1), que es el formato que esperan las capas convolucionales
(altura, ancho, canales).
.astype("float32"): Convierte el tipo de datos a coma flotante de 32 bits, lo que
es común para redes neuronales.
/ 255.0: Normaliza los valores de los píxeles de 0-255 a 0-1. Esto ayuda a la red a
aprender de manera más efectiva.
x_test = np.expand_dims(x_test, -1).astype("float32") / 255.0: Realiza las mismas
operaciones de preprocesamiento para el conjunto de datos de prueba.
7. Entrenamiento del VAE
Python
vae = VAE(encoder, decoder)
[Link](optimizer=[Link]())
[Link](x_train, epochs=30, batch_size=128)
vae = VAE(encoder, decoder): Crea una instancia de la clase VAE que definimos,
pasándole los modelos de codificador y decodificador.
[Link](optimizer=[Link]()): Compila el modelo. Se especifica el
optimizador, en este caso Adam, que ajusta los pesos del modelo durante el
entrenamiento. No se especifica una función de pérdida aquí porque ya la definimos
dentro del train_step personalizado.
[Link](x_train, epochs=30, batch_size=128): Entrena el modelo.
x_train: Los datos de entrenamiento.
epochs=30: El número de veces que el modelo verá todo el conjunto de datos de
entrenamiento.
batch_size=128: El número de muestras que se procesan antes de que se actualicen
los pesos del modelo.
8. Generación de Nuevas Imágenes
Después de entrenar el VAE, podemos usar el decodificador para generar nuevas
imágenes a partir de muestras aleatorias del espacio latente.
Python
n = 10
digit_size = 28
figure = [Link]((digit_size * n, digit_size * n))
# Sample random vectors from a normal distribution
z_sample = [Link](size=(n * n, latent_dim))
# Decode the vectors into images
x_decoded = [Link](z_sample)
# Reshape the images and combine them into a grid
for i in range(n):
for j in range(n):
digit = x_decoded[i * n + j].reshape(digit_size, digit_size)
figure[i * digit_size : (i + 1) * digit_size,
j * digit_size : (j + 1) * digit_size] = digit
# Plot the grid of images
[Link](figsize=(10, 10))
[Link](figure, cmap="Greys_r")
[Link]("off")
[Link]()
n = 10: Define el número de filas y columnas para la cuadrícula de imágenes
generadas (10x10).
digit_size = 28: El tamaño de cada dígito (28x28 píxeles).
figure = [Link]((digit_size * n, digit_size * n)): Crea un array NumPy vacío que
será la cuadrícula donde se colocarán las imágenes generadas. Su tamaño será
(28*10)x(28*10) = 280x280.
z_sample = [Link](size=(n * n, latent_dim)): Genera vectores aleatorios
(z_sample) del espacio latente. Estos vectores provienen de una distribución normal
estándar, ya que el VAE está entrenado para mapear el espacio latente a una normal
estándar. Se generan n*n (100) vectores.
x_decoded = [Link](z_sample): Pasa los vectores latentes generados
aleatoriamente a través del decodificador para convertirlos en imágenes.
for i in range(n): for j in range(n):: Bucle anidado para recorrer la cuadrícula de
10x10.
digit = x_decoded[i * n + j].reshape(digit_size, digit_size): Toma una imagen
decodificada, la reforma a su tamaño original de 28x28.
figure[i * digit_size : (i + 1) * digit_size, j * digit_size : (j + 1) *
digit_size] = digit: Coloca el dígito reconstruido en la posición correcta dentro
de la cuadrícula figure.
[Link](figsize=(10, 10)): Crea una figura para la gráfica con un tamaño de
10x10 pulgadas.
[Link](figure, cmap="Greys_r"): Muestra la cuadrícula de imágenes generadas.
cmap="Greys_r" invierte la escala de grises para que el fondo sea negro y los
dígitos blancos.
[Link]("off"): Desactiva los ejes de la gráfica para mostrar solo la imagen.
[Link](): Muestra la gráfica.
En resumen, este código construye un VAE que aprende a comprimir imágenes de
dígitos en un espacio latente bidimensional y luego a generar nuevos dígitos
"creativos" a partir de ese espacio.