Curso
En este tutorial, vas a aprender qué son las redes generativas antagónicas (GAN) sin entrar en los detalles matemáticos. Después, verás cómo programar una GAN sencilla capaz de generar dígitos.
Analogía
La forma más sencilla de entender qué son las GAN es con una analogía:
Imagina una tienda que compra a clientes ciertos tipos de vino para revenderlos después.

Sin embargo, hay clientes malintencionados que intentan colar vino falso para sacar dinero. En este caso, la persona dueña de la tienda tiene que ser capaz de distinguir entre vinos falsos y auténticos.

Al principio, es fácil que el falsificador cometa muchos errores al intentar vender vino falso y que a la persona dueña de la tienda le resulte sencillo detectar que no es auténtico. A partir de estos fallos, el falsificador irá probando distintas técnicas para imitar los vinos reales y algunas acabarán funcionando. Una vez que descubre qué técnicas pasan los controles de la tienda, puede seguir mejorando sus falsificaciones en base a ellas.
Al mismo tiempo, a la persona dueña de la tienda probablemente le llegará feedback de otras tiendas o de expertos en vino de que algunos de los vinos que tiene no son originales. Eso significa que tendrá que mejorar su forma de distinguir si un vino es falso o auténtico. El objetivo del falsificador es crear vinos indistinguibles de los reales, y el objetivo de la tienda es identificar con precisión si un vino es auténtico o no.
Esta competición constante es la idea clave detrás de las GAN.
Componentes de una red generativa antagónica
Usando el ejemplo anterior, podemos esbozar la arquitectura de una GAN.

En una GAN hay dos componentes principales: el generador y el discriminador. La persona dueña de la tienda en el ejemplo equivale a la red discriminadora, que suele ser una red neuronal convolucional (ya que las GAN se usan sobre todo en tareas de imagen) que asigna una probabilidad de que una imagen sea real.
El falsificador es la red generadora y, por lo general, también es una red neuronal convolucional (con capas de deconvolución). Esta red toma un vector de ruido y produce una imagen. Al entrenar la red generadora, aprende qué zonas de la imagen debe mejorar o cambiar para que al discriminador le cueste más diferenciar sus imágenes generadas de las reales.
La red generadora va produciendo imágenes cada vez más parecidas a las reales, mientras que la red discriminadora intenta detectar las diferencias entre imágenes reales y falsas. La meta final es tener una generadora que produzca imágenes indistinguibles de las reales.
Una GAN sencilla con Keras
Ahora que ya sabes qué son las GAN y sus componentes principales, vamos a programar una muy simple. Usarás Keras; si no conoces esta librería de Python, te conviene leer este tutorial antes de seguir. Este tutorial se basa en la GAN desarrollada aquí.
Lo primero es instalar estos paquetes con pip:
kerasmatplotlibtensorflowtqdm
Usarás matplotlib para las gráficas, tensorflow como backend de Keras y tqdm para mostrar una barra de progreso en cada época (iteración).
El siguiente paso es crear un script en Python. En él, primero importarás todos los módulos y funciones que vas a usar. Más adelante se explicará cada uno cuando los utilicemos.
import os
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
from keras.layers import Input
from keras.models import Model, Sequential
from keras.layers.core import Dense, Dropout
from keras.layers.advanced_activations import LeakyReLU
from keras.datasets import mnist
from keras.optimizers import Adam
from keras import initializers
Ahora define algunas variables:
# Let Keras know that we are using tensorflow as our backend engine
os.environ["KERAS_BACKEND"] = "tensorflow"
# To make sure that we can reproduce the experiment and get the same results
np.random.seed(10)
# The dimension of our random noise vector.
random_dim = 100
Antes de construir el discriminador y el generador, primero debes recopilar y preprocesar los datos. Usarás el conocido conjunto de datos MNIST, que contiene imágenes de dígitos del 0 al 9.

