Projeto Hillary x Trump

Nesse projeto vamos utilizar tweets relacionados a última eleição presidencial dos Estados Unidos, onde Hillary Clinton e Donald Trump dispuram o pleito final. A proposta é utilizar os métodos de aprendizados supervisionados estudados para classificar tweets entre duas categorias: Hillary e Trump.

O primeiro passo foi obter um conjunto de tweets que foi publicado pelas contas oficiais do tweet dos dois candidatos. Para isso, vamos utilizar este dataset disponibilizado pelo Kaglle. Com este conjunto de dados, vamos construir um modelo capaz de aprender, a partir de um conjunto de palavras, se o texto foi digitado pela conta da Hillary ou do Trump.

Uma vez que este modelo foi construído, vamos classificar um conjunto novo de dados relacionados às eleições americadas e classifica-los em um dos discursos. A proposta é tentar classificar tweets que tenham um direcionamento mais próximo do discurso da Hillary e aqueles que são mais próximos do discurso do Trump. Lê-se como discurso os tweets publicados.

Para essa base de teste, vamos utilizar um subconjunto de tweets deste dataset que consta com tweets que foram postados no dia da eleição americana.

Para exibir os resultados, vamos construir uma página html. Além da análise automática, esta página terá informações sobre os termos mais citados pelas contas dos candidatos.

Sendo assim, o primeiro passo é gerar a base de tweets dos candidatos e extrair as informações mais relevantes. Vamos trabalhar primeiro aqui no Jupyter Notebook para testar os métodos. Ao final será gerado um JSON que será lido pela página HTML. Um exemplo da página já alimentada pode ser encontrada neste link.

Vamos começar ;)

Pré-Processamento da Base dos Candidatos


In [1]:
import pandas as pd
import nltk

df = pd.read_csv("https://www.data2learning.com/machinelearning/datasets/tweets.csv")
dataset = df[['text','handle']]
dict_ = dataset.T.to_dict("list")

O objeto dict_ representa todos os textos associados a classe correspondente.

Na etapa de pré-processamento vamos fazer algumas operações:

  • retirar dos textos hashtags, usuários e links. Essas informações serão incluídas em listas separadas paara serem usadas posteriormente.
  • serão eliminados stopwords, símbolos de pontuação, palavras curtas;
  • numerais tambéms serão descartados, mantendo apenas palavras.

Essas etapas de pré-processamento dependem do objetivo do trabalho. Pode ser de interessa, a depender da tarefa de classificação, manter tais símbolos. Para o nosso trabalho, só é de interesse as palavras em si.

Para esta tarefa, vamos utilizar também o NLTK, conjunto de ferramentas voltadas para o processamento de linguagem natural.

vamos criar um método para isso, já que iremos utiliza-lo mais adiante com a base de teste:


In [2]:
from unicodedata import normalize, category
from nltk.tokenize import regexp_tokenize
from collections import Counter, Set
from nltk.corpus import stopwords
import re

def pre_process_text(text):
    
    
    # Expressão regular para extrair padrões do texto. São reconhecidos (na ordem, o símbolo | separa um padrão):
    # - links com https, links com http, links com www, palavras, nome de usuários (começa com @), hashtags (começa com #)
    pattern = r'(https://[^"\' ]+|www.[^"\' ]+|http://[^"\' ]+|[a-zA-Z]+|\@\w+|\#\w+)'
    
    #Cria a lista de stopwords
    english_stop = stopwords.words(['english'])
    
    users_cited = []
    hash_tags = []
    tokens = []
    
    text = text.lower()
    
    patterns = regexp_tokenize(text, pattern)

    users_cited = [e for e in patterns if e[0] == '@']
    hashtags = [e for e in patterns if e[0] == '#']
    
    
    tokens = [e for e in patterns if e[:4] != 'http']
    tokens = [e for e in tokens if e[:4] != 'www.']
    tokens = [e for e in tokens if e[0] != '#']
    tokens = [e for e in tokens if e[0] != '@']
    tokens = [e for e in tokens if e not in english_stop]
    tokens = [e for e in tokens if len(e) > 3]
    
    return users_cited, hashtags, tokens

In [3]:
users_cited_h = []  # armazena os usuários citatdos por hillary
users_cited_t = []  # armazena os usuários citados por trump

