Análise de Sentimento com aprendizado supervisionado e não supervisionado

A versão em inglês deste artigo está disponível em Sentiment Analysis with supervisioned and unsupervisioned learning

A Análise de sentimento, ou mineração de opinião, tem como objetivo identificar o sentimento de alguém por qualquer coisa através de um texto em linguagem natural. A análise é feita para encontrar polaridades no texto, se algo escrito é positivo ou negativo, e não necessariamente encontra emoções mais detalhadas. Essa técnica é extremamente usada pelas empresas para, por exemplo, medir a aceitação de um produto.

As técnicas utilizadas para a análise são de estatística e de machine learning. Neste artigo vou explicar como classificar esses textos usando Redes Neurais, para aprendizado supervisionado, e Orientação Semântica, para um aprendizado não supervisionado (para entender melhor conceitos iniciais de machine learning veja esse artigo aqui).


Redes Neurais Recorrentes (RNN)

As RNNs reconhecem padrões em entradas sequenciais e são usadas para diversos tipos de entradas. As decisões tomadas no tempo t-1 afetarão as decisões no tempo t. Diferente das Redes Neurais comuns, as Redes Recorrentes não recebem apenas a entrada vinda do dataset, mas também recebem o estado da unidade anterior.

Entretanto, RNNs básicas não são muito boas para sequências que dependam de uma longa memória. Por este motivo iremos usar a LSTM (Long-Short Term Memory), uma arquitetura baseada na RNN.

LSTM

σ = função sigmoid; tanh = tangente hiperbólica

A imagem acima demonstra a arquitetura de uma LSTM. Esse tipo de rede possui 3 componentes:

forget gate: Aqui a rede vai “esquecer” o que for desnecessário. Passando a nova entrada e a saída da camada anterior por uma camada sigmoidal, onde tudo que for removido será transformado em 0. Essa camada ainda é multiplicada pelo estado (memória) da célula anterior.

input gate: Essa parte é responsável por adicionar informação ao estado da célula. Primeiro é feita uma verificação, parecida com o forget gate, com uma sigmoid. Isto é feito para repassar apenas as informações importantes, que precisam ser adicionadas. Depois um vetor é criado pela tanh, contendo todos os possíveis valores que podem ser adicionados. O resultado desses dois passos são multiplicados e depois somados com a saída do forget gate.

output gate: Nesse passo, a rede decide qual será a sua saída. Assim como nas etapas anteriores, existe uma sigmoid para normalizar e selecionar quais os valores que precisam estar na saída. Além disso, o estado atual da célula é passado por outra tanh para gerar todos os valores possíveis. Essas duas camadas são multiplicadas, formando o output gate.

Na prática!

O dataset usado aqui é um dataset público, chamado "Yelp" que contém milhares de revisões sobre vários tipos de comércios.

Este dataset vem com uma coluna chamada "stars", com a classificação de cada uma das revisões. Aqui adiciono a coluna "sentiment" para categorizar a polarização das revisões.

Para qualquer tarefa envolvendo textos são necessários alguns pré-processamentos. Nesse caso, vamos utilizar a tokenização. Esse método usa uma sequência de texto e separa todas as suas palavras, removendo algumas pontuações. No código abaixo também foram retirados todos os caracteres especiais.

O modelo que vamos utilizar é bem simples, apenas com uma camada do LSTM e uma camada densa no final.

Essa rede super simples, com uma época, conseguiu 75% de acurácia com 15.000 dados (pouquíssimos dados).

Train on 10800 samples, validate on 1200 samples
Epoch 1/1
10800/10800 [==============================] - 1334s 124ms/step - loss: 0.7541 - acc: 0.6944 - val_loss: 0.6426 - val_acc: 0.7492
Loss score: 0.61
Test Accuracy: 75.37

O dataset (pos = 9908; neutral = 2385; neg = 2707) tem "pos" como a classe predominante, com 66% dos dados. Dado isso, o baseline para esse teste teria que ser uma acurácia de 66%, porque caso "chutássemos" todas as labels como positivas acertaríamos exatamente isso. O que nos mostra que esse modelo realmente aprendeu algo.

