Detecção de Fake News com Redes Neurais

Raffaela Loffredo
32 min readJan 9, 2024

Click here to read this article in English.

As fake news têm sido um problema recorrente na nossa Era pós globalização e com fácil acesso à internet. Para combatê-las é necessário primeiro identificá-las. Isso é possível com o uso de algoritmos treinados para essa finalidade.

Nesse artigo mostro o caminho que eu fiz para criar um modelo supervisionado de aprendizado de máquina com o uso de redes neurais que detecta se uma notícia é ou não confiável. Para isso foi feita uma análise exploratória do conjunto de dados, limpeza e tratamento dos dados, a padronização de textos. Foram criadas duas nuvens de palavras para se familiarizar com os termos mais utilizados nos artigos, bem como foram analisados os autores com a maior quantidade de notícias nesse conjunto de dados. Em seguida, se realizou o pré-processamento necessário para o uso do TensorFlow por meio das técnicas de tokenização e padding. A construção do algoritmo foi modelada de acordo com o problema, por isso, sua base é uma rede neural sequencial empilhada por camadas que conta com: Embedding, Conv1D, GlobalMaxPool1D e Dense. Após a inserção de novos dados no modelo, avaliou-se sua performance por meio dos valores de acurácia e recall, além da construção da matriz de confusão.

* Observação

Este é um relato completo do estudo, incluindo códigos e metodologias utilizadas. Caso queira, publiquei um resumo mais direto, onde trago apenas os resultados dessa pesquisa:

Para acessar o artigo resumido acesse aqui.

Sumário

  1. Sobre o Projeto
  2. Objetivo Geral
    2.1. Objetivos Específicos
  3. Obtenção dos Dados
  4. Dicionário de Variáveis
  5. Importação dos Dados e das Bibliotecas
  6. Análise Exploratória dos Dados
  7. Limpeza dos Dados
    7.1. Dados Ausentes
    7.2. Dados Duplicados
    7.3. Exclusão do atributo id
  8. Tratamento dos Dados
    8.1. Padronização das palavras
    8.2. WordCloud
    8.2.1. WordCloud com os títulos
    8.2.2. WordCloud com conteúdo das notícias
    8.3. Análise dos autores
    8.4. Divisão dos conjuntos: treino, validação e testes
  9. Métricas de Avaliação de Performance
  10. O que são Redes Neurais?
  11. Por que TensorFlow?
  12. Desenvolvimento do Algoritmo utilizando TensorFlow
  13. Previsõs no conjunto de testes
  14. Comparação de Resultados: Validação x Teste
  15. Deploy do Modelo
  16. Conclusão

1. Sobre o Projeto

Com a globalização e a ascensão tecnológica as notícias conseguem percorrer todo o globo em questão de segundos. A internet facilitou o acesso à informação, porém na outra ponta, facilitou a disseminação de notícias falsas, ou mais conhecidas como fake news. Esse tipo de problema se tornou um desafio global que impacta desde simples tarefas rotineiras até mesmo o cenário político internacional.

Em um conceito mais amplo as fake news se referem às notícias manipuladas que são totalmente inverídicas ou as que possuem apenas uma coisa distorcida, fato ou número, e que tem a intenção de levam o leitor a ter outra percepção do fato. Logo, esse tipo de notícia tem como objetivo enganar, influenciar a opinião ou causar um sensacionalismo em torno de uma temática.

As consequências são séries e vão desde o nível pessoal, gerando confusão, criando ansiedade, medo e distorcem a realidade de um indivíduo. Isso pode levá-las a tomar decisões erradas que podem prejudicar diversas esferas como financeira, de relacionamentos e até mesmo a saúde da pessoa.

Contudo, em um cenário mais amplo, as falsas notícias polarizam a sociedade, incitam violência e ódio gratuitos, além de minar a confiança em determinadas instituições. No cenário político, por exemplo, podem afetar o resultado de eleições e a estabilidade democrática de uma nação.

Portanto, a luta contra esse tipo de notícia é de extrema importância e exige a colaboração de governos, empresas de comunicação e tecnologia, bem como do público em geral. Temos que ter senso crítico para questionar as informações que recebemos e auxiliar a identificar e parar que uma fake news seja disseminada.

Além disso, uma outra forma de combater esse tipo notícia é com o uso da tecnologia. Com isso em mente, esse estudo tem o objetivo de usar da tecnologia de ciência de dados para auxiliar a identificação de fake news.

2. Objetivo Geral

Construir um modelo supervisionado de machine-learning que classifique se uma notícia é confiável ou não.

2.1. Objetivos Específicos

  • Fazer uma análise exploratória nos dados com o objetivo de conhecer o dataset e extrair insights que possam auxiliar as etapas posteriores.
  • Realizar o tratamento e processamento dos dados para prepará-los para serem devidamente utilizados no modelo de machine-learning.
  • Criar um modelo de machine-learning com o uso de redes neurais e avaliar seu desempenho.

3. Obtenção dos Dados

Os dados utilizados neste projeto foram originalmente disponibilizados no Kaggle. Esse arquivo, por garantia foi salvo em nuvem, para o eventual caso de ser removido. É a partir do arquivo em nuvem que o dataset é importado posteriormente, mas ele pode ser acessado aqui.

4. Dicionário de Variáveis

A compreensão do conjunto de dados passa pela checagem das variáveis disponíveis nele para que se possa realizar uma boa análise. De acordo com a documentação do site foi construída a tabela abaixo com o nome da variável e seu respectivo significado.

em ordem alfabética

author: Nome do autor da notícia
id: Identificação única para a notícia
label: Variável alvo que indica se a notícia é fake news ou não, por meio de valores booleanos. Logo, 0 representa uma notícia confiável e 1 representa uma notícia não-confiável ou fake news.
text: Texto da notícia (pode estar incompleta)
title: Título da notícia

5. Importação dos Dados e das Bibliotecas

Ao iniciar um projeto é necessário instalar pacotes, importar as bibliotecas que possuem funções específicas a serem utilizadas nas linhas de código seguintes e realizar as configurações necessárias para a saída do código. Também, se prossegue com a importação dos datasets, salvando-os em variáveis específicas para que sejam usadas posteriormente.

# instalar pacotes adicionais
!pip install tensorflow-addons -q
!pip install scikit-plot -q
# importar as bibliotecas necessárias
import pandas as pd # manipulação de dados
import numpy as np # manipulação de arrays
import random as rnd # números aleatórios
import missingno as msno # avaliação dados ausentes
import matplotlib.pyplot as plt # visualização de dados
import seaborn as sns # visualização estatística dos dados
import string as st # funções para lidar com dados do tipo string
import tensorflow as tf # construção de modelos de aprndizado de máquina
import scikitplot as skplt # visualização de dados e métricas de machine learning

