Uma introdução a ConvLSTM

Note: an english version of this article is available here

Introdução

Nos dias de hoje, é muito comum encontrarmos dados na forma de imagens sequenciais. O exemplo mais comum são vídeos nas redes sociais, como YouTube, Facebook e Instagram. Outros exemplos são:

  • Video-chamadas
  • Filmes e trailers
  • Imagens de satélites
  • Câmeras de segurança

Este artigo tem como objetivo explicar como utilizar imagens sequenciais como entrada de um modelo de Redes Neurais para classificação usando o Keras.

A teoria por trás da ConvLSTM

Dados coletados sequencialmente ao longo do tempo são caracterizados como uma série temporal. Nestes casos, uma boa abordagem é usar um modelo baseado em LSTM (Long Short Term Memory), uma arquitetura de Redes Recorrentes. Neste tipo de arquitetura, os estados anteriores da rede são passados adiante. Desta forma, o modelo consegue levar em conta dados do passados para tomar decisões no presente. Em outras palavras, a ordem de entrada dos dados importa.

Um neurônio de uma rede LSTM

Quando se trata de imagens, então utilizamos uma arquitetura CNN (Convolutional Neural Network). Nela, a imagem passa por camadas de convolução, onde filtros extraem informações importantes da imagem, que por fim se conectam a uma rede Dense totalmente conectada de várias camadas.

Convolução de uma imagem com um filtro

No caso de imagens sequenciais, podemos utilizar camadas ConvLSTM. É uma camada recorrente, assim como a LSTM, mas todas as operações de multiplicação interna de matrizes que aconteceria em uma LSTM são substituídas por convoluções. Isto faz com que os dados que fluem pela célula sejam da forma de uma imagem (3D), e não apenas um vetor 1D, como em uma simples LSTM.

Um neurônio de uma rede ConvLSTM

Diferente de uma ConvLSTM, em um modelo Convolução-LSTM a entrada (imagem) primeiro passaria por camadas de convolução. O resultado desta convolução seria transformado para 1D com as características obtidas. Ao fazer isto para todas as imagens no tempo, o resultado é um conjunto de características ao longo do tempo e esta seria a entrada da camada LSTM.

ConvLSTM no Keras

Até o final do artigo, vamos definir que os dados estão no formato "channels_first". Isso implica que as imagens têm o formato (canais, linhas, colunas). No caso de uma foto colorida de 300x300 pixels, a imagem teria 3 canais, um para cada cor primária (vermelho, verde, azul), e o formato (3, 300, 300). Caso fosse "channels_last", que é o padrão do Keras para as camadas de Convolução, o formato seria (linhas, colunas, canais).

Entrada da camada ConvLSTM

A entrada de uma LSTM é um conjunto de dados ao longo do tempo, ou seja, um tensor 3D no formato (amostras, tempo, característica). A entrada de uma camada de Convolução é um conjunto imagens, ou seja, um tensor 4D no formato (amostras, canais, linhas, colunas). A entrada de uma ConvLSTM é uma combinação de imagens no tempo na forma de um tensor 5D na forma (amostras, tempo, canais, linhas, colunas).

Saída da camada ConvLSTM

A saída de uma LSTM depende do atributo return_sequences. Quando ele assume o valor True, significa que a saída é uma sequencia ao longo do tempo. Neste caso, a saída é um tensor 3D com formato (amostras, tempo, características). Caso return_sequences seja False (que é o padrão do Keras), a saída é apenas o último valor da sequência, ou seja, um tensor 2D com formato (amostras, características).

A saída de uma camada de Convolução é um conjunto imagens, ou seja, um tensor 4D no formato (amostras, filtros, linhas, colunas).

A saída de uma ConvLSTM é uma combinação da saída de uma camada de Convolução e de uma LSTM. Se a saída for uma sequência com return_sequences = True, então ela será um tensor 5D na forma (amostras, tempo, filtros, linhas, colunas). Por outro lado, se não retornar uma sequência, então a saída será um tensor 4D na forma (amostras, filtros, linhas, colunas).

Outros parâmetros

