fbpx

19 jul
Usando Redes Neurais Recorrentes na Sugestão de Palavras

Você já teve curiosidade em saber como o Google prevê qual a próxima palavra que você vai digitar em uma pesquisa?

Isso é feito através de uma tarefa conhecida como Next Word Prediction ou Language Modelling. Essa tarefa é uma das mais básicas do Processamento de Linguagem Natural (PLN), e também é usada em teclados de smartphone, e em muitas outras aplicações, como as que envolvem sumarização de texto, por exemplo.

Neste post, apresento um pequeno tutorial de como fazer um preditor de próximas palavras utilizando Redes Neurais implementadas com a ferramenta TensorFlow.

Redes Neurais Recorrentes

Redes Neurais são modelos matemáticos usados na computação para a aprendizagem de máquina e cujo objetivo é simular o sistema nervoso de um animal para “aprender” coisas úteis para uma tarefa. Por exemplo, aprender como uma palavra pode ser traduzida para outro idioma é útil para um sistema de tradução automática. Ou para um transcritor, como o nosso Transcritor Élfico.

Essas redes são formadas por neurônios artificiais interconectados, que computam valores de entrada para fornecer uma saída, simulando o funcionamento de um neurônio biológico. Esses neurônios são unidades de processamento que interagem entre si e estão organizados em camadas. É da interação entre os neurônios e as camadas que vem a capacidade de inteligência da Rede Neural.

Quando o conteúdo é passado mais de uma vez pela rede neural para reforçar o aprendizado, ela passa a ser chamada de Rede Neural Recorrente. O seu uso leva em consideração que o que é aprendido por um neurônio biológico não deve ser esquecido, mas reutilizado para “refinar”o aprendizado.

Fonte da imagem: https://leonardoaraujosantos.gitbooks.io/artificial-inteligence/content/recurrent_neural_networks.html

Vamos ao código

Redes Neurais Recorrentes podem ser implementadas com a ferramenta TensorFlow, dentre outras, que é uma biblioteca de software para aprendizagem de máquina mantida pelo Google. Essa ferramenta fornece uma interface que torna a construção de aplicações de aprendizagem de máquina mais simples.

No nosso exemplo, nós usaremos a API Keras, que tem o objetivo de reduzir o esforço cognitivo necessário para a construção de modelos de aprendizagem de máquina. Portanto, vamos começar instalando essa API e outras dependências do nosso projeto:

pip install tensorflow
pip install keras
pip install numpy
pip install matplotlib
pip install nltk
pip install pickle
This Gist brought to you by gist-it.view rawREADME.md

Base de treinamento

Com tudo instalado, o que precisamos agora é preparar a nossa base de treinamento, que é de onde a nossa rede neural vai aprender a predizer a próxima palavra.

Nós usaremos um corpus (documento de texto) em português brasileiro, extraído a partir de notícias do site da Revista Pesquisa FAPESP. Você deve baixar o corpus usando este link e salvá-lo na pasta data, ou executar a primeira linha do trecho de código abaixo no seu terminal. Depois, precisamos extrair os arquivos baixados (segunda linha), e extrair a parte do corpus que é em português.

        wget -P ./data/ "http://www.nilc.icmc.usp.br/nilc/tools/fapesp-corpora.tar.gz"
        tar -xvzf  ./data/fapesp-corpora.tar.gz -C data
        tar -xvzf ./data/fapesp-corpora/corpora/pt.tgz -C data/fapesp-corpora/corpora/
This Gist brought to you by gist-it.view rawmakefile

O script Python a seguir tem a função de compilar os arquivos do corpus em um único TXT com texto plano. Nele é possível definir a quantidade de arquivos serão utilizados, uma vez que o corpus é muito grande e, dependendo da capacidade de processamento da máquina, usar ele por inteiro poderia fazer o modelo levar muito tempo para ser treinado. De todo modo, fica ao seu critério. Se quiser utilizar o corpus inteiro, só apagar o trecho [:FILES_NUMBER] do script.

from os import listdir
from os.path import isfile, join
dataset_path = './data/fapesp-corpora/corpora/pt/data'
onlyfiles = [f for f in listdir(dataset_path) if isfile(join(dataset_path, f))]

