Text Analytics (NLP) utilizando Python

Guilherme Gomes
Data Hackers
Published in
7 min readDec 30, 2020

Olá pessoal! Tudo bem com vocês?

Dando continuidade na série de textos mostrando um projeto de dados do início ao fim, hoje vou escrever sobre a terceira etapa do projeto: Text Analytics.

Fonte: autoria própria.

Se você caiu nesse texto de paraquedas, fique tranquilo! Nas etapas anteriores foi montado um crawler utilizando o Selenium para coletar comentários do Youtube e depois foi feito o pré-processamento desses dados coletados, utilizando python. Lembrando que todos os links colocados e citados ao longo do texto estão disponíveis na parte de referências.

Série de textos:

01. Como capturar comentários do Youtube usando Python

02. Guia inicial de pré-processamento de texto utilizando Python

03. Text Analytics (NLP) utilizando Python

04. Como carregar um dataframe Pandas no Google BigQuery

05. 4 dicas de visualização de dados

Introdução

Existem diversas análises que podem ser feitas com textos, como por exemplo, análise de sentimento, nuvem de palavras e classificação de motivo de contato — que vou mostrar hoje. Meu objetivo é entender o principal tema que as pessoas comentam no Youtube e para isso precisamos de alguns dados rotulados, ou seja, dados nos quais já sabemos qual foi o motivo de contato. Para isso a estratégia será a seguinte:

Fonte: autoria própria.

Etapa 01 - Rotulagem dos comentários

Imports

Já dando spoiler do que foi feito, segue a lista de libs que precisaremos para criar o nosso modelo:

import pandas as pdimport numpy as npfrom matplotlib import pyplot as pltfrom sklearn.cluster import MiniBatchKMeansfrom sklearn.metrics.pairwise import cosine_similarityfrom sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizerfrom sklearn.metrics import classification_reportfrom sklearn.model_selection import train_test_splitfrom sklearn.linear_model import LogisticRegressionfrom sklearn.feature_extraction import stop_wordsfrom wordcloud import WordCloudfrom operator import itemgetter

TFIDF e Kmeans

De maneira geral, a função pooler executa dois processos importantes: TF-IDF e Kmeans. O TF-IDF é uma das maneiras de transformar os comentários do youtube (documentos) em uma matriz, de acordo com a frequência com que cada termo das frases aparece, entretanto, existem outras formas de fazer isso, como por exemplo, utilizando Word2Vec ou Embedding. Por sua vez, o K-means é responsável por classificar esses textos de acordo com um número de clusters.

def pooler(documents, iters, min_df=5, max_df=0.7, ngram=(1, 3), max_features=1000):# documents vectorizervectorizer = TfidfVectorizer(min_df=min_df,max_df=max_df,ngram_range=ngram,max_features=max_features).fit(documents)vectorized = vectorizer.transform(documents)# pooler modelsse = []models = []for k in iters:model = MiniBatchKMeans(n_clusters=k, init_size=256, batch_size=512, random_state=42).fit(vectorized)models.append(model)sse.append(model.inertia_)model.predict(vectorized)return models, sse, vectorized, vectorizer.get_feature_names()def get_model(models, k):c = ((k — 2) // 2)return models[c]

Definição número de clusters.

Para identificar o número de cluster corretos para o K-means, foi utilizado o método de Elbow, ou cotovelo, que consiste em representar graficamente a variação explicada em função do número de clusters e escolher o cotovelo da curva como o número de clusters para usar. (DATANOVIA, 2018).

iters = range(2, 30 + 1, 2)models, sse, vectorized, vocabulary = pooler(df.comment_stemming.values, iters, min_df=5, max_df=0.6, max_features=1500)plt.plot(iters, sse)plt.show()
Fonte: autoria própria.

Predição do modelo

Nesse exemplo usaremos um total de 18 clusters.

k = 18model = get_model(models, k)model.predict(vectorized)df[‘cluster’] = model.predict(vectorized)

Melhores exemplos

