Análise de Sentimentos com Python! 😁😔

Análise de Sentimentos do IMDb com Python!

Autor: Gabriel Murilo

A análise de sentimento é uma técnica que envolve a classificação das opiniões e emoções expressas em texto. No contexto do IMDb, um dos maiores sites de críticas de filmes, a análise de sentimento será usada para determinar se uma revisão é positiva ou negativa com base no texto escrito (review).

Para isso, usaremos a uma técnica conhecida como NLP (Processamento de Linguagem Natural) que utiliza de redes neurais LSTM para seu funcionamento.

dica: se estiver no Colab, vá em "Ambiente de execução/Alterar tipo de ambiente de execução, coloque em T4 GPU e salve, assim, seu notebook terá uma GPU, e o treinamento da IA será mais rápido e eficaz

Importações das Libs:

from keras.datasets import imdb
from keras.preprocessing import sequence
import keras
from tensorflow.keras.models import load_model
import tensorflow as tf
import os
import numpy as np
from keras.utils.data_utils import pad_sequences

Carregamento dos Dados

VOCAB_SIZE = 88584

MAXLEN = 250
BATCH_SIZE = 64

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words = VOCAB_SIZE)

train_data = pad_sequences(train_data, MAXLEN)
test_data = pad_sequences(test_data, MAXLEN)

Aqui, estamos definindos os hiperparâmetros, Carregandos os dados e padronizando as sequências:

VOCAB_SIZE: especifica o tamanho do vocabulário que será usado. É o número máximo de palavras distintas que o modelo considerará. Neste caso, o vocabulário tem 88.584 palavras. MAXLEN: define o comprimento máximo das sequências de texto. Sequências mais longas serão truncadas e sequências mais curtas serão preenchidas para ter esse comprimento. No seu caso, o comprimento máximo é de 250 palavras. BATCH_SIZE: determina o tamanho dos lotes de dados que serão usados durante o treinamento. Cada lote contém 64 sequências de texto.

pad_sequences: estamos padronizando (preenchendo ou truncando) as sequências de texto para que todas tenham o mesmo comprimento, especificado por MAXLEN. Isso é necessário porque muitos modelos de aprendizado de máquina, como redes neurais, exigem que as entradas tenham o mesmo tamanho.

Criando o Modelo:

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(VOCAB_SIZE, 32),
    tf.keras.layers.LSTM(32),
    tf.keras.layers.Dense(1, activation="sigmoid")
])

Compilação e Treinamento do Modelo:

model.compile(loss="binary_crossentropy",optimizer="rmsprop",metrics=['acc'])

history = model.fit(train_data, train_labels, epochs=50, validation_split=0.2)

results = model.evaluate(test_data, test_labels)
print(results)

Nessa parte, o modelo é compilado com uma função de perda de entropia cruzada binária (binary_crossentropy), otimizado com RMSprop e monitora a precisão. O modelo é treinado com os dados de treinamento, com validação em 20% dos dados, em 50 épocas (épocas são o número de vezes que o modelo vê um mesmo dado).

Codificação e Decodificação

word_index = imdb.get_word_index()

def encode_text(text):
  tokens = keras.preprocessing.text.text_to_word_sequence(text)
  tokens = [word_index[word] if word in word_index else 0 for word in tokens]
  return pad_sequences([tokens], MAXLEN)[0]

text = "that movie was just amazing, so amazing"
encoded = encode_text(text)
print(encoded)

Aqui, resumidamente, essa função codifica algum texto, mapeando as palavras para índices no vocabulário do conjunto de dados IMDB, e padroniza a sequência para um comprimento máximo, preparando-a para alimentar um modelo de análise de sentimentos. No caso que está printanto, ficaria assim:

[  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0  12  17  13  40 477  35 477]

reverse_word_index = {value: key for (key, value) in word_index.items()}

def decode_integers(integers):
    PAD = 0
    text = ""
    for num in integers:
      if num != PAD:
        text += reverse_word_index[num] + " "

    return text[:-1]
  
print(decode_integers(encoded))

Aqui fazemos da decodificação.

Testando o Modelo:

def predict(text):
  encoded_text = encode_text(text)
  pred = np.zeros((1,250))
  pred[0] = encoded_text
  result = model.predict(pred) 
  print(result[0])

positive_review = "That movie was! really loved it and would great watch it again because it was amazingly great"
predict(positive_review)

negative_review = "that movie really sucked. I hated it and wouldn't watch it again. Was one of the worst things I've ever watched"
predict(negative_review)

O resultado varia entre 0 e 1 (pois como é uma classifcação binária, usamos a função de ativação "sigmoid), quanto mais próximo de 1, mais positiva foi a análise.

Neste teste o resultado foi esse:

1/1 [==============================] - 0s 337ms/step
[0.6899334]
1/1 [==============================] - 0s 18ms/step
[0.00852319]

Salvando o Modelo no Colab:

from google.colab import drive
drive.mount('/content/drive')


# Salvar o classificador
model_json = model.to_json()
with open("/content/drive/MyDrive/model.json", "w") as json_file:
    json_file.write(model_json)
model.save_weights("/content/drive/MyDrive/model.h5")

Salvamos em 2 arquivos, um .json que é a "arquitetura" do modelo e o .h5, que são os pesos da rede neural.

Carregando esse Modelo

import numpy as np
from keras.models import model_from_json
from keras_preprocessing.sequence import pad_sequences
import keras
from keras.datasets import imdb


# Load the model
file = open('model.json', 'r')
structure = file.read()
file.close()
loaded_model = model_from_json(structure)
loaded_model.load_weights(r"model.h5")
model = loaded_model

print(model.summary())

# encode function
word_index = imdb.get_word_index()
MAXLEN = 250
def encode_text(text):
  tokens = keras.preprocessing.text.text_to_word_sequence(text)
  tokens = [word_index[word] if word in word_index else 0 for word in tokens]
  return pad_sequences([tokens], MAXLEN)[0]

text = "I loved this movie, its was all good"
encoded = encode_text(text)

# prediction
pred = np.zeros((1,250))
pred[0] = encoded
result = model.predict(pred)

result = (result[0] * 100).item()
print(f'Positive: {result:.2f}%')

Fala Gabriel, obrigado por ter compartilhado isso ae! A dica sobre a execução no Colab com GPU foi muito útil! Como o tamanho do VOCAB_SIZE foi determinado? Há alguma razão específica para escolher 88.584 palavras?

E aí, Silas, blz? O número 88.584 do VOCAB_SIZE representa o número de palavras dististas dentro do dataset. Você pode visualizar isso rodando esse código: ``` palavra_distintas = imdb.get_word_index() n = len(palavra_distintas) print("Número de palavras distintas nas avaliações =", n) ``` O que resultaria em: ``` Número de palavras distintas nas avaliações = 88584 ``` Espero ter ajudado, Silas, qualquer outra dúvida é só mandar. Valeu!