Aula 35 - TensorFlow - Keras - Redes Neurais - RNN Chatbot
Abordagem seq2seq para criar chatbots generativos.
Existem várias tarefas que podem ser resolvidas usando a modelagem
seq2seq:
resumo de texto,
reconhecimento de fala,
legenda de imagem e vídeo e
resposta a perguntas.
Também pode ser usado em genôma para modelagem de sequências de
DNA.
Na prática, são
2 redes neurais recorrentes bem simples que compartilham uma mesma camada oculta e não tem camada de ativação, só uma célula
GRU ou
LSTM que realiza o processamento.
O
seq2seq é considerado um
Modelo de Linguagem Condicional, porque a saída depende do contexto identificado pela rede na entrada, e também das palavras anteriores já previstas como saída, para então prever a próxima palavra da sentença de saída.
O que o modelo prevê é:
P(y|x)
Ou seja, dada a sentença
x como entrada, qual a probabilidade de gerar a sentença de saída
y?
Dada a entrada
x, eu gero o primeiro token,
y1, agora, dado a entrada
x e mais o primeiro token gerado, o
y1. Qual a probabilidade de gerar o segundo token, o
y2?
E assim por diante:
P(x|y) = P(y1|x)
=> P(y2 | y1, x)
=> P(y3 | y1, y2, x) ...
=> P(yT | y1, ..., yT - 1, x)
Um modelo
seq2seq tem duas partes: um
codificador e um
decodificador.
Ambos trabalham separadamente e se unem para formar um grande modelo de rede neural.
O
seq2seq é também chamado de modelo
codificador-decodificador.
Utiliza
Long Short Term Memory-LSTM para geração de texto a partir do corpus de treinamento, ou seja, a base de dados.
Como o modelo seq2seq funciona?
Codificador
O
codificador está na extremidade de alimentação da rede, ele entende a sequência e reduz a dimensão da sequência de entrada.
A sequência tem um tamanho fixo conhecido como vetor de contexto.
Esse vetor de contexto atua como entrada para o decodificador, que gera uma sequência de saída ao atingir o token final.
Essa arquitetura pode lidar com sequências de entrada e saída de comprimento variável.
Decodificador
Se você usar
LSTM para o
codificador, use o mesmo para o
decodificador.
Você pode dizer que o
decodificador está em um "
estado consciente".
Ele sabe quais palavras você gerou até agora e qual era o estado oculto anterior.
A primeira camada do decodificador é inicializada usando o vetor de contexto 'C' da rede do codificador para gerar a saída.
Em seguida, um token especial é aplicado no início para indicar a geração de saída.
Aplica um token semelhante no final.
A primeira palavra de saída é gerada pela execução das camadas LSTM empilhadas.
Uma função de ativação
SoftMax se aplica à última camada.
Seu trabalho é introduzir a não linearidade na rede.
Agora esta palavra é passada pelas camadas restantes e a sequência de geração é repetida.
Vários fatores dependem da melhoria da precisão do modelo
codificador-
decodificador.
Os hiperparâmetros como otimizadores, perda de entropia cruzada, taxa de aprendizado, etc. Desempenham um papel importante na melhoria do desempenho do modelo.
Exemplo
No exemplo abaixo, é feita uma distribuição de probabilidades de sequências de saída, maximizando a probabilidade de uma sequência alvo como saída, em função de uma determinada sequência anterior recebida como entrada.
Ele prevê uma palavra dada na entrada do usuário e em seguida, cada uma das próximas palavras é prevista usando a probabilidade de ocorrência dessa palavra.
O codificador emite um vetor de estado final (memória) que se torna o estado inicial para o decodificador.
Usamos um método chamado de aprendizado forçado, ou
teacher forcing, para treinar o decodificador, que permite prever as seguintes palavras em uma sequência alvo, dada as palavras anteriores.
Como mostrado acima, os estados são passados através do codificador, para cada camada do decodificador.
'
Hi', '
how', '
are' e '
you' são chamados de
tokens de
entrada, enquanto '
i', '
am' e '
fine' são chamados de
tokens alvo.
A probabilidade do token '
am' depende das palavras anteriores e dos estados do codificador.
Estamos adicionando o token '
<END>' para que o decodificador saiba quando parar.
Construindo um chatbot generativo do zero
A primeira tarefa que teremos que fazer é pré-processar o conjunto de dados.
Pré-processamento do conjunto de dados
Os dados que iremos usar contém respostas humanas (
human_text.txt) e respostas de bot(
robot_text.txt).
Há
2363 entradas para cada um.
Primeiro, teremos que limpar nosso corpus com a ajuda de Expressões Regulares.
Em seguida, precisaremos fazer pares de resposta humana-bot, para que possamos treinar nosso modelo
seq2seq.
Faremos essas tarefas conforme mostrado abaixo.
import re
import random
data_path = "human_text.txt"
data_path2 = "robot_text.txt"
# Defining lines as a list of each line
with open(data_path, 'r', encoding='utf-8') as f:
lines = f.read().split('\n')
with open(data_path2, 'r', encoding='utf-8') as f:
lines2 = f.read().split('\n')
lines = [re.sub(r"\[\w+\]",'hi',line) for line in lines]
lines = [" ".join(re.findall(r"\w+",line)) for line in lines]
lines2 = [re.sub(r"\[\w+\]",'',line) for line in lines2]
lines2 = [" ".join(re.findall(r"\w+",line)) for line in lines2]
# grouping lines by response pair
pairs = list(zip(lines,lines2))
#random.shuffle(pairs)
Vale a pena esmiuçar um pouquinho as linhas das regex.
Regex
re.sub()
O
re.sub() pode recebe os seguintes parâmetros:
re.sub(pattern, repl, string, count=0, flags=0)
Retorna a string obtida, substituindo as ocorrências não sobrepostas da extremidade esquerda do pattern na string pela substituição
repl.
Se o padrão não for encontrado, string será retornado inalterado.
O repl pode ser uma string ou uma função, se for uma string, qualquer escape de contrabarra será processado.
O
r significa que a string será tratada como string bruta.
Ao contrário de uma string normal, uma string bruta trata as barras invertidas (
\ ) como caracteres literais.
Strings brutas são úteis quando você lida com strings que possuem muitas barras invertidas, por exemplo, expressões regulares ou caminhos de diretório.
Ou seja,
\n é convertido em um único caractere de nova linha,
\r é convertido em um retorno e assim por diante.
O
\[ e depois no final da string pattern
\], é para poder representar os caracteres especiais
[ e
] e garantir que algumas linhas, como:
[start],
[voice], etc. Passem na correspondência.
\w - corresponde a quando não é alfa numérico, e o
+ conhecido como indicador de ocorrência (ou operador de repetição), indica uma ou mais ocorrências (
1+) da subexpressão anterior.
Nesse caso,
[a-zA-Z]+ ou seja,
\w+ corresponde a um ou mais dígitos.
re.findall()
O
re.findall() retorna todas as correspondências não sobrepostas do padrão na string, como uma lista de strings ou tuplas.
A string é verificada da esquerda para a direita e as correspondências são retornadas na ordem encontrada.
Correspondências vazias são incluídas no resultado.
O resultado depende do número de grupos de captura no padrão.
Se não houver grupos, retorne uma lista de strings que correspondem a todo o padrão.
Se houver exatamente um grupo, retorne uma lista de strings correspondentes a esse grupo.
Se vários grupos estiverem presentes, retorne uma lista de tuplas de strings correspondentes aos grupos.
Grupos sem captura não afetam a forma do resultado.
Depois de criar os pares, também podemos embaralhá-los antes do treino.
Nossos pares ficarão assim agora:
[('hi', 'hi there how are you'), ('oh thanks i m fine this is an evening in my timezone', 'here is afternoon'),...]
Aqui, '
hi' é a sequência de entrada e '
hi there how are you' é uma sequência de saída ou alvo.
Por essa aula é só, nos vemos na próxima, valeu \o/.
Meu github:
Novamente deixo meus link de afiliados:
Obrigado, até a próxima e bons estudos. ;)