hashtags_h = [] # armazena as hashtags de hillary
hashtags_t = [] # armazena as hashtags de trump

words_h = [] # lista de palavras que apareceram no discurso de hillary
words_t = [] # lista de palavras que apareceram no discurso de trump

all_tokens = [] # armazena todos os tokens, para gerar o vocabulário final
all_texts = [] # armazena todos 

for d in dict_:
    
    text_ =  dict_[d][0]
    class_ = dict_[d][1]
    
    users_, hash_, tokens_ = pre_process_text(text_)
    
    if class_ == "HillaryClinton":
        class_ = "hillary"
        users_cited_h += users_
        hashtags_h += hash_
        words_h += tokens_
        
    elif class_ == "realDonaldTrump":
        class_ = "trump"
        users_cited_t += users_
        hashtags_t += hash_
        words_t += tokens_

    
    temp_dict = {
        'text': " ".join(tokens_),
        'class_': class_ 
    }
    
    all_tokens += tokens_
    all_texts.append(temp_dict)

In [4]:
print("Termos mais frequentes ditos por Hillary:")
print()
hillary_frequent_terms = nltk.FreqDist(words_h).most_common(10)

for word in hillary_frequent_terms:
    print(word[0])


Termos mais frequentes ditos por Hillary:

trump
hillary
donald
president
america
people
make
families
women
need

In [5]:
print("Termos mais frequentes ditos por Trump:")
print()
trump_frequent_terms = nltk.FreqDist(words_t).most_common(10)

for word in trump_frequent_terms:
    print(word[0])


Termos mais frequentes ditos por Trump:

thank
great
trump
hillary
people
america
clinton
cruz
crooked
make

Bigrams e Trigram mais frequentes da Hillary


In [6]:
#Pegando os bigram e trigram mais frequentes

from nltk.collocations import BigramCollocationFinder, TrigramCollocationFinder
from nltk.metrics import BigramAssocMeasures, TrigramAssocMeasures

bcf = BigramCollocationFinder.from_words(words_h)
tcf = TrigramCollocationFinder.from_words(words_h)

bcf.apply_freq_filter(3)
tcf.apply_freq_filter(3)

result_bi = bcf.nbest(BigramAssocMeasures.raw_freq, 5)
result_tri = tcf.nbest(TrigramAssocMeasures.raw_freq, 5)


hillary_frequent_bitrigram = []

for r in result_bi:
    w_ = " ".join(r)
    print(w_)
    hillary_frequent_bitrigram.append(w_)
    
print
for r in result_tri:
    w_ = " ".join(r)
    print(w_)
    hillary_frequent_bitrigram.append(w_)


donald trump
hillary clinton
make sure
united states
commander chief
president united states
hillary donald trump
watch live hillary
donald trump says
president donald trump

Bigrams e Trigram mais frequentes do Trump


In [7]:
bcf = BigramCollocationFinder.from_words(words_t)
tcf = TrigramCollocationFinder.from_words(words_t)

bcf.apply_freq_filter(3)
tcf.apply_freq_filter(3)

result_bi = bcf.nbest(BigramAssocMeasures.raw_freq, 5)
result_tri = tcf.nbest(TrigramAssocMeasures.raw_freq, 5)


trump_frequent_bitrigram = []


for r in result_bi:
    w_ = " ".join(r)
    print(w_)
    trump_frequent_bitrigram.append(w_)
print
for r in result_tri:
    w_ = " ".join(r)
    print(w_)
    trump_frequent_bitrigram.append(w_)


crooked hillary
hillary clinton
make america
america great
donald trump
make america great
crooked hillary clinton
make america safe
goofy elizabeth warren
america safe great

Construindo um bag of words


In [8]:
# Cada token é concatenado em uma única string que representa um tweet
# Cada classe é atribuída a um vetor (hillary, trump)

# Instâncias: [t1, t2, t3, t4]
# Classes: [c1, c2, c3, c4]

all_tweets = []
all_class = []

for t in all_texts:
    all_tweets.append(t['text'])
    all_class.append(t['class_'])

In [9]:
print("Criar o bag of words...\n")

#Número de features, coluna da tabela
max_features = 2000


from sklearn.feature_extraction.text import CountVectorizer

