¡Entrene su primer modelo GAN! -Hablemos de Gans

Share on facebook
Share on twitter
Share on linkedin
Share on telegram
Share on whatsapp

Contenidos

Este artículo fue publicado como parte del Blogatón de ciencia de datos

«Generative Adversarial Networks es la idea más interesante en los últimos diez años en Machine Learning» – Yann LeCun

Introducción

comprensión matemática y práctica de la misma, pero antes de eso, si desea echar un vistazo a los conceptos básicos de las GAN, puede continuar con el siguiente enlace:

https://www.analyticsvidhya.com/blog/2021/04/lets-talk-about-gans/

La mayoría de los gigantes de la tecnología (como Google, Microsoft, Amazon, etc.) están trabajando arduamente para aplicar las GAN a un uso práctico, algunos de estos casos de uso son:

  1. Adobe: uso de GAN para su Photoshop de próxima generación.
  2. Google: uso de GAN para la generación de texto.
  3. IBM: uso de GAN para aumento de datos (para generar imágenes sintéticas para entrenar sus modelos de clasificación).
  4. Snap Chat / TikTok: para crear varios filtros de imagen (que es posible que ya haya visto).
  5. Disney: uso de GAN para súper resolución (mejora de la calidad de video) para sus películas.

Algo que es especial con las GAN es que estas empresas dependen de ellas para su futuro, ¿no crees?

Entonces, ¿qué te detiene para obtener el conocimiento de esta tecnología épica? Te responderé, nada, solo necesitas una ventaja y este artículo lo haría. Primero discutamos las matemáticas detrás de Generator y Discriminator.

Funcionamiento matemático del discriminador:

El único propósito del Discriminador es clasificar imágenes reales y falsas. Para la clasificación, utiliza una red neuronal convolucional (CNN) tradicional con una función de costo específica. El proceso de formación de Discriminator funciona de la siguiente manera:

88437discriminator-2252383
Fuente: deeplearning.ai

Donde X e Y son características de entrada y etiquetas respectivamente, la salida se representa con (ŷ) y los parámetros de red se representan con (θ).

Los GAN de entrenamiento necesitan un conjunto de imágenes de entrenamiento y sus respectivas etiquetas, estas imágenes como característica de entrada van a CNN, con un conjunto de parámetros inicializados. Esta CNN genera salida multiplicando la matriz de peso (W) con las características de entrada (X) y agregando un Bias (B) en ella y convirtiéndola en una matriz no lineal pasándola a una función de activación.

Esta salida se conoce como salida predicha, luego la pérdida se calcula en función de los parámetros de peso que se ajustan en la red para minimizar la pérdida.

Funcionamiento matemático del generador:

El objetivo del Generador es generar una imagen falsa a partir de la distribución dada (conjunto de imágenes), lo hace con el siguiente procedimiento:

42922generator-1051664
Fuente: deeplearning.ai

Se pasa un conjunto de vectores de entrada (ruido aleatorio) a través de la red neuronal del generador, que crea una imagen completamente nueva al multiplicar la matriz de peso del generador con el ruido de entrada.

Esta imagen generada funciona como entrada para el discriminador que está entrenado para clasificar imágenes falsas y reales. Luego se calcula la pérdida para las imágenes generadas, en base a qué parámetros se actualizan para el generador hasta que obtengamos una buena precisión.

Una vez que estamos satisfechos con la precisión del Generador, guardamos los pesos del Generador y eliminamos el Discriminador de la red, y usamos esa matriz de pesos para generar más imágenes nuevas pasándole una matriz de ruido aleatorio diferente cada vez.

Pérdida de entropía cruzada binaria para GAN:

Para optimizar los parámetros de las GAN, necesitamos una función de costo que le diga a la red cuánto necesita mejorar simplemente calculando la diferencia entre el valor real y el predicho. La función de pérdida que se utiliza en las GAN se denomina entropía cruzada binaria y se representa como:

76148screenshot202021-05-2220at203-57-2320pm-8133743
Fuente: deeplearning.ai

Donde m es el tamaño del lote, y(I) es el valor de etiqueta real, h es el valor de etiqueta predicho, x(I) es la característica de entrada y θ representa el parámetro.

