sábado, 8 de mayo de 2021

Redes Generativas Adversarias (GAN)

Las Redes Generativas Adversarias se componen de un generador y un discriminador.

Generador

Toma a su entrada una distribución aleatoria, suele ser Gaussiana y devuelve a la salida los datos que suelen ser imágenes. Podemos pensar en las entradas aleatorias como en las imágenes latentes que serán generadas. Como se puede ver, el generador ofrece la misma funcionalidad que en un autoencoder variacional y se puede utilizar para generar nuevas imágenes, alimentado por ruido Gaussiano de donde saca las nuevas imágenes.

Discriminador

Toma como entrada una imagen falsa generada por el Generador y otra del dataset de entrenamiento, entonces trata de averiguar cual es la generada y la del dataset. 

Durante el entrenamiento, el generador y el discriminador tiene objetivos opuestos, el discriminador intenta distinguir la imágenes falsas de las auténticas, mientras el generador intenta producir imágenes tan realistas que el discriminador no la distinga de las imágenes del dataset. Como una GAN está compuesta por dos redes con diferentes objetivos, no se puede entrenar como una red neuronal convencional, cada iteracción en el entrenamiento está dividida en dos fases:

En la primera fase, entrenamos el dicriminador. Introducimos un lote de imágenes generadas por el generador y otrode imágenes del dataset y etiquetamos con 0 las del generador y con 1 las del dataset y entrenamos al discriminador con este nuevo dataset utilizando entropía cruzada binaria. Durante esta fase sólo se actualizan los pesos del discriminador.

La segunda fase consiste en entrenar al generador. Para ello, producimos otro lote de imágenes falsas generadas por el generador y utilizamos de nuevo al discriminador para etiquetar las falsas de las reales. Pero esta vez no añadimos imágenes reales en el lote y todas las imágenes son etiquetadas como reales (ponemos 1). Lo que queremos es que el discriminador etiquete erróneamente imágenes falsas como verdaderas. En este paso congelamos los pesos del discriminador, pues lo que nos interesa es entranar al generador.

Vamos a construir una GAN con nuestro dataset de moda MNIST. Para ello construimos el generador y el discriminador. El generador se´ra similar a un decodificador de un autoencoder, y el discriminador es un clasificador binario regular. Toma una imagen de entrada y termina con una capa densa conteniendo una sola neurona con una función de activación sigmoide. En la segunda fase para cada iteracción de entrenamiento necesitamos el modelo GAN completo conteniendo el generador seguido por el discriminador.

np.random.seed(42)

tf.random.set_seed(42)

codings_size = 30

generator = keras.models.Sequential([

    keras.layers.Dense(100, activation="selu", input_shape=[codings_size]),

    keras.layers.Dense(150, activation="selu"),

    keras.layers.Dense(28 * 28, activation="sigmoid"),

    keras.layers.Reshape([28, 28])

])

discriminator = keras.models.Sequential([

    keras.layers.Flatten(input_shape=[28, 28]),

    keras.layers.Dense(150, activation="selu"),

    keras.layers.Dense(100, activation="selu"),

    keras.layers.Dense(1, activation="sigmoid")

])

gan = keras.models.Sequential([generator, discriminator])

Ahora necesitamos compilar estos modelos. Como el discriminador es un clasificador binario podemos utilizar la pérdida binaria de entropía cruzada. El generador sólo será entrenado a través del modelo gan así que no necesitamos compilarlo. El modelo gan también es un clasificador binario, luego también podemos utilizar la pérdida binaria de entropía cruzada. Es importante no entrenar el discriminador durante la segunda fase, así que lo hacemos no entrenable antes de compilar el modelo gan.

discriminator.compile(loss="binary_crossentropy", optimizer="rmsprop")

discriminator.trainable = False

gan.compile(loss="binary_crossentropy", optimizer="rmsprop")

Durante el entrenamiento no debemos utilizar el método fit() en su lugar podemos escribir un bucle de entrenamiento personalizado, para esto necesitamos crear un Dataset para iterar a través de las imágenes.

batch_size = 32

dataset = tf.data.Dataset.from_tensor_slices(X_train).shuffle(1000)

dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1)

