Análise de Sentimento com Aprendizado Supervisionado e não Supervisionado
Tags: machinelearning, python
Published: 2019-10-28 01:38:34

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](https://medium.com/infosimples-br/principais-conceitos-por-tras-do-machine-learning-a4b942d5d309?source=collection_home---4------2---------------------)).

----------

# 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.

![](https://miro.medium.com/max/30/1*NKhwsOYNUT5xU7Pyf6Znhg.png?q=20)

![](https://miro.medium.com/max/2706/1*NKhwsOYNUT5xU7Pyf6Znhg.png)

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

![](https://miro.medium.com/max/30/1*8v0YY4gZ8YC8MgCgHMXGXw.jpeg?q=20)

![](https://miro.medium.com/max/1044/1*8v0YY4gZ8YC8MgCgHMXGXw.jpeg)

<figcaption> σ = função sigmoid; tanh = tangente hiperbólica </figcaption>

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.

![](https://miro.medium.com/max/30/1*eceM1h65vUxwlQJ-X9HF7w.png?q=20)

![](https://miro.medium.com/max/304/1*eceM1h65vUxwlQJ-X9HF7w.png)

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-_):

![](https://miro.medium.com/max/30/1*u8DrJfGwfDqaAsCtzaHA7w.png?q=20)

![](https://miro.medium.com/max/571/1*u8DrJfGwfDqaAsCtzaHA7w.png)

# **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'](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:

![](https://miro.medium.com/max/30/1*04fetI8dOJY4DOGlD3qdzw.png?q=20)

![](https://miro.medium.com/max/600/1*04fetI8dOJY4DOGlD3qdzw.png)

----------

# Referências

Esse artigo foi baseado principalmente nesses dois outros artigos sobre  [Mineração de dados do Twitter](https://marcobonzanini.com/2015/05/17/mining-twitter-data-with-python-part-6-sentiment-analysis-basics/) e sobre  [LSTM](https://towardsdatascience.com/understanding-lstm-and-its-quick-implementation-in-keras-for-sentiment-analysis-af410fd85b47). 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](https://github.com/giselyalves13/sentiment_analyses)
> 
> [Generating WordClouds in Python](https://www.datacamp.com/community/tutorials/wordcloud-python)
> 
> [LSTM Networks](https://www.youtube.com/watch?v=9zhrxE5PQgY)
> 
> [Yelp dataset](https://www.kaggle.com/yelp-dataset/yelp-dataset/version/4#yelp_review.csv)