Introduction
Problemas de previsão de sequência já existem há muito tempo. Eles são considerados como um dos problemas mais difíceis de resolver na indústria da ciência dos dados. Estes incluem uma grande variedade de problemas; desde prever vendas até encontrar padrões nos dados do mercado de ações, desde compreender enredos de filmes até reconhecer seu modo de falar, desde traduções de idiomas até prever sua próxima palavra no teclado do seu iPhone.
Com os recentes avanços que têm acontecido na ciência dos dados, descobriu-se que para quase todos esses problemas de previsão de seqüências, redes de Memória de Longo Prazo de Curto Prazo, a.k.a LSTMs têm sido observadas como a solução mais eficaz.
LSTMs têm uma vantagem sobre as redes neurais convencionais de alimentação-forward e RNN de muitas maneiras. Isto é devido a sua propriedade de lembrar padrões seletivamente por longas durações de tempo. O objetivo deste artigo é explicar a LSTM e permitir que você a use em problemas da vida real. Vamos dar uma olhada!
Note: Para ler o artigo, você deve ter conhecimentos básicos sobre redes neurais e como funciona o Keras (uma biblioteca de aprendizagem profunda). Você pode consultar os artigos mencionados para entender esses conceitos:
- Entendendo Redes Neurais do Rastro
- Fundamentals of Deep Learning – Introduction to Recurrent Neural Networks
- Tutorial: Otimização de Redes Neurais usando Keras (com estudo de caso de reconhecimento de imagem)
Tabela de Conteúdo
- Flashback: Um olhar sobre Redes Neurais Recorrentes (RNN)
- Limitações de RNNs
- Aprimoramento sobre RNNN : Memória de Curto Prazo Longo (LSTM)
- Arquitetura da LSTM
- Esquecimento da Porta
- Input Gate
- Output Gate
- Geração de texto usando LSTMs.
Flashback: Um olhar sobre Redes Neurais Recorrentes (RNN)
Tomar um exemplo de dados sequenciais, que podem ser os dados da bolsa de valores para uma determinada ação. Um simples modelo de aprendizagem da máquina ou uma Rede Neural Artificial pode aprender a prever os preços das ações com base em uma série de características: o volume da ação, o valor de abertura, etc. Embora o preço da ação dependa dessas características, ele também depende em grande parte dos valores das ações nos dias anteriores. Na verdade, para um trader, esses valores nos dias anteriores (ou a tendência) é um dos principais fatores decisivos para as previsões.
Nas redes neurais convencionais feed-forward, todos os casos de teste são considerados independentes. Ou seja, quando o modelo é adequado para um determinado dia, não há consideração pelos preços das ações nos dias anteriores.
Esta dependência do tempo é obtida através das Redes Neurais Recorrentes. Um RNN típico se parece:
Isso pode ser intimidante à primeira vista, mas uma vez desdobrado, parece muito mais simples:
Agora é mais fácil para nós visualizarmos como essas redes estão considerando a tendência dos preços das ações, antes de prevermos os preços das ações para hoje. Aqui cada previsão no tempo t (h_t) depende de todas as previsões anteriores e das informações aprendidas com elas.
RNNs podem resolver o nosso propósito de tratamento de sequências em grande medida, mas não inteiramente. Queremos que nossos computadores sejam suficientemente bons para escrever sonetos shakespeareanos. Agora os RNNs são ótimos quando se trata de contextos curtos, mas para poder construir uma história e lembrá-la, precisamos de nossos modelos para poder entender e lembrar o contexto por trás das seqüências, assim como um cérebro humano. Isto não é possível com um simples RNNN.
Porquê? Vamos dar uma olhada.
Limitações de RNNs
Redes Neurais Atuais funcionam muito bem quando estamos lidando com dependências de curto prazo. Isso é quando aplicado a problemas como:
RNNs acabam sendo bastante eficazes. Isto porque este problema não tem nada a ver com o contexto da afirmação. O RNN não precisa lembrar o que foi dito antes disso, ou qual era o seu significado, tudo que precisam saber é que na maioria dos casos o céu é azul. Assim a previsão seria:
No entanto, os RNNs de baunilha não entendem o contexto por trás de uma entrada. Algo que foi dito muito antes, não pode ser lembrado ao se fazer previsões no presente. Vamos entender isto como um exemplo:
Aqui, podemos entender que como o autor trabalha na Espanha há 20 anos, é muito provável que ele possua um bom domínio sobre o espanhol. Mas, para fazer uma previsão adequada, o RNN precisa de se lembrar deste contexto. A informação relevante pode estar separada do ponto onde é necessária, por uma enorme carga de dados irrelevantes. É aqui que uma Rede Neural Recorrente falha!
A razão por trás disso é o problema de Vanishing Gradient. Para entender isso, você precisará ter algum conhecimento sobre como uma rede neural feed-forward aprende. Sabemos que para uma rede neural convencional de alimentação para frente, a atualização de peso que é aplicada em uma determinada camada é um múltiplo da taxa de aprendizagem, o termo de erro da camada anterior e a entrada para essa camada. Assim, o termo de erro de uma camada em particular é em algum lugar um produto de todos os erros das camadas anteriores. Quando se trata de funções de ativação como a função sigmóide, os pequenos valores de suas derivadas (que ocorrem na função de erro) são multiplicados várias vezes à medida que nos deslocamos em direção às camadas iniciais. Como resultado disso, o gradiente quase desaparece conforme nos deslocamos em direção às camadas iniciais, e torna-se difícil treinar essas camadas.
Um caso similar é observado em Redes Neurais Recorrentes. O RNN lembra-se das coisas apenas por pequenas durações de tempo, ou seja, se precisarmos da informação após um pequeno tempo ela pode ser reproduzível, mas uma vez que muitas palavras são alimentadas, esta informação se perde em algum lugar. Esta questão pode ser resolvida aplicando uma versão ligeiramente ajustada dos RNNs – a Long ShortTerm Memory Networks.
Improvement over RNNN: LSTM (Long Short-Term Memory) Networks
Quando organizamos nosso calendário para o dia, nós priorizamos nossos compromissos, certo? Se no caso de precisarmos de algum espaço para algo importante, sabemos que reunião poderia ser cancelada para acomodar uma possível reunião.
Everdita que um RNN não o faz. A fim de adicionar uma nova informação, ele transforma completamente a informação existente aplicando uma função. Por causa disso, toda a informação é modificada, no conjunto, ou seja, não há consideração por informação ‘importante’ e informação ‘não tão importante’.
LSTMs, por outro lado, faz pequenas modificações na informação através de multiplicações e adições. Com as LSTMs, a informação flui através de um mecanismo conhecido como estados de célula. Desta forma, as LSTMs podem lembrar ou esquecer as coisas de forma seletiva. A informação em um determinado estado de célula tem três dependências diferentes.
Vamos visualizar isto com um exemplo. Vamos pegar o exemplo de previsão de preços de ações para uma determinada ação. O preço das ações de hoje vai depender:
- A tendência que a ação tem seguido nos dias anteriores, talvez uma tendência de baixa ou de alta.
- O preço da ação no dia anterior, porque muitos comerciantes comparam o preço da ação do dia anterior antes de comprá-la.
- Os fatores que podem afetar o preço da ação de hoje. Esta pode ser uma nova política da empresa que está sendo amplamente criticada, ou uma queda no lucro da empresa, ou talvez uma mudança inesperada na liderança sênior da empresa.
Estas dependências podem ser generalizadas a qualquer problema como:
- O estado anterior da célula (ou seja, a informação que estava presente na memória após o passo de tempo anterior)
- O estado anterior oculto (ou seja isto é o mesmo que a saída da célula anterior)
- A entrada no passo de tempo atual (ou seja, a nova informação que está sendo alimentada naquele momento)
Outra característica importante do LSTM é a sua analogia com as correias transportadoras!
É isso mesmo!
As indústrias utilizam-nas para movimentar produtos para diferentes processos. As LSTMs utilizam este mecanismo para movimentar informações.
Podemos ter alguma adição, modificação ou remoção de informação enquanto ela flui através das diferentes camadas, assim como um produto pode ser moldado, pintado ou embalado enquanto está em uma esteira transportadora.
O diagrama seguinte explica a estreita relação entre as LSTMs e as correias transportadoras.
Fonte
Embora este diagrama não esteja nem perto da arquitetura real de uma LSTM, ele resolve nosso propósito por enquanto.
Apenas por causa desta propriedade das LSTMs, onde elas não manipulam toda a informação, mas as modificam ligeiramente, elas são capazes de esquecer e lembrar as coisas seletivamente. Como eles fazem isso, é o que vamos aprender na próxima seção?
Arquitetura de LSTMs
O funcionamento do LSTM pode ser visualizado ao entender o funcionamento da equipe de um canal de notícias cobrindo uma história de assassinato. Agora, uma notícia é construída em torno de fatos, provas e declarações de muitas pessoas. Sempre que um novo evento ocorre você dá um dos três passos.
Por exemplo, estávamos assumindo que o assassinato foi feito por ‘envenenamento’ da vítima, mas o relatório da autópsia que acabou de chegar dizia que a causa da morte foi ‘um impacto na cabeça’. Fazendo parte desta equipa de jornalistas, o que é que vocês fazem? Você imediatamente esquece a causa anterior da morte e todas as histórias que foram tecidas em torno deste fato.
O que, se um suspeito totalmente novo é introduzido na imagem. Uma pessoa que tivesse ressentimentos com a vítima e pudesse ser o assassino? Você insere essa informação no seu feed de notícias, certo?
Agora todas essas informações quebradas não podem ser servidas na mídia convencional. Então, após um certo intervalo de tempo, você precisa resumir esta informação e dar as coisas relevantes para o seu público. Talvez na forma de “XYZ acaba sendo o principal suspeito”.
Agora vamos entrar nos detalhes da arquitetura da rede LSTM:
Source
Agora, esta não é em nenhum lugar perto da versão simplificada que vimos antes, mas deixe-me acompanhá-lo através dela. Uma típica rede LSTM é composta por diferentes blocos de memória chamados células
(os rectângulos que vemos na imagem). Há dois estados que estão sendo transferidos para a próxima célula; o estado de célula e o estado oculto. Os blocos de memória são responsáveis por lembrar coisas e manipulações a esta memória é feita através de três mecanismos principais, chamados portões. Cada um deles está sendo discutido abaixo.
4.1 Esqueça o Portal
Tomando o exemplo de um problema de predição de texto. Vamos assumir que um LSTM é alimentado, a seguinte frase:
> Assim que a primeira parada completa após “pessoa” é encontrada, a porta do esquecimento percebe que pode haver uma mudança de contexto na próxima frase. Como resultado disso, o assunto da frase é esquecido e o lugar para o assunto é deixado vago. E quando começamos a falar sobre “Dan” esta posição do sujeito é atribuída a “Dan”. Este processo de esquecer o assunto é trazido pela porta do esquecimento.
A porta do esquecimento é responsável por retirar informações do estado da célula. A informação que não é mais necessária para o LSTM entender as coisas ou a informação que é de menor importância é removida através da multiplicação de um filtro. Isto é necessário para otimizar o desempenho da rede LSTM.
Esta porta recebe duas entradas; h_t-1 e x_t.
h_t-1 é o estado oculto da célula anterior ou a saída da célula anterior e x_t é a entrada naquele passo de tempo em particular. Os inputs dados são multiplicados pelas matrizes de peso e um bias é adicionado. Em seguida, a função sigmóide é aplicada a este valor. A função sigmóide produz um vetor, com valores que variam de 0 a 1, correspondendo a cada número no estado da célula. Basicamente, a função sigmóide é responsável por decidir quais valores devem ser mantidos e quais devem ser descartados. Se um ‘0’ é output para um determinado valor no estado da célula, significa que a porta de esquecimento quer que o estado da célula esqueça completamente aquele pedaço de informação. Da mesma forma, um ‘1’ significa que a porta do esquecimento quer lembrar toda essa informação. Esta saída vetorial da função sigmóide é multiplicada para o estado da célula.
4.2 Input Gate
Okay, vamos dar outro exemplo onde o LSTM está analisando uma frase:
Agora a informação importante aqui é que “Bob” sabe nadar e que ele serviu a Marinha por quatro anos. Isto pode ser adicionado ao estado celular, no entanto, o fato de que ele contou tudo isso pelo telefone é um fato menos importante e pode ser ignorado. Este processo de adição de novas informações pode ser feito através da porta de entrada.
Aqui está a sua estrutura:
A porta de entrada é responsável pela adição de informações ao estado da célula. Esta adição de informação é basicamente um processo de três passos como visto no diagrama acima.
- Regulando quais valores precisam ser adicionados ao estado da célula, envolvendo uma função sigmóide. Isto é basicamente muito similar à porta do esquecimento e atua como um filtro para todas as informações de h_t-1 e x_t.
- Criando um vetor contendo todos os valores possíveis que podem ser adicionados (como percebido de h_t-1 e x_t) ao estado da célula. Isto é feito usando a função tanh, que produz valores de -1 a +1.
- Multiplicando o valor do filtro regulador (a porta sigmóide) ao vetor criado (a função tanh) e então adicionando esta informação útil ao estado da célula através da operação de adição.
Após este processo de três etapas ser feito, nós garantimos que somente esta informação é adicionada ao estado da célula que é importante e não é redundante.
4.3 Output Gate
Nada toda a informação que corre ao longo do estado da célula, está apta para ser gerada em um determinado momento. Vamos visualizar isto com um exemplo:
Nesta frase, pode haver uma série de opções para o espaço vazio. Mas sabemos que o input atual de ‘valente’, é um adjetivo que é usado para descrever um substantivo. Assim, qualquer que seja a palavra a seguir, tem uma forte tendência para ser um substantivo. E assim, Bob poderia ser uma saída apt.
Este trabalho de selecionar informações úteis do estado atual da célula e mostrá-la como uma saída é feito através da porta de saída. Aqui está a sua estrutura:
O funcionamento de uma porta de saída pode ser novamente decomposto em três passos:
- Criando um vector depois de aplicar a função tanh ao estado da célula, escalando assim os valores para o intervalo -1 a +1.
- Fazendo um filtro usando os valores de h_t-1 e x_t, de modo que ele possa regular os valores que precisam ser gerados a partir do vetor criado acima. Este filtro novamente emprega uma função sigmóide.
- Multiplicando o valor deste filtro regulador ao vetor criado no passo 1, e enviando-o como output e também para o estado oculto da próxima célula.
O filtro no exemplo acima vai fazer com que ele diminua todos os outros valores exceto ‘Bob’. Assim o filtro precisa ser construído sobre os valores de entrada e estado oculto e ser aplicado no vetor de estado da célula.
Geração de texto usando LSTMs
Tivemos o suficiente de conceitos teóricos e funcionamento de LSTMs. Agora estaríamos tentando construir um modelo que pode prever alguns n números de caracteres após o texto original de Macbeth. A maioria dos textos clássicos não estão mais protegidos por direitos autorais e podem ser encontrados aqui. Uma versão atualizada do arquivo .txt pode ser encontrada aqui.
Utilizaremos a biblioteca Keras, que é uma API de alto nível para redes neurais e funciona em cima de TensorFlow ou Theano. Portanto, certifique-se de que antes de mergulhar neste código você tem Keras instalado e funcional.
Okay, então vamos gerar algum texto!
-
Importar dependências
>
# Importing dependencies numpy and kerasimport numpyfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import LSTMfrom keras.utils import np_utils
Importamos todas as dependências necessárias e isto é praticamente auto-explicativo.
-
Carregar ficheiro de texto e criar caracteres para mapeamentos inteiros
# load textfilename = "/macbeth.txt"text = (open(filename).read()).lower()# mapping characters with integersunique_chars = sorted(list(set(text)))char_to_int = {}int_to_char = {}for i, c in enumerate (unique_chars): char_to_int.update({c: i}) int_to_char.update({i: c})
O ficheiro de texto está aberto, e todos os caracteres são convertidos para letras minúsculas. A fim de facilitar os seguintes passos, estaríamos mapeando cada caractere para um número respectivo. Isto é feito para facilitar o cálculo da parte do LSTM.
-
Preparar conjunto de dados
# preparing input and output datasetX = Y = for i in range(0, len(text) - 50, 1): sequence = text label =text X.append( for char in sequence]) Y.append(char_to_int)
Os dados são preparados num formato tal que se quisermos que o LSTM preveja o ‘O’ em ‘HELLO’, nós iríamos alimentar como a entrada e como a saída esperada. Da mesma forma, aqui nós fixamos o comprimento da seqüência que queremos (definido como 50 no exemplo) e então salvamos as codificações dos primeiros 49 caracteres em X e a saída esperada, ou seja, o 50º caractere em Y.
-
Reestruturação de X
# reshaping, normalizing and one hot encodingX_modified = numpy.reshape(X, (len(X), 50, 1))X_modified = X_modified / float(len(unique_chars))Y_modified = np_utils.to_categorical(Y)
Uma rede LSTM espera que a entrada esteja na forma em que as amostras são o número de pontos de dados que temos, os passos de tempo é o número de passos dependentes do tempo que existem num único ponto de dados, as características referem-se ao número de variáveis que temos para o valor verdadeiro correspondente em Y. Depois escalamos os valores em X_modificado entre 0 a 1 e um hot encode nossos valores verdadeiros em Y_modificado.
-
Definindo o modelo LSTM
# defining the LSTM modelmodel = Sequential()model.add(LSTM(300, input_shape=(X_modified.shape, X_modified.shape), return_sequences=True))model.add(Dropout(0.2))model.add(LSTM(300))model.add(Dropout(0.2))model.add(Dense(Y_modified.shape, activation='softmax'))model.compile(loss='categorical_crossentropy', optimizer='adam')
É usado um modelo sequencial que é uma pilha linear de camadas. A primeira camada é uma camada LSTM com 300 unidades de memória e retorna as seqüências. Isto é feito para garantir que a próxima camada LSTM receba seqüências e não apenas dados dispersos aleatoriamente. Uma camada dropout é aplicada depois de cada camada LSTM para evitar o ajuste excessivo do modelo. Finalmente, nós temos a última camada como uma camada totalmente conectada com uma ativação ‘softmax’ e neurônios iguais ao número de caracteres únicos, porque precisamos emitir um resultado codificado a quente.
-
Ajustando o modelo e gerando caracteres
# fitting the modelmodel.fit(X_modified, Y_modified, epochs=1, batch_size=30)# picking a random seedstart_index = numpy.random.randint(0, len(X)-1)new_string = X# generating charactersfor i in range(50): x = numpy.reshape(new_string, (1, len(new_string), 1)) x = x / float(len(unique_chars)) #predicting pred_index = numpy.argmax(model.predict(x, verbose=0)) char_out = int_to_char seq_in = for value in new_string] print(char_out) new_string.append(pred_index) new_string = new_string
O modelo cabe em mais de 100 épocas, com um tamanho de lote de 30. Em seguida, fixamos uma semente aleatória (para facilitar a reprodutibilidade) e começamos a gerar caracteres. A previsão do modelo dá a codificação do caractere previsto, ele é então decodificado de volta para o valor do caractere e anexado ao padrão.
Esta é a aparência da saída da rede
Eventualmente, após épocas de treinamento suficientes, ela dará melhores e melhores resultados ao longo do tempo. É assim que você usaria LSTM para resolver uma tarefa de predição de seqüência.
End Notes
LSTMs são uma solução muito promissora para problemas relacionados a seqüência e séries temporais. No entanto, a única desvantagem que eu encontro sobre eles, é a dificuldade em treiná-los. Muito tempo e recursos do sistema vão para o treinamento até mesmo de um modelo simples. Mas isso é apenas um constrangimento de hardware! Espero ter tido sucesso em dar-lhe uma compreensão básica destas redes. Para qualquer problema ou questão relacionada ao blog, sinta-se à vontade para comentar abaixo.