Resume meu texto?
Tags: ia, machinelearning, nlp
Published: 2022-07-26 19:26:29

Olá pessoas incríveis da internet, tudo bem com vocês? Trago hoje mais um artigo explorando o mundo de inteligência artificial, onde vamos aprender a sumarizar textos com a ajuda de NLP (Natural Language Processing).
Para começarmos, precisamos entender que sumarização é basicamente a construção de um resumo, onde dado um texto, se produz outro texto que condense o sentido do texto original, focando apenas nos pontos principais.

Atualmente, usamos textos sumarizados para facilitar a análise dos dados que recebemos, por exemplo, uma grande organização usa desse processo para trazer as partes importantes de feedbacks para gerar insights, outro exemplo é quando precisamos resumir partes importantes de um determinado texto para apresentar em links de matérias, blogs.

Apesar de parecer trivial, é uma atividade bastante comum que geralmente é feita de forma manual, isso torna o processo mais tedioso e lento, então como podemos resolver isso? Com os avanços tecnológicos, hoje conseguimos fazer isso de forma automática com a ajuda de Machine Learning, especificadamente, usando processamento de linguagem natural.

Existem duas abordagens que são usadas para a sumarização: o resumo abstrativo que consiste na geração de novas frases a partir do texto original e a sumarização extrativa, onde identificamos frases importantes no texto e extraímos gerando o resumo, está última será a que vamos usar.

Vamos construir uma API, usando [FastAPI](https://fastapi.tiangolo.com/), onde teremos como entrada, tanto texto como url, vamos começar dividindo o artigo em partes. A primeira parte vamos tratar os dados que recebemos, na segunda parte vamos fazer a parte de sumarização.

##Colocando a mão na massa
###Lidando com os dados
* Vamos iniciar instalando o framework do fastAPI

```
pip install fastapi

```
* Vamos precisar também de um servidor ASGI

```
pip install uvicorn

```
Nosso projeto vai ter a seguinte estrutura:

```
text_summarizer_api/ # raiz
┣ api/
┃ ┣ routers/
┃ ┃ ┗ summarization.py # rotas da API (POST/GET)
┃ ┣ schemas/
┃ ┃ ┗ summarization_schema.py # Formato da entrada de dados
┃ ┣ services/
┃ ┃ ┗ summarization_service.py # Sumarização de texto
┣ main.py # Inicial
┗ requirements.txt

```
Começamos construindo nosso serviço de sumarização, lembrando que nessa parte vamos tratar os dados:

Dado o seguinte payload, percebemos que podemos receber tanto texto como url, além de ser obrigatório o idioma, para que a sumarização seja feita de forma correta.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msz38x4278q80tdblzfx.png)

Vamos começar com o método `get_summarization` para identificar que tipo de dado estamos recebendo:

