Redes Neurais | Autoencoders com PyTorch

Uma aplicação em compressão de imagens.

Paulo Sestini
Turing Talks
8 min readApr 5, 2020

--

Introdução

Sabemos que redes neurais podem ser utilizadas para diversos fins, como classificação, aprendizagem por reforço, detecção de objetos e pessoas, reconhecimento de fala, entre outros.

Porém, existem aplicações bem diferentes do que estamos acostumados e que não são tão conhecidas, hoje falaremos de uma delas: os autoencoders!

O que é um autoencoder?

Um autoencoder é uma rede neural que é treinada para reconstruir, de forma aproximada, os dados que são passados para ela.

Por exemplo, se o seu conjunto de dados for constituído por imagens, o autoencoder deve ser capaz de reconstruir cada uma destas de modo que as reconstruções sejam muito semelhantes às originais.

A princípio, esta funcionalidade pode não parecer muito útil, mas o verdadeiro poder dos autoencoders reside em como a estrutura da rede neural é criada, que permite gerar representações codificadas dos dados, como veremos a seguir.

Definição de um autoencoder

Um autoencoder pode ser definido como a implementação de duas funções:

Função de codificação:

Função de decodificação:

Onde a variável x é o seu dado de entrada e C e D são suas formas codificada e decodificada, respectivamente.

Dessa forma, ao inserir um dado na rede, a saída será uma representação D dada por:

Isto é, o trabalho do autoencoder é codificar um certo dado passado para ele e em seguida realizar sua decodificação, reconstruindo o dado original de forma aproximada.

Mas como isso se traduz em uma rede neural?

Para um autoencoder de imagens, poderíamos ter a seguinte estrutura:

A rede neural recebe, de início, uma imagem. Em seguida, esta imagem é passada por camadas da rede, como camadas de convolução, que implementam a função de codificação, extraindo informações importantes da imagem e diminuindo suas dimensões, transformando a imagem de várias formas até que gera, finalmente, uma representação codificada da imagem de tamanho menor do que o tamanho da imagem original.

No diagrama apresentado temos uma imagem no padrão RGB, que possui 3 canais (vermelho, verde e azul), sendo transformada em uma imagem menor de 12 canais que, finalmente, é transformada em uma imagem codificada pequena, de 3 canais.

Após isto, a representação codificada é passada para outras camadas da rede, como camadas de convolução transposta, que implementam a função de decodificação, aumentando as dimensões da imagem e gerando uma imagem decodificada semelhante à imagem original.

A importância do autoencoder vem do fato de que a função de codificação é construída de tal modo que a imagem codificada possui dimensões menores do que as da imagem original. Desse modo, o autoencoder é forçado a extrair somente as informações mais importantes da imagem recebida, de forma a representá-la de modo compacto.

Aplicações de Autoencoders

A capacidade de extrair informações importantes dos dados faz com que os autoencoders possuam aplicações como:

  • Compressão de imagens
  • Redução de dimensionalidade
  • Remoção de ruídos em imagens
  • Extração de features

A seguir, veremos uma implementação de autoencoder para compressão de imagens.

Autoencoder para Compressão de Imagens

Iremos realizar a implementação utilizando a biblioteca open source PyTorch, que possibilita grande flexibilidade na criação de redes neurais.

A rede será aplicada ao conjunto de imagens CIFAR-10, que possui imagens no padrão RGB de dimensões 32x32, das seguintes classes:

  • Avião
  • Automóvel
  • Pássaro
  • Gato
  • Veado
  • Cachorro
  • Sapo
  • Cavalo
  • Avião
  • Caminhão
Conjunto de imagens CIFAR-10, fonte: https://www.cs.toronto.edu/~kriz/cifar.html

Nossa rede neural será compostas pelas seguintes camadas:

  • Camadas convolucionais, responsáveis pela extração de informações importantes das imagens e pela redução de seus tamanhos.
  • Camadas de Batch Normalization, responsáveis por tornar a rede mais eficiente e aperfeiçoar o treinamento.
  • Camadas de convolução transposta, que são como uma convolução em sentido inverso, responsáveis pela restauração da imagem e pelo aumento de seu tamanho.

A função de codificação será implementada pelas seguintes camadas:

  1. Camada de Convolução 2D
  2. Camada de Batch Normalization 2D
  3. Camada de Convolução 2D
  4. Camada de Batch Normalization 2D
  5. Camada de Convolução 2D
  6. Camada de Batch Normalization 2D

A função de decodificação será implementada pelas seguintes camadas:

  1. Camada de Convolução Transposta 2D
  2. Camada de Batch Normalization 2D
  3. Camada de Convolução Transposta 2D
  4. Camada de Batch Normalization 2D
  5. Camada de Convolução Transposta 2D
  6. Camada de Batch Normalization 2D

Com tudo definido, vamos ao código!

Implementação em Torch

Para criarmos uma rede neural em Torch, é necessário criar uma classe para ela seguindo os moldes da biblioteca.

Iremos começar importando os módulos necessários:

Nós utilizaremos os módulos torch.nn e torch.nn.functional da biblioteca Torch. O primeiro módulo contém as implementações das camadas e o segundo as funções de ativação que serão utilizadas.

Em seguida, criamos a classe Autoencoder, herdando a classe nn.Module da biblioteca Torch, necessária para integração de nossa rede com a biblioteca. Também criamos as camadas de codificação e de decodificação da nossa rede, dentro do construtor.