Dividamos esta función de costo en subpartes para comprender mejor. La fórmula dada es la combinación de dos términos donde uno se usa cuando es efectivo cuando la etiqueta es «0» y el otro es importante cuando la etiqueta es «1». El primer término es:

14807screenshot202021-05-2220at204-08-1220pm-9857183
Fuente: deeplearning.ai

si el valor real es «1» y el valor predicho es «~ 0» en este caso, ya que log (~ 0) tiende a infinito negativo o muy alto, y si el valor predicho también es «~ 1», entonces el log ( ~ 1) estaría cerca de «0» o muy menos, por lo que este término ayuda a calcular la pérdida para los valores de la etiqueta «1».

53011screenshot202021-05-2220at204-08-2320pm-7031126
Fuente: deeplearning.ai

Si el valor real es «0» y el valor predicho es «~ 1», entonces log (1- (~ 1)) daría como resultado un infinito negativo o muy alto, y si el valor predicho es «~ 0», entonces el término sería producen resultados “~ 0” o una pérdida muy inferior, por lo que este término se utiliza para los valores de etiqueta reales “0”.

Cualquiera de los términos de la pérdida devolvería los valores negativos en caso de que la predicción sea incorrecta, la combinación de estos términos se denomina Entropía (pérdida logarítmica). Pero como es negativo, para hacerlo mayor que “1” le aplicamos un signo negativo (se puede ver en la fórmula principal), aplicar este signo negativo es lo que lo hace Entropía cruzada (pérdida logarítmica negativa).

Entrenemos el primer modelo GAN:

Crearemos un modelo GAN que podría generar dígitos escritos a mano a partir de la distribución de datos MNIST utilizando el módulo PyTorch.

Primero, importemos los módulos requeridos:

%matplotlib inline
import numpy as np
import torch
import matplotlib.pyplot as plt

Luego leeríamos los datos del submódulo proporcionado por PyTorch llamado conjuntos de datos.

# number of subprocesses to use for data loading
num_workers = 0
# how many samples per batch to load
batch_size = 64
# convert data to torch.FloatTensor
transform = transforms.ToTensor()
# get the training datasets
train_data = datasets.MNIST(root="data", train=True,
                                   download=True, transform=transform)
# prepare data loader
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size,
                                           num_workers=num_workers)

Visualiza los datos

Dado que estaríamos creando nuestro modelo en el marco de PyTorch que usa tensores, estaríamos convirtiendo nuestros datos en tensores de antorcha. Si desea visualizar los datos, puede seguir adelante y usar el siguiente fragmento de código:

# obtain one batch of training images
dataiter = iter(train_loader)
images, labels = dataiter.next()
images = images.numpy()
# get one image from the batch
img = np.squeeze(images[0])
fig = plt.figure(figsize = (3,3)) 
ax = fig.add_subplot(111)
ax.imshow(img, cmap='gray')
64254screenshot202021-05-2320at2012-39-2020pm-6073823
Fuente: Udacity.com

Discriminado

Ahora es el momento de definir la red Discriminator, que es la combinación de varias capas de CNN.

import torch.nn as nn
import torch.nn.functional as F
class Discriminator(nn.Module):
    def __init__(self, input_size, hidden_dim, output_size):
        super(Discriminator, self).__init__()
        # define hidden linear layers
        self.fc1 = nn.Linear(input_size, hidden_dim*4)
        self.fc2 = nn.Linear(hidden_dim*4, hidden_dim*2)
        self.fc3 = nn.Linear(hidden_dim*2, hidden_dim)
        # final fully-connected layer
        self.fc4 = nn.Linear(hidden_dim, output_size)
        # dropout layer 
        self.dropout = nn.Dropout(0.3)
    def forward(self, x):
        # flatten image
        x = x.view(-1, 28*28)
        # all hidden layers
        x = F.leaky_relu(self.fc1(x), 0.2) # (input, negative_slope=0.2)
        x = self.dropout(x)
        x = F.leaky_relu(self.fc2(x), 0.2)
        x = self.dropout(x)
        x = F.leaky_relu(self.fc3(x), 0.2)
        x = self.dropout(x)
        # final layer
        out = self.fc4(x)
        return out

El código anterior sigue la arquitectura tradicional de Python basada en objetos orientados. fc1, fc2, fc3, fc3 son las capas completamente conectadas. Cuando pasamos nuestras entidades de entrada, pasa a través de todas estas capas comenzando desde fc1, y al final, tenemos una capa de abandono que se usa para abordar el problema de sobreajuste.

