Curso
Neste tutorial, você vai entender o que são as redes generativas adversárias (GANs) sem entrar nos detalhes da matemática. Depois, vai aprender a programar uma GAN simples capaz de criar dígitos!
Analogia
A maneira mais fácil de entender o que são GANs é por meio de uma analogia simples:
Imagine uma loja que compra certos tipos de vinho de clientes para depois revender.

No entanto, há clientes mal-intencionados que tentam vender vinho falso para ganhar dinheiro. Nesse cenário, a dona da loja precisa distinguir entre vinhos falsos e autênticos.

No começo, é provável que o falsificador cometa muitos erros ao tentar vender vinho falso, e a dona da loja conseguirá identificar com facilidade que o vinho não é autêntico. Por causa desses fracassos, o falsificador continuará testando técnicas diferentes para simular vinhos autênticos e, com o tempo, algumas darão certo. Ao descobrir quais técnicas passaram pelo crivo da dona da loja, ele pode aprimorar ainda mais os vinhos falsos com base nessas estratégias.
Ao mesmo tempo, a dona da loja provavelmente receberá feedback de outros lojistas ou especialistas em vinho de que alguns dos rótulos que ela tem não são originais. Isso significa que ela também precisará melhorar a forma de determinar se um vinho é falso ou autêntico. O objetivo do falsificador é criar vinhos indistinguíveis dos autênticos, e o objetivo da dona da loja é identificar com precisão se um vinho é verdadeiro ou não.
Essa disputa de gato e rato é a ideia central por trás das GANs.
Componentes de uma rede generativa adversária
Usando o exemplo acima, chegamos à arquitetura de uma GAN.

Existem dois componentes principais em uma GAN: o gerador e o discriminador. No exemplo, a dona da loja representa a rede discriminadora e geralmente é uma rede neural convolucional (já que GANs são usadas principalmente em tarefas com imagens) que atribui uma probabilidade de a imagem ser real.
O falsificador é a rede geradora, que também costuma ser uma rede neural convolucional (com camadas deconvolucionais). Essa rede recebe um vetor de ruído e gera uma imagem. Durante o treinamento, a rede geradora aprende quais áreas da imagem deve melhorar/alterar para que o discriminador tenha mais dificuldade em diferenciar suas imagens das reais.
A rede geradora vai produzindo imagens cada vez mais parecidas com as reais, enquanto a rede discriminadora tenta apontar as diferenças entre imagens reais e falsas. A meta final é ter uma rede geradora capaz de produzir imagens indistinguíveis das verdadeiras.
Uma rede generativa adversária simples com Keras
Agora que você entendeu o que são GANs e seus principais componentes, vamos começar a programar um exemplo bem simples. Você vai usar o Keras e, se ainda não conhece essa biblioteca Python, vale ler este tutorial antes de continuar. Este tutorial é baseado na GAN desenvolvida aqui.
A primeira coisa a fazer é instalar os seguintes pacotes via pip
kerasmatplotlibtensorflowtqdm
Você usará matplotlib para visualização, tensorflow como backend do Keras e tqdm para exibir uma barra de progresso bacana a cada época (iteração).
O próximo passo é criar um script em Python. Nele, primeiro importe todos os módulos e funções que serão usados. A explicação de cada um virá conforme forem utilizados.
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
Agora, defina algumas variáveis:
# 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 começar a construir o discriminador e o gerador, é preciso coletar e pré-processar os dados. Vamos usar o famoso conjunto MNIST, que traz imagens de dígitos de 0 a 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)
Observação: a função mnist.load_data() faz parte do Keras e permite importar o conjunto MNIST para seu ambiente com facilidade.
Agora, vamos criar as redes geradora e discriminadora. Usaremos o otimizador Adam em ambas. Tanto no gerador quanto no discriminador, criaremos uma rede neural com três camadas ocultas usando a função de ativação Leaky ReLU. Também adicionaremos camadas de dropout ao discriminador para aumentar sua robustez em imagens não 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
Hora de juntar o gerador e o 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, podemos criar uma função que salva as imagens geradas a cada 20 épocas. Como isso não é o foco da lição, você não precisa entender cada detalhe agora.
# 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)
Pronto, você já programou a maior parte da sua rede. Agora é treinar e conferir as imagens que foram criadas.
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)
Depois de treinar por 400 épocas, você pode ver as imagens geradas. Nas imagens após a primeira época, ainda não há estrutura clara; após 40 épocas, os dígitos começam a ganhar forma; e, por fim, após 400 épocas, surgem dígitos nítidos — embora alguns ainda fiquem irreconhecíveis.
| Resultados após 1 época | Resultados após 40 épocas | Resultados após 400 épocas |
|---|---|---|
!![]() |
![]() |
![]() |
Esse código leva aproximadamente 2 minutos por época em CPU — e esse é o principal motivo da escolha. Sinta-se à vontade para testar com mais épocas e adicionar mais (e diferentes) camadas tanto no gerador quanto no discriminador. Mas lembre-se: ao usar arquiteturas mais profundas e complexas, o tempo de execução também aumenta se você estiver só com CPU. Mesmo assim, não deixe de experimentar!
Conclusão
Parabéns por chegar até aqui! Você aprendeu os fundamentos das redes generativas adversárias (GANs) de um jeito intuitivo e ainda implementou seu primeiro modelo com a ajuda do Keras. Se quiser se aprofundar em deep learning com Python, confira o curso Deep Learning in Python da DataCamp.