As camadas de codificação realizam as seguintes transformações na imagem:

  1. A imagem de dimensões 32x32 de 3 canais (RGB) é transformada em uma imagem 31x31 de 32 canais.
  2. A imagem 31x31 de 32 canais é transformada em uma imagem 30x30 de 16 canais.
  3. Finalmente, a imagem 30x30 de 16 canais é transformada em uma imagem 15x15 de 3 canais.

Tais transformações ocorrem devido ao tamanho do kernel da convolução e do stride escolhido. Em todas as camadas de convolução o kernel é 2x2. Nas duas primeiras camadas de convolução o stride é 1, já na última o stride é 2.

As camadas de decodificação realizam as mesmas transformações em sentindo contrário, transformando a imagem 15x15 de 3 canais em uma de 32x32 de 3 canais.

Além disso, toda classe de rede neural em Torch precisa possuir um método forward, responsável por propagar os dados pela rede.

Primeiramente, para criação desse método, iremos criar outros dois métodos, o encode e o decode, responsáveis por implementar as funções de codificação e de decodificação, respectivamente.

O método encode propaga a imagem através das camadas responsáveis pela codificação, utilizando funções de ativação ReLU, de modo que temos o seguinte caminho na rede:

Conv. -> Batch Norm. -> ReLU -> Conv. -> Batch Norm. -> ReLU -> Conv. -> Batch Norm. -> ReLU

O método decode propaga a imagem já codificada através das camadas de decodificação, também utilizando ReLU, de forma que temos este caminho:

Conv. Transp. -> Batch Norm. -> ReLU -> Conv. Transp. -> Batch Norm. -> ReLU -> Conv. Transp. -> Batch Norm. -> ReLU

Com estes dois métodos definidos, o método forward consiste em chamar o método encode na imagem e, em seguida, chamar o método decode na imagem codificada.

Isso finaliza a criação do autoencoder, o próximo passo é o treinamento.

Treinamento do Autoencoder

Novamente, importamos os módulos necessários:

Desta vez, o módulo torch.nn será utilizado para a criação da nossa função de custo. O módulo torch.optim disponibilizará o otimizador para treinamento da rede. Já o torchvision nos dará acesso ao dataset CIFAR-10.

Em seguida, importamos nosso dataset e instanciamos nosso Autoencoder. Caso uma GPU esteja disponível, a rede a utilizará, caso contrário, a CPU será escolhida.

Note que, ao importar o dataset, precisamos aplicar uma transformação nas imagens para que estas sejam armazenadas em objetos do tipo torch.Tensor, que são arrays multidimensionais utilizados nos cálculos dentro da biblioteca.

Após isto, definimos a quantidades de treinamentos a serem realizados (epochs), escolhemos o nosso otimizador, que será o Adam, e a nossa função de custo, que será a Mean Squared Error (MSE).

Agora vamos para o processo de treinamento!

Para cada treinamento, nós mandamos nosso batch para a rede, calculamos nosso erro e utilizamos o método backward da função de custo para cálculo dos gradientes na rede. Com os gradientes calculados, utilizamos o método step do otimizador para atualizar os pesos na rede. É importante zerar os gradientes anteriores antes de calcular os novos gradientes, utilizando o método zero_grad do otimizador.

Note que nós também levamos em conta a progressão do treinamento da rede, informando a porcentagem do conjunto de imagens que já foi processada ao longo de cada epoch.

Ao final, salvamos a rede treinada em um arquivo para uso posterior.

Teste do Autoencoder

Com o autoencoder treinado, iremos realizar um teste em uma imagem aleatória do conjunto de imagens de teste. A biblioteca matplotlib será utilizada para visualização das imagens.

Iremos importar os módulos necessários e o conjunto de imagens, escolhendo uma imagem aleatória para teste.

Depois, exibimos a imagem de teste e carregamos o nosso autoencoder treinado. Após isto, passamos a imagem de teste pela função de codificação e exibimos sua forma codificada. Finalmente, enviamos a imagem codificada para a função de decodificação e mostramos a imagem recuperada.

Ao final do código, algumas formatações de exibição são realizadas e a figura final é exibida.

Assim, obtemos o seguinte resultado:

Vemos que o autoencoder foi capaz de comprimir a imagem e recuperá-la de modo satisfatório!

A diminuição das dimensões de 32x32 para 15x15 fazem com que a versão comprimida da imagem ocupe aproximadamente apenas 22% do espaço em memória ocupado pela imagem original!

O código completo pode ser encontrado no github: https://github.com/paulosestini/Autoencoder

Conclusão

Com o encerramento deste episódio do Turing Talks, esperamos que este artigo tenha servido para demonstrar o poder dos autoencoders e mostrar um pouquinho de como a excelente biblioteca PyTorch funciona.

É importante notar que os autoencoders possuem também outras aplicações como as comentadas anteriormente e nós incentivamos que o leitor continue seus estudos!

Se você estiver interessado em saber mais sobre, além de continuar nos acompanhando aqui, não se esqueçam de conferir nossas redes: Medium, Facebook, Instagram e LinkedIn e fique ligado nas próximas postagens.

Agradecemos a atenção e até a próxima!

--

--

Paulo Sestini
Turing Talks

Estudante de Engenharia de Computação na Poli-USP, interessado principalmente em Inteligência Artificial, Programação, Matemática e Física.