from tensorflow import keras # construção de modelos de aprendizado profundo
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator # criação de nuvem de palavras
from sklearn.model_selection import train_test_split # divisão em conjuntos de treino e teste
from keras.preprocessing.text import Tokenizer # criação de tokens
from keras.preprocessing.sequence import pad_sequences # criação de sequências com preenchimento
from keras.optimizers import Adam # otimizador para treinamento redes neurais
from sklearn.metrics import confusion_matrix # gerar matriz de confusão
from sklearn.metrics import accuracy_score, recall_score, precision_score # métricas de avaliaçãod de performance
from sklearn.metrics import classification_report # geração de relatório de desempenho do modelo

import warnings # notificações
warnings.filterwarnings('ignore') # configurar notificações para serem ignoradas

# configurações adicionais
## estilo de gráficos
plt.style.use('ggplot')
sns.set_style('dark')

## definir semente aleatória fixa para resultados reproduzíveis
seed = 123

tf.random.set_seed(seed)
np.random.seed(seed)
rnd.seed(seed)

# configurar a saída para mostrar todas as linhas e colunas
pd.options.display.max_columns = None

# configurar saída de figuras em formato 'svg' (melhor qualidade)
%config InlineBackend.figure_format = 'svg'
# importar os dados e atribuir em uma variável
data_path = 'https://www.dropbox.com/scl/fi/7gubsdfvlvtgswsmp2a7d/train.csv?rlkey=cof2cxeyek1zveki3nnkpygcp&dl=1'
df_raw = pd.read_csv(data_path)

6. Análise Exploratória dos Dados

Essa é uma etapa essencial em projetos de ciência de dados onde se busca compreender melhor os dados seja por identificar padrões, outliers, possíveis relações entre as variáveis, etc. Nesse estudo, se exploraram informações que fossem relevantes para orientar as respostas dos objetivos indicados anteriormente (ver Objetivo Geral e Objetivos Específicos).

Para isso serão utilizadas diversas técnicas e ferramentas que se acharem necessários. Nessa fase, o cientista de dados vira um detetive em busca de coisas que não estão explícitas no data frame. Em razão disso, os dados também serão plotados de diferentes maneiras, para melhor visualizá-los e testar hipóteses iniciais, com o objetivo de obter insights que possam orientar o restante do projeto.

Primeiro, gerei a visualização das 5 primeiras e das 5 últimas entradas para checar a composição do dataset, e verifiquei se ao final dele não havia nenhum registro indevido, como por exemplo somas totais.

# checar as 5 primeiras entradas
df_raw.head()
# checar as 5 últimas entradas
df_raw.tail()

Pode-se perceber que aparentemente os campos estão bem preenchidos e que não há problemas aparentes. Porém, vamos continuar a investigar mais profundamente.

A próxima etapa é saber o tamanho desse conjunto de dados.

# verificar tamanho do data frame
print('Dimensões do conjunto de dados')
print('-' * 30)
print('Total de registros:\t {}'.format(df_raw.shape[0]))
print('Total de atributos:\t {}'.format(df_raw.shape[1]))
'''
Dimensões do conjunto de dados
------------------------------
Total de registros: 20800
Total de atributos: 5
'''

Vamos olhar um pouco melhor para essas 5 variáveis. O objetivo será de compreender o tipo de variável que se encontram nesses atributos, checar valores ausentes, distribuição dos dados, outliers, etc.

