Capítulo 3: Profundizando en las Redes Generativas Antagónicas (GANs)
3.2 Arquitectura de las GANs
La arquitectura de las Redes Generativas Adversariales (GANs), un conjunto único de modelos de aprendizaje automático, consta de dos componentes principales: el generador y el discriminador.
La red del generador tiene la tarea de crear nuevas instancias de datos. Estas instancias, idealmente, deben reflejar las propiedades estadísticas de los datos de entrenamiento. El generador comienza con un vector de ruido aleatorio (vector latente) como entrada, que utiliza para producir muestras de datos a través de una serie de capas completamente conectadas, capas convolucionales y capas de upsampling con el fin de generar datos de alta resolución.
La red del discriminador, por otro lado, tiene la tarea de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Toma una muestra de datos, ya sea real o generada, como entrada y procesa esto a través de una serie de capas convolucionales seguidas de capas completamente conectadas. La salida es un valor único o probabilidad que indica si la entrada es real o falsa.
Entrenar GANs implica actualizar iterativamente tanto el generador como el discriminador. El generador tiene como objetivo producir datos que el discriminador confunda con datos reales, mientras que el discriminador tiene como objetivo identificar correctamente los datos reales y falsos. Este proceso adversarial continúa hasta que el generador se vuelve tan bueno que puede producir datos indistinguibles de los datos reales, o el discriminador ya no puede distinguir entre los dos con alta precisión.
A pesar de su potencial, entrenar GANs puede ser desafiante debido a varios factores como el colapso de modo, la inestabilidad del entrenamiento y la sensibilidad a los hiperparámetros. Sin embargo, los investigadores han desarrollado varias técnicas y modificaciones para abordar estos desafíos y mejorar las capacidades de las GANs.
La arquitectura de las GANs es una estructura fascinante y compleja que ha revolucionado el campo de la modelización generativa. Comprender su arquitectura, proceso de entrenamiento y los desafíos asociados es crucial para aplicar efectivamente las GANs a problemas del mundo real.
3.2.1 La Red del Generador
El generador es una red neuronal que toma un vector de ruido aleatorio como entrada y lo transforma en una muestra de datos que se asemeja a los datos de entrenamiento. El objetivo del generador es producir datos que sean indistinguibles de los datos reales por el discriminador.
Arquitectura del Generador
El generador típicamente consta de varias capas, incluyendo:
- Capas Densas (Completamente Conectadas): Un componente crítico de la arquitectura de la red, estas capas juegan un papel crucial en el modelo. Operan aumentando la dimensionalidad del vector de ruido de entrada. Al realizar esta función, permiten efectivamente que la red aprenda representaciones más complejas y detalladas, facilitando la producción de una gama más amplia de salidas a partir de una entrada dada. Este aumento en la dimensionalidad proporciona a la red la capacidad de comprender e interpretar mejor los datos que está procesando.
- Capa de Reconfiguración (Reshape Layer): Esta es una parte crucial de la arquitectura de la red, ya que transforma la salida de las capas densas anteriores. Esta transformación es necesaria para permitir el procesamiento adicional de los datos. Por ejemplo, si la tarea en cuestión es la generación de imágenes, la capa de reconfiguración manipulará la salida de la capa densa en una forma o formato bidimensional. Esto es esencial porque las imágenes son entidades inherentemente bidimensionales, y las capas subsiguientes en la red probablemente requerirán este formato 2D para realizar sus tareas de manera efectiva. Así, la capa de reconfiguración actúa como un puente para asegurar la compatibilidad entre las capas densas y las etapas posteriores de la red.
- Capas de Convolución Transpuesta (Conv2DTranspose): Estas capas, también conocidas comúnmente como capas deconvolucionales, juegan un papel fundamental en el proceso de upsampling de los datos. La función principal de estas capas es aumentar la resolución de los datos, un proceso que es bastante integral en el campo del aprendizaje profundo. La mayor resolución permite un análisis más detallado, lo que permite que el modelo capture patrones y características más complejas dentro de los datos. Esto puede mejorar significativamente el rendimiento del modelo, particularmente cuando se trata de datos de alta dimensionalidad como imágenes.
- Capas de Activación: En el dominio de las redes neuronales, las capas de activación juegan un papel crucial. Estas capas introducen propiedades no lineales en nuestra red, lo que nos permite modelar una variable de respuesta (también llamada variable objetivo) que varía de manera no lineal con sus variables explicativas. Dos funciones de activación comúnmente utilizadas en estas capas son las funciones ReLU (Unidad Lineal Rectificada) y Tanh (Tangente Hiperbólica). La función ReLU, en particular, es ampliamente utilizada en redes de aprendizaje profundo debido a sus propiedades beneficiosas para tales modelos, como la capacidad de activar un nodo solo si la entrada está por encima de cierta cantidad. Por otro lado, la función Tanh es una función matemática que tiene una curva característica en forma de S, y puede ser útil para normalizar la salida de las neuronas.
Aquí hay un ejemplo de una red de generador diseñada para producir imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Conv2DTranspose
def build_generator(latent_dim):
model = tf.keras.Sequential([
Dense(256 * 7 * 7, input_dim=latent_dim),
LeakyReLU(alpha=0.2),
Reshape((7, 7, 256)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(64, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(1, kernel_size=4, strides=1, padding='same', activation='tanh')
])
return model
# Instantiate and summarize the generator
latent_dim = 100
generator = build_generator(latent_dim)
generator.summary()
En este ejemplo:
El modelo generador se construye utilizando la función build_generator()
. Esta función toma un argumento: la dimensionalidad del vector del espacio latente latent_dim
. El vector del espacio latente es una forma de representación comprimida de los datos y es la entrada para el modelo generador.
La construcción del modelo generador comienza con un objeto tf.keras.Sequential
, que nos permite apilar capas linealmente, con cada capa pasando su salida a la siguiente capa.
La primera capa en el modelo generador es una capa Dense
con 256 * 7 * 7
neuronas, y toma una entrada con una dimensión de latent_dim
. La capa Dense
, también conocida como capa completamente conectada, es un componente crucial de este modelo. Opera aumentando la dimensionalidad del vector de ruido de entrada, permitiendo así que la red aprenda representaciones más complejas y detalladas. Esta mayor dimensionalidad le da a la red una mejor comprensión e interpretación de los datos que está procesando.
A continuación, tenemos una función de activación LeakyReLU
con una pendiente de 0.2 para la parte negativa. Esta es una variante de la función de activación Unidad Lineal Rectificada (ReLU), que introduce no linealidad en la red, permitiéndole aprender patrones complejos. La función LeakyReLU tiene una ventaja sobre la función ReLU regular ya que previene las "neuronas muertas" en escenarios donde una neurona podría dejar de pasar datos hacia adelante a través de la red.
Una capa Reshape
sigue a continuación, transformando la salida de la capa densa anterior en un formato que puede ser procesado por las siguientes capas. En este caso, reformatea la salida en un tensor de forma (7, 7, 256)
. Esta capa es importante para la compatibilidad entre las capas densas y las etapas subsiguientes de la red, especialmente si la tarea en cuestión es la generación de imágenes, ya que las imágenes son entidades inherentemente bidimensionales.
Siguiendo la capa de reconfiguración hay una serie de capas Conv2DTranspose
, también conocidas como capas deconvolucionales. Son clave en el aumento de la resolución de los datos, que es el proceso de aumentar la resolución o tamaño de los datos. Esto se logra añadiendo ceros a los datos de entrada y luego aplicando una operación de convolución regular. Este proceso es integral en el campo del aprendizaje profundo, ya que permite un análisis más detallado, permitiendo que el modelo capture patrones y características más complejas dentro de los datos.
Cada capa Conv2DTranspose
es seguida por una capa de activación LeakyReLU
que introduce no linealidad y previene el problema de las "neuronas muertas". La capa Conv2DTranspose
final utiliza la función de activación 'tanh' para asegurar que los valores de salida caigan dentro del rango de -1 a 1.
Después de crear el modelo generador, se crea una instancia del generador llamando a build_generator(latent_dim)
, donde latent_dim
se establece en 100. Finalmente, se llama a generator.summary()
para mostrar la estructura del modelo generador.
Este modelo generador es un componente clave de una GAN. Trabaja en conjunto con un modelo discriminador para generar datos sintéticos que se asemejan estrechamente a los datos reales. Al entrenar estos dos modelos de manera iterativa, las GANs pueden producir datos altamente realistas, lo que las convierte en una herramienta poderosa en varios campos, como la síntesis de imágenes y voces, la detección de anomalías e incluso la creación de arte.
3.2.2 La Red del Discriminador
El discriminador, que es una parte integral de una Red Generativa Adversarial (GAN), es esencialmente una red neuronal. Esta red acepta una muestra de datos como entrada, que podría ser un punto de datos real o uno generado, y luego produce una probabilidad. Esta probabilidad indica si la muestra alimentada es real o falsa.
La función principal del discriminador, y de hecho su objetivo dentro de la GAN, es clasificar datos con un alto grado de precisión. Apunta a identificar correctamente los puntos de datos reales y distinguirlos de los falsos o generados artificialmente. Este papel crucial del discriminador permite que la GAN mejore progresivamente sus capacidades de generación, permitiendo así la creación de datos sintéticos más realistas.
Arquitectura del Discriminador
El discriminador típicamente consta de varias capas, incluyendo:
- Capas Convolucionales (Conv2D): Estas son un componente crucial de las redes neuronales, específicamente diseñadas para procesar datos de píxeles y extraer características importantes de los datos de entrada. Pueden reconocer patrones con respecto a jerarquías y variaciones espaciales, lo que las hace excepcionalmente buenas en tareas de procesamiento de imágenes y videos. Su función principal es escanear los datos de entrada en busca de ciertas características, que pueden ser útiles para la tarea en cuestión.
- Capa de Aplanamiento (Flatten Layer): La Capa de Aplanamiento sirve una función importante en nuestro modelo. Después de que nuestros datos de entrada han sido procesados por las capas convolucionales, están en un formato 2D. Sin embargo, para que nuestra red neuronal procese estos datos, necesitan estar en un formato 1D. Aquí es donde la Capa de Aplanamiento entra en juego. Efectivamente transforma, o "aplana", la salida 2D de las capas convolucionales en un formato vectorial 1D. Esto permite que los datos procesados sean compatibles y estén listos para las capas subsiguientes de nuestra red neuronal.
- Capas Densas (Completamente Conectadas): Estas son las capas que toman los vectores de características de alta dimensión que han sido generados por las capas anteriores en la red neuronal y reducen su dimensionalidad a un solo valor. Logran esta tarea aplicando una transformación que incluye cada característica en el vector, de ahí el término "completamente conectadas". La función clave de estas capas es interpretar los patrones complejos y de alta dimensionalidad identificados por las capas anteriores y convertirlos en una forma que pueda ser utilizada para la predicción, típicamente un solo valor escalar.
- Capas de Activación: Las capas de activación dictan la salida de una neurona dada una entrada o conjunto de entradas. Algunas de las capas de activación comúnmente usadas incluyen LeakyReLU y Sigmoid. La LeakyReLU es un tipo de función de activación que intenta solucionar el problema de las Unidades Lineales Rectificadas (ReLU) muertas. La función de activación Sigmoid, por otro lado, mapea los valores de entrada entre 0 y 1, lo cual es especialmente útil en la capa de salida de problemas de clasificación binaria.
Aquí hay un ejemplo de una red discriminadora diseñada para clasificar imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, Dense
def build_discriminator(img_shape):
model = tf.keras.Sequential([
Conv2D(64, kernel_size=4, strides=2, padding='same', input_shape=img_shape),
LeakyReLU(alpha=0.2),
Conv2D(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Flatten(),
Dense(1, activation='sigmoid')
])
return model
# Instantiate and summarize the discriminator
img_shape = (28, 28, 1)
discriminator = build_discriminator(img_shape)
discriminator.summary()
En este ejemplo:
En este ejemplo, estamos definiendo la arquitectura de la red del discriminador utilizando TensorFlow y su API de alto nivel Keras.
El discriminador es un tipo de red neuronal que toma una muestra de datos como entrada. Esta muestra puede ser un punto de datos real del conjunto de datos de entrenamiento o una sintética generada por la red del generador. La salida del discriminador es una probabilidad que indica si la muestra es real o falsa.
El objetivo del discriminador es clasificar los datos con precisión, es decir, identificar correctamente los puntos de datos reales y distinguirlos de los sintéticos. Esta capacidad mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
La red del discriminador definida en este código consta de varias capas.
- Capas Conv2D: La capa Conv2D es una capa de convolución que es especialmente efectiva para el procesamiento de imágenes. La primera capa Conv2D toma la imagen de entrada, aplica 64 filtros cada uno de tamaño (4,4) y usa un stride de 2. Se utiliza el padding 'same' para que la salida tenga el mismo ancho y altura que la entrada. La segunda capa Conv2D toma la salida de la primera capa y aplica 128 filtros con los mismos parámetros. Estas capas se utilizan para detectar varias características en la imagen de entrada.
- Capas LeakyReLU: Las capas LeakyReLU son las funciones de activación para las capas Conv2D. Ayudan a introducir no linealidad en el modelo, permitiéndole aprender patrones más complejos. La función LeakyReLU es similar a la función ReLU (Unidad Lineal Rectificada) pero permite pequeños valores negativos cuando la entrada es menor que cero, mitigando el problema de las "neuronas muertas".
- Capa Flatten: La capa Flatten convierte la salida en forma de matriz 2D de las capas anteriores en un vector 1D. Este paso es necesario porque la siguiente capa Dense espera una entrada en formato 1D.
- Capa Dense: La capa Dense es una capa completamente conectada, lo que significa que todas las neuronas en esta capa están conectadas a todas las neuronas en la capa anterior. Esta capa tiene una sola unidad con una función de activación sigmoide. Una función sigmoide produce un valor entre 0 y 1, lo que la hace ideal para problemas de clasificación binaria. En este caso, un valor cercano a 1 indica que es probable que la entrada sea real, y un valor cercano a 0 indica que es probable que sea falsa.
Después de definir la arquitectura, el modelo del discriminador se compila y se imprime un resumen. El resumen incluye los tipos de capas en el modelo, la forma de salida de cada capa, el número de parámetros (pesos y sesgos) en cada capa y el total de parámetros en el modelo.
3.2.3 Interacción Entre el Generador y el Discriminador
Las redes del generador y del discriminador se entrenan en conjunto, con sus roles y objetivos diametralmente opuestos.
El generador y el discriminador se entrenan simultáneamente pero tienen objetivos opuestos. El objetivo del generador es crear datos que se parezcan lo más posible a los datos reales. Comienza con una semilla de ruido aleatorio y transforma este ruido en muestras de datos. A medida que el generador mejora con el tiempo y las iteraciones de entrenamiento, los datos que genera deben volverse cada vez más similares a los datos reales.
Por otro lado, el objetivo del discriminador es clasificar los datos con precisión. Está encargado de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Idealmente, debe producir una alta probabilidad para los datos reales y una baja probabilidad para los datos falsos. La capacidad del discriminador para distinguir con precisión los datos reales de los falsos mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
En el proceso de entrenamiento, se involucran dos pasos principales. Primero, el discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador, con el objetivo de clasificar correctamente las muestras reales como reales y las muestras falsas como falsas. El segundo paso implica entrenar al generador para producir datos que el discriminador no pueda distinguir de los datos reales. En este caso, el objetivo del generador es maximizar el error del discriminador en las muestras falsas, lo que significa que el generador mejora cuando puede engañar al discriminador haciéndole creer que los datos generados son reales.
Este proceso de entrenamiento adversarial continúa iterativamente, con cada red aprendiendo y mejorando a partir de la retroalimentación de la otra. Esto resulta en un generador que puede producir datos altamente realistas y un discriminador que es hábil en detectar datos falsos. Esto convierte a las GANs en una herramienta poderosa en áreas como la generación de imágenes, la superresolución y más.
En resumen, el proceso de entrenamiento involucra dos pasos principales:
- Entrenamiento del Discriminador:
- El discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador.
- El objetivo del discriminador es clasificar correctamente las muestras reales como reales y las muestras falsas como falsas.
- La función de pérdida para el discriminador generalmente usa entropía cruzada binaria para medir el error de clasificación.
- Entrenamiento del Generador:
- El generador se entrena para producir datos que el discriminador no pueda distinguir de los datos reales.
- El objetivo del generador es maximizar el error del discriminador en las muestras falsas (es decir, engañar al discriminador).
- La función de pérdida para el generador también usa entropía cruzada binaria, pero se optimiza en el contexto de engañar al discriminador.
Este proceso de entrenamiento adversarial se puede resumir de la siguiente manera:
- Pérdida del Discriminador: LD=−[log(D(x))+log(1−D(G(z)))]
- Pérdida del Generador: LG=−log(D(G(z)))
Donde D(x) es la salida del discriminador para datos reales x, y D(G(z)) es la salida del discriminador para datos falsos G(z) generados a partir de ruido aleatorio z.
Ejemplo: Entrenando una GAN en Datos MNIST
A continuación se muestra un ejemplo completo de entrenamiento de una GAN en el conjunto de datos MNIST, incluyendo tanto los pasos de entrenamiento del generador como del discriminador:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# Load and preprocess the MNIST dataset
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5 # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
# Training parameters
latent_dim = 100
epochs = 10000
batch_size = 64
sample_interval = 1000
# Build the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Build and compile the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Training the GAN
for epoch in range(epochs):
# Train the discriminator
idx = np.random.randint(0, x_train.shape[0], batch_size)
real_images = x_train[idx]
noise = np.random.normal(0, 1, (batch_size, latent_dim))
fake_images = generator.predict(noise)
d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
# Train the generator
noise = np.random.normal(0, 1, (batch_size, latent_dim))
g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))
# Print progress
if epoch % sample_interval == 0:
print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
# Generate and save images
noise = np.random.normal(0, 1, (10, latent_dim))
generated_images = generator.predict(noise)
fig, axs = plt.subplots(1, 10, figsize=(20, 2))
for i, img in enumerate(generated_images):
axs[i].imshow(img.squeeze(), cmap='gray')
axs[i].axis('off')
plt.show()
En este ejemplo:
Este ejemplo es un script completo para entrenar una Red Generativa Adversarial (GAN) en el famoso conjunto de datos MNIST, que es una colección de 70,000 imágenes en escala de grises de dígitos escritos a mano. Cada imagen tiene un tamaño de 28x28 píxeles. El objetivo es utilizar la GAN para generar nuevas imágenes que se asemejen a los dígitos escritos a mano en el conjunto de datos MNIST.
En este modelo GAN, el generador y el discriminador se entrenan en pasos alternos. Durante la fase de entrenamiento del discriminador, se entrena al discriminador tanto con imágenes reales como falsas. Las imágenes reales provienen directamente del conjunto de datos MNIST, y las imágenes falsas son generadas por el generador. El objetivo del discriminador es clasificar correctamente las imágenes reales como reales y las imágenes falsas como falsas. Después de esta fase de entrenamiento, se actualizan los pesos del discriminador en función de la pérdida incurrida.
A continuación, durante la fase de entrenamiento del generador, el generador genera un nuevo lote de imágenes falsas, y estas imágenes se introducen en el discriminador. Sin embargo, en esta fase, las etiquetas de estas imágenes se establecen como 'reales' en lugar de 'falsas', lo que significa que se entrena al generador para engañar al discriminador. Después de esta fase de entrenamiento, se actualizan los pesos del generador en función de lo bien que logró engañar al discriminador.
Este proceso de entrenamiento alterno continúa durante un número específico de épocas, que en este código se establece en 10,000. En intervalos regulares durante el entrenamiento (después de cada 1,000 épocas en este caso), el programa imprime el número de época actual y las pérdidas incurridas por el discriminador y el generador. También genera un lote de imágenes a partir del generador y las muestra. Esto permite monitorear el progreso del entrenamiento y ver cómo mejoran las imágenes generadas con el tiempo.
En resumen, este ejemplo proporciona una implementación completa de una GAN. Demuestra cómo entrenar la GAN en un conjunto de datos específico, y cómo generar y mostrar nuevas imágenes a partir del modelo entrenado. Este código podría usarse como punto de partida para entrenar una GAN en diferentes tipos de conjuntos de datos o para experimentar con diferentes arquitecturas de GAN.
Ejemplo: Arquitectura Básica de GAN con TensorFlow/Keras
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
# Generator model
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
# Discriminator model
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
# Build and compile the GAN
latent_dim = 100
img_shape = (28, 28, 1)
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Este código de ejemplo proporciona una implementación completa de una Red Generativa Adversarial (GAN) utilizando TensorFlow.
La tarea del generador es producir datos que reflejen los datos de entrenamiento. Comienza con una semilla de ruido aleatorio y la transforma en muestras de datos plausibles. El discriminador, por otro lado, tiene la tarea de distinguir entre datos reales del conjunto de entrenamiento y datos falsos producidos por el generador. Devuelve una probabilidad que indica si una muestra dada es real o falsa.
El código comienza con las importaciones necesarias de TensorFlow y Keras. Keras es una biblioteca de redes neuronales fácil de usar escrita en Python que se ejecuta sobre TensorFlow.
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
El modelo del generador se define en la función build_generator
. Esta función toma como entrada una dimensión latente (latent_dim
) y construye un modelo que genera una imagen de 28x28. El modelo se construye como un modelo Secuencial, lo que significa que las capas están apiladas una sobre la otra. La primera capa es una capa Dense (o completamente conectada), que es seguida por una capa Reshape para organizar los datos en una cuadrícula de 7x7 con 128 canales. Las siguientes capas son capas Conv2DTranspose (o de deconvolución), que aumentan la resolución de los datos a un tamaño de imagen más grande. Se utilizan funciones de activación LeakyReLU entre las capas para introducir no linealidad y ayudar a la red a aprender patrones complejos.
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
El modelo del discriminador se define en la función build_discriminator
. Esta toma como entrada una forma de imagen (img_shape
) y construye un modelo que categoriza las imágenes como reales o falsas. El modelo también se construye como un modelo Secuencial, con capas Conv2D (convolucionales) para procesar los datos de imagen, seguido de una capa Flatten para preparar los datos para la capa Dense final. Al igual que en el generador, se utilizan funciones de activación LeakyReLU para introducir no linealidad.
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
La GAN se construye combinando el generador y el discriminador. El generador y el discriminador se instancian con sus respectivas funciones, y el discriminador se compila con el optimizador Adam y la función de pérdida de entropía cruzada binaria. El entrenamiento del discriminador se establece en False durante el proceso de entrenamiento de la GAN para asegurar que solo el generador aprenda de la retroalimentación del discriminador.
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
Finalmente, el código imprime un resumen del generador, el discriminador y el modelo combinado de la GAN. El resumen incluye las capas del modelo, las formas de salida de cada capa y el número de parámetros (es decir, pesos) en cada capa y en total.
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Esta implementación de GAN es un ejemplo básico y sirve como una buena introducción a las GANs. Puede adaptarse y expandirse para acomodar tareas y conjuntos de datos más complejos. Por ejemplo, puede utilizarse para generar imágenes sintéticas para la aumentación de datos, crear arte o producir muestras realistas de cualquier tipo de datos.
Otro Ejemplo: Arquitectura Básica de GAN con PyTorch
import torch
from torch import nn
from torch.nn import functional as F
class Discriminator(nn.Module):
def __init__(self, in_shape=(28, 28, 1)):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=in_shape[0], out_channels=64, kernel_size=3, stride=2, padding=1),
nn.LeakyReLU(negative_slope=0.2),
nn.Conv2d(64, 128, 3, 2, 1),
nn.LeakyReLU(0.2),
nn.Flatten(),
nn.Linear(7 * 7 * 128, 1),
nn.Sigmoid()
)
def forward(self, x):
return self.model(x)
class Generator(nn.Module):
def __init__(self, latent_dim=100):
super(Generator, self).__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 7 * 7 * 256, bias=False),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, output_padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128, 1, 3, 2, 1, output_padding=1),
nn.Tanh()
)
def forward(self, x):
return self.model(x)
def train(epochs, batch_size, data_loader, generator, discriminator, device):
# Optimizers
g_optimizer = torch.optim.Adam(generator.parameters(), lr=0.0002)
d_optimizer = torch.optim.Adam(discriminator.parameters(), lr=0.0002)
for epoch in range(epochs):
for real_images, _ in data_loader:
real_images = real_images.to(device)
# Train Discriminator: Maximize ability to distinguish real from fake
d_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
fake_labels = torch.zeros(batch_size, device=device)
d_real_loss = F.binary_cross_entropy_with_logits(discriminator(real_images), torch.ones(batch_size, device=device))
d_fake_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images.detach()), fake_labels)
d_loss = (d_real_loss + d_fake_loss) / 2
d_loss.backward()
d_optimizer.step()
# Train Generator: Minimize discriminator ability to distinguish fake from real
g_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
g_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images), torch.ones(batch_size, device=device))
g_loss.backward()
g_optimizer.step()
# Print loss
print(f"Epoch: {epoch+1}/{epochs} || D Loss: {d_loss.item():.4f} || G Loss: {g_loss.item():.4f}")
# Example usage (assuming you have your data loader defined)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
discriminator = Discriminator().to(device)
generator = Generator().to(device)
train(10, 32, data_loader, generator, discriminator, device)
El script es una implementación de una Red Generativa Adversarial (GAN) utilizando la biblioteca PyTorch.
Las GANs consisten en dos redes neuronales: el Generador y el Discriminador, que compiten entre sí en una especie de juego. El Generador intenta crear datos que se parezcan a los datos de entrenamiento, mientras que el Discriminador intenta diferenciar entre datos reales del conjunto de entrenamiento y datos falsos producidos por el Generador.
En este script, el Discriminador se define como una clase que hereda de nn.Module
de PyTorch. La red del Discriminador es una red neuronal convolucional que toma una imagen y la procesa a través de una serie de capas convolucionales y funciones de activación. Luego, produce un solo valor que indica si la imagen de entrada es real o falsa.
El Generador también se define como una clase que hereda de nn.Module
. La red del Generador toma como entrada un vector de ruido aleatorio (también conocido como vector latente) y lo transforma en una imagen a través de una serie de capas lineales, capas de normalización por lotes y funciones de activación, y capas de convolución transpuesta (que pueden considerarse como la inversa de las capas convolucionales).
La función de entrenamiento definida en este script, train
, realiza el proceso iterativo de entrenamiento de la GAN. Alterna entre entrenar el Discriminador y el Generador durante un cierto número de épocas. El Discriminador se entrena para maximizar su capacidad de diferenciar los datos reales de los falsos ajustando sus pesos en función de la diferencia entre sus predicciones y las etiquetas reales (que son todas unos para imágenes reales y todas ceros para imágenes falsas). El Generador, por otro lado, se entrena para engañar al Discriminador generando imágenes que el Discriminador clasificará como reales. Ajusta sus pesos en función de lo bien que logra engañar al Discriminador.
El script concluye con un ejemplo de uso de estas clases y la función de entrenamiento. Primero define el dispositivo para la computación (que será una GPU si hay una disponible, de lo contrario se predetermina a una CPU). Luego, inicializa instancias del Generador y el Discriminador, las mueve al dispositivo correcto y finalmente llama a la función train
para entrenar la GAN en un conjunto de datos especificado.
3.2.5 Mejoras y Modificaciones
Se han propuesto varias mejoras y modificaciones innovadoras para abordar los diversos desafíos inherentes al entrenamiento de Redes Generativas Adversariales (GANs). Estas mejoras tienen como objetivo proporcionar mayor estabilidad y confiabilidad durante el proceso de entrenamiento, y aumentar la calidad general de la salida.
- Wasserstein GAN (WGAN): Este es un cambio de paradigma dentro del proceso de entrenamiento de GAN, introduciendo una nueva función de pérdida basada en la distancia Earth Mover, también conocida como distancia Wasserstein. La implementación de esta función de pérdida ha sido fundamental para mejorar la estabilidad del proceso de entrenamiento y también ha servido para reducir significativamente el fenómeno conocido como colapso de modo, un problema común en las GANs tradicionales.
- Normalización Espectral: Esta es una técnica donde se normaliza la norma espectral de las matrices de pesos, controlando efectivamente la constante de Lipschitz de la función del discriminador. Al mejorar la estabilidad de la GAN, esta modificación hace que el proceso de entrenamiento sea más confiable.
- Crecimiento Progresivo de GANs: Esta ingeniosa estrategia comienza con la generación de imágenes de baja resolución al inicio del proceso de entrenamiento. A medida que avanza el entrenamiento, la resolución de estas imágenes se incrementa gradualmente. Esto lleva a resultados de una calidad significativamente superior en comparación con las GANs tradicionales.
Estas modificaciones y mejoras han tenido un profundo impacto en el rendimiento y la robustez de las GANs. Las mejoras no solo han hecho que las GANs sean más confiables y estables, sino que también han incrementado su practicidad para una variedad de aplicaciones.
3.2 Arquitectura de las GANs
La arquitectura de las Redes Generativas Adversariales (GANs), un conjunto único de modelos de aprendizaje automático, consta de dos componentes principales: el generador y el discriminador.
La red del generador tiene la tarea de crear nuevas instancias de datos. Estas instancias, idealmente, deben reflejar las propiedades estadísticas de los datos de entrenamiento. El generador comienza con un vector de ruido aleatorio (vector latente) como entrada, que utiliza para producir muestras de datos a través de una serie de capas completamente conectadas, capas convolucionales y capas de upsampling con el fin de generar datos de alta resolución.
La red del discriminador, por otro lado, tiene la tarea de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Toma una muestra de datos, ya sea real o generada, como entrada y procesa esto a través de una serie de capas convolucionales seguidas de capas completamente conectadas. La salida es un valor único o probabilidad que indica si la entrada es real o falsa.
Entrenar GANs implica actualizar iterativamente tanto el generador como el discriminador. El generador tiene como objetivo producir datos que el discriminador confunda con datos reales, mientras que el discriminador tiene como objetivo identificar correctamente los datos reales y falsos. Este proceso adversarial continúa hasta que el generador se vuelve tan bueno que puede producir datos indistinguibles de los datos reales, o el discriminador ya no puede distinguir entre los dos con alta precisión.
A pesar de su potencial, entrenar GANs puede ser desafiante debido a varios factores como el colapso de modo, la inestabilidad del entrenamiento y la sensibilidad a los hiperparámetros. Sin embargo, los investigadores han desarrollado varias técnicas y modificaciones para abordar estos desafíos y mejorar las capacidades de las GANs.
La arquitectura de las GANs es una estructura fascinante y compleja que ha revolucionado el campo de la modelización generativa. Comprender su arquitectura, proceso de entrenamiento y los desafíos asociados es crucial para aplicar efectivamente las GANs a problemas del mundo real.
3.2.1 La Red del Generador
El generador es una red neuronal que toma un vector de ruido aleatorio como entrada y lo transforma en una muestra de datos que se asemeja a los datos de entrenamiento. El objetivo del generador es producir datos que sean indistinguibles de los datos reales por el discriminador.
Arquitectura del Generador
El generador típicamente consta de varias capas, incluyendo:
- Capas Densas (Completamente Conectadas): Un componente crítico de la arquitectura de la red, estas capas juegan un papel crucial en el modelo. Operan aumentando la dimensionalidad del vector de ruido de entrada. Al realizar esta función, permiten efectivamente que la red aprenda representaciones más complejas y detalladas, facilitando la producción de una gama más amplia de salidas a partir de una entrada dada. Este aumento en la dimensionalidad proporciona a la red la capacidad de comprender e interpretar mejor los datos que está procesando.
- Capa de Reconfiguración (Reshape Layer): Esta es una parte crucial de la arquitectura de la red, ya que transforma la salida de las capas densas anteriores. Esta transformación es necesaria para permitir el procesamiento adicional de los datos. Por ejemplo, si la tarea en cuestión es la generación de imágenes, la capa de reconfiguración manipulará la salida de la capa densa en una forma o formato bidimensional. Esto es esencial porque las imágenes son entidades inherentemente bidimensionales, y las capas subsiguientes en la red probablemente requerirán este formato 2D para realizar sus tareas de manera efectiva. Así, la capa de reconfiguración actúa como un puente para asegurar la compatibilidad entre las capas densas y las etapas posteriores de la red.
- Capas de Convolución Transpuesta (Conv2DTranspose): Estas capas, también conocidas comúnmente como capas deconvolucionales, juegan un papel fundamental en el proceso de upsampling de los datos. La función principal de estas capas es aumentar la resolución de los datos, un proceso que es bastante integral en el campo del aprendizaje profundo. La mayor resolución permite un análisis más detallado, lo que permite que el modelo capture patrones y características más complejas dentro de los datos. Esto puede mejorar significativamente el rendimiento del modelo, particularmente cuando se trata de datos de alta dimensionalidad como imágenes.
- Capas de Activación: En el dominio de las redes neuronales, las capas de activación juegan un papel crucial. Estas capas introducen propiedades no lineales en nuestra red, lo que nos permite modelar una variable de respuesta (también llamada variable objetivo) que varía de manera no lineal con sus variables explicativas. Dos funciones de activación comúnmente utilizadas en estas capas son las funciones ReLU (Unidad Lineal Rectificada) y Tanh (Tangente Hiperbólica). La función ReLU, en particular, es ampliamente utilizada en redes de aprendizaje profundo debido a sus propiedades beneficiosas para tales modelos, como la capacidad de activar un nodo solo si la entrada está por encima de cierta cantidad. Por otro lado, la función Tanh es una función matemática que tiene una curva característica en forma de S, y puede ser útil para normalizar la salida de las neuronas.
Aquí hay un ejemplo de una red de generador diseñada para producir imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Conv2DTranspose
def build_generator(latent_dim):
model = tf.keras.Sequential([
Dense(256 * 7 * 7, input_dim=latent_dim),
LeakyReLU(alpha=0.2),
Reshape((7, 7, 256)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(64, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(1, kernel_size=4, strides=1, padding='same', activation='tanh')
])
return model
# Instantiate and summarize the generator
latent_dim = 100
generator = build_generator(latent_dim)
generator.summary()
En este ejemplo:
El modelo generador se construye utilizando la función build_generator()
. Esta función toma un argumento: la dimensionalidad del vector del espacio latente latent_dim
. El vector del espacio latente es una forma de representación comprimida de los datos y es la entrada para el modelo generador.
La construcción del modelo generador comienza con un objeto tf.keras.Sequential
, que nos permite apilar capas linealmente, con cada capa pasando su salida a la siguiente capa.
La primera capa en el modelo generador es una capa Dense
con 256 * 7 * 7
neuronas, y toma una entrada con una dimensión de latent_dim
. La capa Dense
, también conocida como capa completamente conectada, es un componente crucial de este modelo. Opera aumentando la dimensionalidad del vector de ruido de entrada, permitiendo así que la red aprenda representaciones más complejas y detalladas. Esta mayor dimensionalidad le da a la red una mejor comprensión e interpretación de los datos que está procesando.
A continuación, tenemos una función de activación LeakyReLU
con una pendiente de 0.2 para la parte negativa. Esta es una variante de la función de activación Unidad Lineal Rectificada (ReLU), que introduce no linealidad en la red, permitiéndole aprender patrones complejos. La función LeakyReLU tiene una ventaja sobre la función ReLU regular ya que previene las "neuronas muertas" en escenarios donde una neurona podría dejar de pasar datos hacia adelante a través de la red.
Una capa Reshape
sigue a continuación, transformando la salida de la capa densa anterior en un formato que puede ser procesado por las siguientes capas. En este caso, reformatea la salida en un tensor de forma (7, 7, 256)
. Esta capa es importante para la compatibilidad entre las capas densas y las etapas subsiguientes de la red, especialmente si la tarea en cuestión es la generación de imágenes, ya que las imágenes son entidades inherentemente bidimensionales.
Siguiendo la capa de reconfiguración hay una serie de capas Conv2DTranspose
, también conocidas como capas deconvolucionales. Son clave en el aumento de la resolución de los datos, que es el proceso de aumentar la resolución o tamaño de los datos. Esto se logra añadiendo ceros a los datos de entrada y luego aplicando una operación de convolución regular. Este proceso es integral en el campo del aprendizaje profundo, ya que permite un análisis más detallado, permitiendo que el modelo capture patrones y características más complejas dentro de los datos.
Cada capa Conv2DTranspose
es seguida por una capa de activación LeakyReLU
que introduce no linealidad y previene el problema de las "neuronas muertas". La capa Conv2DTranspose
final utiliza la función de activación 'tanh' para asegurar que los valores de salida caigan dentro del rango de -1 a 1.
Después de crear el modelo generador, se crea una instancia del generador llamando a build_generator(latent_dim)
, donde latent_dim
se establece en 100. Finalmente, se llama a generator.summary()
para mostrar la estructura del modelo generador.
Este modelo generador es un componente clave de una GAN. Trabaja en conjunto con un modelo discriminador para generar datos sintéticos que se asemejan estrechamente a los datos reales. Al entrenar estos dos modelos de manera iterativa, las GANs pueden producir datos altamente realistas, lo que las convierte en una herramienta poderosa en varios campos, como la síntesis de imágenes y voces, la detección de anomalías e incluso la creación de arte.
3.2.2 La Red del Discriminador
El discriminador, que es una parte integral de una Red Generativa Adversarial (GAN), es esencialmente una red neuronal. Esta red acepta una muestra de datos como entrada, que podría ser un punto de datos real o uno generado, y luego produce una probabilidad. Esta probabilidad indica si la muestra alimentada es real o falsa.
La función principal del discriminador, y de hecho su objetivo dentro de la GAN, es clasificar datos con un alto grado de precisión. Apunta a identificar correctamente los puntos de datos reales y distinguirlos de los falsos o generados artificialmente. Este papel crucial del discriminador permite que la GAN mejore progresivamente sus capacidades de generación, permitiendo así la creación de datos sintéticos más realistas.
Arquitectura del Discriminador
El discriminador típicamente consta de varias capas, incluyendo:
- Capas Convolucionales (Conv2D): Estas son un componente crucial de las redes neuronales, específicamente diseñadas para procesar datos de píxeles y extraer características importantes de los datos de entrada. Pueden reconocer patrones con respecto a jerarquías y variaciones espaciales, lo que las hace excepcionalmente buenas en tareas de procesamiento de imágenes y videos. Su función principal es escanear los datos de entrada en busca de ciertas características, que pueden ser útiles para la tarea en cuestión.
- Capa de Aplanamiento (Flatten Layer): La Capa de Aplanamiento sirve una función importante en nuestro modelo. Después de que nuestros datos de entrada han sido procesados por las capas convolucionales, están en un formato 2D. Sin embargo, para que nuestra red neuronal procese estos datos, necesitan estar en un formato 1D. Aquí es donde la Capa de Aplanamiento entra en juego. Efectivamente transforma, o "aplana", la salida 2D de las capas convolucionales en un formato vectorial 1D. Esto permite que los datos procesados sean compatibles y estén listos para las capas subsiguientes de nuestra red neuronal.
- Capas Densas (Completamente Conectadas): Estas son las capas que toman los vectores de características de alta dimensión que han sido generados por las capas anteriores en la red neuronal y reducen su dimensionalidad a un solo valor. Logran esta tarea aplicando una transformación que incluye cada característica en el vector, de ahí el término "completamente conectadas". La función clave de estas capas es interpretar los patrones complejos y de alta dimensionalidad identificados por las capas anteriores y convertirlos en una forma que pueda ser utilizada para la predicción, típicamente un solo valor escalar.
- Capas de Activación: Las capas de activación dictan la salida de una neurona dada una entrada o conjunto de entradas. Algunas de las capas de activación comúnmente usadas incluyen LeakyReLU y Sigmoid. La LeakyReLU es un tipo de función de activación que intenta solucionar el problema de las Unidades Lineales Rectificadas (ReLU) muertas. La función de activación Sigmoid, por otro lado, mapea los valores de entrada entre 0 y 1, lo cual es especialmente útil en la capa de salida de problemas de clasificación binaria.
Aquí hay un ejemplo de una red discriminadora diseñada para clasificar imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, Dense
def build_discriminator(img_shape):
model = tf.keras.Sequential([
Conv2D(64, kernel_size=4, strides=2, padding='same', input_shape=img_shape),
LeakyReLU(alpha=0.2),
Conv2D(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Flatten(),
Dense(1, activation='sigmoid')
])
return model
# Instantiate and summarize the discriminator
img_shape = (28, 28, 1)
discriminator = build_discriminator(img_shape)
discriminator.summary()
En este ejemplo:
En este ejemplo, estamos definiendo la arquitectura de la red del discriminador utilizando TensorFlow y su API de alto nivel Keras.
El discriminador es un tipo de red neuronal que toma una muestra de datos como entrada. Esta muestra puede ser un punto de datos real del conjunto de datos de entrenamiento o una sintética generada por la red del generador. La salida del discriminador es una probabilidad que indica si la muestra es real o falsa.
El objetivo del discriminador es clasificar los datos con precisión, es decir, identificar correctamente los puntos de datos reales y distinguirlos de los sintéticos. Esta capacidad mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
La red del discriminador definida en este código consta de varias capas.
- Capas Conv2D: La capa Conv2D es una capa de convolución que es especialmente efectiva para el procesamiento de imágenes. La primera capa Conv2D toma la imagen de entrada, aplica 64 filtros cada uno de tamaño (4,4) y usa un stride de 2. Se utiliza el padding 'same' para que la salida tenga el mismo ancho y altura que la entrada. La segunda capa Conv2D toma la salida de la primera capa y aplica 128 filtros con los mismos parámetros. Estas capas se utilizan para detectar varias características en la imagen de entrada.
- Capas LeakyReLU: Las capas LeakyReLU son las funciones de activación para las capas Conv2D. Ayudan a introducir no linealidad en el modelo, permitiéndole aprender patrones más complejos. La función LeakyReLU es similar a la función ReLU (Unidad Lineal Rectificada) pero permite pequeños valores negativos cuando la entrada es menor que cero, mitigando el problema de las "neuronas muertas".
- Capa Flatten: La capa Flatten convierte la salida en forma de matriz 2D de las capas anteriores en un vector 1D. Este paso es necesario porque la siguiente capa Dense espera una entrada en formato 1D.
- Capa Dense: La capa Dense es una capa completamente conectada, lo que significa que todas las neuronas en esta capa están conectadas a todas las neuronas en la capa anterior. Esta capa tiene una sola unidad con una función de activación sigmoide. Una función sigmoide produce un valor entre 0 y 1, lo que la hace ideal para problemas de clasificación binaria. En este caso, un valor cercano a 1 indica que es probable que la entrada sea real, y un valor cercano a 0 indica que es probable que sea falsa.
Después de definir la arquitectura, el modelo del discriminador se compila y se imprime un resumen. El resumen incluye los tipos de capas en el modelo, la forma de salida de cada capa, el número de parámetros (pesos y sesgos) en cada capa y el total de parámetros en el modelo.
3.2.3 Interacción Entre el Generador y el Discriminador
Las redes del generador y del discriminador se entrenan en conjunto, con sus roles y objetivos diametralmente opuestos.
El generador y el discriminador se entrenan simultáneamente pero tienen objetivos opuestos. El objetivo del generador es crear datos que se parezcan lo más posible a los datos reales. Comienza con una semilla de ruido aleatorio y transforma este ruido en muestras de datos. A medida que el generador mejora con el tiempo y las iteraciones de entrenamiento, los datos que genera deben volverse cada vez más similares a los datos reales.
Por otro lado, el objetivo del discriminador es clasificar los datos con precisión. Está encargado de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Idealmente, debe producir una alta probabilidad para los datos reales y una baja probabilidad para los datos falsos. La capacidad del discriminador para distinguir con precisión los datos reales de los falsos mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
En el proceso de entrenamiento, se involucran dos pasos principales. Primero, el discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador, con el objetivo de clasificar correctamente las muestras reales como reales y las muestras falsas como falsas. El segundo paso implica entrenar al generador para producir datos que el discriminador no pueda distinguir de los datos reales. En este caso, el objetivo del generador es maximizar el error del discriminador en las muestras falsas, lo que significa que el generador mejora cuando puede engañar al discriminador haciéndole creer que los datos generados son reales.
Este proceso de entrenamiento adversarial continúa iterativamente, con cada red aprendiendo y mejorando a partir de la retroalimentación de la otra. Esto resulta en un generador que puede producir datos altamente realistas y un discriminador que es hábil en detectar datos falsos. Esto convierte a las GANs en una herramienta poderosa en áreas como la generación de imágenes, la superresolución y más.
En resumen, el proceso de entrenamiento involucra dos pasos principales:
- Entrenamiento del Discriminador:
- El discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador.
- El objetivo del discriminador es clasificar correctamente las muestras reales como reales y las muestras falsas como falsas.
- La función de pérdida para el discriminador generalmente usa entropía cruzada binaria para medir el error de clasificación.
- Entrenamiento del Generador:
- El generador se entrena para producir datos que el discriminador no pueda distinguir de los datos reales.
- El objetivo del generador es maximizar el error del discriminador en las muestras falsas (es decir, engañar al discriminador).
- La función de pérdida para el generador también usa entropía cruzada binaria, pero se optimiza en el contexto de engañar al discriminador.
Este proceso de entrenamiento adversarial se puede resumir de la siguiente manera:
- Pérdida del Discriminador: LD=−[log(D(x))+log(1−D(G(z)))]
- Pérdida del Generador: LG=−log(D(G(z)))
Donde D(x) es la salida del discriminador para datos reales x, y D(G(z)) es la salida del discriminador para datos falsos G(z) generados a partir de ruido aleatorio z.
Ejemplo: Entrenando una GAN en Datos MNIST
A continuación se muestra un ejemplo completo de entrenamiento de una GAN en el conjunto de datos MNIST, incluyendo tanto los pasos de entrenamiento del generador como del discriminador:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# Load and preprocess the MNIST dataset
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5 # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
# Training parameters
latent_dim = 100
epochs = 10000
batch_size = 64
sample_interval = 1000
# Build the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Build and compile the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Training the GAN
for epoch in range(epochs):
# Train the discriminator
idx = np.random.randint(0, x_train.shape[0], batch_size)
real_images = x_train[idx]
noise = np.random.normal(0, 1, (batch_size, latent_dim))
fake_images = generator.predict(noise)
d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
# Train the generator
noise = np.random.normal(0, 1, (batch_size, latent_dim))
g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))
# Print progress
if epoch % sample_interval == 0:
print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
# Generate and save images
noise = np.random.normal(0, 1, (10, latent_dim))
generated_images = generator.predict(noise)
fig, axs = plt.subplots(1, 10, figsize=(20, 2))
for i, img in enumerate(generated_images):
axs[i].imshow(img.squeeze(), cmap='gray')
axs[i].axis('off')
plt.show()
En este ejemplo:
Este ejemplo es un script completo para entrenar una Red Generativa Adversarial (GAN) en el famoso conjunto de datos MNIST, que es una colección de 70,000 imágenes en escala de grises de dígitos escritos a mano. Cada imagen tiene un tamaño de 28x28 píxeles. El objetivo es utilizar la GAN para generar nuevas imágenes que se asemejen a los dígitos escritos a mano en el conjunto de datos MNIST.
En este modelo GAN, el generador y el discriminador se entrenan en pasos alternos. Durante la fase de entrenamiento del discriminador, se entrena al discriminador tanto con imágenes reales como falsas. Las imágenes reales provienen directamente del conjunto de datos MNIST, y las imágenes falsas son generadas por el generador. El objetivo del discriminador es clasificar correctamente las imágenes reales como reales y las imágenes falsas como falsas. Después de esta fase de entrenamiento, se actualizan los pesos del discriminador en función de la pérdida incurrida.
A continuación, durante la fase de entrenamiento del generador, el generador genera un nuevo lote de imágenes falsas, y estas imágenes se introducen en el discriminador. Sin embargo, en esta fase, las etiquetas de estas imágenes se establecen como 'reales' en lugar de 'falsas', lo que significa que se entrena al generador para engañar al discriminador. Después de esta fase de entrenamiento, se actualizan los pesos del generador en función de lo bien que logró engañar al discriminador.
Este proceso de entrenamiento alterno continúa durante un número específico de épocas, que en este código se establece en 10,000. En intervalos regulares durante el entrenamiento (después de cada 1,000 épocas en este caso), el programa imprime el número de época actual y las pérdidas incurridas por el discriminador y el generador. También genera un lote de imágenes a partir del generador y las muestra. Esto permite monitorear el progreso del entrenamiento y ver cómo mejoran las imágenes generadas con el tiempo.
En resumen, este ejemplo proporciona una implementación completa de una GAN. Demuestra cómo entrenar la GAN en un conjunto de datos específico, y cómo generar y mostrar nuevas imágenes a partir del modelo entrenado. Este código podría usarse como punto de partida para entrenar una GAN en diferentes tipos de conjuntos de datos o para experimentar con diferentes arquitecturas de GAN.
Ejemplo: Arquitectura Básica de GAN con TensorFlow/Keras
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
# Generator model
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
# Discriminator model
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
# Build and compile the GAN
latent_dim = 100
img_shape = (28, 28, 1)
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Este código de ejemplo proporciona una implementación completa de una Red Generativa Adversarial (GAN) utilizando TensorFlow.
La tarea del generador es producir datos que reflejen los datos de entrenamiento. Comienza con una semilla de ruido aleatorio y la transforma en muestras de datos plausibles. El discriminador, por otro lado, tiene la tarea de distinguir entre datos reales del conjunto de entrenamiento y datos falsos producidos por el generador. Devuelve una probabilidad que indica si una muestra dada es real o falsa.
El código comienza con las importaciones necesarias de TensorFlow y Keras. Keras es una biblioteca de redes neuronales fácil de usar escrita en Python que se ejecuta sobre TensorFlow.
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
El modelo del generador se define en la función build_generator
. Esta función toma como entrada una dimensión latente (latent_dim
) y construye un modelo que genera una imagen de 28x28. El modelo se construye como un modelo Secuencial, lo que significa que las capas están apiladas una sobre la otra. La primera capa es una capa Dense (o completamente conectada), que es seguida por una capa Reshape para organizar los datos en una cuadrícula de 7x7 con 128 canales. Las siguientes capas son capas Conv2DTranspose (o de deconvolución), que aumentan la resolución de los datos a un tamaño de imagen más grande. Se utilizan funciones de activación LeakyReLU entre las capas para introducir no linealidad y ayudar a la red a aprender patrones complejos.
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
El modelo del discriminador se define en la función build_discriminator
. Esta toma como entrada una forma de imagen (img_shape
) y construye un modelo que categoriza las imágenes como reales o falsas. El modelo también se construye como un modelo Secuencial, con capas Conv2D (convolucionales) para procesar los datos de imagen, seguido de una capa Flatten para preparar los datos para la capa Dense final. Al igual que en el generador, se utilizan funciones de activación LeakyReLU para introducir no linealidad.
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
La GAN se construye combinando el generador y el discriminador. El generador y el discriminador se instancian con sus respectivas funciones, y el discriminador se compila con el optimizador Adam y la función de pérdida de entropía cruzada binaria. El entrenamiento del discriminador se establece en False durante el proceso de entrenamiento de la GAN para asegurar que solo el generador aprenda de la retroalimentación del discriminador.
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
Finalmente, el código imprime un resumen del generador, el discriminador y el modelo combinado de la GAN. El resumen incluye las capas del modelo, las formas de salida de cada capa y el número de parámetros (es decir, pesos) en cada capa y en total.
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Esta implementación de GAN es un ejemplo básico y sirve como una buena introducción a las GANs. Puede adaptarse y expandirse para acomodar tareas y conjuntos de datos más complejos. Por ejemplo, puede utilizarse para generar imágenes sintéticas para la aumentación de datos, crear arte o producir muestras realistas de cualquier tipo de datos.
Otro Ejemplo: Arquitectura Básica de GAN con PyTorch
import torch
from torch import nn
from torch.nn import functional as F
class Discriminator(nn.Module):
def __init__(self, in_shape=(28, 28, 1)):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=in_shape[0], out_channels=64, kernel_size=3, stride=2, padding=1),
nn.LeakyReLU(negative_slope=0.2),
nn.Conv2d(64, 128, 3, 2, 1),
nn.LeakyReLU(0.2),
nn.Flatten(),
nn.Linear(7 * 7 * 128, 1),
nn.Sigmoid()
)
def forward(self, x):
return self.model(x)
class Generator(nn.Module):
def __init__(self, latent_dim=100):
super(Generator, self).__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 7 * 7 * 256, bias=False),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, output_padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128, 1, 3, 2, 1, output_padding=1),
nn.Tanh()
)
def forward(self, x):
return self.model(x)
def train(epochs, batch_size, data_loader, generator, discriminator, device):
# Optimizers
g_optimizer = torch.optim.Adam(generator.parameters(), lr=0.0002)
d_optimizer = torch.optim.Adam(discriminator.parameters(), lr=0.0002)
for epoch in range(epochs):
for real_images, _ in data_loader:
real_images = real_images.to(device)
# Train Discriminator: Maximize ability to distinguish real from fake
d_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
fake_labels = torch.zeros(batch_size, device=device)
d_real_loss = F.binary_cross_entropy_with_logits(discriminator(real_images), torch.ones(batch_size, device=device))
d_fake_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images.detach()), fake_labels)
d_loss = (d_real_loss + d_fake_loss) / 2
d_loss.backward()
d_optimizer.step()
# Train Generator: Minimize discriminator ability to distinguish fake from real
g_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
g_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images), torch.ones(batch_size, device=device))
g_loss.backward()
g_optimizer.step()
# Print loss
print(f"Epoch: {epoch+1}/{epochs} || D Loss: {d_loss.item():.4f} || G Loss: {g_loss.item():.4f}")
# Example usage (assuming you have your data loader defined)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
discriminator = Discriminator().to(device)
generator = Generator().to(device)
train(10, 32, data_loader, generator, discriminator, device)
El script es una implementación de una Red Generativa Adversarial (GAN) utilizando la biblioteca PyTorch.
Las GANs consisten en dos redes neuronales: el Generador y el Discriminador, que compiten entre sí en una especie de juego. El Generador intenta crear datos que se parezcan a los datos de entrenamiento, mientras que el Discriminador intenta diferenciar entre datos reales del conjunto de entrenamiento y datos falsos producidos por el Generador.
En este script, el Discriminador se define como una clase que hereda de nn.Module
de PyTorch. La red del Discriminador es una red neuronal convolucional que toma una imagen y la procesa a través de una serie de capas convolucionales y funciones de activación. Luego, produce un solo valor que indica si la imagen de entrada es real o falsa.
El Generador también se define como una clase que hereda de nn.Module
. La red del Generador toma como entrada un vector de ruido aleatorio (también conocido como vector latente) y lo transforma en una imagen a través de una serie de capas lineales, capas de normalización por lotes y funciones de activación, y capas de convolución transpuesta (que pueden considerarse como la inversa de las capas convolucionales).
La función de entrenamiento definida en este script, train
, realiza el proceso iterativo de entrenamiento de la GAN. Alterna entre entrenar el Discriminador y el Generador durante un cierto número de épocas. El Discriminador se entrena para maximizar su capacidad de diferenciar los datos reales de los falsos ajustando sus pesos en función de la diferencia entre sus predicciones y las etiquetas reales (que son todas unos para imágenes reales y todas ceros para imágenes falsas). El Generador, por otro lado, se entrena para engañar al Discriminador generando imágenes que el Discriminador clasificará como reales. Ajusta sus pesos en función de lo bien que logra engañar al Discriminador.
El script concluye con un ejemplo de uso de estas clases y la función de entrenamiento. Primero define el dispositivo para la computación (que será una GPU si hay una disponible, de lo contrario se predetermina a una CPU). Luego, inicializa instancias del Generador y el Discriminador, las mueve al dispositivo correcto y finalmente llama a la función train
para entrenar la GAN en un conjunto de datos especificado.
3.2.5 Mejoras y Modificaciones
Se han propuesto varias mejoras y modificaciones innovadoras para abordar los diversos desafíos inherentes al entrenamiento de Redes Generativas Adversariales (GANs). Estas mejoras tienen como objetivo proporcionar mayor estabilidad y confiabilidad durante el proceso de entrenamiento, y aumentar la calidad general de la salida.
- Wasserstein GAN (WGAN): Este es un cambio de paradigma dentro del proceso de entrenamiento de GAN, introduciendo una nueva función de pérdida basada en la distancia Earth Mover, también conocida como distancia Wasserstein. La implementación de esta función de pérdida ha sido fundamental para mejorar la estabilidad del proceso de entrenamiento y también ha servido para reducir significativamente el fenómeno conocido como colapso de modo, un problema común en las GANs tradicionales.
- Normalización Espectral: Esta es una técnica donde se normaliza la norma espectral de las matrices de pesos, controlando efectivamente la constante de Lipschitz de la función del discriminador. Al mejorar la estabilidad de la GAN, esta modificación hace que el proceso de entrenamiento sea más confiable.
- Crecimiento Progresivo de GANs: Esta ingeniosa estrategia comienza con la generación de imágenes de baja resolución al inicio del proceso de entrenamiento. A medida que avanza el entrenamiento, la resolución de estas imágenes se incrementa gradualmente. Esto lleva a resultados de una calidad significativamente superior en comparación con las GANs tradicionales.
Estas modificaciones y mejoras han tenido un profundo impacto en el rendimiento y la robustez de las GANs. Las mejoras no solo han hecho que las GANs sean más confiables y estables, sino que también han incrementado su practicidad para una variedad de aplicaciones.
3.2 Arquitectura de las GANs
La arquitectura de las Redes Generativas Adversariales (GANs), un conjunto único de modelos de aprendizaje automático, consta de dos componentes principales: el generador y el discriminador.
La red del generador tiene la tarea de crear nuevas instancias de datos. Estas instancias, idealmente, deben reflejar las propiedades estadísticas de los datos de entrenamiento. El generador comienza con un vector de ruido aleatorio (vector latente) como entrada, que utiliza para producir muestras de datos a través de una serie de capas completamente conectadas, capas convolucionales y capas de upsampling con el fin de generar datos de alta resolución.
La red del discriminador, por otro lado, tiene la tarea de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Toma una muestra de datos, ya sea real o generada, como entrada y procesa esto a través de una serie de capas convolucionales seguidas de capas completamente conectadas. La salida es un valor único o probabilidad que indica si la entrada es real o falsa.
Entrenar GANs implica actualizar iterativamente tanto el generador como el discriminador. El generador tiene como objetivo producir datos que el discriminador confunda con datos reales, mientras que el discriminador tiene como objetivo identificar correctamente los datos reales y falsos. Este proceso adversarial continúa hasta que el generador se vuelve tan bueno que puede producir datos indistinguibles de los datos reales, o el discriminador ya no puede distinguir entre los dos con alta precisión.
A pesar de su potencial, entrenar GANs puede ser desafiante debido a varios factores como el colapso de modo, la inestabilidad del entrenamiento y la sensibilidad a los hiperparámetros. Sin embargo, los investigadores han desarrollado varias técnicas y modificaciones para abordar estos desafíos y mejorar las capacidades de las GANs.
La arquitectura de las GANs es una estructura fascinante y compleja que ha revolucionado el campo de la modelización generativa. Comprender su arquitectura, proceso de entrenamiento y los desafíos asociados es crucial para aplicar efectivamente las GANs a problemas del mundo real.
3.2.1 La Red del Generador
El generador es una red neuronal que toma un vector de ruido aleatorio como entrada y lo transforma en una muestra de datos que se asemeja a los datos de entrenamiento. El objetivo del generador es producir datos que sean indistinguibles de los datos reales por el discriminador.
Arquitectura del Generador
El generador típicamente consta de varias capas, incluyendo:
- Capas Densas (Completamente Conectadas): Un componente crítico de la arquitectura de la red, estas capas juegan un papel crucial en el modelo. Operan aumentando la dimensionalidad del vector de ruido de entrada. Al realizar esta función, permiten efectivamente que la red aprenda representaciones más complejas y detalladas, facilitando la producción de una gama más amplia de salidas a partir de una entrada dada. Este aumento en la dimensionalidad proporciona a la red la capacidad de comprender e interpretar mejor los datos que está procesando.
- Capa de Reconfiguración (Reshape Layer): Esta es una parte crucial de la arquitectura de la red, ya que transforma la salida de las capas densas anteriores. Esta transformación es necesaria para permitir el procesamiento adicional de los datos. Por ejemplo, si la tarea en cuestión es la generación de imágenes, la capa de reconfiguración manipulará la salida de la capa densa en una forma o formato bidimensional. Esto es esencial porque las imágenes son entidades inherentemente bidimensionales, y las capas subsiguientes en la red probablemente requerirán este formato 2D para realizar sus tareas de manera efectiva. Así, la capa de reconfiguración actúa como un puente para asegurar la compatibilidad entre las capas densas y las etapas posteriores de la red.
- Capas de Convolución Transpuesta (Conv2DTranspose): Estas capas, también conocidas comúnmente como capas deconvolucionales, juegan un papel fundamental en el proceso de upsampling de los datos. La función principal de estas capas es aumentar la resolución de los datos, un proceso que es bastante integral en el campo del aprendizaje profundo. La mayor resolución permite un análisis más detallado, lo que permite que el modelo capture patrones y características más complejas dentro de los datos. Esto puede mejorar significativamente el rendimiento del modelo, particularmente cuando se trata de datos de alta dimensionalidad como imágenes.
- Capas de Activación: En el dominio de las redes neuronales, las capas de activación juegan un papel crucial. Estas capas introducen propiedades no lineales en nuestra red, lo que nos permite modelar una variable de respuesta (también llamada variable objetivo) que varía de manera no lineal con sus variables explicativas. Dos funciones de activación comúnmente utilizadas en estas capas son las funciones ReLU (Unidad Lineal Rectificada) y Tanh (Tangente Hiperbólica). La función ReLU, en particular, es ampliamente utilizada en redes de aprendizaje profundo debido a sus propiedades beneficiosas para tales modelos, como la capacidad de activar un nodo solo si la entrada está por encima de cierta cantidad. Por otro lado, la función Tanh es una función matemática que tiene una curva característica en forma de S, y puede ser útil para normalizar la salida de las neuronas.
Aquí hay un ejemplo de una red de generador diseñada para producir imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Conv2DTranspose
def build_generator(latent_dim):
model = tf.keras.Sequential([
Dense(256 * 7 * 7, input_dim=latent_dim),
LeakyReLU(alpha=0.2),
Reshape((7, 7, 256)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(64, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(1, kernel_size=4, strides=1, padding='same', activation='tanh')
])
return model
# Instantiate and summarize the generator
latent_dim = 100
generator = build_generator(latent_dim)
generator.summary()
En este ejemplo:
El modelo generador se construye utilizando la función build_generator()
. Esta función toma un argumento: la dimensionalidad del vector del espacio latente latent_dim
. El vector del espacio latente es una forma de representación comprimida de los datos y es la entrada para el modelo generador.
La construcción del modelo generador comienza con un objeto tf.keras.Sequential
, que nos permite apilar capas linealmente, con cada capa pasando su salida a la siguiente capa.
La primera capa en el modelo generador es una capa Dense
con 256 * 7 * 7
neuronas, y toma una entrada con una dimensión de latent_dim
. La capa Dense
, también conocida como capa completamente conectada, es un componente crucial de este modelo. Opera aumentando la dimensionalidad del vector de ruido de entrada, permitiendo así que la red aprenda representaciones más complejas y detalladas. Esta mayor dimensionalidad le da a la red una mejor comprensión e interpretación de los datos que está procesando.
A continuación, tenemos una función de activación LeakyReLU
con una pendiente de 0.2 para la parte negativa. Esta es una variante de la función de activación Unidad Lineal Rectificada (ReLU), que introduce no linealidad en la red, permitiéndole aprender patrones complejos. La función LeakyReLU tiene una ventaja sobre la función ReLU regular ya que previene las "neuronas muertas" en escenarios donde una neurona podría dejar de pasar datos hacia adelante a través de la red.
Una capa Reshape
sigue a continuación, transformando la salida de la capa densa anterior en un formato que puede ser procesado por las siguientes capas. En este caso, reformatea la salida en un tensor de forma (7, 7, 256)
. Esta capa es importante para la compatibilidad entre las capas densas y las etapas subsiguientes de la red, especialmente si la tarea en cuestión es la generación de imágenes, ya que las imágenes son entidades inherentemente bidimensionales.
Siguiendo la capa de reconfiguración hay una serie de capas Conv2DTranspose
, también conocidas como capas deconvolucionales. Son clave en el aumento de la resolución de los datos, que es el proceso de aumentar la resolución o tamaño de los datos. Esto se logra añadiendo ceros a los datos de entrada y luego aplicando una operación de convolución regular. Este proceso es integral en el campo del aprendizaje profundo, ya que permite un análisis más detallado, permitiendo que el modelo capture patrones y características más complejas dentro de los datos.
Cada capa Conv2DTranspose
es seguida por una capa de activación LeakyReLU
que introduce no linealidad y previene el problema de las "neuronas muertas". La capa Conv2DTranspose
final utiliza la función de activación 'tanh' para asegurar que los valores de salida caigan dentro del rango de -1 a 1.
Después de crear el modelo generador, se crea una instancia del generador llamando a build_generator(latent_dim)
, donde latent_dim
se establece en 100. Finalmente, se llama a generator.summary()
para mostrar la estructura del modelo generador.
Este modelo generador es un componente clave de una GAN. Trabaja en conjunto con un modelo discriminador para generar datos sintéticos que se asemejan estrechamente a los datos reales. Al entrenar estos dos modelos de manera iterativa, las GANs pueden producir datos altamente realistas, lo que las convierte en una herramienta poderosa en varios campos, como la síntesis de imágenes y voces, la detección de anomalías e incluso la creación de arte.
3.2.2 La Red del Discriminador
El discriminador, que es una parte integral de una Red Generativa Adversarial (GAN), es esencialmente una red neuronal. Esta red acepta una muestra de datos como entrada, que podría ser un punto de datos real o uno generado, y luego produce una probabilidad. Esta probabilidad indica si la muestra alimentada es real o falsa.
La función principal del discriminador, y de hecho su objetivo dentro de la GAN, es clasificar datos con un alto grado de precisión. Apunta a identificar correctamente los puntos de datos reales y distinguirlos de los falsos o generados artificialmente. Este papel crucial del discriminador permite que la GAN mejore progresivamente sus capacidades de generación, permitiendo así la creación de datos sintéticos más realistas.
Arquitectura del Discriminador
El discriminador típicamente consta de varias capas, incluyendo:
- Capas Convolucionales (Conv2D): Estas son un componente crucial de las redes neuronales, específicamente diseñadas para procesar datos de píxeles y extraer características importantes de los datos de entrada. Pueden reconocer patrones con respecto a jerarquías y variaciones espaciales, lo que las hace excepcionalmente buenas en tareas de procesamiento de imágenes y videos. Su función principal es escanear los datos de entrada en busca de ciertas características, que pueden ser útiles para la tarea en cuestión.
- Capa de Aplanamiento (Flatten Layer): La Capa de Aplanamiento sirve una función importante en nuestro modelo. Después de que nuestros datos de entrada han sido procesados por las capas convolucionales, están en un formato 2D. Sin embargo, para que nuestra red neuronal procese estos datos, necesitan estar en un formato 1D. Aquí es donde la Capa de Aplanamiento entra en juego. Efectivamente transforma, o "aplana", la salida 2D de las capas convolucionales en un formato vectorial 1D. Esto permite que los datos procesados sean compatibles y estén listos para las capas subsiguientes de nuestra red neuronal.
- Capas Densas (Completamente Conectadas): Estas son las capas que toman los vectores de características de alta dimensión que han sido generados por las capas anteriores en la red neuronal y reducen su dimensionalidad a un solo valor. Logran esta tarea aplicando una transformación que incluye cada característica en el vector, de ahí el término "completamente conectadas". La función clave de estas capas es interpretar los patrones complejos y de alta dimensionalidad identificados por las capas anteriores y convertirlos en una forma que pueda ser utilizada para la predicción, típicamente un solo valor escalar.
- Capas de Activación: Las capas de activación dictan la salida de una neurona dada una entrada o conjunto de entradas. Algunas de las capas de activación comúnmente usadas incluyen LeakyReLU y Sigmoid. La LeakyReLU es un tipo de función de activación que intenta solucionar el problema de las Unidades Lineales Rectificadas (ReLU) muertas. La función de activación Sigmoid, por otro lado, mapea los valores de entrada entre 0 y 1, lo cual es especialmente útil en la capa de salida de problemas de clasificación binaria.
Aquí hay un ejemplo de una red discriminadora diseñada para clasificar imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, Dense
def build_discriminator(img_shape):
model = tf.keras.Sequential([
Conv2D(64, kernel_size=4, strides=2, padding='same', input_shape=img_shape),
LeakyReLU(alpha=0.2),
Conv2D(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Flatten(),
Dense(1, activation='sigmoid')
])
return model
# Instantiate and summarize the discriminator
img_shape = (28, 28, 1)
discriminator = build_discriminator(img_shape)
discriminator.summary()
En este ejemplo:
En este ejemplo, estamos definiendo la arquitectura de la red del discriminador utilizando TensorFlow y su API de alto nivel Keras.
El discriminador es un tipo de red neuronal que toma una muestra de datos como entrada. Esta muestra puede ser un punto de datos real del conjunto de datos de entrenamiento o una sintética generada por la red del generador. La salida del discriminador es una probabilidad que indica si la muestra es real o falsa.
El objetivo del discriminador es clasificar los datos con precisión, es decir, identificar correctamente los puntos de datos reales y distinguirlos de los sintéticos. Esta capacidad mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
La red del discriminador definida en este código consta de varias capas.
- Capas Conv2D: La capa Conv2D es una capa de convolución que es especialmente efectiva para el procesamiento de imágenes. La primera capa Conv2D toma la imagen de entrada, aplica 64 filtros cada uno de tamaño (4,4) y usa un stride de 2. Se utiliza el padding 'same' para que la salida tenga el mismo ancho y altura que la entrada. La segunda capa Conv2D toma la salida de la primera capa y aplica 128 filtros con los mismos parámetros. Estas capas se utilizan para detectar varias características en la imagen de entrada.
- Capas LeakyReLU: Las capas LeakyReLU son las funciones de activación para las capas Conv2D. Ayudan a introducir no linealidad en el modelo, permitiéndole aprender patrones más complejos. La función LeakyReLU es similar a la función ReLU (Unidad Lineal Rectificada) pero permite pequeños valores negativos cuando la entrada es menor que cero, mitigando el problema de las "neuronas muertas".
- Capa Flatten: La capa Flatten convierte la salida en forma de matriz 2D de las capas anteriores en un vector 1D. Este paso es necesario porque la siguiente capa Dense espera una entrada en formato 1D.
- Capa Dense: La capa Dense es una capa completamente conectada, lo que significa que todas las neuronas en esta capa están conectadas a todas las neuronas en la capa anterior. Esta capa tiene una sola unidad con una función de activación sigmoide. Una función sigmoide produce un valor entre 0 y 1, lo que la hace ideal para problemas de clasificación binaria. En este caso, un valor cercano a 1 indica que es probable que la entrada sea real, y un valor cercano a 0 indica que es probable que sea falsa.
Después de definir la arquitectura, el modelo del discriminador se compila y se imprime un resumen. El resumen incluye los tipos de capas en el modelo, la forma de salida de cada capa, el número de parámetros (pesos y sesgos) en cada capa y el total de parámetros en el modelo.
3.2.3 Interacción Entre el Generador y el Discriminador
Las redes del generador y del discriminador se entrenan en conjunto, con sus roles y objetivos diametralmente opuestos.
El generador y el discriminador se entrenan simultáneamente pero tienen objetivos opuestos. El objetivo del generador es crear datos que se parezcan lo más posible a los datos reales. Comienza con una semilla de ruido aleatorio y transforma este ruido en muestras de datos. A medida que el generador mejora con el tiempo y las iteraciones de entrenamiento, los datos que genera deben volverse cada vez más similares a los datos reales.
Por otro lado, el objetivo del discriminador es clasificar los datos con precisión. Está encargado de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Idealmente, debe producir una alta probabilidad para los datos reales y una baja probabilidad para los datos falsos. La capacidad del discriminador para distinguir con precisión los datos reales de los falsos mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
En el proceso de entrenamiento, se involucran dos pasos principales. Primero, el discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador, con el objetivo de clasificar correctamente las muestras reales como reales y las muestras falsas como falsas. El segundo paso implica entrenar al generador para producir datos que el discriminador no pueda distinguir de los datos reales. En este caso, el objetivo del generador es maximizar el error del discriminador en las muestras falsas, lo que significa que el generador mejora cuando puede engañar al discriminador haciéndole creer que los datos generados son reales.
Este proceso de entrenamiento adversarial continúa iterativamente, con cada red aprendiendo y mejorando a partir de la retroalimentación de la otra. Esto resulta en un generador que puede producir datos altamente realistas y un discriminador que es hábil en detectar datos falsos. Esto convierte a las GANs en una herramienta poderosa en áreas como la generación de imágenes, la superresolución y más.
En resumen, el proceso de entrenamiento involucra dos pasos principales:
- Entrenamiento del Discriminador:
- El discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador.
- El objetivo del discriminador es clasificar correctamente las muestras reales como reales y las muestras falsas como falsas.
- La función de pérdida para el discriminador generalmente usa entropía cruzada binaria para medir el error de clasificación.
- Entrenamiento del Generador:
- El generador se entrena para producir datos que el discriminador no pueda distinguir de los datos reales.
- El objetivo del generador es maximizar el error del discriminador en las muestras falsas (es decir, engañar al discriminador).
- La función de pérdida para el generador también usa entropía cruzada binaria, pero se optimiza en el contexto de engañar al discriminador.
Este proceso de entrenamiento adversarial se puede resumir de la siguiente manera:
- Pérdida del Discriminador: LD=−[log(D(x))+log(1−D(G(z)))]
- Pérdida del Generador: LG=−log(D(G(z)))
Donde D(x) es la salida del discriminador para datos reales x, y D(G(z)) es la salida del discriminador para datos falsos G(z) generados a partir de ruido aleatorio z.
Ejemplo: Entrenando una GAN en Datos MNIST
A continuación se muestra un ejemplo completo de entrenamiento de una GAN en el conjunto de datos MNIST, incluyendo tanto los pasos de entrenamiento del generador como del discriminador:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# Load and preprocess the MNIST dataset
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5 # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
# Training parameters
latent_dim = 100
epochs = 10000
batch_size = 64
sample_interval = 1000
# Build the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Build and compile the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Training the GAN
for epoch in range(epochs):
# Train the discriminator
idx = np.random.randint(0, x_train.shape[0], batch_size)
real_images = x_train[idx]
noise = np.random.normal(0, 1, (batch_size, latent_dim))
fake_images = generator.predict(noise)
d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
# Train the generator
noise = np.random.normal(0, 1, (batch_size, latent_dim))
g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))
# Print progress
if epoch % sample_interval == 0:
print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
# Generate and save images
noise = np.random.normal(0, 1, (10, latent_dim))
generated_images = generator.predict(noise)
fig, axs = plt.subplots(1, 10, figsize=(20, 2))
for i, img in enumerate(generated_images):
axs[i].imshow(img.squeeze(), cmap='gray')
axs[i].axis('off')
plt.show()
En este ejemplo:
Este ejemplo es un script completo para entrenar una Red Generativa Adversarial (GAN) en el famoso conjunto de datos MNIST, que es una colección de 70,000 imágenes en escala de grises de dígitos escritos a mano. Cada imagen tiene un tamaño de 28x28 píxeles. El objetivo es utilizar la GAN para generar nuevas imágenes que se asemejen a los dígitos escritos a mano en el conjunto de datos MNIST.
En este modelo GAN, el generador y el discriminador se entrenan en pasos alternos. Durante la fase de entrenamiento del discriminador, se entrena al discriminador tanto con imágenes reales como falsas. Las imágenes reales provienen directamente del conjunto de datos MNIST, y las imágenes falsas son generadas por el generador. El objetivo del discriminador es clasificar correctamente las imágenes reales como reales y las imágenes falsas como falsas. Después de esta fase de entrenamiento, se actualizan los pesos del discriminador en función de la pérdida incurrida.
A continuación, durante la fase de entrenamiento del generador, el generador genera un nuevo lote de imágenes falsas, y estas imágenes se introducen en el discriminador. Sin embargo, en esta fase, las etiquetas de estas imágenes se establecen como 'reales' en lugar de 'falsas', lo que significa que se entrena al generador para engañar al discriminador. Después de esta fase de entrenamiento, se actualizan los pesos del generador en función de lo bien que logró engañar al discriminador.
Este proceso de entrenamiento alterno continúa durante un número específico de épocas, que en este código se establece en 10,000. En intervalos regulares durante el entrenamiento (después de cada 1,000 épocas en este caso), el programa imprime el número de época actual y las pérdidas incurridas por el discriminador y el generador. También genera un lote de imágenes a partir del generador y las muestra. Esto permite monitorear el progreso del entrenamiento y ver cómo mejoran las imágenes generadas con el tiempo.
En resumen, este ejemplo proporciona una implementación completa de una GAN. Demuestra cómo entrenar la GAN en un conjunto de datos específico, y cómo generar y mostrar nuevas imágenes a partir del modelo entrenado. Este código podría usarse como punto de partida para entrenar una GAN en diferentes tipos de conjuntos de datos o para experimentar con diferentes arquitecturas de GAN.
Ejemplo: Arquitectura Básica de GAN con TensorFlow/Keras
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
# Generator model
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
# Discriminator model
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
# Build and compile the GAN
latent_dim = 100
img_shape = (28, 28, 1)
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Este código de ejemplo proporciona una implementación completa de una Red Generativa Adversarial (GAN) utilizando TensorFlow.
La tarea del generador es producir datos que reflejen los datos de entrenamiento. Comienza con una semilla de ruido aleatorio y la transforma en muestras de datos plausibles. El discriminador, por otro lado, tiene la tarea de distinguir entre datos reales del conjunto de entrenamiento y datos falsos producidos por el generador. Devuelve una probabilidad que indica si una muestra dada es real o falsa.
El código comienza con las importaciones necesarias de TensorFlow y Keras. Keras es una biblioteca de redes neuronales fácil de usar escrita en Python que se ejecuta sobre TensorFlow.
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
El modelo del generador se define en la función build_generator
. Esta función toma como entrada una dimensión latente (latent_dim
) y construye un modelo que genera una imagen de 28x28. El modelo se construye como un modelo Secuencial, lo que significa que las capas están apiladas una sobre la otra. La primera capa es una capa Dense (o completamente conectada), que es seguida por una capa Reshape para organizar los datos en una cuadrícula de 7x7 con 128 canales. Las siguientes capas son capas Conv2DTranspose (o de deconvolución), que aumentan la resolución de los datos a un tamaño de imagen más grande. Se utilizan funciones de activación LeakyReLU entre las capas para introducir no linealidad y ayudar a la red a aprender patrones complejos.
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
El modelo del discriminador se define en la función build_discriminator
. Esta toma como entrada una forma de imagen (img_shape
) y construye un modelo que categoriza las imágenes como reales o falsas. El modelo también se construye como un modelo Secuencial, con capas Conv2D (convolucionales) para procesar los datos de imagen, seguido de una capa Flatten para preparar los datos para la capa Dense final. Al igual que en el generador, se utilizan funciones de activación LeakyReLU para introducir no linealidad.
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
La GAN se construye combinando el generador y el discriminador. El generador y el discriminador se instancian con sus respectivas funciones, y el discriminador se compila con el optimizador Adam y la función de pérdida de entropía cruzada binaria. El entrenamiento del discriminador se establece en False durante el proceso de entrenamiento de la GAN para asegurar que solo el generador aprenda de la retroalimentación del discriminador.
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
Finalmente, el código imprime un resumen del generador, el discriminador y el modelo combinado de la GAN. El resumen incluye las capas del modelo, las formas de salida de cada capa y el número de parámetros (es decir, pesos) en cada capa y en total.
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Esta implementación de GAN es un ejemplo básico y sirve como una buena introducción a las GANs. Puede adaptarse y expandirse para acomodar tareas y conjuntos de datos más complejos. Por ejemplo, puede utilizarse para generar imágenes sintéticas para la aumentación de datos, crear arte o producir muestras realistas de cualquier tipo de datos.
Otro Ejemplo: Arquitectura Básica de GAN con PyTorch
import torch
from torch import nn
from torch.nn import functional as F
class Discriminator(nn.Module):
def __init__(self, in_shape=(28, 28, 1)):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=in_shape[0], out_channels=64, kernel_size=3, stride=2, padding=1),
nn.LeakyReLU(negative_slope=0.2),
nn.Conv2d(64, 128, 3, 2, 1),
nn.LeakyReLU(0.2),
nn.Flatten(),
nn.Linear(7 * 7 * 128, 1),
nn.Sigmoid()
)
def forward(self, x):
return self.model(x)
class Generator(nn.Module):
def __init__(self, latent_dim=100):
super(Generator, self).__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 7 * 7 * 256, bias=False),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, output_padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128, 1, 3, 2, 1, output_padding=1),
nn.Tanh()
)
def forward(self, x):
return self.model(x)
def train(epochs, batch_size, data_loader, generator, discriminator, device):
# Optimizers
g_optimizer = torch.optim.Adam(generator.parameters(), lr=0.0002)
d_optimizer = torch.optim.Adam(discriminator.parameters(), lr=0.0002)
for epoch in range(epochs):
for real_images, _ in data_loader:
real_images = real_images.to(device)
# Train Discriminator: Maximize ability to distinguish real from fake
d_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
fake_labels = torch.zeros(batch_size, device=device)
d_real_loss = F.binary_cross_entropy_with_logits(discriminator(real_images), torch.ones(batch_size, device=device))
d_fake_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images.detach()), fake_labels)
d_loss = (d_real_loss + d_fake_loss) / 2
d_loss.backward()
d_optimizer.step()
# Train Generator: Minimize discriminator ability to distinguish fake from real
g_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
g_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images), torch.ones(batch_size, device=device))
g_loss.backward()
g_optimizer.step()
# Print loss
print(f"Epoch: {epoch+1}/{epochs} || D Loss: {d_loss.item():.4f} || G Loss: {g_loss.item():.4f}")
# Example usage (assuming you have your data loader defined)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
discriminator = Discriminator().to(device)
generator = Generator().to(device)
train(10, 32, data_loader, generator, discriminator, device)
El script es una implementación de una Red Generativa Adversarial (GAN) utilizando la biblioteca PyTorch.
Las GANs consisten en dos redes neuronales: el Generador y el Discriminador, que compiten entre sí en una especie de juego. El Generador intenta crear datos que se parezcan a los datos de entrenamiento, mientras que el Discriminador intenta diferenciar entre datos reales del conjunto de entrenamiento y datos falsos producidos por el Generador.
En este script, el Discriminador se define como una clase que hereda de nn.Module
de PyTorch. La red del Discriminador es una red neuronal convolucional que toma una imagen y la procesa a través de una serie de capas convolucionales y funciones de activación. Luego, produce un solo valor que indica si la imagen de entrada es real o falsa.
El Generador también se define como una clase que hereda de nn.Module
. La red del Generador toma como entrada un vector de ruido aleatorio (también conocido como vector latente) y lo transforma en una imagen a través de una serie de capas lineales, capas de normalización por lotes y funciones de activación, y capas de convolución transpuesta (que pueden considerarse como la inversa de las capas convolucionales).
La función de entrenamiento definida en este script, train
, realiza el proceso iterativo de entrenamiento de la GAN. Alterna entre entrenar el Discriminador y el Generador durante un cierto número de épocas. El Discriminador se entrena para maximizar su capacidad de diferenciar los datos reales de los falsos ajustando sus pesos en función de la diferencia entre sus predicciones y las etiquetas reales (que son todas unos para imágenes reales y todas ceros para imágenes falsas). El Generador, por otro lado, se entrena para engañar al Discriminador generando imágenes que el Discriminador clasificará como reales. Ajusta sus pesos en función de lo bien que logra engañar al Discriminador.
El script concluye con un ejemplo de uso de estas clases y la función de entrenamiento. Primero define el dispositivo para la computación (que será una GPU si hay una disponible, de lo contrario se predetermina a una CPU). Luego, inicializa instancias del Generador y el Discriminador, las mueve al dispositivo correcto y finalmente llama a la función train
para entrenar la GAN en un conjunto de datos especificado.
3.2.5 Mejoras y Modificaciones
Se han propuesto varias mejoras y modificaciones innovadoras para abordar los diversos desafíos inherentes al entrenamiento de Redes Generativas Adversariales (GANs). Estas mejoras tienen como objetivo proporcionar mayor estabilidad y confiabilidad durante el proceso de entrenamiento, y aumentar la calidad general de la salida.
- Wasserstein GAN (WGAN): Este es un cambio de paradigma dentro del proceso de entrenamiento de GAN, introduciendo una nueva función de pérdida basada en la distancia Earth Mover, también conocida como distancia Wasserstein. La implementación de esta función de pérdida ha sido fundamental para mejorar la estabilidad del proceso de entrenamiento y también ha servido para reducir significativamente el fenómeno conocido como colapso de modo, un problema común en las GANs tradicionales.
- Normalización Espectral: Esta es una técnica donde se normaliza la norma espectral de las matrices de pesos, controlando efectivamente la constante de Lipschitz de la función del discriminador. Al mejorar la estabilidad de la GAN, esta modificación hace que el proceso de entrenamiento sea más confiable.
- Crecimiento Progresivo de GANs: Esta ingeniosa estrategia comienza con la generación de imágenes de baja resolución al inicio del proceso de entrenamiento. A medida que avanza el entrenamiento, la resolución de estas imágenes se incrementa gradualmente. Esto lleva a resultados de una calidad significativamente superior en comparación con las GANs tradicionales.
Estas modificaciones y mejoras han tenido un profundo impacto en el rendimiento y la robustez de las GANs. Las mejoras no solo han hecho que las GANs sean más confiables y estables, sino que también han incrementado su practicidad para una variedad de aplicaciones.
3.2 Arquitectura de las GANs
La arquitectura de las Redes Generativas Adversariales (GANs), un conjunto único de modelos de aprendizaje automático, consta de dos componentes principales: el generador y el discriminador.
La red del generador tiene la tarea de crear nuevas instancias de datos. Estas instancias, idealmente, deben reflejar las propiedades estadísticas de los datos de entrenamiento. El generador comienza con un vector de ruido aleatorio (vector latente) como entrada, que utiliza para producir muestras de datos a través de una serie de capas completamente conectadas, capas convolucionales y capas de upsampling con el fin de generar datos de alta resolución.
La red del discriminador, por otro lado, tiene la tarea de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Toma una muestra de datos, ya sea real o generada, como entrada y procesa esto a través de una serie de capas convolucionales seguidas de capas completamente conectadas. La salida es un valor único o probabilidad que indica si la entrada es real o falsa.
Entrenar GANs implica actualizar iterativamente tanto el generador como el discriminador. El generador tiene como objetivo producir datos que el discriminador confunda con datos reales, mientras que el discriminador tiene como objetivo identificar correctamente los datos reales y falsos. Este proceso adversarial continúa hasta que el generador se vuelve tan bueno que puede producir datos indistinguibles de los datos reales, o el discriminador ya no puede distinguir entre los dos con alta precisión.
A pesar de su potencial, entrenar GANs puede ser desafiante debido a varios factores como el colapso de modo, la inestabilidad del entrenamiento y la sensibilidad a los hiperparámetros. Sin embargo, los investigadores han desarrollado varias técnicas y modificaciones para abordar estos desafíos y mejorar las capacidades de las GANs.
La arquitectura de las GANs es una estructura fascinante y compleja que ha revolucionado el campo de la modelización generativa. Comprender su arquitectura, proceso de entrenamiento y los desafíos asociados es crucial para aplicar efectivamente las GANs a problemas del mundo real.
3.2.1 La Red del Generador
El generador es una red neuronal que toma un vector de ruido aleatorio como entrada y lo transforma en una muestra de datos que se asemeja a los datos de entrenamiento. El objetivo del generador es producir datos que sean indistinguibles de los datos reales por el discriminador.
Arquitectura del Generador
El generador típicamente consta de varias capas, incluyendo:
- Capas Densas (Completamente Conectadas): Un componente crítico de la arquitectura de la red, estas capas juegan un papel crucial en el modelo. Operan aumentando la dimensionalidad del vector de ruido de entrada. Al realizar esta función, permiten efectivamente que la red aprenda representaciones más complejas y detalladas, facilitando la producción de una gama más amplia de salidas a partir de una entrada dada. Este aumento en la dimensionalidad proporciona a la red la capacidad de comprender e interpretar mejor los datos que está procesando.
- Capa de Reconfiguración (Reshape Layer): Esta es una parte crucial de la arquitectura de la red, ya que transforma la salida de las capas densas anteriores. Esta transformación es necesaria para permitir el procesamiento adicional de los datos. Por ejemplo, si la tarea en cuestión es la generación de imágenes, la capa de reconfiguración manipulará la salida de la capa densa en una forma o formato bidimensional. Esto es esencial porque las imágenes son entidades inherentemente bidimensionales, y las capas subsiguientes en la red probablemente requerirán este formato 2D para realizar sus tareas de manera efectiva. Así, la capa de reconfiguración actúa como un puente para asegurar la compatibilidad entre las capas densas y las etapas posteriores de la red.
- Capas de Convolución Transpuesta (Conv2DTranspose): Estas capas, también conocidas comúnmente como capas deconvolucionales, juegan un papel fundamental en el proceso de upsampling de los datos. La función principal de estas capas es aumentar la resolución de los datos, un proceso que es bastante integral en el campo del aprendizaje profundo. La mayor resolución permite un análisis más detallado, lo que permite que el modelo capture patrones y características más complejas dentro de los datos. Esto puede mejorar significativamente el rendimiento del modelo, particularmente cuando se trata de datos de alta dimensionalidad como imágenes.
- Capas de Activación: En el dominio de las redes neuronales, las capas de activación juegan un papel crucial. Estas capas introducen propiedades no lineales en nuestra red, lo que nos permite modelar una variable de respuesta (también llamada variable objetivo) que varía de manera no lineal con sus variables explicativas. Dos funciones de activación comúnmente utilizadas en estas capas son las funciones ReLU (Unidad Lineal Rectificada) y Tanh (Tangente Hiperbólica). La función ReLU, en particular, es ampliamente utilizada en redes de aprendizaje profundo debido a sus propiedades beneficiosas para tales modelos, como la capacidad de activar un nodo solo si la entrada está por encima de cierta cantidad. Por otro lado, la función Tanh es una función matemática que tiene una curva característica en forma de S, y puede ser útil para normalizar la salida de las neuronas.
Aquí hay un ejemplo de una red de generador diseñada para producir imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Conv2DTranspose
def build_generator(latent_dim):
model = tf.keras.Sequential([
Dense(256 * 7 * 7, input_dim=latent_dim),
LeakyReLU(alpha=0.2),
Reshape((7, 7, 256)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(64, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Conv2DTranspose(1, kernel_size=4, strides=1, padding='same', activation='tanh')
])
return model
# Instantiate and summarize the generator
latent_dim = 100
generator = build_generator(latent_dim)
generator.summary()
En este ejemplo:
El modelo generador se construye utilizando la función build_generator()
. Esta función toma un argumento: la dimensionalidad del vector del espacio latente latent_dim
. El vector del espacio latente es una forma de representación comprimida de los datos y es la entrada para el modelo generador.
La construcción del modelo generador comienza con un objeto tf.keras.Sequential
, que nos permite apilar capas linealmente, con cada capa pasando su salida a la siguiente capa.
La primera capa en el modelo generador es una capa Dense
con 256 * 7 * 7
neuronas, y toma una entrada con una dimensión de latent_dim
. La capa Dense
, también conocida como capa completamente conectada, es un componente crucial de este modelo. Opera aumentando la dimensionalidad del vector de ruido de entrada, permitiendo así que la red aprenda representaciones más complejas y detalladas. Esta mayor dimensionalidad le da a la red una mejor comprensión e interpretación de los datos que está procesando.
A continuación, tenemos una función de activación LeakyReLU
con una pendiente de 0.2 para la parte negativa. Esta es una variante de la función de activación Unidad Lineal Rectificada (ReLU), que introduce no linealidad en la red, permitiéndole aprender patrones complejos. La función LeakyReLU tiene una ventaja sobre la función ReLU regular ya que previene las "neuronas muertas" en escenarios donde una neurona podría dejar de pasar datos hacia adelante a través de la red.
Una capa Reshape
sigue a continuación, transformando la salida de la capa densa anterior en un formato que puede ser procesado por las siguientes capas. En este caso, reformatea la salida en un tensor de forma (7, 7, 256)
. Esta capa es importante para la compatibilidad entre las capas densas y las etapas subsiguientes de la red, especialmente si la tarea en cuestión es la generación de imágenes, ya que las imágenes son entidades inherentemente bidimensionales.
Siguiendo la capa de reconfiguración hay una serie de capas Conv2DTranspose
, también conocidas como capas deconvolucionales. Son clave en el aumento de la resolución de los datos, que es el proceso de aumentar la resolución o tamaño de los datos. Esto se logra añadiendo ceros a los datos de entrada y luego aplicando una operación de convolución regular. Este proceso es integral en el campo del aprendizaje profundo, ya que permite un análisis más detallado, permitiendo que el modelo capture patrones y características más complejas dentro de los datos.
Cada capa Conv2DTranspose
es seguida por una capa de activación LeakyReLU
que introduce no linealidad y previene el problema de las "neuronas muertas". La capa Conv2DTranspose
final utiliza la función de activación 'tanh' para asegurar que los valores de salida caigan dentro del rango de -1 a 1.
Después de crear el modelo generador, se crea una instancia del generador llamando a build_generator(latent_dim)
, donde latent_dim
se establece en 100. Finalmente, se llama a generator.summary()
para mostrar la estructura del modelo generador.
Este modelo generador es un componente clave de una GAN. Trabaja en conjunto con un modelo discriminador para generar datos sintéticos que se asemejan estrechamente a los datos reales. Al entrenar estos dos modelos de manera iterativa, las GANs pueden producir datos altamente realistas, lo que las convierte en una herramienta poderosa en varios campos, como la síntesis de imágenes y voces, la detección de anomalías e incluso la creación de arte.
3.2.2 La Red del Discriminador
El discriminador, que es una parte integral de una Red Generativa Adversarial (GAN), es esencialmente una red neuronal. Esta red acepta una muestra de datos como entrada, que podría ser un punto de datos real o uno generado, y luego produce una probabilidad. Esta probabilidad indica si la muestra alimentada es real o falsa.
La función principal del discriminador, y de hecho su objetivo dentro de la GAN, es clasificar datos con un alto grado de precisión. Apunta a identificar correctamente los puntos de datos reales y distinguirlos de los falsos o generados artificialmente. Este papel crucial del discriminador permite que la GAN mejore progresivamente sus capacidades de generación, permitiendo así la creación de datos sintéticos más realistas.
Arquitectura del Discriminador
El discriminador típicamente consta de varias capas, incluyendo:
- Capas Convolucionales (Conv2D): Estas son un componente crucial de las redes neuronales, específicamente diseñadas para procesar datos de píxeles y extraer características importantes de los datos de entrada. Pueden reconocer patrones con respecto a jerarquías y variaciones espaciales, lo que las hace excepcionalmente buenas en tareas de procesamiento de imágenes y videos. Su función principal es escanear los datos de entrada en busca de ciertas características, que pueden ser útiles para la tarea en cuestión.
- Capa de Aplanamiento (Flatten Layer): La Capa de Aplanamiento sirve una función importante en nuestro modelo. Después de que nuestros datos de entrada han sido procesados por las capas convolucionales, están en un formato 2D. Sin embargo, para que nuestra red neuronal procese estos datos, necesitan estar en un formato 1D. Aquí es donde la Capa de Aplanamiento entra en juego. Efectivamente transforma, o "aplana", la salida 2D de las capas convolucionales en un formato vectorial 1D. Esto permite que los datos procesados sean compatibles y estén listos para las capas subsiguientes de nuestra red neuronal.
- Capas Densas (Completamente Conectadas): Estas son las capas que toman los vectores de características de alta dimensión que han sido generados por las capas anteriores en la red neuronal y reducen su dimensionalidad a un solo valor. Logran esta tarea aplicando una transformación que incluye cada característica en el vector, de ahí el término "completamente conectadas". La función clave de estas capas es interpretar los patrones complejos y de alta dimensionalidad identificados por las capas anteriores y convertirlos en una forma que pueda ser utilizada para la predicción, típicamente un solo valor escalar.
- Capas de Activación: Las capas de activación dictan la salida de una neurona dada una entrada o conjunto de entradas. Algunas de las capas de activación comúnmente usadas incluyen LeakyReLU y Sigmoid. La LeakyReLU es un tipo de función de activación que intenta solucionar el problema de las Unidades Lineales Rectificadas (ReLU) muertas. La función de activación Sigmoid, por otro lado, mapea los valores de entrada entre 0 y 1, lo cual es especialmente útil en la capa de salida de problemas de clasificación binaria.
Aquí hay un ejemplo de una red discriminadora diseñada para clasificar imágenes en escala de grises de 28x28:
import tensorflow as tf
from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, Dense
def build_discriminator(img_shape):
model = tf.keras.Sequential([
Conv2D(64, kernel_size=4, strides=2, padding='same', input_shape=img_shape),
LeakyReLU(alpha=0.2),
Conv2D(128, kernel_size=4, strides=2, padding='same'),
LeakyReLU(alpha=0.2),
Flatten(),
Dense(1, activation='sigmoid')
])
return model
# Instantiate and summarize the discriminator
img_shape = (28, 28, 1)
discriminator = build_discriminator(img_shape)
discriminator.summary()
En este ejemplo:
En este ejemplo, estamos definiendo la arquitectura de la red del discriminador utilizando TensorFlow y su API de alto nivel Keras.
El discriminador es un tipo de red neuronal que toma una muestra de datos como entrada. Esta muestra puede ser un punto de datos real del conjunto de datos de entrenamiento o una sintética generada por la red del generador. La salida del discriminador es una probabilidad que indica si la muestra es real o falsa.
El objetivo del discriminador es clasificar los datos con precisión, es decir, identificar correctamente los puntos de datos reales y distinguirlos de los sintéticos. Esta capacidad mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
La red del discriminador definida en este código consta de varias capas.
- Capas Conv2D: La capa Conv2D es una capa de convolución que es especialmente efectiva para el procesamiento de imágenes. La primera capa Conv2D toma la imagen de entrada, aplica 64 filtros cada uno de tamaño (4,4) y usa un stride de 2. Se utiliza el padding 'same' para que la salida tenga el mismo ancho y altura que la entrada. La segunda capa Conv2D toma la salida de la primera capa y aplica 128 filtros con los mismos parámetros. Estas capas se utilizan para detectar varias características en la imagen de entrada.
- Capas LeakyReLU: Las capas LeakyReLU son las funciones de activación para las capas Conv2D. Ayudan a introducir no linealidad en el modelo, permitiéndole aprender patrones más complejos. La función LeakyReLU es similar a la función ReLU (Unidad Lineal Rectificada) pero permite pequeños valores negativos cuando la entrada es menor que cero, mitigando el problema de las "neuronas muertas".
- Capa Flatten: La capa Flatten convierte la salida en forma de matriz 2D de las capas anteriores en un vector 1D. Este paso es necesario porque la siguiente capa Dense espera una entrada en formato 1D.
- Capa Dense: La capa Dense es una capa completamente conectada, lo que significa que todas las neuronas en esta capa están conectadas a todas las neuronas en la capa anterior. Esta capa tiene una sola unidad con una función de activación sigmoide. Una función sigmoide produce un valor entre 0 y 1, lo que la hace ideal para problemas de clasificación binaria. En este caso, un valor cercano a 1 indica que es probable que la entrada sea real, y un valor cercano a 0 indica que es probable que sea falsa.
Después de definir la arquitectura, el modelo del discriminador se compila y se imprime un resumen. El resumen incluye los tipos de capas en el modelo, la forma de salida de cada capa, el número de parámetros (pesos y sesgos) en cada capa y el total de parámetros en el modelo.
3.2.3 Interacción Entre el Generador y el Discriminador
Las redes del generador y del discriminador se entrenan en conjunto, con sus roles y objetivos diametralmente opuestos.
El generador y el discriminador se entrenan simultáneamente pero tienen objetivos opuestos. El objetivo del generador es crear datos que se parezcan lo más posible a los datos reales. Comienza con una semilla de ruido aleatorio y transforma este ruido en muestras de datos. A medida que el generador mejora con el tiempo y las iteraciones de entrenamiento, los datos que genera deben volverse cada vez más similares a los datos reales.
Por otro lado, el objetivo del discriminador es clasificar los datos con precisión. Está encargado de distinguir entre los datos reales del conjunto de entrenamiento y los datos falsos producidos por el generador. Idealmente, debe producir una alta probabilidad para los datos reales y una baja probabilidad para los datos falsos. La capacidad del discriminador para distinguir con precisión los datos reales de los falsos mejora el rendimiento general de la GAN, ya que un mejor discriminador impulsa al generador a crear datos sintéticos más convincentes.
En el proceso de entrenamiento, se involucran dos pasos principales. Primero, el discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador, con el objetivo de clasificar correctamente las muestras reales como reales y las muestras falsas como falsas. El segundo paso implica entrenar al generador para producir datos que el discriminador no pueda distinguir de los datos reales. En este caso, el objetivo del generador es maximizar el error del discriminador en las muestras falsas, lo que significa que el generador mejora cuando puede engañar al discriminador haciéndole creer que los datos generados son reales.
Este proceso de entrenamiento adversarial continúa iterativamente, con cada red aprendiendo y mejorando a partir de la retroalimentación de la otra. Esto resulta en un generador que puede producir datos altamente realistas y un discriminador que es hábil en detectar datos falsos. Esto convierte a las GANs en una herramienta poderosa en áreas como la generación de imágenes, la superresolución y más.
En resumen, el proceso de entrenamiento involucra dos pasos principales:
- Entrenamiento del Discriminador:
- El discriminador se entrena tanto con muestras de datos reales como con muestras de datos falsos generadas por el generador.
- El objetivo del discriminador es clasificar correctamente las muestras reales como reales y las muestras falsas como falsas.
- La función de pérdida para el discriminador generalmente usa entropía cruzada binaria para medir el error de clasificación.
- Entrenamiento del Generador:
- El generador se entrena para producir datos que el discriminador no pueda distinguir de los datos reales.
- El objetivo del generador es maximizar el error del discriminador en las muestras falsas (es decir, engañar al discriminador).
- La función de pérdida para el generador también usa entropía cruzada binaria, pero se optimiza en el contexto de engañar al discriminador.
Este proceso de entrenamiento adversarial se puede resumir de la siguiente manera:
- Pérdida del Discriminador: LD=−[log(D(x))+log(1−D(G(z)))]
- Pérdida del Generador: LG=−log(D(G(z)))
Donde D(x) es la salida del discriminador para datos reales x, y D(G(z)) es la salida del discriminador para datos falsos G(z) generados a partir de ruido aleatorio z.
Ejemplo: Entrenando una GAN en Datos MNIST
A continuación se muestra un ejemplo completo de entrenamiento de una GAN en el conjunto de datos MNIST, incluyendo tanto los pasos de entrenamiento del generador como del discriminador:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# Load and preprocess the MNIST dataset
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5 # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
# Training parameters
latent_dim = 100
epochs = 10000
batch_size = 64
sample_interval = 1000
# Build the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Build and compile the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Training the GAN
for epoch in range(epochs):
# Train the discriminator
idx = np.random.randint(0, x_train.shape[0], batch_size)
real_images = x_train[idx]
noise = np.random.normal(0, 1, (batch_size, latent_dim))
fake_images = generator.predict(noise)
d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
# Train the generator
noise = np.random.normal(0, 1, (batch_size, latent_dim))
g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))
# Print progress
if epoch % sample_interval == 0:
print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
# Generate and save images
noise = np.random.normal(0, 1, (10, latent_dim))
generated_images = generator.predict(noise)
fig, axs = plt.subplots(1, 10, figsize=(20, 2))
for i, img in enumerate(generated_images):
axs[i].imshow(img.squeeze(), cmap='gray')
axs[i].axis('off')
plt.show()
En este ejemplo:
Este ejemplo es un script completo para entrenar una Red Generativa Adversarial (GAN) en el famoso conjunto de datos MNIST, que es una colección de 70,000 imágenes en escala de grises de dígitos escritos a mano. Cada imagen tiene un tamaño de 28x28 píxeles. El objetivo es utilizar la GAN para generar nuevas imágenes que se asemejen a los dígitos escritos a mano en el conjunto de datos MNIST.
En este modelo GAN, el generador y el discriminador se entrenan en pasos alternos. Durante la fase de entrenamiento del discriminador, se entrena al discriminador tanto con imágenes reales como falsas. Las imágenes reales provienen directamente del conjunto de datos MNIST, y las imágenes falsas son generadas por el generador. El objetivo del discriminador es clasificar correctamente las imágenes reales como reales y las imágenes falsas como falsas. Después de esta fase de entrenamiento, se actualizan los pesos del discriminador en función de la pérdida incurrida.
A continuación, durante la fase de entrenamiento del generador, el generador genera un nuevo lote de imágenes falsas, y estas imágenes se introducen en el discriminador. Sin embargo, en esta fase, las etiquetas de estas imágenes se establecen como 'reales' en lugar de 'falsas', lo que significa que se entrena al generador para engañar al discriminador. Después de esta fase de entrenamiento, se actualizan los pesos del generador en función de lo bien que logró engañar al discriminador.
Este proceso de entrenamiento alterno continúa durante un número específico de épocas, que en este código se establece en 10,000. En intervalos regulares durante el entrenamiento (después de cada 1,000 épocas en este caso), el programa imprime el número de época actual y las pérdidas incurridas por el discriminador y el generador. También genera un lote de imágenes a partir del generador y las muestra. Esto permite monitorear el progreso del entrenamiento y ver cómo mejoran las imágenes generadas con el tiempo.
En resumen, este ejemplo proporciona una implementación completa de una GAN. Demuestra cómo entrenar la GAN en un conjunto de datos específico, y cómo generar y mostrar nuevas imágenes a partir del modelo entrenado. Este código podría usarse como punto de partida para entrenar una GAN en diferentes tipos de conjuntos de datos o para experimentar con diferentes arquitecturas de GAN.
Ejemplo: Arquitectura Básica de GAN con TensorFlow/Keras
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
# Generator model
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
# Discriminator model
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
# Build and compile the GAN
latent_dim = 100
img_shape = (28, 28, 1)
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Este código de ejemplo proporciona una implementación completa de una Red Generativa Adversarial (GAN) utilizando TensorFlow.
La tarea del generador es producir datos que reflejen los datos de entrenamiento. Comienza con una semilla de ruido aleatorio y la transforma en muestras de datos plausibles. El discriminador, por otro lado, tiene la tarea de distinguir entre datos reales del conjunto de entrenamiento y datos falsos producidos por el generador. Devuelve una probabilidad que indica si una muestra dada es real o falsa.
El código comienza con las importaciones necesarias de TensorFlow y Keras. Keras es una biblioteca de redes neuronales fácil de usar escrita en Python que se ejecuta sobre TensorFlow.
import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
El modelo del generador se define en la función build_generator
. Esta función toma como entrada una dimensión latente (latent_dim
) y construye un modelo que genera una imagen de 28x28. El modelo se construye como un modelo Secuencial, lo que significa que las capas están apiladas una sobre la otra. La primera capa es una capa Dense (o completamente conectada), que es seguida por una capa Reshape para organizar los datos en una cuadrícula de 7x7 con 128 canales. Las siguientes capas son capas Conv2DTranspose (o de deconvolución), que aumentan la resolución de los datos a un tamaño de imagen más grande. Se utilizan funciones de activación LeakyReLU entre las capas para introducir no linealidad y ayudar a la red a aprender patrones complejos.
def build_generator(latent_dim):
model = Sequential([
Dense(128 * 7 * 7, activation="relu", input_dim=latent_dim),
Reshape((7, 7, 128)),
Conv2DTranspose(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(64, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Conv2DTranspose(1, kernel_size=4, strides=1, padding="same", activation="tanh")
])
return model
El modelo del discriminador se define en la función build_discriminator
. Esta toma como entrada una forma de imagen (img_shape
) y construye un modelo que categoriza las imágenes como reales o falsas. El modelo también se construye como un modelo Secuencial, con capas Conv2D (convolucionales) para procesar los datos de imagen, seguido de una capa Flatten para preparar los datos para la capa Dense final. Al igual que en el generador, se utilizan funciones de activación LeakyReLU para introducir no linealidad.
def build_discriminator(img_shape):
model = Sequential([
Conv2D(64, kernel_size=4, strides=2, padding="same", input_shape=img_shape),
LeakyReLU(alpha=0.01),
Conv2D(128, kernel_size=4, strides=2, padding="same"),
LeakyReLU(alpha=0.01),
Flatten(),
Dense(1, activation="sigmoid")
])
return model
La GAN se construye combinando el generador y el discriminador. El generador y el discriminador se instancian con sus respectivas funciones, y el discriminador se compila con el optimizador Adam y la función de pérdida de entropía cruzada binaria. El entrenamiento del discriminador se establece en False durante el proceso de entrenamiento de la GAN para asegurar que solo el generador aprenda de la retroalimentación del discriminador.
# Instantiate the generator and discriminator
generator = build_generator(latent_dim)
discriminator = build_discriminator(img_shape)
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Create the GAN
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
img = generator(gan_input)
validity = discriminator(img)
gan = tf.keras.Model(gan_input, validity)
gan.compile(optimizer='adam', loss='binary_crossentropy')
Finalmente, el código imprime un resumen del generador, el discriminador y el modelo combinado de la GAN. El resumen incluye las capas del modelo, las formas de salida de cada capa y el número de parámetros (es decir, pesos) en cada capa y en total.
# Summary of the models
generator.summary()
discriminator.summary()
gan.summary()
Esta implementación de GAN es un ejemplo básico y sirve como una buena introducción a las GANs. Puede adaptarse y expandirse para acomodar tareas y conjuntos de datos más complejos. Por ejemplo, puede utilizarse para generar imágenes sintéticas para la aumentación de datos, crear arte o producir muestras realistas de cualquier tipo de datos.
Otro Ejemplo: Arquitectura Básica de GAN con PyTorch
import torch
from torch import nn
from torch.nn import functional as F
class Discriminator(nn.Module):
def __init__(self, in_shape=(28, 28, 1)):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=in_shape[0], out_channels=64, kernel_size=3, stride=2, padding=1),
nn.LeakyReLU(negative_slope=0.2),
nn.Conv2d(64, 128, 3, 2, 1),
nn.LeakyReLU(0.2),
nn.Flatten(),
nn.Linear(7 * 7 * 128, 1),
nn.Sigmoid()
)
def forward(self, x):
return self.model(x)
class Generator(nn.Module):
def __init__(self, latent_dim=100):
super(Generator, self).__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 7 * 7 * 256, bias=False),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, output_padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128, 1, 3, 2, 1, output_padding=1),
nn.Tanh()
)
def forward(self, x):
return self.model(x)
def train(epochs, batch_size, data_loader, generator, discriminator, device):
# Optimizers
g_optimizer = torch.optim.Adam(generator.parameters(), lr=0.0002)
d_optimizer = torch.optim.Adam(discriminator.parameters(), lr=0.0002)
for epoch in range(epochs):
for real_images, _ in data_loader:
real_images = real_images.to(device)
# Train Discriminator: Maximize ability to distinguish real from fake
d_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
fake_labels = torch.zeros(batch_size, device=device)
d_real_loss = F.binary_cross_entropy_with_logits(discriminator(real_images), torch.ones(batch_size, device=device))
d_fake_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images.detach()), fake_labels)
d_loss = (d_real_loss + d_fake_loss) / 2
d_loss.backward()
d_optimizer.step()
# Train Generator: Minimize discriminator ability to distinguish fake from real
g_optimizer.zero_grad()
noise = torch.randn(batch_size, latent_dim, device=device)
fake_images = generator(noise)
g_loss = F.binary_cross_entropy_with_logits(discriminator(fake_images), torch.ones(batch_size, device=device))
g_loss.backward()
g_optimizer.step()
# Print loss
print(f"Epoch: {epoch+1}/{epochs} || D Loss: {d_loss.item():.4f} || G Loss: {g_loss.item():.4f}")
# Example usage (assuming you have your data loader defined)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
discriminator = Discriminator().to(device)
generator = Generator().to(device)
train(10, 32, data_loader, generator, discriminator, device)
El script es una implementación de una Red Generativa Adversarial (GAN) utilizando la biblioteca PyTorch.
Las GANs consisten en dos redes neuronales: el Generador y el Discriminador, que compiten entre sí en una especie de juego. El Generador intenta crear datos que se parezcan a los datos de entrenamiento, mientras que el Discriminador intenta diferenciar entre datos reales del conjunto de entrenamiento y datos falsos producidos por el Generador.
En este script, el Discriminador se define como una clase que hereda de nn.Module
de PyTorch. La red del Discriminador es una red neuronal convolucional que toma una imagen y la procesa a través de una serie de capas convolucionales y funciones de activación. Luego, produce un solo valor que indica si la imagen de entrada es real o falsa.
El Generador también se define como una clase que hereda de nn.Module
. La red del Generador toma como entrada un vector de ruido aleatorio (también conocido como vector latente) y lo transforma en una imagen a través de una serie de capas lineales, capas de normalización por lotes y funciones de activación, y capas de convolución transpuesta (que pueden considerarse como la inversa de las capas convolucionales).
La función de entrenamiento definida en este script, train
, realiza el proceso iterativo de entrenamiento de la GAN. Alterna entre entrenar el Discriminador y el Generador durante un cierto número de épocas. El Discriminador se entrena para maximizar su capacidad de diferenciar los datos reales de los falsos ajustando sus pesos en función de la diferencia entre sus predicciones y las etiquetas reales (que son todas unos para imágenes reales y todas ceros para imágenes falsas). El Generador, por otro lado, se entrena para engañar al Discriminador generando imágenes que el Discriminador clasificará como reales. Ajusta sus pesos en función de lo bien que logra engañar al Discriminador.
El script concluye con un ejemplo de uso de estas clases y la función de entrenamiento. Primero define el dispositivo para la computación (que será una GPU si hay una disponible, de lo contrario se predetermina a una CPU). Luego, inicializa instancias del Generador y el Discriminador, las mueve al dispositivo correcto y finalmente llama a la función train
para entrenar la GAN en un conjunto de datos especificado.
3.2.5 Mejoras y Modificaciones
Se han propuesto varias mejoras y modificaciones innovadoras para abordar los diversos desafíos inherentes al entrenamiento de Redes Generativas Adversariales (GANs). Estas mejoras tienen como objetivo proporcionar mayor estabilidad y confiabilidad durante el proceso de entrenamiento, y aumentar la calidad general de la salida.
- Wasserstein GAN (WGAN): Este es un cambio de paradigma dentro del proceso de entrenamiento de GAN, introduciendo una nueva función de pérdida basada en la distancia Earth Mover, también conocida como distancia Wasserstein. La implementación de esta función de pérdida ha sido fundamental para mejorar la estabilidad del proceso de entrenamiento y también ha servido para reducir significativamente el fenómeno conocido como colapso de modo, un problema común en las GANs tradicionales.
- Normalización Espectral: Esta es una técnica donde se normaliza la norma espectral de las matrices de pesos, controlando efectivamente la constante de Lipschitz de la función del discriminador. Al mejorar la estabilidad de la GAN, esta modificación hace que el proceso de entrenamiento sea más confiable.
- Crecimiento Progresivo de GANs: Esta ingeniosa estrategia comienza con la generación de imágenes de baja resolución al inicio del proceso de entrenamiento. A medida que avanza el entrenamiento, la resolución de estas imágenes se incrementa gradualmente. Esto lleva a resultados de una calidad significativamente superior en comparación con las GANs tradicionales.
Estas modificaciones y mejoras han tenido un profundo impacto en el rendimiento y la robustez de las GANs. Las mejoras no solo han hecho que las GANs sean más confiables y estables, sino que también han incrementado su practicidad para una variedad de aplicaciones.