Você está na página 1de 10

Calculando Similaridade de Notı́cias Utilizando FastText e ELMo

Universidade Federal Fluminense


Instituto de Computação
Av. Gal. Milton Tavares de Souza, s/no
São Domingos - Niterói - RJ
CEP: 24210-346

Abstract pecı́fica de cálculo de similaridade entre textos, em notı́cias


O monitoramento de mı́dias adversas é uma grande
relacionadas à polı́tica e economia dos principais veı́culos
necessidade das empresas atualmente. Dada a enorme de comunicação, que contiverem termos relacionados à
quantidade de notı́cias veiculadas todos os dias, é pre- corrupção e que mencionem uma empresa especı́fica, com
ciso uma forma de agrupar notı́cias similares de modo o objetivo de agrupar as notı́cias por similaridade e, assim,
a facilitar o seu monitoramento. Vários algoritmos tem facilitar o trabalho de monitoramento de mı́dia adversa. A
sido desenvolvidos nos últimos anos com o objetivo empresa selecionada foi a Petróleo Brasileiro S.A (Petro-
de resolver várias tarefas de NLP. Neste trabalho serão bras) por conta do seu passado recente de envolvimento com
comparados dois algoritmos bem conhecidos: FastText corrupção e a grande quantidade de notı́cias disponı́veis na
e ELMo, para verificar o desempenho deles na tarefa de mı́dia sobre as fases da operação da Polı́cia Federal con-
similaridade entre textos. hecida como Lavajato que desmontou um intrigado es-
quema de corrupção envolvendo a Petrobras e outras em-
Introdução presas.
O monitoramento de mı́dias adversas é atualmente uma im- Além disso, propõe-se comparar os resultados ao se
portante necessidade das grandes empresas. Este tipo de avaliar a similaridade de notı́cias utilizando todo o seu
monitoramento envolve ler notı́cias de dezenas de veı́culos conteúdo ou somente o tı́tulo, já que em muitos casos o tı́tulo
de comunicação e avaliar se estas possuem conteúdo de in- resume o conteúdo da notı́cia. Ao final, pretende-se desco-
teresse. Isto constitui um grande desafio por conta da grande brir quais dos métodos de processamento de linguagem nat-
quantidade de notı́cias a serem avaliadas. Como muitas ural estudados em sala são os mais eficiente para este tipo de
dessas notı́cias possuem conteúdos similares, seria interes- dado.
sante se pudéssemos agrupar previamente todas as notı́cias
similares antes de avaliar sua relevância. Objetivos Especı́ficos
Uma vez agrupadas por similaridade, bastaria avaliar • Capturar notı́cias relacionadas economia e polı́tica nos
uma notı́cia representante do grupo em vez de todas elas. mais diversos veı́culos online de notı́cias;
Ao marcar uma notı́cia representante do grupo como rele-
• Calcular os embeddings dos textos das notı́cias utilizando
vante, todas as outras notı́cias também seriam selecionadas,
o FastText;
poupando assim uma grande quantidade de trabalho manual.
Mas avaliar a similaridade entre textos não é uma • Calcular os embeddings dos textos das notı́cias utilizando
tarefa trivial. Vários algoritmos têm sido desenvolvidos nos o ELMo;
últimos anos com o objetivo de resolver não só este como • Calcular a média dos embeddings de todas as palavras de
vários outros problema de processamento de linguagem nat- cada texto;
ural (NLP). Dentre tais algoritmos, podemos destacar dois:
FastText e ELMo que foram utilizados neste trabalho para • Calcular o cosseno dos embeddings resultantes da média
extrair representações de palavras (embeddings). entre quaisquer dois textos;
O objetivo final é comparar o desempenho do FastText e • Comparar os ı́ndices de similaridade produzidos pelo
do ELMo na tarefa de calcular similaridade entre dois textos. ELMo e o FastText;
Na verdade estamos avaliando a qualidade dos embeddings
gerados pelos modelos, uma vez que o cálculo de similar- Embeddings de Palavras
idade nada mais é do que o cálculo do cosseno entre dois O embedding de uma palavra é uma representação vetorial
vetores (embeddings). capaz de capturar o contexto de uma palavra em um doc-
umento, permitindo assim inferir a similaridade semântica
Objetivos Gerais e sintática entre dois textos, além de outras relações entre
Esta trabalho tem como objetivo avaliar e aplicar dois algo- elas. Vários algoritmos foram implementados tendo como
ritmos de processamento de linguagem natural, na tarefa es- princı́pio básico a geração de representações vetoriais de
palavras, sub-palavras e até caracteres. Dentre tais algorit- as notı́cias que passaram pelo teste das expressões regulares
mos podemos destacar dois: FastText e ELMo. foram selecionadas para o cálculo de similaridades, total-
izando um pouco mais de 9 mil notı́cias.
FastText Para evitar comparar todas as 9 mil entre si, gerando
O modelo FastText (Bojanowski et al. 2016), (Joulin et al. mais de 40 milhões de comparações, cada notı́cia foi com-
2016b) e (Joulin et al. 2016a) foi desenvolvido pelo Face- parada com as notı́cias que foram veiculadas em um espaço
book e possui como principal caracterı́stica, como o próprio de tempo de ±1 dia, para notı́cias veiculadas depois de
nome já deixa subentendido, a sua velocidade de treina- 01/01/2019. Isto diminuiu a quantidade de notı́cias para 227.
mento dos modelos. Para atingir grandes velocidades de pro- Embora seja uma quantidade de notı́cias muito menor que
cessamento frente a seus rivais, utiliza hierachical softmax o total capturado, servirá para avaliar o desempenho dos al-
e negative sampling, além de oferecer a possibilidade de goritmos e verificar a eficiência no cálculo de similaridade.
treinamento utilizando bow ou skip-gram. A ideia inicial era anotar notı́cias similares baseando-se
Para este trabalho foi utilizado um modelo FastText pré- no resultado de busca do motor de busca Google. Foi obser-
treinado no corpus da Wikipedia em Português. vado, no entanto, que a relevância dos resultados da busca é
muito dependente da query de busca digitada pelo usuário,
ELMo o que introduziria um viés indesejado. Essa ideia foi aban-
donada e decidiu-se por anotar manualmente os 100 pares de
O modelo ELMo (Peters et al. 2018) foi desenvolvido notı́cias mais similares e os 100 pares menos similares dos
pela AllenNLP e, diferentemente do FastText, produz dois modelos.
representações de palavras profundamente contextualizadas, Para comparar a precisão dos dois modelos no cálculo de
ou seja, consegue capturar mais de um contexto/sentido de similaridade de textos, foram anotados 100 pares de notı́cias
uma palavra. Em vez de gerar diretamente os embeddings de como similares ou não similares. O desempenho dos algorit-
palavras, como é o caso do FastText, o ELMo produz os em- mos foi medido em comparação com tais notı́cias anotadas.
beddings em tempo de execução, ou seja, geramos os vetores
a medida que sabemos qual é o seu contexto. As vantagens
são óbvias: com o ELMo conseguimos extrair mais de um
Problemas Enfrentados
significado de uma palavra dependendo do seu contexto. Um dos problemas enfrentados foi o alto custo computa-
Para este trabalho foi utilizado um modelo ELMo pré- cional para a geração da média dos mbeddings dos textos.
treinado no corpus da Wikipedia em Português e a terceira No caso do ELMo o problema se agravou por conta da ne-
camada de embedding. cessidade de calcular o embedding do texto antes de calcular
a média.
Metodologia Embora o crawler tenha capturado mais de 200 mil
notı́cias, o gargalo computacional tornou proibitivo realizar
O conjunto de dados de notı́cias que foi utilizado no trabalho o cálculo dos embeddings de todas estas notı́cias. Por isso,
foi formado através do crawler dos principais sites e blogs decidiu-se escolher apenas o grupo de notı́cias que perten-
de notı́cias relacionados à economia e polı́tica. O framework ciam aos blogs do portal G1 que tratavam de notı́cias rela-
Scrapy foi utilizado para este fim. Ao final do processo de cionadas à polı́tica, economia e da operação lava jato, que
crawler, foi obtido um dataset contendo mais de 200 mil são os tópicos alvo deste estudo.
notı́cias com as seguintes features: url, data, tı́tulo e texto. A princı́pio pensou-se em realizar a anotação automática
Uma vez de posse dos textos das notı́cias e dos mode- das notı́cias relevantes baseando-se nos resultados de busca
los pré-treinados, foram gerados vetores para cada uma das do motor de pesquisas Google. Entretanto, os resultados de-
palavras utilizando tanto o FastText como o ELMo. pendiam muito do termo de busca utilizado, o que acabava
A fim de capturar o sentido de todo o texto, foi gerado um por retornar notı́cias que continham as palavras do termo de
vetor resultante da média de todos os vetores das palavras de busca, mas não necessariamente eram similares. Por isso,
um texto. decidiu-se por anotar manualmente 100 pares de notı́cias
Para calcular a similaridade entre dois textos, foi uti- dentre as mais similares e 100 dentre as menos similares
lizada a similaridade do cosseno (Huang 2008) que consiste apontadas por ambos os modelos.
em tratar a similaridade entre dois textos como o valor do
cosseno entre os embeddings (vetores) dos textos. Resultados
Por conta do grande esforço computacional para o cálculo
dos embeddings e geração do vetor de média, forma escolhi- Foram anotados os 100 pares de notı́cias mais similares e
dos 8 blogs do portal G11 como um subconjunto dos dados os 100 menos similares para os dois algoritmos. A porcent-
para a análise, totalizando um pouco mais de 20 mil notı́cias. agem de acertos é mostrada logo abaixo. Podemos notar que
Para diminuir mais ainda a quantidade de notı́cias e focar a quantidade de acertos dois dois algoritmos é muito pare-
apenas no escopo da análise, foram criadas expressões reg- cida.
ulares com termos de fraude e corrupção, uma lista das em- ELMo:
presas do grupo Petrobras e uma lista com todas as empresas Similares: 62%
denunciadas ou condenadas na operação lava jato. Somente Não Similares: 98%
FastText:
1
https://g1.globo.com Similares: 63%
Não Similres: 94% Uma outra tarefa seria identificar a nota de corte para a
Vale ressaltar, no entanto, que as similaridades calculadas similaridade, ou seja, a partir de qual valor as notı́cias pas-
pelo FastText tiveram uma menor variação entre os valores sam a ser diferentes.
mı́nimos e máximos que o ELMo. Os valores calculados Por fim, após o cálculo de similaridade entre os documen-
pelo FastText variaram de 0.91 a 0.99, enquanto os valores tos seria interessante utilizar um algoritmo de agrupamento
do ELMo variaram de 0.55 a 0.98. como por exemplo o K-Means (Strehl, Ghosh, and Mooney
Podemos inferir disso que o ELMo faz um cálculo mais 2000) para tentar agrupar automaticamente as notı́cias simi-
preciso dos embeddings do que o FastText, uma vez que lares baseando-se nos seus ı́ndices de similaridade.
atribui valores mais baixos para menores similaridades e
vice-versa. References
É importante frisar também, que a classificação manual Bojanowski, P.; Grave, E.; Joulin, A.; and Mikolov, T. 2016.
das 100 maiores e das 100 menores similaridades foi feita Enriching word vectors with subword information. arXiv
em relação ao tı́tulo da notı́cia, ou seja, pode ocorrer casos preprint arXiv:1607.04606.
onde o conteúdo da notı́cia cubra muito mais matéria do que
Huang, A. 2008. Similarity measures for text document
o conteúdo descrito em seu tı́tulo.
clustering. In Proceedings of the sixth new zealand com-
Outro ponto de destaque é que dada a própria carac-
puter science research student conference (NZCSRSC2008),
terı́stica da geração dos embeddings, notı́cias que tenham
Christchurch, New Zealand, volume 4, 9–56.
estruturas similares, mas que tratam de pessoas ou empresas
distintas possuem embeddings muito próximos, causando Joulin, A.; Grave, E.; Bojanowski, P.; Douze, M.; Jégou, H.;
uma similaridade alta. and Mikolov, T. 2016a. Fasttext.zip: Compressing text clas-
Uma possı́vel solução desse problema talvez fosse atribuir sification models. arXiv preprint arXiv:1612.03651.
pesos de similaridades para notı́cias que tratassem das mes- Joulin, A.; Grave, E.; Bojanowski, P.; and Mikolov, T.
mas pessoas ou empresas. Para isso, seria preciso executar 2016b. Bag of tricks for efficient text classification. arXiv
outra tarefa de NLP, conhecida como Named Entity Recon- preprint arXiv:1607.01759.
gition (NER). Peters, M. E.; Neumann, M.; Iyyer, M.; Gardner, M.; Clark,
C.; Lee, K.; and Zettlemoyer, L. 2018. Deep contextualized
Conclusão word representations. In Proc. of NAACL.
O uso de representações de palavras (embeddings) para o Strehl, A.; Ghosh, J.; and Mooney, R. 2000. Impact of simi-
cálculo de similaridade entre textos demonstrou bons re- larity measures on web-page clustering. In Workshop on ar-
sultados, embora tenha apresentado algumas dificuldades, tificial intelligence for web search (AAAI 2000), volume 58,
como o alto custo computacional para cálculo da média dos 64.
embeddings de um texto e para cálculo de similaridade entre
pares de textos.
O ELMo demonstrou uma amplitude maior de valores de
similaridade, demonstrando ser mais sensı́vel à notı́cias não
similares que o FastText.
Notı́cias que possuem mesma estrutura, mas falam de pes-
soas ou empresas distintas tendem ter notas altas de sim-
ilaridade. No entanto, para o contexto de monitoramento
de mı́dias adversas, provavelmente não seriam classificadas
como similares.
Tal tipo de problema poderia ser amenizado através da
extração das entidades existentes nas notı́cias.
Ficou demonstrado neste trabalho que os algoritmos Fast-
Text e ELMo foram capazes de calcular com eficiência a
similaridade entre dois texto, sendo que o ELMo demon-
strou mais sensibilidade nesta tarefa.
O uso de algoritmos como ELMo e FastText podem, por-
tanto, ser de grande ajuda para o trabalho de monitoramento
de mı́dias adversas por auxiliar no agrupamento de notı́cias
similares.