# gerar informações do data frame
df_raw.info()
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20800 entries, 0 to 20799
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 id 20800 non-null int64
1 title 20242 non-null object
2 author 18843 non-null object
3 text 20761 non-null object
4 label 20800 non-null int64
dtypes: int64(2), object(3)
memory usage: 812.6+ KB
'''

Com a saída acima podemos verificar que há dados ausentes nas variáveis title, author e text. Isso exige tratamento específico.

Além disso, vemos que id e label são números inteiros ou int, enquanto title, author e text são do tipo string. Logo, os atributos estão em tipos corretos e não há necessidade de nenhuma alteração nesse ponto.

Conforme foi visto, temos variáveis com valores ausentes. Vamos verificar isso de forma mais detalhada imprimindo a quantidade (em porcentagem) de dados ausentes em casa atributo, organizados de forma descendente, ou seja, do maior para o menor.

# verificar quantidade de dados ausentes
print(((df_raw.isnull().sum() / df_raw.shape[0]) * 100).sort_values(ascending=False).round(2))
'''
author 9.41
title 2.68
text 0.19
id 0.00
label 0.00
dtype: float64
'''

Observa-se no topo da coluna temos o atributo author com maior quantidade de dados ausentes, representando 9.41% do conjunto total sem o devido preenchimento. Em seguida estão as variáveis title, com 2.68% e text com 0.19%.

Portanto, de forma geral há que se pontuar a necessidade de tratar os valores ausentes.

Vamos visualizar abaixo a quantidade de dados ausentes em cada atributo, para facilitar o entendimento da qualidade desse conjunto de dados.

# imprimir gráfico para verificar dados ausentes
msno.bar(df_raw, figsize=(10,4), fontsize=8);

Por fim, vou checar o balanceamento dos dados na variável-alvo label.

# representação da quantidade de 'label' em porcentagem
print('Total de (FALSE): {}'.format(df_raw.label.value_counts()[0]))
print('Total de (TRUE): {}'.format(df_raw.label.value_counts()[1]))
print('-' * 30)
print('O total de fake news representa {:.2f}% do conjunto de dados.'.format(((df_raw.label.value_counts()[1]) * 100) / df_raw.shape[0]))
'''
Total de (FALSE): 10387
Total de (TRUE): 10413
------------------------------
O total de fake news representa 50.06% do conjunto de dados.
'''

Como temos um total de 50.06% do conjunto dos dados representando notícias fake news sabemos que temos um conjunto de dados balanceado e que, por isso, não há necessidade de um tratamento específico.

7. Limpeza dos Dados

Como temos a presença de dados ausentes é essencial realizar o devido tratamento, pois eles podem causar problemas durante o treinamento do modelo. Por estarmos lidando com texto, irei excluir os registros com a presença de dados nulos.

Também se faz necessário excluir dados duplicados.

Além disso, vou criar uma nova variável df_clean para identificar o conjunto que possui dados tratados e também manter os dados originais intactos.

7.1. Dados Ausentes

# excluir registros com dados ausentes
df_clean = df_raw.dropna()

Vou checar se a exclusão foi bem sucedida:

# verificar quantidade de dados ausentes
print(((df_clean.isnull().sum() / df_clean.shape[0]) * 100).sort_values(ascending=False).round(2))
'''
id 0.0
title 0.0
author 0.0
text 0.0
label 0.0
dtype: float64
'''

Com isso, nosso conjunto de dados passa a ter a seguinte dimensionalidade:

# verificar tamanho do data frame
print('Dimensões do conjunto de dados')
print('-' * 30)
print('Total de registros:\t {}'.format(df_clean.shape[0]))
print('Total de atributos:\t {}'.format(df_clean.shape[1]))
'''
Dimensões do conjunto de dados
------------------------------
Total de registros: 18285
Total de atributos: 5
'''

7.2. Dados Duplicados

Vale verificar também a presença de dados duplicados. Para isso, primeiro vou olhar a quantidade de valores únicos em cada atributo.

# verificar quantidade de entradas únicas
print('Entradas únicas')
print('-' * 30)
print('Total de registros no dataset:\t {}'.format(df_clean.shape[0]))
print('Valores únicos em cada atributo:')
display(df_clean.nunique())
'''
Entradas únicas
------------------------------
Total de registros no dataset: 18285
Valores únicos em cada atributo:
id 18285
title 17931
author 3838
text 18017
label 2
dtype: int64
'''

Como temos 18285 registros, logo, os atributos title e text podem ter valores duplicados. Já a variável author é natural que tenha valores duplicados e esses serão mantidos.

Vamos excluir os dados duplicados e verificar se a ação foi bem sucedida:

# excluir dados duplicados do dataset
df_clean = df_clean.drop_duplicates()

# verificar resultado
display(df_clean.nunique())
'''
id 18285
title 17931
author 3838
text 18017
label 2
dtype: int64
'''

Vemos que não houve alterações, ou seja, nada foi excluído. Isso não significa que não dá dados duplicados. Com uma avaliação mais específica sabemos que, ainda que o atributo text contenha apenas uma parte da notícia, as chances dela ser exatamente igual a outra são nula, pois isso caracterizaria plágio.

Além disso, os títulos dos artigos, disponíveis na variável titles, conforme vimos, não são curtos e nem genéricos. Logo, também podemos descartar a possibilidades de existirem títulos iguais.

Vamos proceder à exclusão dos dados duplicados por atributo ao invés de olharmos para o conjunto inteiro, como feito no código anterior. Dessa forma iremos garantir a remoção dessas informações duplicadas que podem interferir na construção do algoritmo.

# excluir dados duplicados de 'title'
df_clean = df_clean[~df_clean.title.duplicated(keep='last')].reset_index(drop=True)

# excluir dados duplicados de 'text'
df_clean = df_clean[~df_clean.text.duplicated(keep='last')].reset_index(drop=True)

# verificar dados únicos para confirmar a exclusão
display(df_clean.nunique())
'''
id 17714
title 17714
author 3813
text 17714
label 2
dtype: int64
'''

Pela quantidade de registros dado por id podemos ver que os atributos title e text possuem a mesma quantidade, ou seja, 17714 entradas.

7.3. Exclusão do atributo id

Podemos também excluir o atributo id uma vez que ele não acrescenta dados relevantes para o nosso modelo.

# excluir atributo 'id'
df_clean.drop(['id'], axis=1, inplace=True)

# verificar as 5 primeiras entradas para checar as alterações feitas
df_clean.head()

8. Tratamento dos Dados

Nesse projeto estamos lidando com um problema de linguagem natural, ou seja, os dados estão em formato do tipo string. Por esse motivo é necessário conhecimento em NLP (Processamento de Linguagem Natural) para poder tratar os dados adequadamente e obter os resultados desejados.

Com os dados padronizados podemos realizar algumas análises complementares, como a WordCloud e análise dos dados categóricos, que nesse caso iremos olhar para a autoria das notícias.

8.1. Padronização das palavras

Para isso será criada a função clean_text para fazer a padronização dos textos das notícias.

# criar a função 'clean_text' que recebe o argumento 'text'
def clean_text(text):
# criar variável 'word' e dividir o texto por palavras e usar espaços em branco como delimitador
words = str(text).split()

# converter palavras em letras minúsculas, adicionando espaço ao final de cada palavra
words = [i.lower() + " " for i in words]

# unir palavras em uma única string, mas separadas por espaços
words = " ".join(words)

# remover pontuação das strings, usando o método st.punctutation como argumento
words = words.translate(words.maketrans('', '', st.punctuation))

return words

# aplicar a função 'clean_text' nos atributos: 'title', 'author' e 'text'
df_clean.title = df_clean.title.apply(clean_text)
df_clean.author = df_clean.author.apply(clean_text)
df_clean.text = df_clean.text.apply(clean_text)

# verificar as 5 primeiras entradas para checar as alterações feitas
df_clean.head()

8.2. WordCloud

Vamos criar uma nuvem de palavras com base nos títulos (titles) e no conteúdo (text) das notícias para termos uma ideia de quais são as palavras mais utilizadas.

8.2.1. WordCloud com os títulos

# criar variável com base no atributo 'title'
title = df_clean.title

# tratamento dos títulos
## concatenar palavras
all_title = ' '.join(s for s in title)

# criar variável com lista de stopwords
stopwords = set(STOPWORDS)

# instanciar a wordcloud
wordcloud = WordCloud(stopwords=stopwords,
background_color='black').generate(all_title)

# imprimir imagem com o resultado
fig, ax = plt.subplots(figsize=(10, 6))
ax.imshow(wordcloud, interpolation='bilinear')
ax.set_axis_off()

# imprirmir WordCloud
plt.imshow(wordcloud)

# salvar imagem gerada
wordcloud.to_file('news_titles_wordcloud.png')

Nota-se que as palavras em maior destaque são: new york, york times, breitbart, trump, donald trump, hillary, hillary clinton.

Também podemos verificar a quantidade de palavras que foram utilizadas para criação da nuvem acima:

# imprimir total de palavras utilizadas para criar nuvem
print('Total de palavras utilizadas: {}'.format(len(all_title)))
'''
Total de palavras utilizadas: 1540706
'''

8.2.2. WordCloud com conteúdo das notícias

# criar variável com base no atributo 'text'
text = df_clean.text

# tratamento dos textos
## concatenar palavras
all_text = ' '.join(s for s in text)

# criar variável com lista de stopwords
stopwords = set(STOPWORDS)

# instanciar a wordcloud
wordcloud = WordCloud(stopwords=stopwords,
background_color='black').generate(all_text)

# imprimir imagem com o resultado
fig, ax = plt.subplots(figsize=(10, 6))
ax.imshow(wordcloud, interpolation='bilinear')
ax.set_axis_off()

# imprirmir WordCloud
plt.imshow(wordcloud)

# salvar imagem gerada
wordcloud.to_file('news_titles_wordcloud.png')

Já aqui, percebe-se que as palavras em maior destaque são: said, one, people, mr trump, many, now.

Vamos verificar a quantidade de palavras que foram utilizadas para criação da nuvem acima:

# imprimir total de palavras utilizadas para criar nuvem
print('Total de palavras utilizadas: {}'.format(len(all_text)))
'''
Total de palavras utilizadas: 97524186
'''

8.3. Análise dos autores

Vamos olhar com mais detalhes a autoria das notícias do conjunto de dados em análise. Primeiro vamos verificar quantos autores temos ao todo, depois, vamos fazer um ranking com a quantidade de artigos publicados.

# verificar quantidade de entradas únicas
print('Esse conjunto tem {} autores distintos.'.format(len(df_clean.author.unique())))
'''
Esse conjunto tem 3804 autores distintos.
'''
# ranking dos 10 autores com maior quantidade de artigos publicados
best_authors = df_clean.author.value_counts()[:10]

print(best_authors)
'''
pam key 243
admin 216
jerome hudson 166
charlie spiering 141
john hayward 140
katherine rodriguez 124
warner todd huston 122
ian hanchett 119
breitbart news 118
daniel nussbaum 112
Name: author, dtype: int64
'''
# extrair nomes dos autores e a quantidade de artigos publicados
authors = best_authors.index
article_counts = best_authors.values

# criar gráfico de barras horizontais
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(authors, article_counts, color='#91aac9')
ax.set_xlabel('Quantidade de Artigos')
ax.set_ylabel('Autor')

# título
ax.text(-60, -1.5, 'Top 10 autores com maior quantidade de artigos publicados', fontsize=16, color='#004a8f',
fontweight='bold')

# quantidade de artigos por autor
## pam key
ax.text(230, 0.15, '243', fontsize=12, color='#004a8f')

## admin
ax.text(203, 1.15, '216', fontsize=12, color='#004a8f')

## jerome hudson
ax.text(153, 2.15, '166', fontsize=12, color='#004a8f')

## charlie spiering
ax.text(127, 3.15, '141', fontsize=12, color='#004a8f')

## john hayward
ax.text(127, 4.15, '140', fontsize=12, color='#004a8f')

## katherine rodriguez
ax.text(111, 5.15, '124', fontsize=12, color='#004a8f')

## warner todd huston
ax.text(109, 6.15, '122', fontsize=12, color='#004a8f')

## ian hanchett
ax.text(105, 7.15, '119', fontsize=12, color='#004a8f')

## breitbart news
ax.text(105, 8.15, '118', fontsize=12, color='#004a8f')

## daniel nussbaum
ax.text(100, 9.15, '112', fontsize=12, color='#004a8f')

plt.gca().invert_yaxis() # Inverte o eixo y para que o autor com mais artigos fique no topo
plt.show()

8.4. Divisão dos conjuntos: treino, validação e testes

A seguir, vamos criar um conjunto de validação partir do conjunto de treino, com uma proporção 75:25. Ou seja, 75% do conjunto continuará sendo para os dados de treinamento do modelo, enquanto 25% será utilizado para validar o modelo criado pelo algoritmo.

# dividir dados de treino, teste e validação
## stratify= df_clean.label (para dividir de forma que as classes tenham a mesma proporção)
## random_state para que o resultado seja replicável
train, df_temp = train_test_split(df_clean, test_size=0.25, stratify=df_clean.label, shuffle=True, random_state=123)
validation, test = train_test_split(df_temp, test_size=0.5,stratify=df_temp.label, shuffle=True, random_state=123)

# verificar tamanho dos conjuntos
print('O conjunto de treino tem \t{} registros'.format(train.shape[0]))
print('O conjunto de testes tem \t{} registros'.format(test.shape[0]))
print('O conjunto de validação tem \t{} registros'.format(validation.shape[0]))
'''
O conjunto de treino tem 13285 registros
O conjunto de testes tem 2215 registros
O conjunto de validação tem 2214 registros
'''

9. Métricas de Avaliação de Performance

Antes de iniciar a construção de um modelo de aprendizado de máquina, é importante definir como iremos avaliar o modelo. Isto é, decidir se ele está ou não performando como se espera.

Nesse estudo, irei utilizar 2 métricas: a acurácia e o recall e a precisão. Com maior ênfase na acurácia e no recall.

A acurácia é uma métrica mais simples pois nos mostra um panorama geral com o resultado da quantidade de previsões corretas do total de previsões feitas. Sua fórmula é dada por:

Contudo, a acurácia não é melhor métrica para se olhar nos casos em que os dados são desbalanceados, por exemplo: fraudes bancárias e detecção de doenças. Nesses casos, esperamos que a fraude e a pessoa doente seja exceção e não a regra. Isso naturalmente torna os dados desbalanceados. E esse pode ser o nosso caso aqui. Apesar de termos visto que o conjunto de dados está devidamente balanceado, na vida real esperamos nos deparar na maior parte das vezes com notícias confiáveis. Logo, fake news devem ser a exceção e não a regra.

Nesses casos, geralmente utilizamos o recall como métrica de avaliação e é por esse motivo que também iremos olhar para esses valores aqui nesse estudo.

Sua fórmula é dada por:

Por fim, usarei também a matriz de confusão para comparar os valores previstos com os valores reais, mostrando os erros e acertos do modelo. Sua saída tem 4 valores diferentes, dispostos da seguinte forma:

Sendo que, cada um desses valores corresponde a:

Acertos do Modelo

  • Verdadeiro Positivo: É fake news e o modelo classificou como fake news.
  • Verdadeiro Negativo: Não é fake news e o modelo classificou como não sendo fake news.

Erros do Modelo

  • Falso Positivo: Não é fake news, mas o modelo acusa como fake news.
  • Falso Negativo: É fake news, mas o modelo classifica como não sendo fake news.

10. O que são Redes Neurais?

De acordo com o SAS, as redes neurais “são sistemas de computação com nós interconectados que funcionam como os neurônios do cérebro humano. Usando algoritmos, elas podem reconhecer padrões escondidos e correlações em dados brutos, agrupá-los e classificá-los, e — com o tempo — aprender e melhorar continuamente” (Fonte: SAS).

Apesar de no início o uso da rede neural visava recriar o funcionamento do cérebro humano para a resolução de problemas, com o tempo se passou a usá-las para resolver problemas específicos como reconhecer fala, traduzir textos, jogar xadrez, auxiliar nos diagnósticos da área da saúde, visão computacional, para citar alguns. Com isso, foram se criando diversos tipos diferentes de redes neurais. Aqui, iremos utilizar o tipo convolucional (RNCs) que são ideais para problemas om imagens, como detecção de objetos e também no processamento da linguagem natural.

Vamos ver um exemplo. Um nó pode ser comparado a um neurônio. Quando há estímulo, correntes elétricas são emanadas para ativar outro nó ou neurônio. São as sinapses. E assim vai, percorrendo toda a rede ou, na nossa analogia, pelo nosso cérebro. Ao final desse estímulo é gerado uma resposta.

Aposto que você já bateu o minguinho do seu pé e vivenciou uma das sinapses mais rápidas que quando se deu conta já xingou até a terceira geração de uma família que você nem conhece. Ou, quando sentimos o cheiro daquela comida que só a nossa vó sabia fazer…

Da mesma forma é o que buscamos com nosso modelo: construir os neurônios, que são nossos nós, para que eles gerem ou não estímulo e ao final nos deem uma resposta. Mas isso tudo ocorre com a execução de cálculos matemáticos complexos, onde se tenta identificar padrões e então decidir se há razões suficientes para passar o estímulo adiante ou não.

Veja na imagem abaixo. É um modelo que de classificação, assim como esse que está sendo construído nesse projeto. Porém, aqui, o algoritmo deve responder se a imagem informada é de um gato ou de um cachorro. Damos a imagem de um cachorro, que é processada, inserida no modelo e gera uma saída que diz se é gato ou cachorro. Apesar da imagem ser uma exemplificação do que acontece, ela ajuda a entender os conceitos mencionados.

De uma forma mais completa, podemos dizer que os dados são inseridos na rede neural pela camada de entrada que, então, faz a comunicação entres as camadas ocultas. São nessas camadas que os dados são processados e ponderados. Ou seja, os dados são calculados com diferentes coeficientes, que geram resultados específicos de acordo com a entrada atribuída ao modelo. Ao final, soma-se tudo e o resultado passa pela função de ativação. De acordo com o resultado ele irá ou não ativar o nó seguinte, e assim por diante. Cada ativação ou não de um nó auxilia o modelo a determinar o resultado que será dado ao final.

11. Por que TensorFlow?

O TensorFlow é uma biblioteca de código aberto para aprendizado de máquina que é desenvolvida pelo Google Brain Team. Suas características como trabalhar com tensores, conseguir lidar com grandes volumes de dado e sua flexibilidade na construção e treinamento de modelos avançados de machine-learning, por meio de uma grande variedade de ferramentas, faz ela ser considerada uma das melhores bibliotecas para o desenvolvimento de modelos de aprendizado profundo, ou deep learning.

Entre as vantagens de utilizá-lo para a construção de redes neurais para a classificação binária de fake news, pode-se citar:

  • capacidade de processar grandes volumes de texto, que auxiliar na capacidade analítica e de aprendizado do conteúdo das notícias
  • construção de modelos complexos capazes de capturar nuances e padrões no texto, que ajuda na distinção de notícias falsas das verdadeiras
  • testar diferentes parâmetros e inserir n camadas para otimização e precisão do modelo

12. Desenvolvimento do Algoritmo utilizando TensorFlow

Para conseguirmos desenvolver o melhor algoritmo que atenda ao objetivo desse projeto, precisamos realizar um pré-processamento dos dados. Essa etapa é importante para simplificar o texto, tornando-o mais fácil de ser processado e treinado. Para isso utiliza-se a técnica de tokenização e de preenchimento conhecidos por tokenizer e padding, respectivamente.

A tokenização é uma etapa que irá reduzir o texto em unidades menores como grupo de palavras, palavras individuais, caracteres, pontuação ou símbolos. Essa unidade recebe o nome técnico de tokens. Veja na imagem abaixo uma representação de tokenização do texto em tokens, onde a unidade.

A partir disso, cada token recebe um valor numérico que o representa, e isso fica salvo em forma de dicionário, ou seja, possui as informações de chave, no caso o token e valor, a sua identificação numérica.

A etapa de padding, como seu próprio nome indica, ou seja, ‘preenchimento’, visa colocar um padrão de comprimento das sequências. Isto é, todas terão o mesmo tamanho para facilitar o treinamento do modelo. Para isso, é determinado um tamanho máximo e informamos qual valor deve ser inserido nos casos que a sequência seja menor do que o valor máximo informado.

# configurações
vocab_size = 10000 # quantidade máxima de palavras tokenizadas
trunc_type = 'post' # configurar truncate
pad_type = 'post' # configurar preenchimento
oov_tok = '<OOV>' # token padrão para tokenização fora do vocabulário

# tokenizar os dados de treino
tokenizer = Tokenizer(num_words = vocab_size,
oov_token = oov_tok)
tokenizer.fit_on_texts(train.text)

# guardar o index dos dados de treino em uma variável
word_index = tokenizer.word_index

# aplicar tokenizing e padding
training_sequences = tokenizer.texts_to_sequences(np.array(train.text))
training_padded = pad_sequences(training_sequences,
truncating = trunc_type,
padding = pad_type)

# definir comprimento máximo de preenchimento
max_length = len(training_padded[0])

# codificar a sequência dos dados de validação
validation_sequences = tokenizer.texts_to_sequences(np.array(validation.text))

# aplicar padding aos dados de validação
validation_padded = pad_sequences(validation_sequences,
padding = pad_type,
truncating = trunc_type,
maxlen = max_length)

# criar arrays com os inputs
x_train = np.copy(training_padded)
x_val = np.copy(validation_padded)
y_train = train['label'].values
y_val = validation['label'].values

Como pré-processamento realizado, vamos verificar algumas saídas para exemplificar o que foi feito nessa etapa.

Primeiro, vamos checar o processo de tokenização. Mencionamos acima que ele retorna um dicionário com chave:valor, onde a chave é o token e o valor é a informação numérica que corresponde a esse token. Vamos conferir essas informações com o código abaixo.

# verificar o tipo da variável 'word_index'
print('A variável "word_index" é do tipo: {}'.format(type(word_index)))

# visualizar os primeiros 10 pares chave-valor de 'word_index'
print('\nPrimeiras 10 entradas em "word_index":')
for word, index in list(word_index.items())[:10]:
print(word, index)
'''
A variável "word_index" é do tipo: <class 'dict'>