Para selecionar os melhores exemplos em cada cluster foi utilizado a similaridade de cossenos. Uma outra abordagem comum é a utilização da distância Euclidiana. De maneira geral, a distância euclidiana corresponde à norma L2 de uma diferença entre vetores, já a similaridade do cosseno é proporcional ao produto escalar de dois vetores e inversamente proporcional ao produto de suas magnitudes. Portanto, vetores com uma pequena distância euclidiana um do outro estão localizados na mesma região de um espaço vetorial e os vetores com alta similaridade de cosseno estão localizados na mesma direção geral da origem. (BAELDUN, 2020).

def sort_best_binds(key):# calculate similaritiessimilarities = []centroid = model.cluster_centers_[key]for v in vectorized:similarities.append(cosine_similarity([centroid], v))# best bindsindexes = np.array([s[0][0] for s in similarities])indexes = np.argsort(indexes)[::-1]return indexes

Top N exemplos

Para tentar entender a classificação feita pelo modelo, foram selecionados os 15 exemplos principais de cada cluster:

topn = 15for i in range(len(model.cluster_centers_)):indexes = sort_best_binds(i)values = df.iloc[indexes[:topn]].comment_stop_word.valuesprint(f’\nCluster: {i}’)for v in range(topn):print(values[v][:200])

Definição das categorias

Analisando os dados presentes em cada cluster, gerou-se um dicionário com os motivos de contato. Com os clusters que não foram possíveis identificar o motivo de contato, foi feita uma rotulagem chamada “desconhecida”, onde posteriormente foi realizada a rotulagem manual.

maps = {0: ‘água na boca’,1: ‘bordão’,2: ‘elogio’,3: ‘desconhecido’,4: ‘duvida’,5: ‘elogio’,6: ‘desconhecido’,7: ‘José Almiro’,8: ‘elogio’,9: ‘desconhecido’,10: ‘desconhecido’,11: ‘bordão’,12: ‘crítica’,13: ‘elogio’,14: ‘água na boca’,15: ‘elogio’,16: ‘elogio’,17: ‘vou fazer’}top_binds = 50for key in maps:if maps[key]:# best bindsindexes = sort_best_binds(key)# labeling recordsdf[‘category’].iloc[indexes[:top_binds]] = maps[key]df.groupby([‘category’])[‘author’].count()

Por fim, esse dataset foi salvo e utilizado para rotulagem manual.

