DEV Community

Cover image for nltk e processamento de linguagem natural
thaisandre
thaisandre

Posted on

nltk e processamento de linguagem natural

"And what is the use of a book", thought Alice, "without pictures or conversations?"

nltk (Natural Language Toolkit) é um pacote para trabalhar com Processamento de Linguagem Natural (NLP) no Python. Por linguagem natural entende-se a linguagem usada no dia a dia na comunicação entre pessoas.

Muitos dados são gerados, dia a dia, por blogs, redes sociais e páginas web. A maioria destes dados estão em formato de texto não estruturado e são usados por empresas para entender seus clientes e melhorar (e produzir novos) produtos. NLP é útil em detecção de erros gramaticais e traduções automáticas, análise de sentimento em discursos, motores de busca, filtros de spam, etc…

O nltk possui muitos recursos para análise de texto e conheceremos alguns deles.

instalação

sudo pip3 install -U nltk
Enter fullscreen mode Exit fullscreen mode

acessando os dados

Vamos analisar o livro Alice no País das Maravilhas, de Lewis Carroll, disponível pelo projeto Gutenberg neste link.

O nltk possui um pacote com amostras de dados para uso, inclusive alguns do próprio projeto Gutenberg. A lista completa dos dados disponíveis pode ser acessada aqui. O pacote não vem habilitado por padrão, para instalá-lo, faça:

>>> import nltk
>>> nltk.download()
Enter fullscreen mode Exit fullscreen mode

Uma janela deve abrir. É recomendado que você modifique o local do download para usr/share/nltk_data - diretório do Linux para armazenar dados que não serão modificados (mais informações aqui). Caso esteja utilizando Windows, a recomendação é salvar no diretório C:\nltk_datae no Mac em /usr/local/share/nltk_data.

Também é possível instalar via linha de comando:

$ sudo python3 -m nltk.downloader -d /usr/local/share/nltk_data all
Enter fullscreen mode Exit fullscreen mode

Uma vez que o download dos dados esteja concluído, podemos carregar e usar seu pacotes através do comando import:

>>> import nltk.corpus as corpus
Enter fullscreen mode Exit fullscreen mode

A palavra corpus é utilizada na língua inglesa para denotar uma coleção de material escrito ou falado em formato legível por máquina, montado para fins de pesquisa lingüística. O pacote corpus possui exatamente isso, várias amostras de textos que podemos utilizar para análise. Usaremos o atributo gutenberg para acessar alguns textos do projeto Gutenberg que carregamos com o download dos dados do nltk.

>>> type(corpus.gutenberg)
<class 'nltk.corpus.reader.plaintext.PlaintextCorpusReader'>
Enter fullscreen mode Exit fullscreen mode

corpus.gutenberg é um atributo do tipo PlaintextCorpusReader. Este objeto, segundo a documentação (acesse pelo comando help(corpus.gutenberg)), é um leitor para corpus de texto plano - é assumido que parágrafos são divididos usando quebra de linha, as frases
finalizam com pontuações específicas (como ponto, exclamação ou interrogação) e palavras podem ser dividas em unidades.

Vamos checar os textos disponíveis neste objeto:

>>> corpus.gutenberg.fileids()
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-k
jv.txt', 'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.tx
t', 'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt',
'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.t
xt', 'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamle
t.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']
Enter fullscreen mode Exit fullscreen mode

A função fileids() mostra os arquivos disponíveis. Para acessar o livro Alice no País das Maravilhas, usaremos o seu fileid que é o carroll-alice.txt através do método raw():

>>> alice = corpus.gutenberg.raw('carroll-alice.txt')
>>> type(alice)
<class 'str'>
Enter fullscreen mode Exit fullscreen mode

O método raw() retorna uma string com todo o conteúdo do livro. Quem já trabalhou com strings conhece a dificuldade de trabalhar com estes objetos. Outros objetos do nltk irão nos ajudar para conseguir analisar melhor este conteúdo.