def load_minst_data():
# load the data
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# normalize our inputs to be in the range[-1, 1]
x_train = (x_train.astype(np.float32) - 127.5)/127.5
# convert x_train with a shape of (60000, 28, 28) to (60000, 784) so we have
# 784 columns per row
x_train = x_train.reshape(60000, 784)
return (x_train, y_train, x_test, y_test)
Nota: mnist.load_data() forma parte de Keras y te permite importar fácilmente el conjunto de datos MNIST a tu espacio de trabajo.
Ahora puedes crear las redes generadora y discriminadora. Usarás el optimizador Adam para ambas. Tanto para el generador como para el discriminador, crearás una red neuronal con tres capas ocultas y función de activación Leaky ReLU. También deberías añadir capas de dropout al discriminador para mejorar su robustez con imágenes no vistas.
# You will use the Adam optimizer
def get_optimizer():
return Adam(lr=0.0002, beta_1=0.5)
def get_generator(optimizer):
generator = Sequential()
generator.add(Dense(256, input_dim=random_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
generator.add(LeakyReLU(0.2))
generator.add(Dense(512))
generator.add(LeakyReLU(0.2))
generator.add(Dense(1024))
generator.add(LeakyReLU(0.2))
generator.add(Dense(784, activation='tanh'))
generator.compile(loss='binary_crossentropy', optimizer=optimizer)
return generator
def get_discriminator(optimizer):
discriminator = Sequential()
discriminator.add(Dense(1024, input_dim=784, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(512))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(256))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer)
return discriminator
Por fin, ¡vamos a juntar el generador y el discriminador!
def get_gan_network(discriminator, random_dim, generator, optimizer):
# We initially set trainable to False since we only want to train either the
# generator or discriminator at a time
discriminator.trainable = False
# gan input (noise) will be 100-dimensional vectors
gan_input = Input(shape=(random_dim,))
# the output of the generator (an image)
x = generator(gan_input)
# get the output of the discriminator (probability if the image is real or not)
gan_output = discriminator(x)
gan = Model(inputs=gan_input, outputs=gan_output)
gan.compile(loss='binary_crossentropy', optimizer=optimizer)
return gan
Para completar, puedes crear una función que guarde las imágenes generadas cada 20 épocas. Como no es el centro de esta lección, no necesitas entenderla en detalle.
# Create a wall of generated MNIST images
def plot_generated_images(epoch, generator, examples=100, dim=(10, 10), figsize=(10, 10)):
noise = np.random.normal(0, 1, size=[examples, random_dim])
generated_images = generator.predict(noise)
generated_images = generated_images.reshape(examples, 28, 28)
plt.figure(figsize=figsize)
for i in range(generated_images.shape[0]):
plt.subplot(dim[0], dim[1], i+1)
plt.imshow(generated_images[i], interpolation='nearest', cmap='gray_r')
plt.axis('off')
plt.tight_layout()
plt.savefig('gan_generated_image_epoch_%d.png' % epoch)
Ya has programado la mayor parte de la red. Solo queda entrenarla y echar un vistazo a las imágenes generadas.
def train(epochs=1, batch_size=128):
# Get the training and testing data
x_train, y_train, x_test, y_test = load_minst_data()
# Split the training data into batches of size 128
batch_count = x_train.shape[0] / batch_size
# Build our GAN netowrk
adam = get_optimizer()
generator = get_generator(adam)
discriminator = get_discriminator(adam)
gan = get_gan_network(discriminator, random_dim, generator, adam)
for e in xrange(1, epochs+1):
print '-'*15, 'Epoch %d' % e, '-'*15
for _ in tqdm(xrange(batch_count)):
# Get a random set of input noise and images
noise = np.random.normal(0, 1, size=[batch_size, random_dim])
image_batch = x_train[np.random.randint(0, x_train.shape[0], size=batch_size)]
# Generate fake MNIST images
generated_images = generator.predict(noise)
X = np.concatenate([image_batch, generated_images])
# Labels for generated and real data
y_dis = np.zeros(2*batch_size)
# One-sided label smoothing
y_dis[:batch_size] = 0.9
# Train discriminator
discriminator.trainable = True
discriminator.train_on_batch(X, y_dis)
# Train generator
noise = np.random.normal(0, 1, size=[batch_size, random_dim])
y_gen = np.ones(batch_size)
discriminator.trainable = False
gan.train_on_batch(noise, y_gen)
if e == 1 or e % 20 == 0:
plot_generated_images(e, generator)
if __name__ == '__main__':
train(400, 128)
Tras entrenar durante 400 épocas, puedes ver las imágenes generadas. Si miras las imágenes producidas después de la primera época, verás que no tienen estructura. Tras 40 épocas, los dígitos empiezan a tomar forma y, finalmente, después de 400 épocas se aprecian dígitos claros, aunque un par siguen siendo irreconocibles.
| Resultados tras 1 época | Resultados tras 40 épocas | Resultados tras 400 épocas |
|---|---|---|
!![]() |
![]() |
![]() |
Este código tarda aproximadamente 2 minutos por época en una CPU, y esa es la razón principal por la que lo hemos elegido. Puedes experimentar con más épocas y añadiendo más (y diferentes) capas tanto al generador como al discriminador. Eso sí, si usas arquitecturas más complejas y profundas, el tiempo de ejecución aumentará si solo cuentas con CPU. Aun así, no dejes de probar.
Conclusión
¡Enhorabuena! Has llegado al final del tutorial, donde has aprendido los fundamentos de las redes generativas antagónicas (GAN) de forma intuitiva. Además, has implementado tu primer modelo con la ayuda de la librería Keras. Si quieres saber más sobre deep learning con Python, échale un vistazo al curso Deep Learning in Python de DataCamp.