```python
async def get_summarization(summarization):
    formatted_text = summarization.text if summarization.url is None else scraping_text(summarization.url)

```
Se passarmos uma URL, vamos precisar fazer um web scraping para extrair as informações de texto que vamos precisar e para fazer essa extração vamos usar um módulo chamado [BeautifulSoup](https://beautiful-soup-4.readthedocs.io/en/latest/).

```
pip install beautifulsoup4 requests
```
Não abordaremos de forma profunda como é feita essa raspagem, talvez em outro artigo, mas para atender nossa demanda, faremos o seguinte:
* Enviaremos uma requisição na url recebida
* Vamos recuperar o conteúdo HTML como texto
* Examinaremos a estrutura recebida em soup e vamos buscar pelo elemento especifico
* Por fim, vamos extrair os textos e realizar uma limpeza, removendo todos os caracteres especiais, acentos e espaçamentos inutilizados.

```python
from bs4 import BeautifulSoup as bs

def scraping_text(url):
    response = requests.get(url)
    soup = bs(response.content, 'lxml')
    paragraph = soup.find_all(name='p', attrs= {"class": "pw-post-body-paragraph"}, limit=40)
    joined_text = ''.join([p.text.strip().lower() for p in paragraph])
    joined_text = format_text(joined_text)
    return joined_text
    
def format_text(text):
    formatted_text = unicodedata.normalize('NFD', text).encode('ascii', 'ignore').decode('utf-8')
    formatted_text = re.sub(r'\[[0-9]*\]', ' ', formatted_text)
    formatted_text = re.sub(r'\s+', ' ', formatted_text)
    return formatted_text

```
Pronto, temos os dados tratados e agora entramos na segunda parte do artigo.

###Sumarização
Para começar nosso processo de sumarização, precisamos instalar e importar as bibliotecas necessárias:

```
pip install nltk
```
NLTK, significa Natural Language Toolkit, ou seja, um conjunto de ferramentas para linguagem natural que contem vários algoritmos implementados para trabalhar com linguagem natural.

Usaremos os pacotes Corpus e os Tokenizers, quando falamos em “tokenização”, falamos em dividir um texto em uma série de tokens, já um corpus de texto é um grande corpo de texto ou coleção de texto, que podem ser conjuntos de dados, como obras de um autor, poemas, ou um conjunto de palavras de paradas.

O NLKT inclui alguns corpora que são essas listas de palavras irrelevantes, ou seja, palavras de alta frequência, que podemos filtrar em um documento antes de iniciar o processamento, no nosso caso vamos filtrar essas palavras de parada, adicioná-las a um dicionário python para verificar a frequência com que elas aparecem no texto antes de serem removidas.

Vamos usar este dicionário sobre cada frase para saber quais tem o conteúdo mais relevante no texto geral.

```python
import re
import requests
from bs4 import BeautifulSoup as bs
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize, sent_tokenize

async def get_summarization(summarization):
    formatted_text = summarization.text if summarization.url is None else scraping_text(summarization.url)
    stop_words = set(stopwords.words(str(summarization.language)))
    frequences_word = handle_frequence_word(stop_words, formatted_text)
    print('Dicionário de frequencia de palavras de parada', frequences_word)
    
def handle_frequence_word(stop_words, text):
    words = word_tokenize(text)
    frequences_word = dict()
    for word in words:
        if word in stop_words:
            continue
        if word in frequences_word:
            frequences_word[word] += 1
        else:
            frequences_word[word] = 1
            
    return frequences_word
    
def scraping_text(url):
    response = requests.get(url)
    soup = bs(response.content, 'lxml')
    paragraph = soup.find_all(name='p', attrs= {"class": "pw-post-body-paragraph"}, limit=40)
    joined_text = ''.join([p.text.strip().lower() for p in paragraph])
    joined_text = format_text(joined_text)
    return joined_text

def format_text(text):
    formatted_text = unicodedata.normalize('NFD', text).encode('ascii', 'ignore').decode('utf-8')
    formatted_text = re.sub(r'\[[0-9]*\]', ' ', formatted_text)
    formatted_text = re.sub(r'\s+', ' ', formatted_text)
    return formatted_text
```
Agora, lidaremos com as sentenças, ou seja, os tokens de frases mais tarde usaremos essa lista no resumo do texto. Nesta avaliação analisaremos a frequência de ocorrência de cada termo, logo, estaremos pontuando cada frase por suas palavras; isto é, somando a frequência de cada palavra importante encontrada na frase.

```python
async def get_summarization(summarization):
    formatted_text = summarization.text if summarization.url is None else scraping_text(summarization.url)
    stop_words = set(stopwords.words(str(summarization.language)))
    frequences_word = handle_frequence_word(stop_words, formatted_text)
    sentences = sent_tokenize(formatted_text)
    frequences_sentence = handle_sentences(frequences_word, sentences)

def handle_sentences(frequences_word, sentences):
    frequences_sentence = dict()
    for sentence in sentences:
        for word, freq in frequences_word.items():
            if word in sentence:
                if sentence in frequences_sentence:
                    frequences_sentence[sentence] += freq
                else:
                    frequences_sentence[sentence] = freq
    
    return frequences_sentence
```
Para concluirmos a parte de sumarização, criaremos um resumo escolhendo 30% das frases mais ponderadas.

```
select_lenght = int(len(frequences_sentence) * 0.3) # peso 30%
```
Com a ajuda da função [nlargest](https://pythontic.com/algorithms/heapq/nlargest) do módulo heapq, vamos retornar o número especificado de maiores elementos de um iterável, no nosso caso, encontraremos os maiores n elementos, aqui o valor da lista com as frases com peso 30% em cima do iterável que é nosso dicionário de sentenças.

```
summary = nlargest(select_lenght, frequences_sentence, key=frequences_sentence.get)
```
Por fim, percorremos o sumário gerado concatenando em uma string, sendo essa o resultado do nosso resumo.

O código completo do nosso service fica assim:

```python
import re
import requests
import unicodedata
from bs4 import BeautifulSoup as bs
from heapq import nlargest
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize, sent_tokenize

async def get_summarization(summarization):
    formatted_text = summarization.text if summarization.url is None else scraping_text(summarization.url)
    stop_words = set(stopwords.words(str(summarization.language)))
    frequences_word = handle_frequence_word(stop_words, formatted_text)
    sentences = sent_tokenize(formatted_text)
    frequences_sentence = handle_sentences(frequences_word, sentences)
    return get_resume(frequences_sentence)
    
def scraping_text(url):
    response = requests.get(url)
    soup = bs(response.content, 'lxml')
    paragraph = soup.find_all(name='p', attrs= {"class": "pw-post-body-paragraph"}, limit=40)
    joined_text = ''.join([p.text.strip().lower() for p in paragraph])
    joined_text = format_text(joined_text)
    return joined_text

def format_text(text):
    formatted_text = unicodedata.normalize('NFD', text).encode('ascii', 'ignore').decode('utf-8')
    formatted_text = re.sub(r'\[[0-9]*\]', ' ', formatted_text)
    formatted_text = re.sub(r'\s+', ' ', formatted_text)
    return formatted_text

def handle_frequence_word(stop_words, text):
    words = word_tokenize(text)
    frequences_word = dict()
    for word in words:
        if word in stop_words:
            continue
        if word in frequences_word:
            frequences_word[word] += 1
        else:
            frequences_word[word] = 1
            
    return frequences_word

def handle_sentences(frequences_word, sentences):
    frequences_sentence = dict()
    for sentence in sentences:
        for word, freq in frequences_word.items():
            if word in sentence:
                if sentence in frequences_sentence:
                    frequences_sentence[sentence] += freq
                else:
                    frequences_sentence[sentence] = freq
    
    return frequences_sentence

def get_resume(frequences_sentence):
    select_lenght = int(len(frequences_sentence) * 0.3) # peso 30%
    summary = nlargest(select_lenght, frequences_sentence, key=frequences_sentence.get)
    final = [word for word in summary]
    summary = ''.join(final)
    return summary

```
###Finalizando…

Para finalizarmos nossa API, agora vamos consumir os serviços nas rotas e gerar endpoints de entrada, o código completo está no meu [github](https://github.com/thaisribeiro/text_summarizer_api).
Para finalizarmos nossa API, agora vamos consumir os serviços nas rotas e gerar endpoints de entrada, o código completo está no meu github.

Iniciaremos nosso servidor: uvicorn main:app --reload e podemos ter acesso a API: http://127.0.0.1:8000/docs


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3pejr009yucgwbfmuoy2.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hml8raff1r5hlwh72lwf.png)

Obtemos a saída resumida e mantendo a lógica do texto original

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ys0gbn3lwj1wfbdzw17r.png)

Bom pessoal, não aprofundamos na criação de API’s porque o que quero mostrar para vocês é como podemos automatizar alguns processos morosos utilizando machine learning, espero que tenham gostado do conteúdo e por hoje, ficamos por aqui.