Construindo sua primeira GAN com PyTorch

Crie sua primeira rede neural que gera imagens!

Wesley Almeida
Turing Talks
8 min readFeb 7, 2021

--

Olá, seja bem-vindo a mais uma edição do Turing Talks! Nesta edição iremos dar uma introdução às Redes Adversárias Generativas, também conhecidas como GANs.

As Redes Adversárias Generativas (GAN) foram introduzidas a comunidade científica em 2014 neste artigo feito por Ian Goodfellow e outros co-autores. Neste tipo de modelo temos 2 redes neurais profundas distintas competindo entre si com objetivos opostos.

É interessante notar que Modelos Generativos como as GANs têm um grande potencial, pois elas podem aprender a imitar qualquer distribuição de dados, desde simples números feitos à mão até a geração de rostos super realistas como pode ser visto a seguir.

Imagens geradas por uma GAN

Ficou interessado para entender como esse tipo de rede funciona? Gostaria de entender como você pode aumentar seu dataset? Pois então você está no post certo!

Bibliotecas e Dataset utilizados

Calma, calma! Antes de começarmos a construir de fato nossa GAN, é necessário decidir qual dataset e quais biblioteca serão utilizados. Como esse texto trata-se de uma introdução ao assunto, visando principalmente entender o funcionamento das Redes Adversárias Generativas, vamos utilizar um dos datasets mais conhecidos e simples na área de Visão Computacional: O MNIST.

O MNIST é um banco de imagens de dígitos manuscritos composto de um conjunto de treinamento com 60 mil exemplos e um conjunto de teste com 10 mil exemplos. Além disso, as imagens possuem tamanho de 28x28 pixels e apenas um canal de cor (imagens preto e branco).

Além disso, para o desenvolvimento da GAN será utilizada a biblioteca PyTorch. Assim, Os imports necessários para o download do dataset e criação das redes é apresentado a seguir, assim como a criação do Dataloader.

Se o leitor não está familiarizado com o PyTorch, é possível ter uma ótima introdução à biblioteca e a redes neurais pelo seguinte Turing Talks. Com um conhecimento mínimo de ambos os temas, podemos começar a construir nossa GAN!

Entendendo o funcionamento de uma GAN

Como já foi dito no início desse post, as GANs são constituídas de 2 redes neurais profundas distintas conhecidas como Gerador e Discriminador.

É possível entender uma GAN como um jogo entre 2 jogadores: a rede Geradora é responsável por pegar um ruído (como um array com valores aleatoriamente gerados) e, a partir disso, tentar gerar uma imagem o mais próximo possível do real para enganar o Discriminador. Já o Discriminador tem como objetivo tentar distinguir se uma imagem é real ou não.

Estrutura básica de uma GAN

Gerador

Agora que já compreendemos qual o objetivo do Gerador, podemos começar a pensar em como ela gera as imagens a partir de um ruído aleatório. Para facilitar o entendimento, iremos utilizar uma rede neural simples para descrever nosso Gerador.

Assim, nossa rede irá receber como entrada um ruído de tamanho arbitrário e, após aplicar transformações não-lineares por meio das camadas da rede, irá gerar um conjunto de saídas do tamanho das imagens do dataset. No nosso caso, por exemplo, as imagens tem tamanho de 28x28 pixels, dessa forma a rede Geradora terá 28x28 = 784 saídas, representando cada um dos pixels da imagem falsa gerada.

Primeiro, vamos criar um bloco que representa cada camada que construirá nossa rede Geradora:

Em seguida, vamos definir a estrutura do nosso Gerador que utiliza 3 blocos conforme descrito acima:

Discriminador

Assim como no Gerador, nosso Discriminador será formado por uma rede composta de camadas totalmente conectadas (neste tipo de camada todas os nós da camada n são ligados a camada n+1). É importante notar que, como o objetivo do Discriminador é receber uma imagem e classificá-la como real ou falsa, a saída do discriminador será binária.

De forma análoga ao que foi feito para o Gerador, primeiro vamos definir o bloco base que formará nossa rede:

Assim, podemos construir nossa rede Discriminadora:

Com isso construímos os 2 blocos principais que compõe uma GAN. Entretanto, ainda é necessário entender como as perdas de cada uma das redes é calculada para podermos treiná-la.

Calculando as perdas das redes

Em uma GAN geralmente temos duas funções de perda distintas: uma para treinamento do gerador e outra para treinamento do discriminador, embora derivem de uma única fórmula como será visto a seguir.

A perda Minimax

No artigo que introduziu as GANs, a rede Geradora tenta minimizar a seguinte função enquanto a Discriminadora tenta maximizá-la (daí o nome minimax):

Onde:

D(x): é a estimativa do Discriminador de que a imagem real x seja real;

Ex: é a esperança matemática sobre todas as instâncias de dados reais;

G(z): é a saída do Gerador (no nosso caso uma imagem) quando sua entrada é o ruído z;

D(G(z)): é a estimativa do Discriminador de que uma imagem gerada (falsa) seja real;

Ez: é o valor esperado sobre todas as entradas aleatórias para o Gerador.