Entretanto, devemos tomar cuidado. O método de amostragem usado, apesar de ter a mesma proporção de dados da classe dominante no dataset, não considera a distribuição dos dados quando seleciona as instâncias. Isso pode gerar um modelo enviesado. Um método melhor seria selecionar essas instancias mantendo a proporção da população.

Mas, e se os dados não conterem nenhuma classificação numérica, ou seja, nenhum rótulo?


Orientação Semântica

Existem algumas outras formas de classificar textos sem nenhum rótulo. A utilizada neste artigo é a orientação semântica (SO) de uma palavra, que calcula a distância de um termo para outro termo como ‘bom’ ou ‘mal’. O cálculo dessa distância é dado pelo PMI (Pointwise Mutual Information). Onde t1 e t2 podem ser quaisquer palavras e P(tx) são suas probabilidades de aparecerem no texto.

E a orientação semântica de uma palavra é calculada com base nos resultados do PMI, usando um termo (t) da frase analisada e comparando com algum termo pertencente ao conjunto de termos (t’) positivos (V+) ou negativos (V-):

Na prática!

Para essa tarefa coletei alguns tweets que continham a hashtag #WomensWave (um dos Trending Topics no Twitter sobre as eleições americanas).

Como no código anterior, aqui também é feita uma tokenização no pré-processamento. Como os tweets podem ter muitas expressões que não são reconhecidas como token é preciso deixá-las explícitas. Essas expressões estão sendo identificadas aqui com regex.

Além do pré-processamento, é necessário tirar algumas stopwords. Stopwords são palavras que em geral não tem um significado, como algumas conjunções e artigos.

Nessa parte coletamos a frequência de cada palavra e a frequência da co-ocorrência entre duas palavras no texto.

Quando um texto é analisado, usar um contexto torna essa análise mais real do que olhar para as palavras separadamente. Por isso a matriz de co-ocorrência é montada.

Aqui é calculada a probabilidade dos termos para que, posteriormente, seja calculado o PMI e o SO.

Abaixo seguem os vocabulários positivos e negativos e os cálculos do PMI e SO

As palavras mais positivas e as mais negativas: 
TOP POS: 
[(‘diversity’, 13.247582522786834),
(‘#usmidtermelections’, 11.880054410636202),
(‘guy’, 10.995531628056138),
(‘beating’, 10.647393653845928),
(‘general’, 10.647393653845928),
(‘function’, 10.440942776378503),
(‘adding’, 10.358613835175579),
(‘diverse’, 9.880054410636202),
(‘goddamned’, 8.866248611111173),
(‘#electionresults2’, 8.866248611111173)]
TOP NEG: 
[(‘control’, -6.451211111832329),
(‘sad’, -9.451211111832329),
(‘believe’, -9.451211111832329),
(‘1950’, -9.451211111832329),
(‘blinded’, -10.451211111832329),
(‘https://t.co/nsvwwzd1dx', -10.451211111832329),
(‘cant’, -10.451211111832329),
(‘feel’, -10.451211111832329),
(‘civilty’, -10.451211111832329),
(‘flow’, -10.451211111832329)]

Vocês podem perceber que algumas palavras não parecem ser o que elas foram classificadas. Isso porque a análise feita aqui foi bem simples, olhando apenas para termos próximos. Para que um aprendizado não supervisionado seja mais assertivo pode ser preciso também uma análise linguística, de como a frase é formada, e com isso identificar meios naturais de expressão.

Para finalizar, criei uma word cloud com o resultado dos termos positivos:


Referências

Esse artigo foi baseado principalmente nesses dois outros artigos sobre Mineração de dados do Twitter e sobre LSTM. Abaixo deixo mais algumas outras referências e meus repositórios no github com os dois projetos completos, incluindo o código da coleta dos tweets e da word cloud.

Meu github
Generating WordClouds in Python
LSTM Networks
Yelp dataset