PLN – Processamento de Linguagem Natural para Iniciantes

PLN ou Processamento de Linguagem Natural é a forma como as máquinas entendem e lidam com as linguagens humanas. Esta técnica lida com dados não estruturados de texto e embora seja difícil de dominá-la é fácil de entender seus conceitos.

Cientes disso, devemos entender que a disponibilidade e geração de dados são complexidades envolvidas, em geral, com qualquer tipo de caso de uso de Machine Learning (ML). Mas o PLN é o campo onde esse problema é relativamente menos pronunciado, pois há muitos dados de texto ao nosso redor. Os e-mails que escrevemos, os comentários que postamos, os blogs que escrevemos são alguns exemplos.

Tipos:

1) Reconhecimento de entidade nomeada 

O processo de extração de entidades de nomeadas no texto, nome de pessoas, países, organizações, extrai informações úteis que podem ser usadas para vários fins como: classificação, recomendação, análise de sentimento, entre outros. 

Um chatbot é o exemplo de uso mais comum. A consulta do usuário é entendida por meio das entidades no texto e respondida de acordo com elas.

2) Resumo do texto 

É onde os conceitos-chave do texto são extraídos e o resumo parafraseado é construído em torno dele. Isso pode ser útil em resultados de pesquisas extensos.

3) Tradução 

Conversão de texto de uma linguagem para outra. O tradutor do Google é o exemplo mais comum que temos.

4) Fala em texto

Converte voz em dados de texto, sendo o exemplo mais comum os assistentes em nossos smartphones.

5) NLU 

Natural Language Understanding é uma forma de entender as palavras e frases no que diz respeito ao contexto. Eles são úteis na análise de sentimento dos comentários de usuários e consumidores. Modelos NLU são comumente usados na criação de chatbot.

6) NLG 

Natural Language Generation vai além do processamento da máquina ou da compreensão do texto. Essa é a capacidade das máquinas de escrever conteúdo por si mesmas. Uma rede profunda, usando Transformers GTP-3, escreveu este artigo.

Iniciando

Os insights sobre como o Machine Learning lida com dados não estruturados de texto são apresentados por meio de um exemplo básico de classificação de texto.

Entrada:

Uma coluna de ‘texto’ com comentários de revisão por usuário. Uma coluna de ‘rótulo’ com um sinalizador para indicar se é um comentário positivo ou negativo.

Saída:

A tarefa é classificar os comentários com base no sentimento como positivos ou negativos.

Etapas de pré-processamento

Um pré-processamento será feito para transformar os dados em algoritmos de ML. Como o texto não pode ser tratado diretamente por máquinas, ele é convertido em números. Dessa forma, os dados não estruturados são convertidos em dados estruturados.

NLTK é uma biblioteca Python que pode ajudar você nos casos de uso de PLN e atende muito bem às necessidades de pré-processamento.

1) Remoção de palavras irrelevantes

Palavras irrelevantes ocorrem com frequência e não acrescentam muito significado ao texto. Os mecanismos de pesquisa também são programados para ignorar essas palavras. Podemos citar como exemplo as palavras: de, o, isso, tem, seu, o quê, etc.

A remoção dessas palavras ajuda o código a se concentrar nas principais palavras-chave do texto que adicionam mais contexto.

 

Código de explicação:

import nltk
nltk.download(‘stopwords’)
from nltk.corpus import stopwords
stop = stopwords.words(‘english’)
print(stop)

 

Saída:

[‘i’, ‘me’, ‘my’, ‘myself’, ‘we’, ‘our’, ‘ours’, ‘ourselves’, ‘you’, “you’re”, “you’ve”, “you’ll”, “you’d”, ‘your’, ‘yours’,…]

 

Código de implementação:

Aplicando a remoção de palavras irrelevantes a um data frame do Pandas com uma coluna de ‘texto’.

input_df[‘text’] = input_df[‘text’].apply(lambda x: “ “.join(x for x in x.split() if x not in stop))

 

2) Remoção de emojis e caracteres especiais

Os comentários do usuário são carregados de emojis e caracteres especiais. Eles são representados como caracteres Unicode no texto, denotados como U +, variando de U + 0000 a U + 10FFFF.

Código de referência:

 

import re
def remove_emoji(text):
        emoji_pattern = re.compile("["
        u"U0001F600-U0001F64F"  # emoticons
        u"U0001F300-U0001F5FF"  # symbols & pictographs
        u"U0001F680-U0001F6FF"  # transport & map symbols
        u"U0001F1E0-U0001F1FF"  # flags (iOS)
        u"U0001F1F2-U0001F1F4"  # Macau flag
        u"U0001F1E6-U0001F1FF"  # flags
        u"U0001F600-U0001F64F"
        u"U00002702-U000027B0"
        u"U000024C2-U0001F251"
        u"U0001f926-U0001f937"
        u"U0001F1F2"
        u"U0001F1F4"
        u"U0001F620"
        u"u200d"
        u"u2640-u2642"
        "]+", flags=re.UNICODE)