En el mismo código, verá una función llamada forward (self, x), esta función es la implementación del mecanismo de propagación hacia adelante real donde cada capa (fc1, fc2, fc3 y fc4) va seguida de una función de activación (leaping_relu ) para convertir la salida del trazador de líneas en no lineal.

Modelo de generador

Después de esto, verificaremos el segmento Generador de GAN:

class Generator(nn.Module):
    def __init__(self, input_size, hidden_dim, output_size):
        super(Generator, self).__init__()
        # define hidden linear layers
        self.fc1 = nn.Linear(input_size, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim*2)
        self.fc3 = nn.Linear(hidden_dim*2, hidden_dim*4)
        # final fully-connected layer
        self.fc4 = nn.Linear(hidden_dim*4, output_size)
        # dropout layer 
        self.dropout = nn.Dropout(0.3)
    def forward(self, x):
        # all hidden layers
        x = F.leaky_relu(self.fc1(x), 0.2) # (input, negative_slope=0.2)
        x = self.dropout(x)
        x = F.leaky_relu(self.fc2(x), 0.2)
        x = self.dropout(x)
        x = F.leaky_relu(self.fc3(x), 0.2)
        x = self.dropout(x)
        # final layer with tanh applied
        out = F.tanh(self.fc4(x))
        return out

La red del generador también se construye a partir de las capas completamente conectadas, las funciones de activación de relu con fugas y la deserción. Lo único que lo hace diferente de Discriminator es que da salida dependiendo del parámetro output_size (que es el tamaño de la imagen a generar).

Ajuste de hiperparámetros

Los hiperparámetros que vamos a utilizar para entrenar las GAN son:

# Discriminator hyperparams
# Size of input image to discriminator (28*28)
input_size = 784
# Size of discriminator output (real or fake)
d_output_size = 1
# Size of last hidden layer in the discriminator
d_hidden_size = 32
# Generator hyperparams
# Size of latent vector to give to generator
z_size = 100
# Size of discriminator output (generated image)
g_output_size = 784
# Size of first hidden layer in the generator
g_hidden_size = 32

Crear una instancia de los modelos

Y finalmente, la red completa se vería así:

# instantiate discriminator and generator
D = Discriminator(input_size, d_hidden_size, d_output_size)
G = Generator(z_size, g_hidden_size, g_output_size)
# check that they are as you expect
print(D)
print( )
print(G)

99398screenshot202021-05-2320at2012-35-1020pm-3329328
Fuente: Udacity.com

Calcular pérdidas

Hemos definido el Generador y el Discriminador ahora es el momento de definir sus pérdidas para que esas redes mejoren con el tiempo. Para las GAN tendríamos dos pérdidas reales de función de pérdida y pérdida falsa que se definirían así:

# Calculate losses
def real_loss(D_out, smooth=False):
    batch_size = D_out.size(0)
    # label smoothing
    if smooth:
        # smooth, real labels = 0.9
        labels = torch.ones(batch_size)*0.9
    else:
        labels = torch.ones(batch_size) # real labels = 1
    # numerically stable loss
    criterion = nn.BCEWithLogitsLoss()
    # calculate loss
    loss = criterion(D_out.squeeze(), labels)
    return loss
def fake_loss(D_out):
    batch_size = D_out.size(0)
    labels = torch.zeros(batch_size) # fake labels = 0
    criterion = nn.BCEWithLogitsLoss()
    # calculate loss
    loss = criterion(D_out.squeeze(), labels)
    return loss

Optimizadores

Una vez definidas las pérdidas, elegiríamos un optimizador adecuado para el entrenamiento:

import torch.optim as optim
# Optimizers
lr = 0.002
# Create optimizers for the discriminator and generator
d_optimizer = optim.Adam(D.parameters(), lr)
g_optimizer = optim.Adam(G.parameters(), lr)

Entrenamiento de los modelos

Dado que hemos definido Generador y Discriminador tanto las redes, sus funciones de pérdida como los optimizadores, ahora usaríamos las épocas y otras características para entrenar toda la red.