Primeiras 10 entradas em "word_index":
<OOV> 1
the 2
to 3
of 4
and 5
a 6
in 7
that 8
is 9
for 10
'''

Acima, confirmamos que a variável word_index onde salvamos o índice do processo de tokenização, de fato é um dicionário. Em seguida, imprimimos as primeiras entradas para verificação. Por exemplo, agora sabemos que for recebeu o valor 10, enquanto OOV, nosso token de preenchimento no processo do padding, recebeu o valor 1.

Por se tratar de um dicionário, podemos fazer a pesquisa por sua chave para descobrir o valor. Vamos pesquisar a palavra ‘york’ que apareceu na nuvem de palavras.

# encontrar valor da chave 'york'
value = word_index.get('york', 'palavra não encontrada')
print('O valor para "york" é: {}'.format(value))
'''
O valor para "york" é: 178
'''

Vamos verificar agora as variáveis training_sequences e training_padded, que são as sequências de tokens que foram criadas e depois, devidamente preenchidas, quando houve necessidade.

# verificar o tipo da variável 'training_sequences' e 'training_padded_
print('A variável "training_sequences" é do tipo: {}'.format(type(training_sequences)))
print('A variável "training_sequences" é do tipo: {}'.format(type(training_padded)))
'''
A variável "training_sequences" é do tipo: <class 'list'>
A variável "training_sequences" é do tipo: <class 'numpy.ndarray'>
'''

Como temos uma lista e um array, podemos imprimir exemplares para verificar sua estrutura:

# amostra de 'training_sequences'
training_sequences[:2]

Na saída acima solicitamos a impressão de 2 amostras, ou seja, temos 2 sequências. E elas estão em formato de lista, dentro de outra lista.

# amostra de 'training_padded'
training_padded[:3]
'''
array([[ 2, 1610, 1, ..., 0, 0, 0],
[1579, 1, 323, ..., 0, 0, 0],
[ 6, 3950, 273, ..., 0, 0, 0]], dtype=int32)
'''

Agora, podemos ver uma parte de 3 sequências, que foram devidamente preenchidas (e por isso os valores ‘0’ no final) para que todas tivessem o mesmo tamanho. E, por terem o mesmo tamanho que elas puderam ser transformadas em arrasys, uma vez também que isso facilita o desempenho da construção do algoritmo. Podemos imprimir sua dimensão:

# verificar dimensão da variável 'training_padded'
print('Dimensão da variável "training_padded":{}'.format(training_padded.shape))
'''
Dimensão da variável "training_padded":(13285, 24195)
'''

Para finalizar o pré-processamento, vamos checar também o tamanho final do nosso conjunto de treino:

# verificar o tamanho dos conjuntos de treino
print('Quantidade de Registros')
print('Dados de Treino: \t', len(x_train))
print('Variável-Alvo: \t\t', len(y_train))
'''
Quantidade de Registros
Dados de Treino: 13285
Variável-Alvo: 13285
'''

Agora, com os dados devidamente preparados para serem utilizados para a construção do algoritmo, vamos criá-lo!

O modelo foi desenvolvido especificamente para o problema em questão, ou seja, determinar se uma notícia é ou não confiável. Logo, temos um problema de classificação binária. Para isso utilizou-se de uma rede neural sequencial e empilhada por camadas.

Ao todo temos 4 camadas:

  1. Embedding
    Cria a camada de incorporação do modelo, onde determina-se o tamanho do vocabulário a ser utilizado, bem como o tamanho máximo da sequência. Com isso, busca-se dar valores próximos aos tokens que tiverem significado semelhantes.
  2. Conv1D
    Essa é a camada de convolução que aplica o aprendizado do modelo em lotes, com o objetivo de melhor identificar as características das sequências, onde foram configurados que se faça use de 16 filtros de tamanho 5, bem como que se use o modelo relu ( ReLU — Rectified Linear Unit)para ativação dos neurônios.
  3. GlobalMaxPool1D
    Cria a camada de pooling global, ou seja, para que seja retornado apenas o valor máximo de cada lote para com isso enfatizar ainda mais as características das sequências e reduzir a dimensão de saída.
  4. Dense
    Por fim, se tem a criação da camada densa, onde foi configurada com 1 unidade e ativada pela função sigmoide, que retorna valores booleanos 0 ou 1 para determinar a confiabilidade da notícia.

Cabe ressaltar que foi feito um RandomSearch para encontrarmos os melhores parâmetros para o nosso modelo em específico. Por se tratar de um processo que demanda tempo e força computacional, o mesmo foi realizado em um notebook à parte desse projeto para que aqui ficasse mais prático e direto nessa etapa.

A seguir, se tem a construção, o treino e a impressão das características do modelo final.

# construção do modelo de rede neural de classificação binária
model = tf.keras.Sequential([ # criar um modelo de rede neural sequencial empilhado por camadas
tf.keras.layers.Embedding(vocab_size, (155), input_length=max_length), # criar camada de incorporação para dar valores próximos aos tokens que forem semelhantes
tf.keras.layers.Conv1D(16, 5, activation='relu'), # criar camada de convolução para aprendizado em lotes para melhor identificar características
tf.keras.layers.GlobalMaxPooling1D(), # criar camada de pooling global para retornar somente o valor máximo de cada lote para enfatizar ainda mais as características
tf.keras.layers.Dense(1, activation='sigmoid') # criar camada densa com uma unidade e aplicar a ativação pela função sigmoidal
])

# compilar o modelo
model.compile(loss='binary_crossentropy', # definir a função de perda
optimizer=Adam(learning_rate=.001), # definir otimizador e taxa de aprendizado
metrics=['accuracy','Recall','Precision','FalseNegatives']) # definir métricas para avaliação do modelo durante o treinamento e testes
# treinar o modelo
## verbose=2 para mostrar uma mensagem por epoch
## epochs=4 define a quantidade de épocas que o modelo passará por todo o conjunto de treinamento
history = model.fit(x_train, y_train, verbose = 2, epochs = 4,
validation_data = (x_val, y_val), # dados de validação
callbacks=[tf.keras.callbacks.EarlyStopping('val_loss', patience=3)]) # interromper o treinamento quando não houver melhoria no 'val_loss' por 3 epochs consecutivos
'''
Epoch 1/4
416/416 - 105s - loss: 0.2203 - accuracy: 0.9029 - recall: 0.8282 - precision: 0.9304 - false_negatives: 948.0000 - val_loss: 0.0620 - val_accuracy: 0.9783 - val_recall: 0.9750 - val_precision: 0.9729 - val_false_negatives: 23.0000 - 105s/epoch - 252ms/step
Epoch 2/4
416/416 - 72s - loss: 0.0416 - accuracy: 0.9867 - recall: 0.9853 - precision: 0.9827 - false_negatives: 81.0000 - val_loss: 0.0444 - val_accuracy: 0.9833 - val_recall: 0.9717 - val_precision: 0.9878 - val_false_negatives: 26.0000 - 72s/epoch - 172ms/step
Epoch 3/4
416/416 - 60s - loss: 0.0124 - accuracy: 0.9980 - recall: 0.9976 - precision: 0.9976 - false_negatives: 13.0000 - val_loss: 0.0347 - val_accuracy: 0.9860 - val_recall: 0.9880 - val_precision: 0.9785 - val_false_negatives: 11.0000 - 60s/epoch - 144ms/step
Epoch 4/4
416/416 - 54s - loss: 0.0037 - accuracy: 0.9998 - recall: 1.0000 - precision: 0.9995 - false_negatives: 0.0000e+00 - val_loss: 0.0325 - val_accuracy: 0.9864 - val_recall: 0.9848 - val_precision: 0.9826 - val_false_negatives: 14.0000 - 54s/epoch - 131ms/step
'''

Na saída acima temos os valores das métricas de avaliação para cada epoch e o tempo que durou o treinamento. De forma geral se nota que a cada epoch há uma otimização dessas métricas. Isso mostra a evolução do modelo em buscar uma melhor performance.

Vejamos os resultados por época, por métrica de avaliação e por dados de treino e de validação:

Agora, vou imprimir o modelo construído.

# imprimir modelo
print(model.summary())
'''
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, 24195, 155) 1550000

