|
import os |
|
import subprocess |
|
import gradio as gr |
|
import wget |
|
from ftlangdetect import detect |
|
from cleantext import clean |
|
from keybert import KeyBERT |
|
from keyphrase_vectorizers import KeyphraseCountVectorizer |
|
from sklearn.feature_extraction.text import CountVectorizer |
|
from functools import partial |
|
from sentence_transformers import SentenceTransformer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model_id = ["paraphrase-multilingual-mpnet-base-v2", "sentence-transformers/LaBSE", "distiluse-base-multilingual-cased-v1"] |
|
model_name = ["SBERT multilingual", "LaBSE", "DistilBERT mltilingual (v1)"] |
|
|
|
|
|
|
|
|
|
kw_model_0 = KeyBERT(model=model_id[0]) |
|
|
|
|
|
kw_model = { |
|
0: kw_model_0, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import spacy |
|
|
|
spacy_pipeline = "pt_core_news_lg" |
|
|
|
os.system(f"python -m spacy download {spacy_pipeline}") |
|
|
|
|
|
|
|
nlp = spacy.load(spacy_pipeline) |
|
|
|
|
|
|
|
nlp.add_pipe('sentencizer') |
|
|
|
|
|
|
|
output = subprocess.run(["python", "stopwords.py"], capture_output=True, text=True) |
|
stop_words = list(eval(output.stdout)) |
|
|
|
|
|
|
|
|
|
|
|
pos_pattern = '<PROPN.*>*<N.*>*<ADJ.*>*' |
|
|
|
|
|
|
|
vectorizer_options = ["keyword", "3gramword", "nounfrase"] |
|
|
|
|
|
|
|
def get_kw_html(doc, top_n, diversity, vectorizer_option, model_id, pos_pattern): |
|
|
|
|
|
lowercase = False |
|
|
|
|
|
def get_vectorizer(vectorizer_option): |
|
|
|
|
|
if vectorizer_option == "keyword": |
|
vectorizer = CountVectorizer( |
|
ngram_range=(1, 1), |
|
stop_words=stop_words, |
|
lowercase=lowercase |
|
) |
|
|
|
|
|
elif vectorizer_option == "3gramword": |
|
vectorizer = CountVectorizer( |
|
ngram_range=(1, 3), |
|
|
|
lowercase=lowercase |
|
) |
|
|
|
|
|
elif vectorizer_option == "nounfrase": |
|
vectorizer = KeyphraseCountVectorizer( |
|
spacy_pipeline=spacy_pipeline, |
|
|
|
pos_pattern=pos_pattern, |
|
lowercase=lowercase |
|
) |
|
|
|
return vectorizer |
|
|
|
|
|
def get_lang(doc): |
|
doc = clean(doc, |
|
fix_unicode=True, |
|
to_ascii=False, |
|
lower=True, |
|
no_line_breaks=True, |
|
no_urls=True, |
|
no_emails=False, |
|
no_phone_numbers=False, |
|
no_numbers=False, |
|
no_digits=False, |
|
no_currency_symbols=False, |
|
no_punct=False, |
|
replace_with_punct="", |
|
replace_with_url="<URL>", |
|
replace_with_email="<EMAIL>", |
|
replace_with_phone_number="<PHONE>", |
|
replace_with_number="<NUMBER>", |
|
replace_with_digit="0", |
|
replace_with_currency_symbol="<CUR>", |
|
lang="pt" |
|
) |
|
res = detect(text=str(doc), low_memory=False) |
|
lang = res["lang"] |
|
score = res["score"] |
|
|
|
return lang, score |
|
|
|
def get_passages(doc): |
|
|
|
doc = doc.replace("\r\n", "\n").replace("\n", " ") |
|
doc = nlp(doc) |
|
|
|
paragraphs = [] |
|
for sent in doc.sents: |
|
if len(sent.text.strip()) > 0: |
|
paragraphs.append(sent.text.strip()) |
|
|
|
window_size = 2 |
|
passages = [] |
|
paragraphs = [paragraphs] |
|
for paragraph in paragraphs: |
|
for start_idx in range(0, len(paragraph), window_size): |
|
end_idx = min(start_idx+window_size, len(paragraph)) |
|
passages.append(" ".join(paragraph[start_idx:end_idx])) |
|
|
|
return passages |
|
|
|
|
|
def get_kw(doc, kw_model=kw_model[model_id], top_n=top_n, diversity=diversity, vectorizer=get_vectorizer(vectorizer_option)): |
|
|
|
keywords = kw_model.extract_keywords( |
|
doc, |
|
vectorizer = vectorizer, |
|
use_mmr = True, |
|
diversity = diversity, |
|
top_n = top_n, |
|
) |
|
|
|
return keywords |
|
|
|
def get_embeddings(doc, candidates, kw_model=kw_model[model_id]): |
|
|
|
doc_embeddings = kw_model.model.embed([doc]) |
|
word_embeddings = kw_model.model.embed(candidates) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return doc_embeddings, word_embeddings |
|
|
|
|
|
def get_html(keywords, doc=doc): |
|
|
|
|
|
list3 = [keyword[0] for keyword in keywords] |
|
list2 = [len(item.split()) for item in list3] |
|
list1 = list(range(len(list2))) |
|
list2, list1 = (list(t) for t in zip(*sorted(zip(list2, list1)))) |
|
list1 = list1[::-1] |
|
keywords_list = [list3[idx] for idx in list1] |
|
|
|
|
|
html_doc = doc |
|
for idx,keyword in enumerate(keywords_list): |
|
if sum([True if keyword in item else False for item in keywords_list[:idx]]) == 0: |
|
if keyword not in '<span style="color: black; background-color: yellow; padding:2px">' and keyword not in '</span>': |
|
html_doc = html_doc.replace(keyword, '<span style="color: black; background-color: yellow; padding:2px">' + keyword + '</span>') |
|
html_doc = '<p style="font-size:120%; line-height:120%">' + html_doc + '</p>' |
|
|
|
return html_doc |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(doc) == 0: |
|
|
|
|
|
keywords, keywords_list_json = [("",0.)], {"":0.} |
|
html_doc = '<p style="font-size:150%; line-height:120%"></p>' |
|
label = "O texto do documento não pode estar vazio. Recomece, por favor." |
|
|
|
else: |
|
|
|
|
|
lang, score = get_lang(doc) |
|
|
|
|
|
if lang!="pt" or score<0.9: |
|
|
|
|
|
keywords, keywords_list_json = [("",0.)], {"":0.} |
|
html_doc = '<p style="font-size:150%; line-height:120%"></p>' |
|
label = "O APP não tem certeza de que o texto do documento está em português. Recomece com um texto em português, por favor." |
|
|
|
|
|
else: |
|
|
|
|
|
passages= get_passages(doc) |
|
num_passages = len(passages) |
|
|
|
|
|
candidates_list = list() |
|
passages_embeddings = dict() |
|
candidates_embeddings_list = list() |
|
|
|
|
|
for i,passage in enumerate(passages): |
|
keywords = get_kw(passage) |
|
|
|
candidates = [keyword for keyword,prob in keywords] |
|
candidates_list.extend(candidates) |
|
|
|
passages_embeddings[i], candidates_embeddings = get_embeddings(passage, candidates) |
|
candidates_embeddings_list.extend(candidates_embeddings) |
|
|
|
if len(candidates_list) > 0: |
|
|
|
|
|
candidates_unique_list = list(set(candidates_list)) |
|
candidates_embeddings_unique_list = [candidates_embeddings_list[candidates_list.index(candidate)] for candidate in candidates_unique_list] |
|
num_candidates_unique = len(candidates_unique_list) |
|
|
|
|
|
|
|
from keybert._mmr import mmr |
|
from keybert._maxsum import max_sum_distance |
|
keywords_list = list() |
|
for i in range(num_passages): |
|
keywords_list.append(mmr(passages_embeddings[i], |
|
candidates_embeddings_unique_list, |
|
candidates_unique_list, |
|
num_candidates_unique, |
|
diversity = 0) |
|
) |
|
|
|
|
|
keywords_with_distance_list = dict() |
|
for i in range(num_passages): |
|
for keyword, prob in keywords_list[i]: |
|
if i == 0: keywords_with_distance_list[keyword] = prob |
|
else: keywords_with_distance_list[keyword] += prob |
|
|
|
|
|
keywords_list_sorted = {k: v for k, v in sorted(keywords_with_distance_list.items(), key=lambda item: item[1], reverse=True)} |
|
keywords_with_distance_list_sorted = [(keyword, round(keywords_with_distance_list[keyword]/num_passages, 4)) for keyword in keywords_list_sorted] |
|
keywords_with_distance_list_sorted = keywords_with_distance_list_sorted[:top_n] |
|
|
|
|
|
label = f"A palavra/frase chave com a maior similaridade é <span style='color: black; background-color: yellow; padding:2px'>{keywords_with_distance_list_sorted[0][0]}</span>." |
|
|
|
|
|
keywords_list_json = {keyword:prob for keyword, prob in keywords_with_distance_list_sorted} |
|
|
|
|
|
html_doc = get_html(keywords_with_distance_list_sorted) |
|
|
|
else: |
|
|
|
label, keywords_list_json, html_doc = "O APP não encontrou de palavras/frases chave no texto.", {"":0.}, "" |
|
|
|
return label, keywords_list_json, html_doc |
|
|
|
def get_kw_html_0(doc, top_n, diversity, vectorizer_option, model_id=0, pos_pattern=pos_pattern): |
|
return get_kw_html(doc, top_n, diversity, vectorizer_option, model_id, pos_pattern) |
|
|
|
|
|
title = "Extração das palavras/frases chave em português" |
|
|
|
description = '<p>(17/12/2022) Forneça seu próprio texto em português e o APP vai fazer a extração das palavras/frases chave com as maiores similaridades ao texto.</p>\ |
|
<p>Este aplicativo usa os modelos seguintes:\ |
|
<br />- <a href="https://huggingface.co/sentence-transformers/paraphrase-multilingual-mpnet-base-v2">SBERT multilingual</a>,\ |
|
<br />- <a href="https://maartengr.github.io/KeyBERT/index.html">KeyBERT</a> para calcular as similaridades entre as palavras/frases chave e o texto do documento.</p>' |
|
|
|
|
|
doc_original_0 = """ |
|
As contas de pelo menos seis jornalistas norte-americanos que cobrem tecnologia foram suspensas pelo Twitter na noite desta quinta-feira (15). Os profissionais escrevem sobre o tema para diversos veículos de comunicação dos Estados Unidos, como os jornais 'The New York Times' e 'Washington Post'. |
|
|
|
A rede social afirmou apenas que suspende contas que violam as regras, mas não deu mais detalhes sobre os bloqueios. |
|
|
|
Assim que comprou o Twitter, Elon Musk disse defender a liberdade de expressão, e reativou, inclusive, a conta do ex-presidente Donald Trump, suspensa desde o ataque ao Capitólio, em 2021. |
|
|
|
Os jornalistas que tiveram as contas bloqueadas questionaram o compromisso de Musk com a liberdade de expressão. |
|
|
|
Eles encararam o bloqueio como uma retaliação de Musk às críticas que o bilionário vem recebendo pela forma como está conduzindo a rede social: com demissões em massa e o desmonte de áreas, como o conselho de confiança e segurança da empresa. |
|
|
|
Metade dos funcionários do Twitter foram demitidos desde que ele assumiu o comando da empresa e outros mil pediram demissão. |
|
""" |
|
|
|
doc_original_1 = """ |
|
O bilionário Elon Musk restabeleceu neste sábado (17) as contas suspensas de jornalistas no Twitter. A súbita suspensão, um dia antes, provocou reações de entidades da sociedade e setores políticos, além de ameaças de sanção por parte da União Europeia. |
|
|
|
O empresário, que comprou a rede social em outubro, acusou os repórteres de compartilhar informações privadas sobre seu paradeiro, sem apresentar provas. |
|
|
|
Ainda na sexta (16), o empresário publicou uma enquete na rede social perguntando se as contas deveriam ser reativadas "agora" ou "em sete dias": 58,7% votaram pela retomada imediata; 41,3%, em sete dias. |
|
|
|
"O povo falou. Contas envolvidas em doxing (revelação intencional e pública de informações pessoais sem autorização) com minha localização terão sua suspensão suspensa agora", tuitou o empresário neste sábado. |
|
|
|
O g1 verificou a conta de alguns dos jornalistas suspensos, que pertencem a funcionários de veículos como a CNN, o The New York Times, o The Washington Post, e as páginas estavam ativas. |
|
|
|
As exceções, até a última atualização desta reportagem, eram a conta @ElonJet, que rastreava o paradeiro do jato do próprio Elon Musk, e o perfil do criador, Jack Sweeney. |
|
|
|
Ao acessar ambas as páginas, é possível visualizar a seguinte mensagem: "O Twitter suspende as contas que violam as Regras do Twitter". |
|
|
|
A plataforma também suspendeu a conta da rede social Mastodon, concorrente do Twitter. |
|
|
|
O Twitter Spaces também foi tirado do ar na sexta, após Musk ter sido questionado ao vivo sobre essas últimas decisões, informou a agência de notícias Bloomberg – o bilionário disse que o recurso voltou ao ar na tarde do mesmo dia. |
|
""" |
|
|
|
|
|
num_results = 5 |
|
diversity = 0.3 |
|
|
|
examples = [ |
|
[doc_original_0.strip(), num_results, diversity, vectorizer_options[0]], |
|
[doc_original_1.strip(), num_results, diversity, vectorizer_options[0]], |
|
|
|
] |
|
|
|
|
|
num_results = 5 |
|
diversity = 0.3 |
|
|
|
|
|
interface_0 = gr.Interface( |
|
fn=get_kw_html_0, |
|
inputs=[ |
|
gr.Textbox(lines=15, label="Texto do documento"), |
|
gr.Slider(1, 20, value=num_results, step=1., label=f"Número das palavras/frases chave a procurar (0: mínimo - 20: máximo - padrão: {num_results})"), |
|
gr.Slider(0, 1, value=diversity, step=0.1, label=f"Diversidade entre as palavras/frases chave encontradas (0: mínimo - 1: máximo - padrão: {diversity})"), |
|
gr.Radio(choices=vectorizer_options, value=vectorizer_options[0], label=f"Tipo de resultados (keyword: lista de palavras únicas - 3gramword: lista de 1 a 3 palavras - nounfrase: lista de frases nominais)"), |
|
], |
|
outputs=[ |
|
gr.HTML(label=f"{model_name[0]}"), |
|
gr.Label(show_label=False), |
|
gr.HTML(), |
|
] |
|
) |
|
|
|
|
|
demo = gr.Parallel( |
|
interface_0, |
|
title=title, |
|
description=description, |
|
examples=examples, |
|
allow_flagging="never" |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |