Análise de sentimento usando LSTM no PyTorch

Como redes neurais recorrentes podem ser usadas para analisar texto e descobrir se estão falando bem de você.

Piero Esposito
Turing Talks
6 min readMay 3, 2020

--

Fonte: https://cdn.analyticsvidhya.com/wp-content/uploads/2018/07/performing-twitter-sentiment-analysis1.jpg — Acesso em 2020–04–25

Post escrito por Ana Laura Moraes Tamais e Piero Esposito (ordem alfabética)

Link para o repositório aqui.

Introdução

Processamento de Linguagem Natural (NLP), quando combinado com Deep Learning, se torna um dos tópicos mais sexys que existem dentre as áreas de estudo da Inteligência Artificial.

Com a arquitetura correta, redes neurais podem ser treinadas usando bases de dados de textos (devidamente pré-processadas) e criar abstrações da semântica de línguas humanas, e essas podem ser extremamente úteis para extração de insights e tomada de decisão automatizada.

Neste Turing Talks, vamos, primeiro, explicar um pouco da relação entre as Redes Neurais Recorrentes (RNNs — Recurrent Neural Networks) e Processamento de Linguagem natural (NLP). Depois, usando o dataset Amazon Fine Food Reviews (que vamos encodar pra funcionar numa rede neural), vamos treinar uma LSTM, usado PyTorch, para classificar uma análise como positiva ou negativa.

Por fim, para conferir se tudo deu certo, vamos ver a acurácia desse modelo numa parte do dataset separada para teste.

Processamento de Linguagem Natural e as Redes Neurais Recorrentes

Podemos definir textos, em termos de dados, como sequências (de palavras ou, num nível de abstração mais baixo, de caracteres). O que importa é que, para podermos extrair o máximo de informação enquanto modelamos esse dataset, precisamos colocar a ordem das sequências no modelo.

É aí que entram as redes neurais recorrentes: em tratamento de sequências, elas preservam uma parte do output como um “estado”, de forma a persistir a informação através do tempo:

Ilustração de rede neural recorrente. Fonte: https://colah.github.io/posts/2015-08-Understanding-LSTMs/ — Acessado em 2020–04–26

Para a modelagem do nosso dataset, vamos usar uma arquitetura específica de RNN, a LSTM.

O dataset

Nosso dataset é o Amazon Fine Foods Reviews. O arquivo que recebemos é um .csv, no qual usaremos as colunas “Text” e “Score”, que contêm, respectivamente, o texto de uma review e o score, que vai de 1 a 5 estrelas.

Para podermos usá-lo com a nossa rede neural, vamos encodar (isso é, transformar) os textos como uma sequência de números, em que cada número corresponde ao índice da palavra dentro do nosso vocabulário, que é uma estrutura de dados do tipo set (conjunto) que contém todas as palavras presentes dentro do dataset.

Pré-processamento

Para podermos treinar uma rede neural com dados de texto, precisamos, de alguma forma, encodar o texto como números. Existem diversas formas de fazê-lo, e, para nossa análise, vamos usar o de text-to-sequences.

Na abordagem de text-to-sequences, o encoding do texto ocorre em duas etapas: primeiro, cria-se um vocabulário: ele é uma conjunto (set) que contém todas as palavras presentes em todos os textos, sem repeti-las.

Depois disso, criamos um dataset que, no lugar de cada texto, tem vetor de números com o índice de cada palavra no vocabulário. No final, o input para nossa rede neural, para cada texto, fica mais ou menos assim:

Ilustração de text-to-sequence. Fonte: https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html — Acessado em 2020–04–28

Para nossa sorte, já existe uma lib, o TorchNLP, que cuida disso. E ela também vai cuidar de um outro probleminha que aparece na hora de montar o tensor-dataset em NLP, que é o do tamanho das sequências: se forem de tamanhos diferentes, não podem virar tensor e passar de forma otimizada pelo nosso modelo.

A solução para isso é o padding: determinamos um tamanho máximo para a sequência. Nas menores, colocamos 0 ao final até que atinjam o tamanho; as maiores nós só cortamos fora.

Dito isso, vamos ao código do preprocessamento, que vamos começar com os imports.

Depois, vamos importar o dataframe e tirar dele tudo que não seja texto e label. Como as pontuações estão de 1 a 5 estrelas, vamos tirar as 3 e 4 e adotar: 1 e 2 estrelas como negativo e 5 estrelas como positivo.

Lembre-se de baixar o dataset pelo site ou pela API do Kaggle:

kaggle datasets download -d snap/amazon-fine-food-reviews

Também vamos remover aleatoriamente dos textos com análise positiva até que o dataset esteja equilibrado. Por fim, aproveitamos para, no mesmo snippet, transformar isso tudo em listas — o que vai facilitar a manipulação desses dados nos próximos passos.

Então, mas especificamente, vamos tirar as colunas que não queremos com df.drop , remover as pontuações 3 a 4 de formas diferentes, pra mostrar as formas que isso pode ser feito com o pandas, e depois transformar em lista, pra poder encodá-los.

Vamos agora fazer o encoding dos dados. Nesse passo, o gargalo é a criação do nosso encoder. Isso acontece porque o SpacyEncoder vai iterar pelo dataset e criar o vocabulário.

É necessário também analisar a possibilidade de termos textos muito longos (outliers na distribuição dos tamanhos de textos) e, caso isso ocorra, eliminá-los. Faremos isso porque, caso contrário, os textos com muitos 0-paddings vão enviesar a predição e os gradientes.

Vamos também plotar a distribuição dos textos e ver se isso é verdade.

Função densidade de probabilidade para o tamanho dos textos.

Vimos também que o percentil 90 dessa distribuição é 191. Vamos remover os textos cujo tamanho ultrapasse esse valor para garantir que nossa RNN treine de forma menos enviesada.

Por fim, podemos fazer o padding e criar o dataset. Vamos também fazer a divisão entre os datasets de treino e teste:

Feito isso, podemos ir à criação do modelo.

Criação do modelo com PyTorch

Para nosso modelo, vamos usar uma camada nn.Embedding, que vetorizará as sequências de índices do vocabulário, seguida de uma nn.LSTM para tratamento de sequência e uma nn.Linear para lidar com a complexidade do modelo.

Veja que, após passar pela LSTM, pegamos só a última predição de cada sequência para passar pela camada Linear. Dito isso, podemos instanciar nossos objetos (a rede, otimizador, DataLoader, etc…) e ir para o loop de treinamento.

Preparação do loop de treinamento

Estamos indo para os últimos passos. Nos preparando para o loop de treinamento e teste, vamos criar os objetos que usaremos nele.

Além da rede, do classificador e do loss-object, vamos criar um DataLoader a partir de um torch.TensorDataset , que, por sua vez, vai ser construído a partir dos nossos X_train , y_train , X_test , y_test que montamos antes:

E podemos, por fim, ir para o loop de treinamento. Veja que usamos o tqdm para ter outputs mais bonitos e uma barra de progresso.

Com nosso learning rate de 0.02, otimizador Adam, vamos treinar a rede por 10 épocas e com batch size de 64. Veja o loop de treinamento:

Dito isso, podemos concluir esse post.

Resultados e conclusão

Com nossa arquitetura e nossos hiperparâmetros, você deve obter uma acurácia perto de 85%.

Considerando que temos um vocabulário gigante e uma arquitetura de rede pouco complexa e com poucos nós, é possível dizer que obtivemos um bom resultado.

Por fim, como trabalho posterior, pode ser interessante, com base na simplicidade do preprocessamento dos dados e do modelo, pensar em como dar deploy nele e criar uma API para análise de sentimento de texto.

Referências

--

--

Piero Esposito
Turing Talks

Google Developer Expert in Machine Learning and Machine Learning Engineer @ Sticker Mule; I’m your friendly neghborhood nerdy guy.