É possível perceber que o Gerador só tem efeito sobre o termo log(1 — D(G(z)) . A partir disso, conseguimos compreender porque o objetivo da rede Geradora é minimizar tal fórmula: D(G(z)) é a classificação do Discriminador sobre as imagens falsas (geradas). Ao treinar o Gerador estamos buscando aproximar tal valor de 1, pois isso indica que nossas imagens geradas estão parecidas com imagens reais.

Dessa maneira, queremos aproximar a 1 que na formula indica a minimização delog(1-D(G(Z))) , já que quando D(G(z))tende a 1 1-D(G(Z) tende a 0 e log(0) , em termos práticos, pode ser considerado como um valor muito baixo.

Já para o Discriminador precisamos nos atentar a ambos os termos. D(x) , como já foi dito, é a estimativa do discriminador da probabilidade de que a instância real de dados x seja real. Dessa forma, para imagens reais queremos que D(x) seja igual ou mais próximo possível de 1, pois isso indica que o Discriminador está classificando corretamente as imagens como reais.

Por outro lado, o termo D(G(z)) para o cálculo da perda do Discriminador deve tender a 0, uma vez que nesse caso estaríamos prevendo corretamente as imagens geradas como falsas. Assim, como queremos que D(x) tenda a 1 e D(G(z)) tenda a 0, estamos buscando maximizar log(D(x)) + log(1-D(G(z))) .

Por último, é interessante notar a fórmula acima deriva da entropia cruzada entre as distribuições real e gerada, o que influenciará diretamente na nossa escolha de critério utilizado na implementação.

Implementando as perdas

Agora que já entendemos como as perdas são calculadas, a implementação fica fácil! Antes de mais nada, vamos decidir o critério para medir a distância entre as imagens reais e geradas. Nessa caso, utilizaremos uma Binary Cross Entropy (Entropia Binária Cruzada), pois essencialmente nossa perda é baseada na entropia cruzada entre as distribuições real e gerada.

Outro ponto que vale ser ressaltado é a criação de uma função para criar os ruídos que serão utilizados para geração das imagens. Veja:

A implementação prática da função de perda da GAN e das atualizações do modelo é direta. Para o Gerador precisamos realizar os seguintes passos (É interessante o leitor ir comparando o passo a passo a seguir com a fórmula descrita no tópico anterior):

  1. Gerar o ruído aleatório (z);
  2. A partir do ruído, gerar as imagens falsas passando o ruído pela rede (G(z));
  3. Utilizando o Discriminador, prevemos se as imagens são reais ou falsas (D(G(z)));
  4. Por fim, utilizando o critério definido calculamos o quão longe do ideal está nossa imagem. Nesse caso, o ideal de previsão seria 1, pois para o Gerador queremos que o discriminador seja “enganado” prevendo as imagens como reais (log(1 - D(G(z))).

Para o Discriminador, precisamos lembrar que ambos os termos da perda Minimax são afetados. Dessa forma, os seguintes passos são realizados (Assim como para o Gerador, vá comparando com a fórmula do tópico anterior):

  1. Gerar o ruído aleatório (z);
  2. A partir do ruído, gerar as imagens falsas passando o ruído pela rede (G(z));
  3. Realizar a predição do Discriminador para as imagens falsas e para as imagens reais do dataset (D(G(z)) e D(x), respectivamente).
  4. Comparamos as predições do Discriminador com os valores ideias. Para o Discriminador, o valor ideal de comparação para as imagens geradas é 0 e para as imagens reais é 1, pois quando estamos treinando o Discriminador queremos que ele distingua corretamente qual imagem é real e qual não é (log(D(x)) e log(1-D(G(z)))).
  5. Por fim, calculamos a perda total do Discriminador como a média das perdas calculadas para as imagens reais e para as imagens falsas.

Treinamento

Antes de começarmos o loop de treinamento em si, precisamos decidir qual otimizador utilizar. Otimizadores são algoritmos ou métodos usados ​​para alterar os atributos da rede neural, como pesos e taxa de aprendizagem para reduzir as perdas. Nessa caso, para ambas as redes utilizaremos o otimizador Adam:

Com isso podemos realizar o loop de treinamento. Neste caso iteramos por 100 épocas::

Bônus

O leitor deve ter visto no loop de treino que utilizamos uma função chamada exibir_imgs. Tal função é utilizada para plotar imagens intermediárias do treinamento e podermos ir acompanhando o desenvolvimento da rede.

Para implementar tal função pegamos nosso Tensor de saída da rede que tem tamanho 784 e o transformamos em uma matriz (1, 28, 28) que é o tamanho de uma imagem do MNIST. Além disso, para imagens com 3 canais de cores é necessário inverter os canais, pois o PyTorch utiliza como ordem BGR (blue-green-red) e o matplotlib, por sua vez, usa como padrão a ordem RGB (red-green-blue).

A imagem a seguir apresenta algumas imagens geradas durante o treinamento da GAN.

Imagens intermediárias de treinamento da GAN (Época 5, 20 e 70)

Conclusão

Ufa! O caminho foi longo, mas conseguimos construir nossa primeira GAN. Com esse post, o leitor conseguiu compreender toda a base necessária para construir Redes Adversárias Generativas e caso queira já tem totais condições de explorar redes mais complexas como uma CycleGAN ou uma DCGAN.

Ficou interessado no tema? Gostaria de saber ainda mais sobre GANs e Inteligência Artificial? Então acompanhe o Grupo Turing em nossas redes sociais: Facebook, Linkedin, Instagram, Medium! Você também pode entrar em nosso Discord onde estamos constantemente realizando aulas abertas e divulgando diversos conteúdos sobre IA.

--

--