return emoji_pattern.sub(r”, text)
sample_text= ‘That was very funny ?. Have a lovely day ? ‘
remove_emoji(sample_text)

Saída:
‘That was very funny . Have a lovely day ‘

Código de implementação:

input_df[‘text’] = input_df[‘text’].apply(lambda x: remove_emoji(x))

3)  Flexão

Flexão é a modificação de uma palavra para expressar diferentes categorias gramaticais como tempo verbal, voz, aspecto, pessoa, número, gênero e humor. Por exemplo, as derivações de ‘venha’ são ‘veio’, ‘vem’. Para obter o melhor resultado, as flexões de uma palavra devem ser tratadas da mesma maneira. Para lidar com isso usamos a lematização.

A lematização resolve as palavras em sua forma de dicionário (conhecida como lema), para a qual requer dicionários detalhados nos quais o algoritmo pode pesquisar e vincular palavras aos lemas correspondentes.

Por exemplo, as palavras “correr”, “corre” e “correu” são todas formas da palavra “correr”, portanto “correr” é o lema de todas as palavras anteriores.

    a) Stemming

Stemming é uma abordagem baseada em regras que converte as palavras em sua palavra raiz (radical) para remover a flexão sem se preocupar com o contexto da palavra na frase. Isso é usado quando o significado da palavra não é importante. A palavra raiz pode ser uma palavra sem sentido em si mesma.

 

Código de explicação:

from nltk.stem import PorterStemmer</span> <span style="font-weight: 400;" data-mce-style="font-weight: 400;">porter = PorterStemmer()

 

Amostra 1:

print(porter.stem('trembling'),
porter.stem('tremble'),
porter.stem('trembly'))

 

Saída:

trembl trembl trembl

 

Amostra 2:

print(porter.stem('study'),
porter.stem('studying'),
porter.stem('studies'))

 

Saída:

studi studi studi

 

Código de implementação

def stemming_text(text):
    stem_words = [porter.stem(w) for w in w_tokenizer.tokenize(text)]
    return ‘ ‘.join(stem_words)
input_df[‘text’] = input_df[‘text’].apply(lambda x: stemming_text(x))

 

    b) Lematização

A lematização, ao contrário de Stemming, reduz as palavras flexionadas adequadamente, garantindo que a palavra raiz (lema) pertence ao idioma. Embora a lematização seja mais lenta em comparação com a Stemming, ela considera o contexto da palavra levando em consideração a palavra anterior, o que resulta em melhor precisão.

 

Código de explicação:

w_tokenizer = nltk.tokenize.WhitespaceTokenizer()
lemmatizer = nltk.stem.WordNetLemmatizer()
def lemmatize_text(text):
    lemma_words = [lemmatizer.lemmatize(w) for w in w_tokenizer.tokenize(text)]
    return ‘ ‘.join(lemma_words)
print(lemmatize_text('study'),
     lemmatize_text('studying') ,
     lemmatize_text('studies'))

 

Saída:

study studying study

print(lemmatize_text('trembling'),
     lemmatize_text('tremble') ,
     lemmatize_text('trembly'))

 

Saída:

trembling tremble trembly

 

Código de implementação:

input_df[‘text’] = input_df[‘text’].apply(lemmatize_text)

 

4) Vetorizador

Esta é a etapa em que as palavras são convertidas em números, que podem ser processados ​​pelos algoritmos. Esses números resultantes estão na forma de vetores, daí o nome.

    1) Modelo Bag of words

Este é o mais básico dos vetorizadores. O vetor formado contém palavras no texto e sua frequência. É como se as palavras fossem colocadas em um saco. A ordem das palavras não é mantida.

 

Código de explicação:

from sklearn.feature_extraction.text import CountVectorizer
bagOwords = CountVectorizer()
print(bagOwords.fit_transform(text).toarray())
print('Features:', bagOwords.get_feature_names())

 

Entrada:

text = [“I like the product very much. The quality is very good.”,

“The product is very very good”,

“Broken product delivered”,

“The product is good, but overpriced product”,

“The product is not good”]

 

Saída:

array([[0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 2, 2],

[0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1],

[1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],

[0, 1, 0, 1, 1, 0, 0, 0, 1, 2, 0, 1, 0],

[0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0]])

Features: [‘broken’, ‘but’, ‘delivered’, ‘good’, ‘is’, ‘like’, ‘much’, ‘not’, ‘overpriced’, ‘product’, ‘quality’, ‘the’, ‘very’]

 

Os vetores dos textos 2 e 5 (ao contrário do que se espera), por serem de sentidos opostos, não diferem tanto. A matriz retornada é esparsa.

    2) n-gramas

Ao contrário da abordagem do saco de palavras, a abordagem de n-gram depende da ordem das palavras para derivar seu contexto. O n-gram é uma sequência contígua de “n” itens em um texto, portanto, o conjunto de recursos criado com o recurso n-grams terá um número n de palavras consecutivas como recursos. O valor para “n” pode ser fornecido como um intervalo.

 