Con esto ya estamos preparados para el entrenamiento. Lo vamos a envolver en una función que llamaremos train_gan().

def train_gan(gan, dataset, batch_size, codings_size, n_epochs=50):

    generator, discriminator = gan.layers

    for epoch in range(n_epochs):

        print("Epoch {}/{}".format(epoch + 1, n_epochs))                      for X_batch in dataset:

            # fase 1 entrenamos el discriminador

            noise = tf.random.normal(shape=[batch_size, codings_size])

            generated_images = generator(noise)

            X_fake_and_real = tf.concat([generated_images, X_batch], axis=0)

            y1 = tf.constant([[0.]] * batch_size + [[1.]] * batch_size)

            discriminator.trainable = True

            discriminator.train_on_batch(X_fake_and_real, y1)

            # fase 2 – entrenamos el generador

            noise = tf.random.normal(shape=[batch_size, codings_size])

            y2 = tf.constant([[1.]] * batch_size)

            discriminator.trainable = False

            gan.train_on_batch(noise, y2)

        plot_multiple_images(generated_images, 8)                    

        plt.show()                                                

Como se ha indicado antes, podemos ver dos fases en cada iteracción.

En la fase uno alimentamos con ruido Gaussiano el generador, para producir imágenes falsas y completamos el lote concatenando un número igual de imágenes reales. A las imágenes reales y1 las ponemos la etiqueta 1 y a las falsas la etiqueta 0. También ponemos el atributo trainable del discriminador como true, esto es solo para evitar que salga una advertencia de Keras cuando el atributo es ahora false y anteriormente era true o viceversa.

En la fase 2 alimentamos la GAN con algo de ruido gausiano. Su generador debe producir imágenes falsas y el discriminador debe averiguar si las imágenes son generadas o del dataset. Nosotros queremos que el discriminador crea que las imágenes falsas sean reales. Así que los objetivos y2 son puestos a 1. Ponemos el atributo trainable del discriminador a False una vez más para evitar el Warning.

Desafortunadamente las imágenes nunca serán mucho mejores que estas, incluso si entrenamos la red más épocas la GAN parece olvidar lo aprendido.

tf.random.set_seed(42)

np.random.seed(42)

noise = tf.random.normal(shape=[batch_size, codings_size])

generated_images = generator(noise)

plot_multiple_images(generated_images, 8)

save_fig("gan_generated_images_plot", tight_layout=False)

train_gan(gan, dataset, batch_size, codings_size)

Redes Generativas Adversarias (GAN)

Dificultades de entrenar GANs

Cuando el generador produce imágenes perfectamente realistas  y el discriminador es forzado a adivinar el 50% falsas y el 50 % verdaderas se debería alcanzar el equilibrio de Nash 

Consideramos que si entrenamos una GAN un tiempo suficiente, esta alcanzará dicho equilibrio, teniendo de este modo un generador perfecto, pero no hay nada que nos garantice que se alcanzará dicho equilibrio.

La principal dificultad se llama modo colapso. Consiste en que el generador cada vez saca imágenes menos diversas. Pues si consigue engañar al discriminador mejor con zapatos que con otras prendas, tratará de generar zapatos más amenudo. Gradualmente olvidará genera otras prendas y generará solo zapatos. Del mismo modo el discriminador olvidará cómo discriminar otras prendas que no sean zapatos con lo que forzará al generador a moverse hacia otras prendas pero ambos olvidarán los zapatos, de modo que irán rotando por diferentes prendas y olvidando las anteriores con lo entra en un bucle cerrado del que no saldrá ninguna mejora de la red.

Además como el generador y el discriminador están constantemente luchando uno contra otro, sus parámetros pueden acabar oscilando y haciéndose inestables. Con lo que de repente pueden comenzar a diverger sin razón aparente. Po resto las GAN son muy sensibles a los hiperparámetros y es necesario un gran esfuerzo en ajustarlas convenientemente.

Traducido del capítulo 17 de “Hands-On Machine Learning with Scikit-Learn, Keras and Tensorflow, 2nd Edition by Aurelien Géron (O’Reilly). Copyright 2019 Kiwisoft S.A.S., 978-1-492-03264-9”


No hay comentarios:

Publicar un comentario