FILES_NUMBER = 20

trainin_text = ''
for file_ in onlyfiles[:FILES_NUMBER]:
    trainin_text += open(dataset_path+"/"+file_).read()

training_corpus = open('./data/training_corpus.txt', 'w')
training_corpus.write(trainin_text)

Treinamento

Primeiro vamos importar as bibliotecas necessárias no treinamento:

import numpy as np
from nltk.tokenize import RegexpTokenizer
from keras.models import Sequential, load_model
from keras.layers import LSTM
from keras.layers.core import Dense, Activation
from keras.optimizers import RMSprop
import matplotlib.pyplot as plt
import json
import pickle
This Gist brought to you by gist-it.view rawtrain.py

Depois, carregamos a base de treinamento.

path = 'data/training_corpus.txt'
text = open(path).read().lower()
This Gist brought to you by gist-it.view rawtrain.py

Então, utilizamos um tokenizador para dividir o corpus em tokens, ou seja, em palavras.

tokenizer = RegexpTokenizer(r'\w+')
words = tokenizer.tokenize(text)
This Gist brought to you by gist-it.view rawtrain.py

Em seguida, precisamos ter a lista das palavras únicas presentes no corpus. Além disso, precisamos de um dicionário (<chave: valor>) com cada palavra da lista de palavras únicas como chave e sua posição correspondente como valor (e.g., <clube: 128 , dos: 129, …>).

unique_words = np.unique(words)
print("Qtd. de palavras únicas:", len(unique_words))
unique_word_index = dict((c, i) for i, c in enumerate(unique_words))
This Gist brought to you by gist-it.view rawtrain.py

Engenharia de Features

Em Aprendizagem de Máquina features são características que descrevem um determinado recurso de aprendizagem. Por exemplo, se você construir um modelo para identificar objetos em fotografias, é natural que as features sejam características desses objetos (cor, tamanho, forma, etc.).

Em se tratando de texto, as features podem ser informações sobre como as palavras estão dispostas nas frases, ou como elas se relacionam, etc. No nosso caso, usaremos como features as palavras que antecedem aquela que desejamos predizer. Para isso, definimos a variável SEQUENCE_LENGTH, que determina a quantidade de palavras anteriores que usaremos na predição. Além disso, criamos uma lista vazia chamada prev_words, onde as palavras anteriores serão armazenadas, e uma lista next_words para as suas “próximas palavras” correspondentes. Preenchemos essas listas fazendo um loop em um intervalo de 3 menor que o comprimento das palavras.

SEQUENCE_LENGTH = 3
prev_words = []
next_words = []
for i in range(len(words) - SEQUENCE_LENGTH):
    prev_words.append(words[i:i + SEQUENCE_LENGTH])
    next_words.append(words[i + SEQUENCE_LENGTH])
This Gist brought to you by gist-it.view rawtrain.py

Depois, criamos dois arrays utilizando a biblioteca numpy: X para as features e Y para os rótulos correspondentes, ou seja, as próximas palavras.

X = np.zeros((len(prev_words), SEQUENCE_LENGTH, len(unique_words)), dtype=bool)
Y = np.zeros((len(next_words), len(unique_words)), dtype=bool)
for i, each_words in enumerate(prev_words):
    for j, each_word in enumerate(each_words):
        X[i, j, unique_word_index[each_word]] = 1
    Y[i, unique_word_index[next_words[i]]] = 1
This Gist brought to you by gist-it.view rawtrain.py

Esses arrays podem parecer estranhos à primeira vista, mas vale destacar que esse tipo de encoding, conhecido como One Hot Encoding torna a aprendizagem e a predição mais eficiente, como explicado no vídeo abaixo (ative as legendas):

O nosso modelo

Nós usamos um modelo LSTM com uma única camada e 128 neurônios, uma camada de conexão (Dense), e a função softmax para ativação.

model = Sequential()
model.add(LSTM(128, input_shape=(SEQUENCE_LENGTH, len(unique_words))))
model.add(Dense(len(unique_words)))
model.add(Activation('softmax'))
This Gist brought to you by gist-it.view rawtrain.py