Código de explicação:

count_vec = CountVectorizer(analyzer='word', ngram_range=(1, 2))
print('Features:', count_vec.get_feature_names())
print(count_vec.fit_transform(text).toarray())

 

Saída:

[[0 0 0 0 0 1 0 1 0 0 1 1 1 1 1 0 0 0 0 1 0 0 1 1 1 2 1 1 2 1 1]

[0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 1 0 1 1 0]

[1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0]

[0 0 1 1 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 2 0 1 0 0 0 1 1 0 0 0 0]

[0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 1 1 0 0 1 0 1 0 0 0 1 1 0 0 0 0]]

Features: [‘broken’, ‘broken product’, ‘but’, ‘but overpriced’, ‘delivered’, ‘good‘, ‘good but’, ‘is’, ‘is good’, ‘is not’, ‘is very’, ‘like’, ‘like the’, ‘much’, ‘much the’, ‘not’, ‘not good’, ‘overpriced’, ‘overpriced product’, ‘product’, ‘product delivered’, ‘product is’, ‘product very’, ‘quality’, ‘quality is’, ‘the’, ‘the product’, ‘the quality’, ‘very’, ‘very good’, ‘very much’]

 

    3) TF-IDF

Só porque uma palavra aparece com alta frequência não significa que a palavra acrescenta um efeito significativo sobre o sentimento que procuramos. A palavra pode ser comum a todos os textos de amostra. Por exemplo, a palavra ‘product’ em nossa amostra é redundante e não fornece muitas informações relacionadas ao sentimento. Isso apenas aumenta a duração do recurso.

Frequência do termo (TF) – é a frequência das palavras em um texto de amostra.

Frequência inversa do documento (IDF) – destaca a frequência das palavras em outros textos de amostra. Os recursos são raros ou comuns nos textos de exemplo é a principal preocupação aqui.

Quando usamos ambos os TF-IDF juntos (TF * IDF), as palavras de alta frequência em um texto de exemplo que tem baixa ocorrência em outros textos de exemplo recebem maior importância.

 

Código de explicação

from sklearn.feature_extraction.text import TfidfVectorizer
tf_idf_vec = TfidfVectorizer(use_idf=True,
                        smooth_idf=False,
                        ngram_range=(1,1))
print(tf_idf_vec.fit_transform(text).toarray())
print('Features:', tf_idf_vec.get_feature_names())

 

Saída

No terceiro texto de exemplo, as palavras de valores ‘broken e delivered‘ são raras em todos os textos e recebem pontuação mais alta do que ‘product, que é uma palavra recorrente.

 

Código de implementação      

tfidf_vec = TfidfVectorizer(use_idf=True)
                    tfidf_vec.fit(input_df[‘text’])
                    tfidf_result = tfidf_vec.transform(input_df[‘text’])

 

5) Desequilíbrio de classe

Geralmente, esse tipo de cenário terá um desequilíbrio de classe. Os dados de texto incluíam mais casos de sentimento positivo do que negativo. A maneira mais simples de lidar com o desequilíbrio de classe é aumentando os dados com cópias exatas da classe minoritária (neste caso, os cenários de sentimento negativo). Essa técnica é chamada de sobreamostragem.

Algoritmo de Machine Learning

Após a conclusão das etapas de processamento, os dados estão prontos para serem passados ​​para um algoritmo de ML para ajuste e previsão. Este é um processo iterativo no qual um algoritmo adequado é escolhido e o ajuste do hiperparâmetro é feito.

Um ponto a ser observado aqui é que, aparentemente, como qualquer outro problema de ML, as etapas de pré-processamento devem ser tratadas após a divisão de treinamento e teste.

 

Código de implementação

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
rnd_mdl = RandomForestClassifier()
rnd_mdl.fit(tfidf_result, input_df[‘label’])
#Using the fitted model to predict from the test data
#test_df is the test data and tfidf_result_test is the preprocessed test text data
output_test_pred = rnd_mdl.predict(tfidf_result_test)
#finding f1 score for the generated model 
test_f1_score = f1_score(test_df[‘label’], output_test_pred)

 

Biblioteca pré-construída

Há uma biblioteca pré-construída em NLTK que pontua os dados de texto com base no sentimento. Ele não precisa dessas etapas de pré-processamento. É denominado nltk.sentiment.SentimentAnalyzer.

Finalizando

Existem muitos modelos avançados de Deep Learning pré-treinados disponíveis para PLN. O pré-processamento envolvido, ao usar essas redes profundas, varia consideravelmente da abordagem de ML fornecida aqui.

Esta é uma introdução simples ao interessante mundo da PLN! É um vasto espaço em constante evolução!

Traduzido de analyticsvidhya.com

Autores

Coordenadora de Pesquisa | Doutora em Ciência da Computação pela Universidade Federal do Ceará.