Nesse exercício vamos utilizar os métodos de clusterização estudados para agrupar documentos. Neste exemplo, documentos serão compostos pelas sinópses de filmes. Vamos seguir a proposto deste tuturial. Para construir a base de dados, coletamos as sinopses dos 100 filmes mais bem votados do IMDb. Para isso, utilizamos a API do imdbpie que permite coletar informações diretamente da base do IMDb. Para o pré-processamento dos dados textuais utilizamos o NLTK.
Para funcionar é preciso instalar o pacode que permite carregar os dados do IMDb e o NLKT (caso não tenha instalado). Para isso, siga os seguintes passos:
Para quem usa a máquina do C9.io
source ia_env/bin/activate
pip install imdbpie
pip install nltk
Pronto. Agora é só executar o comando a seguir.
In [1]:
# Imports necessários para este exercício
from __future__ import print_function
import nltk
import re
import pandas as pd
from sklearn.cluster import KMeans
from imdbpie import Imdb
from nltk.stem.snowball import SnowballStemmer
from sklearn.externals import joblib
from IPython.display import YouTubeVideo, Image
In [29]:
imdb = Imdb()
imdb = Imdb(anonymize=True) # to proxy requests
Caso seja a primeira vez que esteja executando o tutorial é necessário rodar o código a seguir para gerar a base. Caso contrário, pode pular e carregar as informações diretamente do arquivo.
In [30]:
top100 = imdb.top_250()[:100]
title_object = []
for movie in top100:
print("Collecting %s" % movie['title'])
title = imdb.get_title_by_id(movie['tconst'])
title_object.append(title)
joblib.dump(title_object, "top100titles.pkl")
Out[30]:
In [31]:
# Carregando as informações diretamente do arquivo gerado
top100titles = joblib.load('top100titles.pkl')
A variável top100titles é uma lista de objetos do tipo Title. Esse objeto encapsula um filme do IMDb. Para ter acesso a todas as informações disponíveis, acesse a documentação da API. A seguir usamos o comando a função dir do python para listar todos os métodos que possuem na classe que implementa o Objeto Title.
In [32]:
title0 = top100titles[0]
dir(title0)
Out[32]:
Nossa base de dados consiste em uma lista de Título dos Filmes e uma lista correspondente das Sinopses destes filmes. O código a seguir gera essas duas listas a partir da lista com os Top100 filmes do IMDb. Uma filme pode ter mais de uma sinopse no IMDb. Desta forma, concatemos todas estes textos em um único que representa o conteúdo do documento.
In [33]:
list_of_titles = []
list_of_synopses = []
list_of_ids = []
for title in top100titles:
plot_str = ""
list_of_titles.append(title.title)
for plot in title.plots:
plot_str += plot + " "
list_of_synopses.append(plot_str)
list_of_ids.append(title.imdb_id)
Por fim, para completar a base vamos gerar o vocabulário. O vocabulário será formado de duas formas: uma lista das palavras e uma lista do stemmer. O stemmer é o radical da palavra. Para classificação isso se mostrar interessante porque não diferencia palavras como run, running, runner. Todas, independente da conjugação, remetem a ação de correr. Sendo assim, considerando uma palavra como uma feature, teríamos somente uma run que seria referencianda em qualquer ocorrência de seus derivados.
A biblioteca do NLTK já vem com métodos que realiza essa tarefa. Primeiro precisamos instanciar o objeto stemmer.
In [34]:
stemmer = SnowballStemmer("english")
Vão ser criadas duas funções: tokenize_and_stem que tokeniza o texto utilizando o stemmer. E a função tokenize_only que faz a tokenização padrão do texto.
In [35]:
def tokenize_and_stem(text):
# first tokenize by sentence, then by word to ensure that punctuation is caught as it's own token
tokens = [word for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]
filtered_tokens = []
# filter out any tokens not containing letters (e.g., numeric tokens, raw punctuation)
for token in tokens:
if re.search('[a-zA-Z]', token):
filtered_tokens.append(token)
stems = [stemmer.stem(t) for t in filtered_tokens]
return stems
def tokenize_only(text):
# first tokenize by sentence, then by word to ensure that punctuation is caught as it's own token
tokens = [word.lower() for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]
filtered_tokens = []
# filter out any tokens not containing letters (e.g., numeric tokens, raw punctuation)
for token in tokens:
if re.search('[a-zA-Z]', token):
filtered_tokens.append(token)
return filtered_tokens
Por fim é gerado os dois vocabulários e um DataFrame com um índice do stemmer para a palavra original.
In [9]:
totalvocab_stemmed = []
totalvocab_tokenized = []
for i in list_of_synopses:
allwords_stemmed = tokenize_and_stem(i) #for each item in 'synopses', tokenize/stem
totalvocab_stemmed.extend(allwords_stemmed) #extend the 'totalvocab_stemmed' list
allwords_tokenized = tokenize_only(i)
totalvocab_tokenized.extend(allwords_tokenized)
In [36]:
vocab_frame = pd.DataFrame({'words': totalvocab_tokenized}, index = totalvocab_stemmed)
print('Existem ' + str(vocab_frame.shape[0]) + ' itens no vocab_frame')
In [37]:
vocab_frame.head()
Out[37]:
Montado nosso dataset, precisamos construir o objeto que de fato será passado para os métodos de clusterização. Cada texto será representado pelas palavras que o copoõe já que é isso que diferencia os textos entre si. No entanto, não vamos utilizar somente uma grande matriz com todas as palavras e sua frequência. Para isso, vamos utilizar a métrica tf-idf para caracterizar cada palavra no texto. Para entender um pouco mais sobre essa métrica acesse este link ou os dois vídeos que seguem.
In [12]:
YouTubeVideo("t2Nq3MFK_pg")
Out[12]:
In [13]:
YouTubeVideo("xYQb6f1SIEk")
Out[13]:
Para construir essa representação dos textos, vamos utilizar o método TfidfVectorizer do Scikit-Learn. São passados os seguintes parâmetros. Detalhes podem ser encontrados na documentação do método. Explicando alguns:
In [38]:
from sklearn.feature_extraction.text import TfidfVectorizer
#define vectorizer parameters
tfidf_vectorizer = TfidfVectorizer(max_df=0.8, max_features=200000,
min_df=0.2, stop_words='english',
use_idf=True, tokenizer=tokenize_and_stem, ngram_range=(1,3))
%time tfidf_matrix = tfidf_vectorizer.fit_transform(list_of_synopses) #fit the vectorizer to synopses
print(tfidf_matrix.shape)
A matriz final tem dimensão (100x92), ou seja, 100 textos caracterizados por 92 termos. Observe que isso diminui bastante a dimensionalidade se comparado com todas as palavras do vocabulário. O vocabulário final pode ser obtido pelo código a seguir:
In [39]:
terms = tfidf_vectorizer.get_feature_names()
print(terms)
01) Sabendo que tfidf_matrix é sua matriz onde as linhas são os filmes e as colunas as palavras, aplique os algoritmos do KMeans e do DBScan para classificar os filmes.
02) Pesquisa na documentação dos métodos não supervisionados como exibir os itens de cada cluster. Mostre os filmes que fazem parte de cada cluster.
03) Crie uma base de dados para ser utilizada na tarefa de classificação supervisionada com os 100 textos categorizados pelos algoritmos não supervisionado.