# Initialize the "CountVectorizer" object, which is scikit-learn's
# bag of words tool.  
vectorizer = CountVectorizer(analyzer = "word",   \
                             tokenizer = None,    \
                             preprocessor = None, \
                             stop_words = None,   \
                             max_features = max_features) 

# fit_transform() does two functions: First, it fits the model
# and learns the vocabulary; second, it transforms our training data
# into feature vectors. The input to fit_transform should be a list of 
# strings.
X = vectorizer.fit_transform(all_tweets)

# Numpy arrays are easy to work with, so convert the result to an 
# array
X = X.toarray()
y = all_class

print("Train data: OK!")


Criar o bag of words...

Train data: OK!

In [10]:
X.shape


Out[10]:
(6444, 2000)

Ao final deste processo já temos nossa base de dados dividido em duas variáveis: X e y.

X corresponde ao bag of words, ou seja, cada linha consiste de um twitter e cada coluna de uma palavra presente no vocabulário da base dados. Para cada linha/coluna é atribuído um valor que corresponde a quantidade de vezes que aquela palavra aparece no respectivo tweet. Se a palavra não está presente, o valor de 0 é atribuído.

y corresponde a classe de cada tweet: hillary, tweet do perfil @HillaryClinton e trump, tweet do perfil @realDonaldTrump.

Testando diferentes modelos

Vamos testar os diferentes modelos estudados com a base de dados criada e escolher aquele que melhor generaliza o conjunto de dados. Para testar os modelos, vamos utilizar validação cruzada de 10 folds. Aplique os modelos estudados:

  • KNN (teste diferentes valores de K e escolha o melhor)
  • Árvore de Decisão
  • SVM (varie o valor de C e escolha o melhor)

Além disso, teste dois outros modelos:

  • RandomForest
  • Naive Bayes

Para estes dois, pesquise no Google como utiliza-los no Scikit-Learn.

Para cada modelo imprima a acurácia no treino e na média dos 10 folds da validação cruzada. A escolha do melhor deve ser feita a partir do valor da média da validação cruzada.

O melhor modelo será utilizado para classificar outros textos extraídos do twitter e na implementação da página web.

Atenção: dada a quantidade de dados, alguns modelos pode demorar alguns minutos para executar


In [ ]:
# Teste os modelos a partir daqui

Atenção: as tarefas a seguir serão disponibilizadas após a entrega da primeira parte. Sendo assim, não precisa enviar o que se pede a seguir. Quando passar a data da entrega, disponibilizo o notebook completo. No entanto, fiquem a vontade de fazer a próxima tarefa como forma de aprendizado. É um bom exercício ;)

Usando o melhor modelo em novos textos

Vamos executar o melhor clasificador em um conjunto de textos novos. Esses textos não tem classificação. Eles foram postados durante o dia da eleição americana. A idéia é identificar de forma automática os tweets que estão mais próximos dos discursos da Hillary Clinton e de Donald Trump.

Essa tarefa será realizada em sala após a entrega da atividade do melhor modelo

Gerando o .json lido pela página web

Esse será o JSON gerado após a etapa de teste do melhor modelo. Essa tarefa também será realizada em sala após a entrega do teste dos melhores modelos.


In [ ]:
hillary_frequent_hashtags = nltk.FreqDist(hashtags_h).most_common(10)
trump_frequent_hashtags = nltk.FreqDist(hashtags_t).most_common(10)

dict_web = {
    'hillary_information': {
        'frequent_terms': hillary_frequent_terms,
        'frequent_bitrigram': hillary_frequent_bitrigram,
        'frequent_hashtags': hillary_frequent_hashtags
    },
    'trump_information': {
        'frequent_terms': trump_frequent_terms,
        'frequent_bitrigram': trump_frequent_bitrigram,
        'frequent_hashtags': trump_frequent_hashtags
    },
    'classified_information': {
        'hillary_terms': hillary_classified_frequent_terms,
        'hillary_bigram': hillary_classified_bitrigram,
        'trump_terms': trump_classified_frequent_terms,
        'trump_bigram': trump_classified_bitrigram,
        'texts_classified': all_classified
    }
}

with open('data.json', 'w') as outfile:  
    json.dump(dict_web, outfile)