Las redes adversas generativas (GAN) son una poderosa clase de redes neuronales que se utilizan para el aprendizaje no supervisado . Fue desarrollado e introducido por Ian J. Goodfellow en 2014. Las GAN se componen básicamente de un sistema de dos modelos de redes neuronales que compiten entre sí y pueden analizar, capturar y copiar las variaciones dentro de un conjunto de datos.
¿Por qué se desarrollaron las GAN en primer lugar?
Se ha notado que la mayoría de las redes neuronales convencionales pueden ser fácilmente engañadas para clasificar erróneamente las cosas agregando solo una pequeña cantidad de ruido a los datos originales. Sorprendentemente, el modelo después de agregar ruido tiene una mayor confianza en la predicción incorrecta que cuando predijo correctamente. La razón de tal adversario es que la mayoría de los modelos de aprendizaje automático aprenden de una cantidad limitada de datos, lo cual es un gran inconveniente, ya que es propenso al sobreajuste. Además, el mapeo entre la entrada y la salida es casi lineal. Aunque puede parecer que los límites de separación entre las diversas clases son lineales, en realidad están compuestos de linealidades e incluso un pequeño cambio en un punto en el espacio de características podría conducir a una clasificación errónea de los datos.
¿Cómo funcionan las GAN?
Las redes adversarias generativas (GAN) se pueden dividir en tres partes:
- Generativo: para aprender un modelo generativo, que describe cómo se generan los datos en términos de un modelo probabilístico.
- Adversarial: El entrenamiento de un modelo se realiza en un entorno adversario.
- Redes: use redes neuronales profundas como algoritmos de inteligencia artificial (IA) para fines de capacitación.
En las GAN, hay un generador y un discriminador . El Generador genera muestras falsas de datos (ya sea una imagen, audio, etc.) e intenta engañar al Discriminador. El Discriminador, por otro lado, trata de distinguir entre las muestras reales y las falsas. Tanto el generador como el discriminador son redes neuronales y ambos compiten entre sí en la fase de entrenamiento. Los pasos se repiten varias veces y en esto, el Generador y el Discriminador mejoran cada vez más en sus respectivos trabajos después de cada repetición. El funcionamiento se puede visualizar mediante el siguiente diagrama:
Aquí, el modelo generativo captura la distribución de datos y se entrena de tal manera que trata de maximizar la probabilidad de que el Discriminador cometa un error. El Discriminador, por otro lado, se basa en un modelo que estima la probabilidad de que la muestra que obtuvo se reciba de los datos de entrenamiento y no del Generador.
Las GAN están formuladas como un juego minimax, donde el Discriminador está tratando de minimizar su recompensa V(D, G) y el Generador está tratando de minimizar la recompensa del Discriminador o, en otras palabras, maximizar su pérdida. Se puede describir matemáticamente mediante la siguiente fórmula:
donde,
G = Generador
D = Discriminador
Pdata(x) = Distribución de datos reales
P(z) = Distribución del generador
x = Muestra de Pdata(x)
z = Muestra de P(z)
D(x) = Discriminador de red
G( z) = Red de generadores
Entonces, básicamente, entrenar un GAN tiene dos partes:
- Parte 1: El Discriminador se entrena mientras el Generador está inactivo. En esta fase, la red solo se propaga hacia adelante y no se realiza una propagación hacia atrás. El Discriminador está entrenado en datos reales para n épocas, y ve si puede predecirlas correctamente como reales. Además, en esta fase, el Discriminador también se entrena en los datos falsos generados por el Generador y ve si puede predecirlos correctamente como falsos.
- Parte 2: El Generador se entrena mientras el Discriminador está inactivo. Después de que el Discriminador sea entrenado por los datos falsos generados por el Generador, podemos obtener sus predicciones y usar los resultados para entrenar el Generador y mejorar el estado anterior para tratar de engañar al Discriminador.
El método anterior se repite durante algunas épocas y luego verifica manualmente los datos falsos si parecen genuinos. Si parece aceptable, entonces se detiene el entrenamiento, de lo contrario, se permite que continúe por unas pocas épocas más.
Diferentes tipos de GAN:
las GAN ahora son un tema de investigación muy activo y ha habido muchos tipos diferentes de implementación de GAN. Algunos de los más importantes que se están utilizando actualmente se describen a continuación:
- Vanilla GAN: este es el tipo de GAN más simple. Aquí, el Generador y el Discriminador son simples perceptrones multicapa. En Vanilla GAN, el algoritmo es realmente simple, intenta optimizar la ecuación matemática mediante el descenso de gradiente estocástico.
- GAN condicional (CGAN): CGAN se puede describir como un método de aprendizaje profundo en el que se establecen algunos parámetros condicionales. En CGAN, se agrega un parámetro adicional ‘y’ al Generador para generar los datos correspondientes. Las etiquetas también se colocan en la entrada del Discriminador para que el Discriminador ayude a distinguir los datos reales de los datos falsos generados.
- Deep Convolutional GAN (DCGAN): DCGAN es una de las implementaciones más populares y exitosas de GAN. Se compone de ConvNets en lugar de perceptrones multicapa. Las ConvNets se implementan sin agrupación máxima, que de hecho se reemplaza por zancada convolucional. Además, las capas no están completamente conectadas.
- Pirámide laplaciana GAN (LAPGAN): La pirámide laplaciana es una representación de imagen lineal invertible que consta de un conjunto de imágenes de paso de banda, espaciadas con una octava de separación, más un residuo de baja frecuencia. Este enfoque utiliza múltiples números de redes generadoras y discriminadoras y diferentes niveles de la pirámide laplaciana. Este enfoque se utiliza principalmente porque produce imágenes de muy alta calidad. La imagen se muestrea primero primero en cada capa de la pirámide y luego se vuelve a escalar en cada capa en un paso hacia atrás donde la imagen adquiere algo de ruido de la GAN condicional en estas capas hasta que alcanza su tamaño original.
- GAN de superresolución (SRGAN): SRGAN, como sugiere el nombre, es una forma de diseñar una GAN en la que se usa una red neuronal profunda junto con una red antagónica para producir imágenes de mayor resolución. Este tipo de GAN es particularmente útil para escalar de manera óptima imágenes nativas de baja resolución para mejorar sus detalles y minimizar los errores al hacerlo.
Ejemplo de código de Python que implementa una red antagónica generativa:
las GAN son muy costosas desde el punto de vista computacional. Requieren GPU de alta potencia y mucho tiempo (una gran cantidad de épocas) para producir buenos resultados. Para nuestro ejemplo, usaremos el famoso conjunto de datos MNIST y lo usaremos para producir un clon de un dígito aleatorio.
# importing the necessary libraries and the MNIST dataset import tensorflow as tf import numpy as np import matplotlib.pyplot as plt from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("MNIST_data") # defining functions for the two networks. # Both the networks have two hidden layers # and an output layer which are densely or # fully connected layers defining the # Generator network function def generator(z, reuse = None): with tf.variable_scope('gen', reuse = reuse): hidden1 = tf.layers.dense(inputs = z, units = 128, activation = tf.nn.leaky_relu) hidden2 = tf.layers.dense(inputs = hidden1, units = 128, activation = tf.nn.leaky_relu) output = tf.layers.dense(inputs = hidden2, units = 784, activation = tf.nn.tanh) return output # defining the Discriminator network function def discriminator(X, reuse = None): with tf.variable_scope('dis', reuse = reuse): hidden1 = tf.layers.dense(inputs = X, units = 128, activation = tf.nn.leaky_relu) hidden2 = tf.layers.dense(inputs = hidden1, units = 128, activation = tf.nn.leaky_relu) logits = tf.layers.dense(hidden2, units = 1) output = tf.sigmoid(logits) return output, logits # creating placeholders for the outputs tf.reset_default_graph() real_images = tf.placeholder(tf.float32, shape =[None, 784]) z = tf.placeholder(tf.float32, shape =[None, 100]) G = generator(z) D_output_real, D_logits_real = discriminator(real_images) D_output_fake, D_logits_fake = discriminator(G, reuse = True) # defining the loss function def loss_func(logits_in, labels_in): return tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits( logits = logits_in, labels = labels_in)) # Smoothing for generalization D_real_loss = loss_func(D_logits_real, tf.ones_like(D_logits_real)*0.9) D_fake_loss = loss_func(D_logits_fake, tf.zeros_like(D_logits_real)) D_loss = D_real_loss + D_fake_loss G_loss = loss_func(D_logits_fake, tf.ones_like(D_logits_fake)) # defining the learning rate, batch size, # number of epochs and using the Adam optimizer lr = 0.001 # learning rate # Do this when multiple networks # interact with each other # returns all variables created(the two # variable scopes) and makes trainable true tvars = tf.trainable_variables() d_vars =[var for var in tvars if 'dis' in var.name] g_vars =[var for var in tvars if 'gen' in var.name] D_trainer = tf.train.AdamOptimizer(lr).minimize(D_loss, var_list = d_vars) G_trainer = tf.train.AdamOptimizer(lr).minimize(G_loss, var_list = g_vars) batch_size = 100 # batch size epochs = 500 # number of epochs. The higher the better the result init = tf.global_variables_initializer() # creating a session to train the networks samples =[] # generator examples with tf.Session() as sess: sess.run(init) for epoch in range(epochs): num_batches = mnist.train.num_examples//batch_size for i in range(num_batches): batch = mnist.train.next_batch(batch_size) batch_images = batch[0].reshape((batch_size, 784)) batch_images = batch_images * 2-1 batch_z = np.random.uniform(-1, 1, size =(batch_size, 100)) _= sess.run(D_trainer, feed_dict ={real_images:batch_images, z:batch_z}) _= sess.run(G_trainer, feed_dict ={z:batch_z}) print("on epoch{}".format(epoch)) sample_z = np.random.uniform(-1, 1, size =(1, 100)) gen_sample = sess.run(generator(z, reuse = True), feed_dict ={z:sample_z}) samples.append(gen_sample) # result after 0th epoch plt.imshow(samples[0].reshape(28, 28)) # result after 499th epoch plt.imshow(samples[49].reshape(28, 28))
Producción:
on epoch0 on epoch1 ... ... ... on epoch498 on epoch499
Resultado después de la época 0:
Resultado después de la época 499:
Entonces, del ejemplo anterior, vemos que en la primera imagen después de la época 0, los píxeles están dispersos por todo el lugar y no pudimos descifrar nada.
Pero a partir de la segunda imagen, pudimos ver que los píxeles están organizados de forma más sistemática y pudimos darnos cuenta de que es el dígito ‘7’ el que el código ha elegido al azar y la red ha intentado hacer un clon de él. En nuestro ejemplo, hemos tomado 500 como el número de épocas. Pero podría aumentar ese número para refinar aún más su resultado.