df[~df[‘category’].isin([‘Outro’])].to_csv(‘croresid_labeled.csv’, sep=’,’, encoding=’utf-8', index=False)

Se você chegou até aqui, parabéns! Agora nós temos o dataset rotulado e podemos utilizar a Regressão Logística para predizer as categorias dos demais comentários dos vídeos.

Etapa 02 - Classificação dos comentários

Leitura dos dados rotulados:

Para fazer a classificação dos comentários, leu-se o dataset com os dados rotulados:

data = pandas.read_csv(train_data)data = data[~data[‘category’].isin([‘ignorar’])]

TFIDF:

Assim como na etapa de rotulagem dos dados, se fez necessário transformar o texto em uma matriz numérica. Novamente utilizando o TFIDF.

vectorizer = TfidfVectorizer(ngram_range=(1, 3),max_features=1500,min_df=5, max_df=0.8).fit(documents)vectorizer

Regressão logística:

Os dados rotulados foram divididos da seguinte forma:

Fonte: autoria própria.
X = vectorizer.transform(documents)y = data.category.valuesoversample = RandomOverSampler(random_state=42)X_over, y_over = oversample.fit_resample(X, y)X_train, X_test, y_train, y_test = train_test_split(X_over, y_over, test_size=0.3, random_state=42)classifier = LogisticRegression(multi_class=’ovr’,solver=’lbfgs’,class_weight=’balanced’,random_state=42).fit(X_train, y_train)classifiery_pred = classifier.predict(X_test)print(classification_report(y_test, y_pred))

Para analisar o resultado do modelo, utilizou-se a métrica de precisão, recall e f1-score. Antes de explicar as métricas, é necessário explicar o que é matriz de confusão, pois as métricas são tiradas a partir dela.

Fonte: autoria própria.

Cada uma das classificações representa o erro ou acerto do modelo de acordo com o valor real, dessa forma, o positivo e negativo verdadeiros é quando o modelo acerta o valor. Já o positivo e negativo falso é quando o modelo inverte o resultado, ou seja, era positivo e o resultado foi negativo (e vice-versa).

Com a matriz de confusão é possível tirar algumas métricas:

  • Precision: Entre a classe positivo, quantos o modelo acertou.
  • Recall: Entre a classe positiva com valor esperado, quantas estão certas.
  • F1-score: Média harmônica entre Precision e Recall.

Dessa forma, no nosso modelo temos o seguinte resultado:

Fonte: autoria própria.

Resultado com a base real

Tendo treinado o modelo com a base de dados rotulados, foi possível aplicá-lo em todo o restante dos dados e assim saber o motivo de contato.

real_data = pandas.read_csv(file_full)real_data = real_data[~real_data[‘comment_stemming’].isna()]real_data = real_data[[‘video_title’,‘author’,‘comment’,‘comment_cleaned’,‘comment_stop_word’,‘comment_tokenized’,‘comment_stemming’,‘comment_lematized’]]real_data[‘label’] = classifier.predict(vectorizer.transform(real_data.comment_stemming.values))real_data.groupby([‘label’])[‘author’].count().sort_values(ascending=False).plot.barh()
Fonte: autoria própria.

Conclusão

Classificar o motivo de contato é algo muito importante quando existe interação com um número alto de pessoas, como por exemplo, em canais de atendimento de empresas e redes sociais. Dessa maneira é possível identificar os principais temas e tomar decisões importantes com base em dados, nesse exemplo é possível criar vídeos baseados em dúvidas dos inscritos do canal e entender quais são as sugestões feitas pelos inscritos, assim como entender qual a razão dos elogios.

Se você chegou até aqui, muito obrigado! Espero que tenha gostado do conteúdo e que ele tenha te ajudado a entender um pouco mais sobre text analytics. Fico aberto a feedbacks e não deixe de acompanhar outros textos que tenho publicado aqui no Medium. Forte abraço e boas festas !

Série de textos:

01. Como capturar comentários do Youtube usando Python

02. Guia inicial de pré-processamento de texto utilizando Python

03. Text Analytics (NLP) utilizando Python

04. Como carregar um dataframe Pandas no Google BigQuery

05. 4 dicas de visualização de dados

Referências:

DATANOVIA, 2018. Determining the optimal number of clusters 3 must know methods. Disponível em: <https://www.datanovia.com/en/lessons/determining-the-optimal-number-of-clusters-3-must-know-methods/>DATANOVIA, 2018. Determing the optimal number of clusters 3 must know methods. Disponível em: <https://www.datanovia.com/en/lessons/determining-the-optimal-number-of-clusters-3-must-know-methods/>

BAELDUN, 2020. Euclidean distance vs cosine similarity. Disponível em: <https://www.baeldung.com/cs/euclidean-distance-vs-cosine-similarity#:~:text=The%20Euclidean%20distance%20corresponds%20to,the%20product%20of%20their%20magnitudes.>

<https://medium.com/data-hackers/como-capturar-coment%C3%A1rios-do-youtube-usando-python-64ea39a483e6?sk=83f92a1160812c8889e81c70f86b068>

<https://medium.com/dextra-digital/guia-inicial-de-pr%C3%A9-processamento-de-texto-utilizando-python-10950eac2ffe>

<https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html>

<https://scikit-learn.org/stable/modules/clustering.html#k-means>

<https://www.tensorflow.org/tutorials/text/word2vec>

<https://keras.io/api/layers/core_layers/embedding/>

--

--

Guilherme Gomes
Data Hackers

Profissional da área de dados, apaixonado por produtos digitais e hambúrguer.