import pickle as pkl
# training hyperparams
num_epochs = 100
# keep track of loss and generated, "fake" samples
samples = []
losses = []
print_every = 400
# Get some fixed data for sampling. These are images that are held
# constant throughout training, and allow us to inspect the model's performance
sample_size=16
fixed_z = np.random.uniform(-1, 1, size=(sample_size, z_size))
fixed_z = torch.from_numpy(fixed_z).float()
# train the network
D.train()
G.train()
for epoch in range(num_epochs):
    for batch_i, (real_images, _) in enumerate(train_loader):
        batch_size = real_images.size(0)
        ## Important rescaling step ## 
        real_images = real_images*2 - 1  # rescale input images from [0,1) to [-1, 1)
        # ============================================
        #            TRAIN THE DISCRIMINATOR
        # ============================================
        d_optimizer.zero_grad()
        # 1. Train with real images
        # Compute the discriminator losses on real images 
        # smooth the real labels
        D_real = D(real_images)
        d_real_loss = real_loss(D_real, smooth=True)
        # 2. Train with fake images
        # Generate fake images
        # gradients don't have to flow during this step
        with torch.no_grad():
            z = np.random.uniform(-1, 1, size=(batch_size, z_size))
            z = torch.from_numpy(z).float()
            fake_images = G(z)
        # Compute the discriminator losses on fake images        
        D_fake = D(fake_images)
        d_fake_loss = fake_loss(D_fake)
        # add up loss and perform backprop
        d_loss = d_real_loss + d_fake_loss
        d_loss.backward()
        d_optimizer.step()
        # =========================================
        #            TRAIN THE GENERATOR
        # =========================================
        g_optimizer.zero_grad()
        # 1. Train with fake images and flipped labels
        # Generate fake images
        z = np.random.uniform(-1, 1, size=(batch_size, z_size))
        z = torch.from_numpy(z).float()
        fake_images = G(z)
        # Compute the discriminator losses on fake images 
        # using flipped labels!
        D_fake = D(fake_images)
        g_loss = real_loss(D_fake) # use real loss to flip labels
        # perform backprop
        g_loss.backward()
        g_optimizer.step()
        # Print some loss stats
        if batch_i % print_every == 0:
            # print discriminator and generator loss
            print('Epoch [{:5d}/{:5d}] | d_loss: {:6.4f} | g_loss: {:6.4f}'.format(
                    epoch+1, num_epochs, d_loss.item(), g_loss.item()))
    ## AFTER EACH EPOCH##
    # append discriminator loss and generator loss
    losses.append((d_loss.item(), g_loss.item()))
    # generate and save sample, fake images
    G.eval() # eval mode for generating samples
    samples_z = G(fixed_z)
    samples.append(samples_z)
    G.train() # back to train mode
# Save training generator samples
with open('train_samples.pkl', 'wb') as f:
    pkl.dump(samples, f)

Una vez que ejecute el fragmento de código anterior, el proceso de entrenamiento comenzaría así:

54477screenshot202021-05-2320at2012-36-2120pm-1797165
Fuente: Udacity.com

Generar imágenes

Finalmente, cuando se entrena el modelo, puede usar el generador entrenado para producir las nuevas imágenes escritas a mano.

# randomly generated, new latent vectors
sample_size=16
rand_z = np.random.uniform(-1, 1, size=(sample_size, z_size))
rand_z = torch.from_numpy(rand_z).float()
G.eval() # eval mode
# generated samples
rand_images = G(rand_z)
# 0 indicates the first set of samples in the passed in list
# and we only have one batch of samples, here
view_samples(0, [rand_images])

A la salida generada con el siguiente código le gustaría algo como esto:

77359screenshot202021-05-2320at2012-37-2120pm-6412238
Fuente: Udacity.com

Entonces, ahora que tiene su propio modelo GAN entrenado, puede usar este modelo para entrenarlo en un conjunto diferente de imágenes, para producir nuevas imágenes invisibles.

Referencias:

1. Aprendizaje profundo de Udacity: https://www.udacity.com/

2. Inteligencia artificial de aprendizaje profundo: https://www.deeplearning.ai/

Gracias por leer este artículo. Si has aprendido algo nuevo, siéntete libre de comentar ¡¡¡Nos vemos la próxima vez !!! ❤️

Los medios que se muestran en este artículo no son propiedad de DataPeaker y se utilizan a discreción del autor.

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.