Treinando

Vamos treinar nosso modelo em 120 epochs, valor que pode ser alterado de acordo considerando capacidade de processamento da sua máquina, sua pressa e o desempenho desejado para a sua rede neural. Usaremos o algoritmo RMSprop para otimização.

optimizer = RMSprop(lr=0.01)

model.compile(loss='categorical_crossentropy',
              optimizer=optimizer, metrics=['accuracy'])

history = model.fit(X, Y, validation_split=0.05,
                    batch_size=128, epochs=120, shuffle=True).history
This Gist brought to you by gist-it.view rawtrain.py

Por fim, salvamos nosso modelo e o histórico de treinamento, e plotamos o histórico para ver a evolução da acurácia obitida pelo modelo.

model.save('saved_models/word_prediction.h5')
pickle.dump(history, open("saved_models/history.p", "wb"))

plt.plot(history['accuracy'])
plt.plot(history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

plt.plot(history['loss'])
plt.plot(history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
This Gist brought to you by gist-it.view rawtrain.py

Testando

Só lembrando: o nosso modelo deve prever a próxima palavra considerando as 3 anteriores, ou as SEQUENCE_LENGH anteriores. Por isso, precisamos de um texto prévio para a predição. Esse texto é preparado pela seguinte função:

def prepare_input(text):
    x = np.zeros((1, SEQUENCE_LENGTH, len(unique_words)))
    for t, word in enumerate(text.split()):
        print(word)
        x[0, t, unique_words.index(word)] = 1
    return x
This Gist brought to you by gist-it.view rawtest.py

Usamos a seguinte função para escolher as palavras com maior probabilidade, conforme predição do modelo:

def sample(preds, top_n=3):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds)
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
This Gist brought to you by gist-it.view rawtest.py

E fazemos a previsão assim:

def predict_completions(text, n=3):
    if text == "":
        return("0")
    x = prepare_input(text)
    preds = model.predict(x, verbose=0)[0]
    next_indices = sample(preds, n)
    return [unique_words[idx] for idx in next_indices]

q = "a relação entre a"
print("correct sentence: ", q)
seq = " ".join(tokenizer.tokenize(q.lower())[0:SEQUENCE_LENGTH])
print("Sequence: ", seq)
print("next possible words: ", predict_completions(seq, 5))
This Gist brought to you by gist-it.view rawtest.py

Geração de linguagem natural

Nós ainda podemos usar o nosso modelo para fazer geração de texto automático. Tomando como entrada 3 palavras e gerando uma frase como palavras, semelhante ao que fizemos com as Cadeias de Markov.

sentence = 'os únicos envolvidos'
words = sentence.split(" ")
while len(words) < SENTENCE_LENGTH:
    q = (["0"]*SEQUENCE_LENGTH)[:SEQUENCE_LENGTH-len(words)] + words
    sentence = ' '.join(q)
    seq = " ".join(tokenizer.tokenize(sentence.lower())[0:SEQUENCE_LENGTH])
    preds = predict_completions(seq, 1)
    if len(preds) > 0:
        words.append(preds[0])

print("Generated sentence: ", " ".join(words))

Conclusões

Nesse exemplo usamos como vocabulário apenas as palavras presentes no corpus de treinamento, que não é um número muito grande. Assim, se você quiser um modelo mais abrangente, precisa usar um vocabulário maior, como o do Freeling, por exemplo, ou de algum dicionário do português.

Pode ser que os resultados obtidos nos testes a partir da execução do nosso modelo, não sejam muito bons. Para melhorá-los, você pode tentar aumentar o tamanho do corpus de treinamento, a quantidade de épocas, ou utilizar Word Embeddings para a extração de features (Cenas dos próximos capítulos).

O código usado neste tutorial está disponível no GitHub.

Referências

Publicado por Clube dos Geeks

Deixe um comentário

Seja o Primeiro a Comentar!

avatar

Compartilhe com um amigo

Tags

Tem uma pergunta?
Nós estamos aqui para ajudar. Envie-nos um e-mail ou ligue-nos para (86) 3133-7070
Entrar em contato!
© 2017 iCEV Instituto de Ensino Superior