Trabalho Futuros
Uma das tarefas futuras seria a aplicação da tarefa de NER
nas notı́cias para extrair as entidades (pessoas e empresas)
mencionadas nos textos. Desta forma, seria possı́vel diferen-
ciar notı́cias que tenham altos ı́ndices de similaridade, mas
que falam de pessoas ou empresas diferentes.
Apêndice A - Crawler das Notı́cias

# -*- coding: utf-8 -*-


from abc import ABCMeta, abstractmethod
import scrapy
import re
from crawler.items import Noticia, Pdf
from os import path

#from apdutils import RegEx as reg

class ApdSpider(scrapy.Spider, metaclass=ABCMeta):


noticia: Noticia
crawled_count = 0
pdf_count = 0
NUM_CRAWLED = 10

@abstractmethod
def parse(self, response):
pass

@abstractmethod
def parse_details(self, response):
pass

def parse_noticia(self, response):


# URL
page_url = response.request.url
noticia = response.meta.get('noticia')
root_elements = response.meta.get('root_elements')
if (noticia):
# Font
noticia['veiculo'] = self.name
# Title
title = response.xpath('//title/text()').get()
formated_title = self.removeWhiteSpaces(title)
# Text
text = ''
if root_elements == None:
for t in response.xpath(
'//p//text() | //blockquote//text() | //h1//text() | //h2//text() | //h3//
text() | //h4//text() | //h5//text() | //h6//text() | //div//text()').extract():
if (t):
text = text + t + ' '
else:
element = response
for e in root_elements:
element = element.css(e)

for t in element.xpath(
'.//p//text() | .//blockquote//text() | .//h1//text() | .//h2//text() | .//h3//
text() | .//h4//text() | .//h5//text() | .//h6//text() | .//div//text()').extract():
if (t):
text = text + t + ' '

formated_text = self.removeWhiteSpaces(text)

if noticia['autor']!=None:
noticia['autor'] = self.removeWhiteSpaces(noticia['autor'])

if noticia['descricao']!=None:
noticia['descricao'] = self.removeWhiteSpaces(noticia['descricao'])

if noticia['texto'] != None:
noticia['texto'] = self.removeWhiteSpaces(noticia['texto'])
else:
# If text has not already previously filled
noticia['texto']=formated_text

if noticia['titulo'] != None:
noticia['titulo'] = self.removeWhiteSpaces(noticia['titulo'])
else:
# If title has not already previously filled
noticia['titulo'] = formated_title

noticia['url']=page_url
Apêndice A - Crawler das Notı́cias

#p1 = reg()
#noticia['reg_petro'] = p1.match_texto_petrobras(formated_text) != None
#noticia['reg_empresas_lavajato'] = p1.match_texto_empresas_lavajato(formated_text) != None
#noticia['reg_corrupcao'] = p1.match_texto_corrupcao(formated_text) != None
#noticia['reg_dinheiro'] = p1.match_texto_reg_dinheiro(formated_text) != None

self.crawled_count += 1
self.logger.info('News Count %s : %s' % (self.crawled_count, page_url))

# Looking for pdf


for a in response.xpath('//a[@href]/@href'):
link = a.extract()
pdf_url = response.urljoin(link)
if (link.endswith('.pdf') or link.endswith('/at_download/file')) and (not
self.urlExists(pdf_url)):

pdf = Pdf(veiculo=self.name,
url_mae=page_url,
url=pdf_url)
self.pdf_count += 1
self.logger.info('PDF Count %s : %s' % (self.pdf_count, pdf_url))
yield scrapy.Request(pdf_url, callback=self.save_pdf, meta={'pdf':pdf})
else:
if (self.domain in page_url) and (not self.urlExists(page_url)):
yield scrapy.Request(page_url, callback=self.parse_noticia)
if (not self.urlExists(page_url)):
yield noticia

def removeWhiteSpaces(self, string):


return re.sub('(\|+)|(\s+)', ' ', string)

def urlExists(self, url):


return (url in set(self.df_crawled['url']))

def save_pdf(self, response):


if response.headers.get('content-type') == b"application/pdf":
pdf = response.meta.get('pdf')

if response.url.endswith('.pdf'):
file = self.project_path + 'pdf/' + self.name + '/' + re.sub('[^\\w|\\s]', '_',
response.url).replace('_pdf',

'.pdf')
else:
file = self.project_path + 'pdf/' + self.name + '/' + re.sub('[^\\w|\\s]', '_',
response.url) + '.pdf'
try:
if (not path.isfile(file)):
with open(file, 'wb') as f:
f.write(response.body)
except Exception as ex:
self.logger.error(ex)
self.df_crawled = self.df_crawled.append({'url': response.url}, ignore_index=True)
if (not self.urlExists(response.url)):
yield pdf
Apêndice A - Crawler das Notı́cias

# -*- coding: utf-8 -*-

import scrapy

class Noticia(scrapy.Item):
data = scrapy.Field()
veiculo = scrapy.Field()
descricao = scrapy.Field()
texto = scrapy.Field()
autor = scrapy.Field()
titulo = scrapy.Field()
url = scrapy.Field()
#reg_petro = scrapy.Field()
#reg_empresas_lavajato = scrapy.Field()
#reg_corrupcao = scrapy.Field()
#reg_dinheiro = scrapy.Field()

class Pdf(scrapy.Item):
veiculo = scrapy.Field()
url_mae = scrapy.Field()
url = scrapy.Field()
Apêndice A - Crawler das Notı́cias

# -*- coding: utf-8 -*-


import scrapy
import json
from crawler.apdspider import ApdSpider
from crawler.items import Noticia
import crawler.settings
from dateutil import parser

class G1Spider(ApdSpider):
name = 'g1'
sites = [{'nome': 'Blog da Julia Duailibi',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/7f8f0359-
e9d7-42e2-add0-b41d82b138f8/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'Blog da Cristiana Lobo',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
21a52bf6-286b-4094-9384-5beffa8806e6/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'Blog do Camarotti',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
edb56541-7a67-4e0e-85b9-bfa305d3d11a/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'Blog do Matheus Leitao',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
a824ae77-9930-44ca-b665-37f118648436/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'Operacao Lava-Jato',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
f237164d-7855-4714-b2d8-bf535da06bf3/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'Blog do Valdo Cruz',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
6cdf9cc8-73e8-4d4b-95f8-2f5bf3688f9a/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'Blog da Andreia Sadi',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
384d9047-117e-4d94-b225-1849e4b6201f/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False},
{'nome': 'G1 Politica',
'base_url': 'https://falkor-cda.bastian.globo.com/tenants/g1/instances/
1b9deafa-9519-48a2-af13-5db036018bad/posts/page/%s',
'count': 1,
'crawled_count': 0,
'done': False}]
domain = 'g1.globo.com/politica/'
'''
custom_settings = {
'LOG_FILE': crawler.settings.PROJECT_ROOT + "/log/" + name + "/" + name + "_log.txt"
}
'''

def start_requests(self):
for site in self.sites:
yield scrapy.Request(site['base_url'] % site['count'], meta={'site': site},
callback=self.parse)

def parse(self, response):


"""
Propriedades que devem ser extraídas pelos spider individualmente:
Apêndice A - Crawler das Notı́cias

1. descricao
2. data
3. autor
"""
# Lê o JSON do corpo da resposta
jsonResponse = json.loads(response.body_as_unicode())
items = jsonResponse['items']
site = response.meta.get('site')
if len(items) > 0:
for item in items:
url = item['content']['url']
if (url != None) and (self.domain in url):
if (not self.urlExists(url)):
yield scrapy.Request(url, callback=self.parse_details, meta={'item': item})
else:
site['crawled_count'] += 1
site['done'] = site['crawled_count'] > self.NUM_CRAWLED
else:
site['done'] = True
site['count'] += 1
if not self.allDone() and (not site['done']):
yield scrapy.Request(site['base_url'] % site['count'], meta={'site': site},
callback=self.parse)

def parse_details(self, response):


item = response.meta.get('item')
description = item['content']['summary']
date = item['publication']
if (date):
date = parser.parse(date)
date = str(date.day) + '/' + str(date.month) + '/' + str(date.year)
author = response.xpath("//span[contains(@itemprop,'author')]/meta/@content").get()
noticia = Noticia(descricao=description, data=date, autor=author)
# Chamada do método "parse_noticia" da classe mãe para preencher o restante das informações
yield scrapy.Request(response.request.url, meta={'noticia': noticia},
callback=super(G1Spider, self).parse_noticia, dont_filter=True)

def allDone(self):
for site in self.sites:
if not site['done']:
return False
return True
Apêndice B - Código para Embeddings e Similaridade

import pandas as pd
import gensim
import numpy as np
import re
from dateutil import parser
from datetime import datetime, timedelta
from allennlp.commands.elmo import ElmoEmbedder
from sklearn.metrics.pairwise import cosine_similarity

# Carregando os dados
df_g1 = pd.read_csv('data/g1_match.csv', sep='|', index_col=0)
df_g1_sim = pd.DataFrame(columns = ['index1', 'index2','url1', 'url2', 'data1', 'data2', 'fasttext',
'elmo'])

# Aquivos de opções e pesos do ELMo treinado na Wikipedia em Poruguês


elmo_options_file = 'elmo/elmo_pt_options.json'
elmo_weights_file = 'elmo/elmo_pt_weights.hdf5'

# Carregando modelo do ELMo


elmo_model = ElmoEmbedder(
options_file = elmo_options_file,
weight_file = elmo_weights_file
)

# Carregando modelo do FastText


fasttext_model = gensim.models.KeyedVectors.load_word2vec_format('fasttext/cc.pt.300.vec',
binary=False)

# Função que retorna o embedding FastText representando a média dos embeddings de um texto
def calculate_fasttext_mean_vector(model, words):
words = [word for word in words if word in model.vocab]
if len(words) >= 1:
return np.mean(model[words], axis=0)
else:
return []

# Função que retorna o embedding ELMo representando a média dos embeddings de um texto
def calculate_elmo_mean_vector(model, document):
if len(document) >= 1:
embeddings = model.embed_sentence(document)
mean = np.mean(embeddings[2],axis=0)
return mean

# Vetor que conterá os embeddings de todos os textos


fasttext_vectors = []
elmo_vectors = []

#documents = []
for index1, row1 in df_g1.iterrows():
date = row1['data']
if (date):
date = parser.parse(date)
if date >= limit_date:
documents.append(str(row1['texto']).split())

df_g1_petro = df_g1[df_g1['match_empresas_grupo']==True]
df_g1_petro = df_g1_petro[df_g1_petro['match_fraude']==True]

df_g1_lavajato = df_g1[df_g1['match_empresas_lavajato']==True]
df_g1_lavajato = df_g1_lavajato[df_g1_lavajato['match_fraude']==True]

df_filtrado = pd.concat([df_g1_petro, df_g1_lavajato])

df_filtrado['data'] = pd.to_datetime(df_filtrado['data'])
mask = (df_filtrado['data']>= limit_date)
df_filtrado = df_filtrado.loc[mask]
df_filtrado.reset_index(inplace=True)

cnt = 1
total = len(df_filtrado['texto'])

# Percorrendo os textos e extraindo os embeddings


print('Gerando Embeddings')
for text in df_filtrado['texto']:
Apêndice B - Código para Embeddings e Similaridade

text = re.sub(r'[^\w\s]','', text).split()


fasttext_vectors.append(calculate_fasttext_mean_vector(fasttext_model.wv, text))
elmo_vectors.append(calculate_elmo_mean_vector(elmo_model, text))
print(round(cnt/total*100,2), '%')
cnt += 1

# Salva os embeddings calculados para serem utilizados posteriormente


print('Salvando Embeddings')
np.save('elmo/vectors.npy', elmo_vectors)
np.save('fasttext/vectors.npy', fasttext_vectors)

# Calcula e salva as matrizes de similaridade


print('Gerando as matrizes de similaridade')
fasttext_similarity = cosine_similarity(fasttext_vectors, fasttext_vectors)
elmo_similarity = cosine_similarity(elmo_vectors, elmo_vectors)
np.save('elmo/elmo_matrix.npy', elmo_similarity)
np.save('fasttext/fasttext_matrix.npy', fasttext_similarity)

# Calculando as similaridades
print('Calculando as similaridades')
i = 1
limit_date = parser.parse('01/01/2019')
for index1, row1 in df_filtrado.iterrows():
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
print(str(i))
for index2, row2 in df_filtrado.iterrows():
url1 = row1['url']
url2 = row2['url']
data1 = row1['data']
data2 = row2['data']
data_ant = row1['data'] - timedelta(days=1)
data_post = row1['data'] + timedelta(days=1)
if (url1 != url2) and (row2['data'] >= data_ant) and (row2['data'] <= data_post):
doc1 = row1['texto']
doc2 = row2['texto']
if doc1 and doc2:
fasttext = fasttext_similarity[index1][index2]
elmo = elmo_similarity[index1][index2]
df_g1_sim = df_g1_sim.append({'index1':index1, 'index2':index2, 'url1': url1, 'url2':
url2, 'data1' : data1, 'data2' : data2, 'fasttext': fasttext, 'elmo':elmo}, ignore_index=True)
i += 1

# Salvando os resultados
print('Salvando os resultados')
df_filtrado.to_csv('result/g1_filtered.csv')
df_g1_sim.to_csv('result/g1_result.csv')