tokenization

Tokenization é o nome dado para o processo de dividir uma grande quantidade de texto em pequenas quantidades - essas pequenas quantidades são chamadas de tokens. Essa é uma divisão importante para fazer análises textuais.

O nltk possui um módulo chamado tokenize que facilita o processo de divisão de um texto. A função word_tokenize() divide um texto por palavras e pontuações:

from nltk.tokenize import word_tokenize
>>> texto = 'Hello, how are you?'
>>> word_tokenize(texto)
['Hello', ',', 'how', 'are', 'you', '?']
Enter fullscreen mode Exit fullscreen mode

Além da divisão por palavras, podemos querer dividir um texto em sentenças para calcular a média de palavras por sentenças, por exemplo. Para isso usamos a função sent_tokenize():

from nltk.tokenize import sent_tokenize
>>> texto = 'Hello, how are you? Fine, thanks!'
>>> sent_tokenize(texto)
['Hello, how are you?', 'Fine, thanks!']
Enter fullscreen mode Exit fullscreen mode

E para dividir em parágrafos, usamos line_tokenize():

from nltk.tokenize import line_tokenize
>>> texto = 'Lorem ipsum dolor sit amet, amet blandit suscipit quam tellu
s vitae mauris, ut metus tellus, curabitur et elit, in volutpat, fringill
a aliquam. \nDonec cras tristique, quis eu. Ipsum integer quis sapien ves
tibulum wisi vel, scelerisque justo massa nulla tempor in, placerat ut se
m nunc ultrices ac, quis conubia.'
>>> line_tokenize(texto)
>>> ['Lorem ipsum dolor sit amet, amet blandit suscipit quam tellus vitae
mauris, ut metus tellus, curabitur et elit, in volutpat, fringilla aliqua
m.',
'Donec cras tristique, quis eu. Ipsum integer quis sapien vestibulum wis
i vel, scelerisque justo massa nulla tempor in, placerat ut sem nunc ultr
ices ac, quis conubia.']
Enter fullscreen mode Exit fullscreen mode

No próprio objeto gutenberg, temos funções prontas como paras(), sents() e words() que retornam o conteúdo do texto separados por parágrafos, sentenças e palavras, respectivamente. Você pode testar cada um deles:

>>> paragrafos = corpus.gutenberg.paras('carroll-alice.txt')
>>> paragrafos
[[['[', 'Alice', "'", 's', 'Adventures', 'in', 'Wonderland', 'by', 'Lewi
s', 'Carroll', '1865', ']']], [['CHAPTER', 'I', '.'], ['Down', 'the', 'Ra
bbit', '-', 'Hole']], ...]
Enter fullscreen mode Exit fullscreen mode
>>> sentencas = corpus.gutenberg.sents('carroll-alice.txt')
>>> sentencas
[['[', 'Alice', "'", 's', 'Adventures', 'in', 'Wonderland', 'by', 'Lewis'
, 'Carroll', '1865', ']'], ['CHAPTER', 'I', '.'], ...]
Enter fullscreen mode Exit fullscreen mode
>>> palavras = corpus.gutenberg.words('carroll-alice.txt')
>>> palavras
['[', 'Alice', "'", 's', 'Adventures', 'in', ...]
Enter fullscreen mode Exit fullscreen mode
>>> type(paragrafos)
<class 'nltk.corpus.reader.util.StreamBackedCorpusView'>
>>> type(sentencas)
<class 'nltk.corpus.reader.util.StreamBackedCorpusView'>
>>> type(palavras)
<class 'nltk.corpus.reader.util.StreamBackedCorpusView'>
Enter fullscreen mode Exit fullscreen mode

Todos eles retornam um objeto do tipo StremBackedCorpusView que representa uma visualização do corpus através de uma sequência de tokens iterável. Este objeto também possui métodos, como o count() para contar o número de vezes que um elemento aparece na sequência de tokens.

primeiras análises

Vamos usar o count() para checar o número de vezes que determinados personagens são citados no texto. Por exemplo, se queremos saber quantas vezes a palavra "Alice" aparece, fazemos:

>>> palavras.count('Alice')
396
Enter fullscreen mode Exit fullscreen mode

Vamos fazer a contagem de mais alguns personagens:

>>> personagens = ['Alice', 'Rabbit', 'Caterpillar', 'Cat', 'Hatter', 'Do
rmouse', 'Hare', 'Queen', 'King', 'Gryphon' ]
>>> for p in personagens:
...     print('{}: {}'.format(p, palavras.count(p)))
...
Alice: 396
Rabbit: 45
Caterpillar: 0
Cat: 26
Hatter: 55
Dormouse: 40
Hare: 31
Queen: 74
King: 61
Gryphon: 55
Enter fullscreen mode Exit fullscreen mode

Legal, obtemos a frequência de aparição de cada personagem no livro. Mas e se quisermos saber em quais partes do livro estes personagens aparecem? O nltk facilita essa visualização através da classe Text.

A classe Text funciona como um wrapper de uma sequência de tokens (como nossa variável palavras) e ajuda na exploração de textos. Seus métodos executam várias análises e exibe seus resultados, como é o caso do método dispersion_plot(), que plota um gráfico de dispersão. Primeiro precisamos de uma instância de Text com o conteúdo do livro, e obteremos através da variável palavras:

>>> palavras_text = nltk.Text(palavras)
Enter fullscreen mode Exit fullscreen mode

Vamos checar a frequência da palavra "Alice" ao longo do texto:

>>> palavras_text.dispersion_plot(['Alice'])
Enter fullscreen mode Exit fullscreen mode

Gráfico dispersão da palavra Alice

Agora vamos visualizar as partes em que os personagens que definimos anteriormente aparecem no livro através de um gráfico de dispersão:

palavras_text.dispersion_plot(personagens)
Enter fullscreen mode Exit fullscreen mode

Gráfico dispersão dos personagens

Essa imagem, além de mostrar a frequência que cada personagem aparece na história, permite visualizar os momentos que eles interagem. O eixo x representa o deslocamento das palavras referenciando o índice da sequência do texto.

"Alice", sendo a protegonista, aparece com mais frequência e por toda a história do livro. Vemos que "Caterpillar" (a Lagarta Azul) interage pouco - apenas no início da história - e "Hatter" (Chapeleiro), "Dormouse" (Arganaz) e "Hare" (Lebre de Março) aparecem com uma frequência parecida no meio da história, no momento do famoso chá. O "Rabbit" (Coelho Branco) já interage com "Alice" em vários momentos da história (começo, meio e fim) mas com um frequência pequena.

Podemos usar a função de distribuição de frequência do nltk para determinar as palavras mais frequentes (especificamente tokens), que são usadas em um determinado texto. Para facilitar a visualização, vamos utilizar a classe FreqDist que representa a distribuição dos valores que aparecem com maior frequência em uma amostra. Essa classe possui um método plot() que recebe um número inteiro como parâmetro representando a quantidade dos valores que queremos mostrar. Vamos gerar um gráfico com as 20 palavras mais frequentes no
texto:

>>> freq = nltk.FreqDist(palavras_text)
>>> freq.plot(20)
Enter fullscreen mode Exit fullscreen mode

Gráfico das 20 palavras mais frequentes

Esse gráfico não está bom. Veja que ele está contando as palavras que são pontuações (como ponto final e exclamações). Vamos eliminar as pontuações. Podemos filtrar pela função isalpha() do objeto str do próprio Python:

>>> freq_sem_pontuacao = nltk.FreqDist(dict((word, freq) for word, freq i
n freq.items() if word.isalpha()))
>>> freq_sem_pontuacao.plot(20, title="20 palavras mais populares (sem po
ntuação)")
Enter fullscreen mode Exit fullscreen mode

Gráfico das 20 palavras mais frequentes

Já está um pouco melhor! Ainda assim, o gráfico traz palavras comuns como “a”, “the”, “to”, etc… Queremos eliminar este tipo de palavra que é irrelevante para nossa busca e aparece com frequência em qualquer texto. Para isso, o nltk possui um recurso chamado de stopwords (palavras de parada). Vamos eliminar também essas stopwords.

Primeiro guardaremos as stopwords da língua inglesa - já que o texto está em inglês - em uma variável chamada stopwords:

>>> stopwords = nltk.corpus.stopwords.words('english')
Enter fullscreen mode Exit fullscreen mode

E filtramos também por essas stopwords (no caso, sem elas):

>>> freq_sem_pontuacao_e_sem_stopwords = nltk.FreqDist(dict((word, freq)
for word, freq in freq.items() if word not in stopwords and word.isalpha
()))
>>> freq_sem_pontuacao_e_sem_stopwords.plot(20, title="20 palavras mais p
opulares (sem stopwords ou pontuações)")
Enter fullscreen mode Exit fullscreen mode

Gráfico das 20 palavras mais frequentes

Agora está bem melhor. Vemos que a personagem mais citada no texto, depois de "Alice" é a "Queen" (a Rainha). Podemos facilmente plotar esse gráfico com todos os nomes próprios (ou o que a morfologia chama de substantivos próprios) se existisse uma maneira fácil de identificá-los pelo texto. O nltk possui esse recurso.

explorando o tagger

O nltk possui uma função chamada pos_tag() que infere uma classe (substantivo, adjetivo, advérbio, verbo, etc..) para cada palavra. Como esta função recebe uma lista de tokens, vamos usar a função word_tokenize() com o texto puro:

>>> alice_txt = nltk.corpus.gutenberg.raw('carroll-alice.txt')
>>> words = nltk.word_tokenize(alice_txt)
>>> tags = nltk.pos_tag(words)
>>> tags
[('[', 'JJ'), ('Alice', 'NNP'),("'s", 'POS'), ('Adventures', 'NNS'), ('i
n', 'IN'), ('Wonderland', 'NNP'), ('by', 'IN'), ('Lewis', 'NNP'), ('Carro
ll', 'NNP'), ('1865', 'CD'), (']', 'NNP'), ('CHAPTER', 'NNP'), ('I', 'PR
P'), ('.', '.'), ('Down', 'RP'), ('the', 'DT'), ('Rabbit-Hole', 'JJ'), (
'Alice', 'NNP'), ('was', 'VBD'), ...
Enter fullscreen mode Exit fullscreen mode

Veja que para cada token foi gerada uma tag. A lista completa com a definição de cada uma delas pode ser acessada através do código abaixo:

>>> nltk.help.upenn_tagset()
Enter fullscreen mode Exit fullscreen mode

Estamos interessados nos substativos próprios no singular, ou seja, na tag NNP:

>>> substantivos_proprios= []
>>> for word,tag in tags:
...     if tag in ['NNP'] and word.isalpha():
...         substantivos_proprios.append(word)
...
>>>
Enter fullscreen mode Exit fullscreen mode

Agora que temos a lista de todos os substantivos próprios, vamos plotar o gráfico dos 20 mais frequentes:

>>> freq_substantivos_proprios=nltk.FreqDist(substantivos_proprios)
>>> freq_substantivos_proprios.plot(20, title='20 substantivos próprios m
ais frequentes')
Enter fullscreen mode Exit fullscreen mode

Gráfico substantivos mais frequentes

Agora sabemos mais claramente os 20 personagens da história mais citados no texto. E podemos plotar o gráfico da dispersão de com eles, da mesma maneira que fizemos anteriormente com nossa lista de personagens.

>>> mais_comuns = freq_substantivos_proprios.most_common(n=20)
>>> personagens_mais_comuns = []
>>> for nome,freq in mais_comuns:
...     personagens_mais_comuns.append(nome)
...
>>> palavras_text.dispersion_plot(personagens_mais_comuns)
Enter fullscreen mode Exit fullscreen mode

Gráfico dispersão 20 personagens mais frequentes

Agora, além dos 20 personagens que aparecem com maior frequência no texto, temos como eles estão dispersos no texto. Note que as palavras "Rabbit" e "White" aparecem com uma frequência parecida, já que fazem referência ao "Coelho Branco", mas nem sempre aparecem juntas. O mesmo acontece com "Hare" e "March" - já que essas palavras fazem referência a "Lebre de Março" . Neste caso, poderíamos excluir desta lista as palavras "White" e "March" para uma análise mais precisa.

Da mesma maneira, podemos querer plotar o gráfico de frequência dos 20 verbos mais usados no texto. Usamos, desta vez, a tag VB:

>>> verbos = []
>>> for word,tag in tags:
...     if tag in ['VB']:
...         verbos.append(word)
...
>>> freq_verbos=nltk.FreqDist(verbos)
>>> freq_verbos.plot(20, title='20 verbos mais frequentes')
Enter fullscreen mode Exit fullscreen mode

Gráfico 20 verbos mais frequentes

E ver analisar sua dispersão pelo texto:

>>> mais_comuns = freq_verbos.most_common(n=20)
>>> verbos_mais_comuns = []
>>> for nome,freq in mais_comuns:
...     verbos_mais_comuns.append(nome)
...
>>> palavras_text.dispersion_plot(verbos_mais_comuns)
Enter fullscreen mode Exit fullscreen mode

Gráfico dispersão 20 verbos mais frequentes

Tente fazer o mesmo com os adjetivos, advérbios e substantivos.

análise de sentimento

O nltk é um pacote bastante completo e possui subpacotes para analisar o sentimento de frases. Será que o texto possui mais sentenças que passam sentimentos negativos, positivos ou
neutros? Vamos analisar isso.

O subpacote para analisar sentimento é o nltk.sentiment que possui o módulo vader com diversas funcionalidade para obter métricas de sentimento de uma sentença. VADER é acrônimo de Valence Aware Dictionary and Sentiment Reasoner, ou seja, um dicionário de valência e de raciocínio de sentimento. A palavra valência , em linguística, é o número de elementos gramaticais com os quais uma determinada palavra, especialmente um verbo, pode combinar em uma sentença.

Análise de sentimentos em um texto é uma tarefa complexa e exige muito estudo de linguística. Sorte que o nltk já fez todo este trabalho por nós. O código deste módulo pode ser acessado através deste link.

Para analisar o sentimento de uma sentença, vamos utilizar a classe SentimentIntensityAnalyzer que nos dá uma pontuação de intensidade de sentimento de determinadas sentenças.

Primeiro, vamos importar essa classe e instanciá-la:

>>> from nltk.sentiment.vader import SentimentIntensityAnalyzer
>>> sa = SentimentIntensityAnalyzer()
Enter fullscreen mode Exit fullscreen mode

Esta classe possui o método polarity_scores() que vai gerar as pontuações. Vejamos um exemplo:

>>> frase = 'Good job, nltk!'
>>> sa.polarity_scores(frase)
{'neg': 0.0, 'neu': 0.385, 'pos': 0.615, 'compound': 0.4926}
Enter fullscreen mode Exit fullscreen mode

O retorno é um dicionários com pontuações de -1 a 1, do tipo float, para as chaves neg (sentimento negativo), neu (sentimento neutro), pos (sentimento positivo) e compound (sentimento dos três anteriores combinados). Esta pontuação é exatamente o cálculo da valência.

Veja que a frase "God Job, nltk!" ("Bom trabalho, nltk!") tem uma pontuação positiva alta e a pontuação composta de 0.4926 - ou seja, acima de 0, portanto é uma frase que passa um sentimento positivo.

O vader é muito utilizado para analisar sentimentos de posts em redes sociais. É extremamente rápido e não necessita de treinamento.

Vamos analisar nosso texto para ter uma métrica do número de sentenças positivas, neutras e negativas que ele possui. Para isso utilizaremos a pontuação composta.

Primeiro, vamos dividir nosso texto em sentenças:

>>> alice_txt = nltk.corpus.gutenberg.raw('carroll-alice.txt')
>>> sentencas = nltk.sent_tokenize(alice_txt)
>>> sentencas
Enter fullscreen mode Exit fullscreen mode

Agora, vamos criar duas listas, uma para as sentenças e outra para as pontuações:

>>> lista_pontuacoes = []
>>> lista_sentencas = []
>>> for sentenca in sentencas:
...     lista_sentencas.append(sentenca)
...     pontuacao = sa.polarity_scores(sentenca)
...     lista_pontuacoes.append(pontuacao['compound'])
...
Enter fullscreen mode Exit fullscreen mode

Para plotar o gráfico de quantidade de cada uma das classificações (neutras, negativas e positivas), vamos usar o pandas, uma biblioteca do Python bastante usada para análise de dados. Para instalar o pandas, execute o código abaixo no terminal:

$ pip3 install pandas
Enter fullscreen mode Exit fullscreen mode

Vamos criar um dataframe com estes dados:

>>> import pandas as pd
>>> df = pd.DataFrame({'sentenca': lista_sentencas, 'pontuacao': lista_po
ntuacoes})
Enter fullscreen mode Exit fullscreen mode

A variável df guarda um dataframe, um tipo de dado retangular que representa uma tabela com as colunas senteca e pontuacao. Cada linha dessa tabela, portanto, possui o valor da sentença e sua respectiva pontuação de análise de sentimento (no caso, a pontuação composta).

Você pode checar esta estrutura através da função head() do dataframe:

>>> df.head(10)
Enter fullscreen mode Exit fullscreen mode

Após executado, vai mostrar as primeiras 10 linhas desta tabela. Agora, vamos selecionar apenas as sentenças negativas. O pandas consegue selecionar isso muito facilmente. Uma sentença com sentimento negativo é aquela que possui pontuação menor do que 0 :

>>> selecao_neg = df.pontuacao < 0
>>> negativas = df[selecao_neg]
>>> negativas.head(10)
Enter fullscreen mode Exit fullscreen mode

Selecionamos e guardamos apenas as sentenças negativas em outro dataframe que nomeamos de negativas. Vamos ver sua quantidade:

>>> len(negativas)
394
Enter fullscreen mode Exit fullscreen mode

Legal! Agora sabemos que 394 sentenças do livro Alice no País das Maravilhas passa um sentimento negativo. Vamos fazer o mesmo para os positivos e neutros:

>>> selecao_pos = df.pontuacao > 0
>>> positivas = df[selecao_pos]
>>> len(positivas)
443
>>> selecao_neu = df.pontuacao == 0
>>> neutras = df[selecao_neu]
>>> len(neutras)
788
Enter fullscreen mode Exit fullscreen mode

Sabemos que o texto possui mais sentenças que passam um sentimento positivo do que negativo, mas as neutras superam. Vamos plotar um gráfico de barras para visualizar melhor os resultados:

>>> sentimentos = pd.DataFrame({'sentimentos':['negativo', 'neutro', 'pos
itivo'], 'quantidade':[len(negativas), len(neutras), len(positivas)]})
>>> sentimentos.plot(kind='bar', x='sentimentos', y='quantidade', color=[
'lightblue'], rot=0)
Enter fullscreen mode Exit fullscreen mode

Gráfico da pontuação de análise de sentimento

referências

Documentação do nltk. http://www.nltk.org

Steve Bird, Edwan Klein e Edward Loper; Processamento de Linguagem Natural com Python - Analizando textos com Natural Language Toolkit. http://www.nltk.org/book/

Top comments (0)