Brincando de Processamento Natural de Linguagem com spaCy
Essa semana eu descobri o spaCy, uma bilbioteca Python para Processamento de Linguagem Natural (PLN) que me pareceu excelente. Ao brincar um pouco mais com ela, eu percebi que ela era ainda mais divertida do que eu imaginava e já com um modelo pronto em português, o que facilita bastante para dar uma arranhada na superfície do assunto que é o PLN (ou NLP em inglês).
Eu, particularmente, não sei nada muito a fundo sobre PLN, então a minha apresentação aqui vai ser bem superficial :)
Vamos à uma rápida apresentação do que eu vi até agora…
A problemática
Processamento inteligente de texto puro é algo muito difícil: muitas palavras são raras, palavras completamente distintas podem ter significados iguais enquanto a mesma palavra pode ter um significado completamente diferente dependendo do contexto.
Mesmo a divisão entre palavras pode ser muito difícil em algumas línguas. Mesmo que seja possível utilizar apenas caracteres puros, normalmente é melhor usar o conhecimento da linguística para adicionar informações úteis.
Esse o objetivo do spaCy: pegar textos puros e retornar um objeto Doc, que contém diversas informações.
A biblioteca
spaCy é uma biblioteca em Python relativamente nova para PLN em escala industrial. Ela foi desenvolvida pela Explosion AI e é mantida pelo Matthew Honnibal e Ines Montani.
O desenvolvimento da spaCy foi feito especificamente para uso em produção e ajudar a criar aplicações que conseguem processar e “entender” um grande volume de texto. Ela pode ser usada para extrair informações ou entendimento de linguagem natural ou pré-processar texto para deep learning.
Em 2015, uma pesquisa da Emory University e Yahoo! Labs mostrou que o spaCy oferecia o parser sintático mais rápido do mundo e que sua acurácia estava entre as melhores disponveis (Choi et al, 2015). A segunda versão foi lançada agora em 2017 e conta com diversas melhorias
Instalação
O spaCy pode ser facilmente instalado com o pip
$ pip install spacy
O problema é que muitos exemplos utilizam uma espécie de biblioteca pré-pronta que não vem disponível por padrão. Me bati um pouco até entender que esses modelos devem ser baixados de forma separada. (O melhor? Tem em pt!)
$ python -m spacy download en
$ python -m spacy download pt
Usando um modelo pré-pronto
Podemos usar direto o modelo de português que acabamos de baixar.
Para isso, separamos o modelo e chamamos ele genericamente de nlp
. Como estamos carregando um modelo já pronto,
essa declaração tende a demorar um pouquinho.
>>> nlp = spacy.load('pt')
A partir de agora podemos usar esse modelo para entender frases em português.
Vamos usar o nlp
para estudar uma frase simples:
>>> doc = nlp(u'Você encontrou o livro que eu te falei, Carla?')
Obs: você deve declarar a string como unicode para que ele funcione corretamente.
Um pouco sobre docs e tokens…
Um Doc
, objeto como aquele que acabamos de criar, é uma sequência de objetos do tipo Token
e possui diversas
informações sobre o texto que ele contém. Por dividir a frase em tokens
, esse documento é uma
estrutura iterável e portanto, deve ser acessada como tal. Já um Token
é uma parte da estrutura e pode ser uma frase, palavra,
uma pontuação, um espaço em branco, etc. No nosso caso, como iremos avaliar uma frase, os tokens
serão constituídos de palavras e pontuações.
Primeiro, vamos analisar a frase da maneira mais simples: dividindo-a com o método split
de qualquer string.
>>> doc.text.split()
['Você', 'encontrou', 'o', 'livro', 'que', 'eu', 'te', 'falei,', 'Carla?']
Podemos ver que apesar de ser coerente a divisão por espaços, o verbo falei
e a vírgula estão dentro de um
mesmo token
, assim como Carla
e a interrogação.
O nlp
consegue entender a diferença entre eles e, portanto, quando usamos os tokens
dentro da
estrutura do documento, temos uma divisão mais coerente:
>>> [token for token in doc]
[Você, encontrou, o, livro, que, eu, te, falei, ,, Carla, ?]
Repare que agora a estrutura considerou a pontução e as palavras como estruturas separadas. Também não temos mais uma lista de strings,
mas uma lista de Tokens
.
Se não quisermos os objetos Tokens
, mas sim as strings que cada Token
contém podemos usar o método .orth_
:
>>> [token.orth_ for token in doc]
['Você',
'encontrou',
'o',
'livro',
'que',
'eu',
'te',
'falei',
',',
'Carla',
'?']
Entendendo diferença entre palavras e pontuações
Como o spaCy entende que existe uma diferença entre uma palavra e uma pontuação, também podemos fazer filtragens. E se eu quisesse apenas as palavras da frase?
>>> [token.orth_ for token in doc if not token.is_punct]
['Você', 'encontrou', 'o', 'livro', 'que', 'eu', 'te', 'falei', 'Carla']
Similaridade
O spaCy também permite avaliar similaridade entre palavras. O método .similarity
de um Token
avalia a similaridade
semântica estimada entre as palavras. Quanto maior o valor, mais similar são as palavras.
Vamos avaliar a similaridade entre 3 palavras: você, livro e eu.
Primeiro vamos armazenar o tokens
em uma lista para acessá-los de forma independente:
>>> tokens = [token for token in doc]
Dessa forma, temos que tokens[0]
representa o meu token
da palavra Você
e tokens[5]
representa a palavra eu
.
A análise de similaridade pode ser dada por:
>>> tokens[0].similarity(tokens[5])
0.29921812
Legal, temos um valor de 0,29. E o que isso significa? Sabemos intuitivamente que eu
e você
devem ser muito mais
similares que você
e livro
, por exemplo. Vamos investigar:
>>> tokens[0].similarity(tokens[3])
-0.067587696
O valor é negativo, ou seja, você
de fato está semânticamente muito mais próximo de eu
do que de livro
, o que faz
todo sentido, certo?
Análise de classes gramaticais
Podemos também entender as classes gramaticais de cada palavra dentro do nosso contexto:
>>> [(token.orth_, token.pos_) for token in doc]
[('Você', 'PRON'),
('encontrou', 'VERB'),
('o', 'DET'),
('livro', 'NOUN'),
('que', 'PRON'),
('eu', 'PRON'),
('te', 'PRON'),
('falei', 'VERB'),
(',', 'PUNCT'),
('Carla', 'PROPN'),
('?', 'PUNCT')]
]
Então assim conseguimos ver que encontrou
e falei
são os verbos da frase. E a vírgula e o ponto de interrogação
foi corretamente definido como pontuação (PUNCT
).
Encontrei, encontraram, encontrarão, encontrariam….
Agora, imagine que você tem um texto enorme e diversos tempos verbais diferentes. A análise passa a ser infinitamente
mais complicada! O que podemos fazer, então, é analisar não o verbo no tempo verbal que ele foi escrito, mas ele em sua
raiz. O nome desse método de encontrar a raiz das palavras é lematização por isso, o método .lemma_
faz exatamente isso. Vamos olhar:
>>> [token.lemma_ for token in doc if token.pos_ == 'VERB']
['encontrar', 'falar']
E isso vale para diversos tempos verbais MESMO!
>>> doc = nlp(u'encontrei, encontraram, encontrarão, encontrariam')
>>> [token.lemma_ for token in doc if token.pos_ == 'VERB']
['encontrar', 'encontrar', 'encontrar', 'encontrar']
Ancestrais
Do mesmo jeito que podemos encontrar as raízes de uma palavra, podemos checar se uma palavra é raíz de outra:
>>> doc = nlp(u'encontrar encontrei')
>>> tokens = [token for token in doc]
>>> tokens[0].is_ancestor(tokens[1])
True
E por fim… entidades
Por fim, podemos avaliar as entidades presentes em uma frase. Por exemplo, peguemos a frase:
doc = nlp(u'Machado de Assis um dos melhores escritores do Brasil, foi o primeiro presidente da Academia Brasileira de Letras')
Se formos analisar as entidades presentes nessa frase, percebemos que temos 3 entidades identificadas automaticamente:
>>> doc.ents
(Machado de Assis, Brasil, Academia Brasileira de Letras)
Ao analisarmos detalhadamente, podemos ver que o Spacy identificou Machado de Assis
como uma pessoa
(PER
de person
, em inglês), Brasil
como um local (LOC
) e Academia Brasileira de Letras
como uma organização (ORG
).
>>> [(entity, entity.label_) for entity in doc.ents]
[(Machado de Assis, 'PER'),
(Brasil, 'LOC'),
(Academia Brasileira de Letras, 'ORG')]
Claro que em inglês o modelo está bem avançado, e consegue identificar entidades bem mais complexas do que eu pude verificar nos textos em português. Por exemplo:
>>> wiki_obama = """Barack Obama is an American politician who served as
the 44th President of the United States from 2009 to 2017. He is the first
African American to have served as president,
as well as the first born outside the contiguous United States."""
>>> nlp = spacy.load('en')
>>> nlp_obama = nlp(wiki_obama)
>>> [(i, i.label_) for i in nlp_obama.ents]
[(Barack Obama, 'PERSON'),
(American, 'NORP'),
(, 'GPE'),
(44th, 'ORDINAL'),
(the United States, 'GPE'),
(2009 to 2017, 'DATE'),
(first, 'ORDINAL'),
(African American, 'NORP'),
(, 'GPE'),
(first, 'ORDINAL'),
(United States, 'GPE')]
É isso! Espero que tenham se divertido tanto quanto eu :)
❤ Abraço! Letícia