O processamento de linguagem natural é um ramo da Inteligência Artificial (IA) dedicado a capacitar computadores a entender e processar a linguagem humana. As técnicas de processamento de linguagem natural alimentam muitas das aplicações que estão revolucionando a IA. Chatbots conversacionais, assistentes virtuais, análise de sentimentos, agregadores de notícias, análises para SEO são alguns exemplos das aplicações da área.
Para aproveitar os recursos avançados do processamento de linguagem natural em projetos desenvolvidos em Python, uma das melhores opções é usar bibliotecas específicas.
SpaCy é uma biblioteca Python de código aberto para lidar com tarefas de processamento de linguagem natural. Ela foi desenvolvida com grau industrial e seus recursos vão bem além do processamento de strings e limpeza de textos. Ela tem funcionalidades que se aprofundam na compreensão do significado e da estrutura da linguagem. Portanto, spaCy é uma ferramenta valiosa para desenvolvedores e empresas.
SpaCy
A instalação da spaCy com pip é feita com o comando abaixo:
pip install -U spacy
A spaCy possui vários modelos pré-treinados capazes de executar as tarefas de processamento de linguagem natural mais comuns. A seleção de um desses modelos é um pré-requisito para utilizar várias funcionalidades da biblioteca. Para usar um dos modelos, basta fazer seu download com um comando digitado diretamente no terminal (veja o site oficial para mais informações). Para este post, o modelo usado será o de português com tamanho médio. O comando para seu download na data do post é:
python -m spacy download pt_core_news_md
Para usar spaCy, é preciso importar a biblioteca num código Python e carregar o modelo como mostrado abaixo. O processo de carregamento do modelo cria um objeto nlp. Esse objeto pode ser usado para fazer diversas análises em textos.
import spacy
nlp = spacy.load("pt_core_news_md")
O objeto Doc
Quando o objeto nlp é chamado em um texto, spaCy primeiro tokeniza o texto para produzir um objeto Doc. Tokens são entidades que compõem um texto. Eles podem ser palavras, pontuações, espaços, etc.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Nunca conheci quem tivesse levado porrada.
Todos os meus conhecidos têm sido campeões em tudo."""
meu_doc = nlp(texto)
print(type(meu_doc))
Um objeto Doc é um contêiner para acessar anotações linguísticas. Informações como o lema, entidades nomeadas, vetores são pré-computadas e prontamente armazenadas no objeto Doc. Cada uma dessas informações é acessada com um comando específico. Por exemplo, tokens textuais podem ser acessados com o comando text como mostrado abaixo.
for token in meu_doc:
print(token.text)
Nunca
conheci
quem
tivesse
levado
porrada
.
Todos
os
meus
conhecidos
têm
sido
campeões
em
tudo
.
Pipelines
Como mencionado acima, quando o objeto nlp é chamado em um texto, spaCy produz um objeto Doc. Esse objeto é processado em várias etapas — isso também é conhecido como pipeline de processamento. Pipelines de modelos pré-treinados normalmente incluem tokenização (tokenizer), marcação (tagger), lematização (lemmatizer), um analisador (parser) e um reconhecedor de entidade (entity recognizer). Mas esses componentes podem mudar ligeiramente de um modelo para o outro.
Na configuração em pipeline, cada componente retorna o Doc processado, que é então passado para o próximo componente. Para visualizar os componentes do pipeline de um modelo, basta digitar o comando pipe_names:
import spacy
nlp = spacy.load("pt_core_news_md")
print(nlp.pipe_names)
O resultado mostra os componentes presentes no pipeline do modelo utilizado.
['tok2vec', 'morphologizer', 'parser', 'lemmatizer', 'attribute_ruler', 'ner']
Criar e adicionar novos elementos em um pipeline pode ser feito com facilidade com os comandos create_pipe e add_pipe mostrados abaixo. Veja que é importante indicar em que posição do pipeline o novo elemento será adicionado.
nlp.add_pipe(nlp.create_pipe('meu_elemento'), before='ner')
A remoção de um elemento de um pipeline é feita com a função remove_pipe seguida do nome do elemento.
nlp.remove_pipe("meu_elemento")
Tokenização de Sentenças e Frase Nominativas
A spaCy realiza com eficiência a tokenização de elementos individuais de um texto, como palavras e pontuações. Mas ela também pode tokenizar outros elementos como sentenças. Com sents, a biblioteca retorna uma lista de objetos do tipo span contendo as sentenças individuais de um texto.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Em avenidas importantes da cidade, como a W3, o alagamento do asfalto interrompeu a circulação de veículos e o trânsito precisou ser desviado. Uma enchente também atingiu São Sebastião, região administrativa do DF. Moradores da capital também relataram queda de energia."""
meu_doc = nlp(texto)
doc = nlp(texto)
sentencas = doc.sents
for sent in sentencas:
print(sent.text)
Em avenidas importantes da cidade, como a W3, o alagamento do asfalto interrompeu a circulação de veículos e o trânsito precisou ser desviado.
Uma enchente também atingiu São Sebastião, região administrativa do DF.
Moradores da capital também relataram queda de energia.
A biblioteca também identifica e tokeniza frases nominais em textos. Uma frase nominal é um grupo de palavras que funciona como um substantivo em uma frase. Elas podem atuar como sujeito, objeto, complemento ou objeto de uma preposição. Abaixo, spaCy identifica as frases nominais do texto mostrado anteriormente.
chunks = doc.noun_chunks
print("Frase Nominais:", [chunk.text for chunk in chunks])
Frase Nominais: ['avenidas importantes', 'cidade', ', como a W3', 'o alagamento', 'asfalto', 'a circulação', 'veículos', 'o trânsito', 'Uma enchente', 'São Sebastião, região administrativa do DF', 'Moradores', 'capital', 'queda', 'energia']
Customização de Delimitadores
Em muitas situações, as ferramentas de uma biblioteca não são suficientes para nossos casos específicos. Para oferecer flexibilidade, várias funcionalidades da spaCy podem ser personalizadas. Uma das funcionalidades que pode ser personalizada é a delimitação de sentenças. Isso permite a identificação de sentenças baseadas em critérios novos.
Um exemplo de como essa personalização pode ser feita é mostrado abaixo. Nele, dois pontos em sequência (..) foram usados como novos delimitadores de sentenças. Após essa definição, a nova função precisa ser adicionada no pipeline do modelo pré-treinado com o comando add_pipe mencionado anteriormente.
import spacy
from spacy.language import Language # importa classe language
@Language.component("novos_delimitadores")
def novos_delimitadores(doc): # cria novo delimitador
"""Adiciona .. como delimitador adicional de sentenças"""
for token in doc:
if token.text == '..':
doc[token.i + 1].is_sent_start = True
return doc
custom_nlp = spacy.load("pt_core_news_md") # carrega modelo pré-treinado
custom_nlp.add_pipe("novos_delimitadores", before="parser") # adiciona novo delimitador como elemento do pipeline do modelo
texto = """Outro perfil nas redes sociais escreveu: .. Sim, começa com Aécio.. O que veio depois disso.. todos sabem, postou outra pessoa."""
custom_doc = nlp(texto) # aplica modelo no texto para criar objeto Doc
custom_doc_sentences = custom_doc.sents # tokeniza sentenças
for sentence in custom_doc_sentences:
print(sentence)
Outro perfil nas redes sociais escreveu: ..
Sim, começa com Aécio..
O que veio depois disso..
todos sabem, postou outra pessoa.
Pré-processamento de textos com SpaCy
Algoritmos de machine learning e outras técnicas de IA geralmente não trabalham com textos, mas com números. O processo de transformação de textos em números envolve várias etapas. Entre elas está o pré-processamento de textos.
SpaCy pode ser usada no pré-processamento de textos. Ela possui várias funcionalidades comumente empregadas nesse processo, como tokenização, remoção de palavras de parada e lematização.
No código abaixo, as remoções de palavras de parada (is_stop), pontuações (is_punct) e números (like_num) ocorrem simultaneamente. Com essas funcionalidades implementadas em combinação, a limpeza de textos se torna altamente eficiente.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Tive devolução sem problemas, observando que comprei 6 produtos, chegaram em até 1 semana!"""
meu_doc = nlp(texto)
texto_limpo = []
for token in meu_doc:
if token.is_stop == False and token.is_punct == False and token.like_num == False:
texto_limpo.append(token.text)
print(texto_limpo)
['devolução', 'problemas', 'observando', 'comprei', 'produtos', 'chegaram', 'semana']
Identificações de e-mails e URLs
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Acesse nosso site: www.exemplo.com
Corra para aproveitar antes que o estoque acabe. Ou entre em contato: contato@email.com"""
meu_doc = nlp(texto)
for token in meu_doc:
if token.like_email or token.like_url:
print(token.text)
www.exemplo.com
contato@email.com
Marcação POS (part of speech) e Morfologia
A marcação POS é o processo de atribuição de tipos gramaticais a tokens. Ela marcar cada token numa parte específica de um texto com base em seu contexto e definição. Algumas palavras podem funcionar de mais de uma maneira quando usadas em circunstâncias diferentes. Consequentemente, a marcação POS desempenha um papel crucial para entender em que contexto uma palavra é usada numa frase.
A marcação POS da spaCy retorna símbolos como NOUN (substantivo), ADJ (adjetivo). Para facilitar a compreensão dos símbolos usados, a marcação POS pode ser usada em conjunto com uma explicação (spacy.explain(token.pos_)).
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Excelente site entrega super rápida produtos de qualidade."""
meu_doc = nlp(texto)
for token in meu_doc:
print(token.text, '--->', token.pos_, '--->', spacy.explain(token.pos_))
Excelente ---> ADJ ---> adjective
site ---> NOUN ---> noun
entrega ---> VERB ---> verb
super ---> ADV ---> adverb
rápida ---> ADJ ---> adjective
produtos ---> NOUN ---> noun
de ---> ADP ---> adposition
qualidade ---> NOUN ---> noun
. ---> PUNCT ---> punctuation
Outra funcionalidade de marcação e identificação interessante da spaCy é a morfologia (morph). Ela retorna várias informações sobre as palavras de um texto como gênero, número (singular ou plural), conjugação verbal.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Chegou no prazo combinado."""
meu_doc = nlp(texto)
for token in meu_doc:
if token.is_punct == False:
print(token.text, '--->', token.morph)
Chegou ---> Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin
no ---> Definite=Def|Gender=Masc|Number=Sing|PronType=Art
prazo ---> Gender=Masc|Number=Sing
combinado ---> Gender=Masc|Number=Sing
Lematização
Lematização é uma técnica de redução de palavras a suas formas-base. Veja um exemplo abaixo. Nesse exemplo, a lematização é realizada apenas nos tokens identificados como verbos para ilustrar a capacidade da spaCy de reconhecer a raiz comum em diferentes conjugações.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """Ele iria. Eles foram."""
meu_doc = nlp(texto)
for token in meu_doc:
if token.pos_ == "VERB":
print(token.text, '--->', token.lemma_)
iria ---> ir
foram ---> ir
Reconhecimento de Entidades Nomeada (NER)
O reconhecimento de entidade nomeada (NER) é o processo de localizar e identificar entidades como pessoas, locais, organizações. SpaCy retorna todas as entidades nomeadas reconhecidas através da função ents.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = """O médico e integrante do Parlamento Europeu pela Alemanha, Peter Liese,
defendeu que os cigarros eletrônicos são uma alternativa para quem quer deixar
de fumar cigarros tradicionais. """
meu_doc = nlp(texto)
entidades = meu_doc.ents
print(entidades)
(Parlamento Europeu, Alemanha, Peter Liese)
As entidades identificadas são classificadas em categorias predefinidas, como nomes de pessoas, organizações, locais, valores monetários, etc. Para acessar as classificações identificadas pela spaCy, basta usar a função label_.
for ent in entidades:
print(ent.text, '-->', ent.label_)
Parlamento Europeu --> ORG
Alemanha --> LOC
Peter Liese --> PER
NER possui várias aplicações. Por exemplo, é possível usar essa funcionalidade para retornar todos os nomes de pessoas encontrados em um texto ou para agrupar textos em categorias relevantes. NER também pode ser usado para ocultar informações em textos como nomes de pessoas ou de organizações.
Visualizações
Visualizações de análises de dependência ou entidades nomeadas em textos podem ser incrivelmente úteis para processamentos e depurações de códigos. É por isso que a spaCy conta com os visualizadores displaCy e displaCy ENT.
A maneira mais rápida de visualizar um objeto Doc é usar displacy.serve. Isso permite a visualização dos resultados de marcas como NER e POS diretamente no seu navegador. No código abaixo, fazemos a visualização do texto mostrado na sessão anterior.
port = 5000
spacy.displacy.serve(meu_doc, style="ent", port=port)
O resultado da visualização do texto com NER é mostrado abaixo. A spaCy marca as entidades nomeadas reconhecidas e também atribui labels a elas como ORG (organização) e LOC (localidade):
O trecho de código mostrado a seguir contém um exemplo de visualização de uma análise de dependências.
import spacy
nlp = spacy.load("pt_core_news_md")
texto = 'Comprei um ventilador de teto com controle remoto.'
meu_doc = nlp(texto)
port = 5000
# mostrando tokens com suas tags POS
spacy.displacy.serve(meu_doc, style='dep', port=port)
Matcher
import spacy
nlp = spacy.load("pt_core_news_md")
from spacy.matcher import Matcher
matcher = Matcher(nlp.vocab) # inicia ferramenta matcher de busca de correspondências
texto = ("Lançado em 1999 como uma melhoria do Windows 98, o Windows 98 Second Edition (SE) "
"ofereceu correções de bugs e algumas novas ferramentas."
"Lançado em fevereiro de 2000, o Windows 2000 "
"foi um sistema operacional projetado para atender às necessidades de ambientes de trabalho e servidores. ")
meu_doc = nlp(texto)
minha_regra = [{"LOWER": "windows"}, {"LIKE_NUM": True}] # regra de correspondência
matcher.add('LocalizaVersao', [minha_regra]) # adiciona regra na ferramenta matcher
matches = matcher(meu_doc) # aplica matcher ao documento
for match_id, start, end in matches:
string_id = nlp.vocab.strings[match_id] # obtém as strings dos tokens
span = meu_doc[start:end] # extrai texto correspondente
print(start, end, span.text) # imprime localizações dos tokens e seus textos
7 9 Windows 98
11 13 Windows 98
34 36 Windows 2000
Também é possível criar regras de correspondências com outras funcionalidades da spaCy como lematização e entidade nomeada. A lista completa de funcionalidades que podem ser usadas está aqui. No exemplo a seguir, combinamos lematização do verbo visitar com reconhecimento de entidades nomeadas para locais para extrair cidades visitadas do texto.
texto = ("Eu gostaria de visitar Fortaleza."
"Ele visitou Holambra."
"Eles visitaram Olinda.")
meu_doc = nlp(texto)
minha_regra2 = [{"LEMMA": "visitar"}, {"ENT_TYPE": "LOC"}]
matcher.add('AchaLocal', [minha_regra2])
matches = matcher(meu_doc)
for match_id, start, end in matches:
string_id = nlp.vocab.strings[match_id]
span = meu_doc[start:end]
print(start, end, span.text)
3 5 visitar Fortaleza
7 9 visitou Holambra
11 13 visitaram Olinda
Similaridade
token1 = nlp("laranja")
token2 = nlp("cadeira")
token3 = nlp("melancia")
similarity_score1 = token1.similarity(token2) # similaridade entre token1 e 2
similarity_score2 = token1.similarity(token3) # similaridade entre token1 e 3
print(similarity_score1, similarity_score2)
0.2144113383584651 0.6280860235293657
sent1 = nlp("Eu gosto de pizza.")
sent2 = nlp("Vamos à praia?")
sent3 = nlp("Eu comi uma salada.")
similarity_score1 = sent1.similarity(sent2) # similaridade entre sent1 e 2
similarity_score2 = sent1.similarity(sent3) # similaridade entre sent1 e 3
print(similarity_score1, similarity_score2)
0.07466922097155061 0.8169204250390183