conv1d (Conv1D) (None, 24191, 16) 12416

global_max_pooling1d (Glob (None, 16) 0
alMaxPooling1D)

dense (Dense) (None, 1) 17

=================================================================
Total params: 1562433 (5.96 MB)
Trainable params: 1562433 (5.96 MB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
None
'''

Os resultados são bons e podem ser alvo de uma análise mais detalhada para determinar se houve ou não sobreajuste. Para o momento, seguiremos com o objetivo mais educacional do projeto que é da construção e aplicação do modelo em si.

No código de impressão do modelo podemos verificar a arquitetura do modelo, que ele é do tipo sequencial, com 4 camadas (cada linha especifica uma das camadas construídas), a forma de saída de cada uma delas e a quantidade de parâmetros que cada uma delas possui. Isso é interessante para a compreensão de como os dados fluem pelo modelo e como os parâmetros estão distribuídos nas camadas.

A seguir, vou plotar as métricas de avaliação em gráficos, de acordo com a época dos dados. Assim, poderemos entender um pouco mais da evolução do modelo. Por se tratarem de 5 gráficos com estrutura semelhante, vou construir uma função para manter um padrão entre os gráficos e também para agilizar essa etapa.

# criar função para plotar os resultados
def plot_graphs(history, string):
plt.plot(history.history[string])
plt.plot(history.history['val_'+string])
plt.xlabel('Epochs')
plt.xticks(ticks=history.epoch)
plt.ylabel(string)
plt.legend([string, 'val_'+string])
plt.title(string.upper() + ' vs. Epochs')
plt.show()

Vamos avaliar os resultados, de acordo com a métrica de avaliação e seu desempenho de acordo com cada epoch.

# plotar gráfico da acurácia
plot_graphs(history, 'accuracy')

Podemos observar que o valor da acurácia, durante o primeiro treino, foi abaixo de 0.9, enquanto os dados de validação performaram muito melhor que isso. Já no epoch seguinte, houve uma melhora significativa dos dados de treino e após isso os conjuntos de treino e de validação andam mais próximos, com os dados de validação performando um pouco abaixo dos dados de treino.

# plotar gráfico da perda
plot_graphs(history, 'loss')

Aqui, quanto menor for o valor, melhor. Logo. vemos que o primeiro treino resultou em um loss mais alto do que nos dados de validação. Contudo, no epoch seguinte isso se ajusta, tal como ocorreu com a acurácia.

# plotar gráfico do recall
plot_graphs(history, 'recall')

Mais uma vez buscamos valores mais próximos de 1 para ser considerado um modelo com melhor performance. E, assim como aconteceu com as duas métricas, aqui observa-se o mesmo padrão: um primeiro epoch com dados ruins no treino, mas melhorando e convergindo na sequência.

# plotar gráfico de precisão
plot_graphs(history, 'precision')

Nessa métrica, notamos uma pequena diferença dos gráficos anteriores. Ela atinge seu melhor resultado nas épocas 2 e 3 e começa a decair de performance no último epoch.

# plotar gráfico de falsos negativos
plot_graphs(history, 'false_negatives')

Por último, nos falsos negativos, buscamos o menor valor possível. Ao olharmos pelos resultados dos conjuntos de dados notamos que tanto o de treino quanto o de validação melhoraram ao longo das épocas, com exceção dos epochs 1 para o 2 e do 3 para o 4 do conjunto de validação, contudo, dentro de uma margem saudável.

13. Previsõs no conjunto de testes

Assim como foi feito no conjunto de treino, é necessário passar os dados de testes pelo mesmo processamento, ou seja, o tokenizer e o padding. Então, será possível alimentar esses novos dados no modelo que criamos e ver qual é a real performance dele.

# tratar dados de teste
test_sequences = tokenizer.texts_to_sequences(np.array(test.text))
test_padded = pad_sequences(test_sequences, padding=pad_type, truncating=trunc_type, maxlen = max_length)

Agora, podemos fazer as previsões.

# realizar previsões
preds = np.round(model.predict(test_padded))
'''
70/70 [==============================] - 2s 24ms/step
'''

E vamos checar a quantidade e registros que tem no conjunto de testes e apenas confirmar o tamanho final do conjunto previsto pelo modelo. Logo, devemos obter os mesmos valores como resultado.

# checar quantidade de registros previstos
print('Quantidade de registros no conjunto de testes: {}'.format(test.shape[0]))
print('Quantidade de previsões realizadas pelo modelo: {}'.format(len(preds)))
'''
Quantidade de registros no conjunto de testes: 2215
Quantidade de previsões realizadas pelo modelo: 2215
'''

Com os dados de teste em mãos, podemos comparar os resultados obtidos do modelo, com os resultados reais. Dessa forma, conseguimos calcular as métricas de avaliação para verificar a performance do modelo. Abaixo, vou imprimir os valores da acurácia, do recall e da precisão, com o objetivo de mostrar que é possível obtê-las individualmente, se necessário.

# calcular métricas de avaliação
## acurácia
accuracy = accuracy_score(test['label'].values, preds)
print('Acurácia: \t{:.4f}'.format(accuracy))

## recall
recall = recall_score(test['label'].values, preds)
print('Recall: \t{:.4f}'.format(recall))

## precisão
precision = precision_score(test['label'].values, preds)
print('Precisão: \t{:.4f}'.format(precision))
'''
Acurácia: 0.9792
Recall: 0.9772
Precisão: 0.9729
'''

Já podemos ver acima que os valores indicam que o modelo tem uma boa performance. Agora, vou imprimir um relatório mais completo dessas métricas, e com apenas uma linha de código. Isso utilizando o método classification_report da biblioteca sklearn.

Ademais, vou plotar dois gráficos, ambos referentes à matriz de confusão dos dados de teste, porém, um com os valores normalizados e outra com os valores referentes ao conjunto de dados.

# salvar dados previstos em uma variável
y_pred = np.round(model.predict(test_padded))

# imprimir relatório de métricas de avaliação
print('Relatório de Métricas de Avaliação'.center(65) + ('\n') + ('-' * 65))
print(classification_report(test['label'], y_pred, digits=4) + ('\n') + ('-' * 15))

# plotar gráficos
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))

# matriz de confusão normalizada
skplt.metrics.plot_confusion_matrix(test['label'], y_pred, normalize=True,
title='Matriz de Confusão Normalizada',
text_fontsize='large', ax=ax[0])

# matriz de confusão
skplt.metrics.plot_confusion_matrix(test['label'], y_pred,
title='Matriz de Confusão',
text_fontsize='large', ax=ax[1])

plt.show()
'''
70/70 [==============================] - 1s 20ms/step
Relatório de Métricas de Avaliação
-----------------------------------------------------------------
precision recall f1-score support

0 0.9837 0.9807 0.9822 1295
1 0.9729 0.9772 0.9751 920

accuracy 0.9792 2215
macro avg 0.9783 0.9789 0.9786 2215
weighted avg 0.9793 0.9792 0.9792 2215

---------------
'''

Observamos acima que no relatório gerado temos mais informações das métricas de avaliação. E, com os gráficos, podemos ver os acertos do modelo pela matriz de confusão:

Acertos do Modelo

  • Verdadeiro Positivo: É fake news e o modelo classificou como fake news, nesse caso 899 notícias.
  • Verdadeiro Negativo: Não é fake news e o modelo classificou como não sendo fake news, aqui foram 1270 notícias.

Erros do Modelo

  • Falso Positivo: Não é fake news, mas o modelo acusa como fake news, nesse caso 25 notícias.
  • Falso Negativo: É fake news, mas o modelo classifica como não sendo fake news, aqui foram 21 notícias.

14. Comparação de Resultados: Validação x Teste

Nessa última seção desse estudo, vamos comparar o desempenho do modelo construído para compreendermos a sua performance diante de novos dados. Dessa forma teremos uma visão mais clara de como esse algoritmo se comportará com dados reais.

Primeiro precisamos realizar as previsões nos dados de treino e de validação. Em seguida, plotar o relatório de performance para cada um dos conjuntos (de treino, validação e testes). Por último, gerar as respectivas matrizes de confusão.

# salvar as previsões dados de treino e de validação em variáveis
y_pred_train = np.round(model.predict(x_train))
y_pred_val = np.round(model.predict(x_val))
'''
416/416 [==============================] - 9s 22ms/step
70/70 [==============================] - 1s 20ms/step
'''
# imprimir relatório de métricas de avaliação dos dados de treino
print('Relatório de Métricas de Avaliação - Dados de Treino'.center(65) + ('\n') + ('-' * 65))
print(classification_report(train['label'], y_pred_train, digits=4) + ('\n') + ('-' * 65))

# imprimir relatório de métricas de avaliação nos dados de validação
print('Relatório de Métricas de Avaliação - Dados de Validação'.center(65) + ('\n') + ('-' * 65))
print(classification_report(validation['label'], y_pred_val, digits=4) + ('\n') + ('-' * 65))

# imprimir relatório de métricas de avaliação nos dados de teste
print('Relatório de Métricas de Avaliação - Dados de Teste'.center(65) + ('\n') + ('-' * 65))
print(classification_report(test['label'], y_pred, digits=4) + ('\n') + ('-' * 65))
'''
Relatório de Métricas de Avaliação - Dados de Treino
-----------------------------------------------------------------
precision recall f1-score support

0 1.0000 0.9999 0.9999 7766
1 0.9998 1.0000 0.9999 5519

accuracy 0.9999 13285
macro avg 0.9999 0.9999 0.9999 13285
weighted avg 0.9999 0.9999 0.9999 13285

-----------------------------------------------------------------
Relatório de Métricas de Avaliação - Dados de Validação
-----------------------------------------------------------------
precision recall f1-score support

0 0.9892 0.9876 0.9884 1294
1 0.9826 0.9848 0.9837 920

accuracy 0.9864 2214
macro avg 0.9859 0.9862 0.9861 2214
weighted avg 0.9865 0.9864 0.9865 2214

-----------------------------------------------------------------
Relatório de Métricas de Avaliação - Dados de Teste
-----------------------------------------------------------------
precision recall f1-score support

0 0.9837 0.9807 0.9822 1295
1 0.9729 0.9772 0.9751 920

accuracy 0.9792 2215
macro avg 0.9783 0.9789 0.9786 2215
weighted avg 0.9793 0.9792 0.9792 2215

-----------------------------------------------------------------
'''

Acima, se olharmos apenas para a acurácia vemos que ela sai de quase 1 nos dados de treino, reduz a performance nos dados de validação e temos um valor um pouco mais baixo nos dados de teste.

Mais uma vez confirma-se o que foi visto nos relatórios de performance. Isto é, que nos dados de treino a performance é excelente, nos dados de validação as métricas caem um pouco e nos dados de teste caem ainda mais. Isso se deve ao fato de nos dados de treino o modelo já teve contato com essas informações antes. Ou seja, ele “colou” para dar os resultados. Isso já não ocorre nos dados de validação e nem no de teste. Por isso a performance é reduzida.

Entretanto, os dados de treino foram colocados aqui apenas para dar esse contexto. O que mais nos interessa é olhar para os dados de validação e de teste, pois uma distorção muito grande entre eles poderia indicar algum problema no modelo. O que não é o caso aqui, os dados encontram-se muito próximos. Nos dados de validação a acurácia foi de 0.9864, enquanto nos dados de teste o valor foi de 0.9792, o que nos indica que o modelo está lidando bem com dados que ele nunca teve acesso antes.

15. Deploy do Modelo

Para que o modelo possa ser colocado em produção é necessário salvá-lo.

# salvar o modelo como TensorFlow
fakenews_classifier = model.save('fakenews_cfl.tf')

# salvar os pesos do modelo
# classifier_fakenews_weights = model.save_weights('clFakeNews_weights.tf')

Abaixo, disponibilizei o código para fazer o carregamento do modelo. Lembrando que é necessário instalar o módulo load_model da biblioteca TensorFlow.

# carregar o modelo
# from tensorflow.keras.models import load_model
# fakenews_model = load_model('fakenews_clf.tf')

16. Conclusão

Esse estudo teve como objetivo criar um modelo supervisionado de machine-learning que classifique se uma notícia é ou não confiável. Para isso, foi realizada uma análise exploratória dos dados, onde se tomou conhecimento da estrutura e conteúdo do conjunto de dados. Nesse momento foi detectado que os dados da classe-alvo estavam balanceados e, ainda, a presença de dados ausentes.

A limpeza e tratamento dos dados envolveu a remoção dos dados ausentes, a detecção e remoção de dados duplicados, a remoção do atributo id e a padronização do texto.

Após isso, se iniciou a preparação dos dados para serem inseridos em modelos de aprendizado de máquina. Nessa etapa foi utilizado o tokenizer e o padding para tranformar o texto em tokens e criar sequência com o mesmo comprimento. Então, foi criado um modelo de rede neural sequencial, com o objetivo de realizar uma classificação binária, que é composto de 4 camadas: de incorporação do modelo (embedding), de convolução (Conv1D), de maxpool (GlobalMaxPool1D) e a densa (dense).

Com o algoritmo criado, se utilizou o conjunto de testes para avaliar a performance do modelo. Obteve-se uma acurácia de 0.9792 e um recall de 0.9772. Portanto, sendo capaz de distinguir notícias falsas das verdadeiras e podendo ser uma ferramenta útil para auxiliar no combate desse tipo de problema.

Saiba Mais

Esse estudo encontra-se disponível nas plataformas Google Colab e GitHub. Basta clicar nos links das imagens abaixo para ser redirecionado.

[LoffredoDS] Detecção de Fake News com Redes Neurais.ipynb
raffaloffredo/fake_news_detection_portuguese

--

--

Raffaela Loffredo

💡 Transforming data into impactful solutions | Co-founder @BellumGalaxy | Data Scientist | Blockchain Data Analyst