Os demais atributos da ConvLSTM têm sua origem das camadas de Convolução e LSTM.

Da parte de Convolução temos principalmente:

  • filters: número de filtros.
  • kernel_size: as dimensões da janela de convolução.
  • padding: "valid" para não utilizar nenhum e "same" para que o tamanho de linhas e colunas da imagem de entrada seja mantido na imagem de saída.
  • data_format: formato das imagens, se o canal aparece primeiro (“channels_first") ou por último ("channels_last").
  • activation: Função de ativação. Por padrão é a função linear a(x) = x.

Da parte de LSTM temos principalmente:

  • recurrent_activation: Função de ativação utilizada nos passos de recorrência.
  • return_sequences: True se deve retornar toda a sequência ou False para retornar apenas o último valor dela. O padrão é False.

Exemplo prático

O objetivo é identificar os gêneros que aparecem ao longo do trailer de um filme. Simplificando, vamos assumir que ao longo do trailer podem ter cenas de comédia, ação, terror, suspense, musical ou outro. Ou seja, temos 6 categorias. Como um exemplo, se em uma cena do trailer aparece uma explosão, então deve ser classificado como ação. Se a próxima aparece um palhaço assassino, então deve ser classificado como terror.

Arquitetura do modelo

O modelo possui uma única entrada, os frames em sequência do trailer, e 6 saídas independentes, uma para cada categoria.

A entrada do modelo é um vídeo. Teremos então uma entrada no formato (amostras, frames, canais, linhas, colunas). Definindo um limite de 1000 frames por amostra e cada imagem com 3 canais e 400x400 pixels, a entrada então será (amostras, 1000, 3, 400, 400), onde amostras é o número de trailers disponíveis para treino.

Neste exemplo vamos usar return_sequences = True, então a saída deveria ser (amostras, frames, categorias), mas, como é um modelo com saídas independentes, ela será (categorias, amostras, frames, 1), mais precisamente (6, amostras, 1000, 1). Isto significa que, no final, o modelo irá classificar cada frame em uma categoria.

A arquitetura do modelo começa com duas camadas ConvLSTM, cada uma seguida de BatchNormalization e MaxPooling. Depois, o modelo é separado em ramos, um para cada categoria. Em cada ramo, há mais uma ConvLSTM seguida de MaxPooling. Depois, os dados passam por duas camadas Dense fully-connected. Finalmente, a última camada é uma Dense de um neurônio. A imagem abaixo ilustra o modelo simplificado para apenas duas categorias (comédia e outra).

Arquitetura simplificada com apenas duas categorias do modelo utilizado

O código do modelo:

Treinamento

Na prática, subir vários vídeos diretamente na memória RAM para alimentar o modelo de uma única vez não é viável. Este é um problema comum quando trabalhamos com imagens. Por isso, vamos usar um gerador, que permite alimentar o modelo amostra por amostra usando o .fit_generator() em vez de apenas .fit().

Assumindo que os Trailers estejam pré-tratados e salvos separadamente em arquivos trailer_{id}.npy, onde id é um número que varia de 0 até num_trailers:

Repare que o modelo possui seis saídas independentes, e cada saída espera um tensor 3D no formato (amostras, tempo, 1), que é (1, 1000, 1).

Podemos definir o gerador como:

Repare que o gerador recebe uma lista de ids de trailers disponíveis para treino e, um trailer por vez, carrega os arquivos salvos e os retorna como (entrada, saída_esperada) para treinar o modelo por uma amostra.

Depois, é só treinar o modelo:

Referências

http://bsautermeister.de/research/frame-prediction/
https://www.quora.com/What-is-the-difference-between-ConvLSTM-and-CNN-LSTM
https://datascience.stackexchange.com/questions/23183/why-convolutions-always-use-odd-numbers-as-filter-size/23186
https://keras.io/layers/recurrent/
https://towardsdatascience.com/illustrated-guide-to-lstms-and-gru-s-a-step-by-step-explanation-44e9eb85bf21
https://medium.com/technologymadeeasy/the-best-explanation-of-convolutional-neural-networks-on-the-internet-fbb8b1ad5df8