Tutorial RNN (rede neural recorrente): exemplo do TensorFlow
Por que precisamos de uma Rede Neural Recorrente (RNN)?
Rede Neural Recorrente (RNN) permite modelar unidades de memória para persistir dados e modelar dependências de curto prazo. Também é usado na previsão de séries temporais para a identificação de correlações e padrões de dados. Também ajuda a produzir resultados preditivos para dados sequenciais, proporcionando um comportamento semelhante ao do cérebro humano.
A estrutura de uma Rede Neural Artificial é relativamente simples e trata principalmente da multiplicação de matrizes. Durante a primeira etapa, as entradas são multiplicadas por pesos inicialmente aleatórios e o viés é transformado com uma função de ativação e os valores de saída são usados para fazer uma previsão. Esta etapa dá uma ideia de quão distante a rede está da realidade.
A métrica aplicada é a perda. Quanto maior a função de perda, mais burro é o modelo. Para melhorar o conhecimento da rede, é necessária alguma otimização ajustando os pesos da rede. A descida gradiente estocástica é o método empregado para alterar os valores dos pesos na direção direita. Feito o ajuste, a rede pode usar outro lote de dados para testar seu novo conhecimento.
O erro, felizmente, é menor do que antes, mas não é pequeno o suficiente. A etapa de otimização é feita iterativamente até que o erro seja minimizado, ou seja, nenhuma informação mais possa ser extraída.
O problema desse tipo de modelo é que ele não possui memória. Isso significa que a entrada e a saída são independentes. Em outras palavras, o modelo não se importa com o que veio antes. Isso levanta algumas questões quando você precisa prever séries temporais ou sentenças porque a rede precisa ter informações sobre os dados históricos ou palavras anteriores.
Para superar esse problema, um novo tipo de arquitetura foi desenvolvido: Rede Neural Recorrente (doravante RNN)
O que é uma rede neural recorrente (RNN)?
A Rede Neural Recorrente (RNN) é uma classe de Rede neural artificial em que a conexão entre diferentes nós forma um gráfico direcionado para fornecer um comportamento dinâmico temporal. Ajuda a modelar dados sequenciais derivados de redes feedforward. Funciona de forma semelhante ao cérebro humano para fornecer resultados preditivos.
Uma rede neural recorrente é bastante semelhante a uma rede neural tradicional, exceto que um estado de memória é adicionado aos neurônios. O cálculo para incluir uma memória é simples.
Imagine um modelo simples com apenas um neurônio alimentado por um lote de dados. Em uma rede neural tradicional, o modelo produz a saída multiplicando a entrada pelo peso e pela função de ativação. Com um RNN, essa saída é enviada de volta para si mesma várias vezes. Nós chamamos intervalo de tempo a quantidade de tempo que a saída se torna a entrada da próxima multiplicação da matriz.
Por exemplo, na imagem abaixo, você pode ver que a rede é composta por um neurônio. A rede calcula a multiplicação das matrizes entre a entrada e o peso e adiciona não linearidade com a função de ativação. Torna-se a saída em t-1. Esta saída é a entrada da segunda multiplicação da matriz.
Abaixo, codificamos um RNN simples no TensorFlow para entender a etapa e também o formato da saída.
A rede é composta por:
- Quatro entradas
- Seis neurônios
- Etapas de 2 vezes
A rede continuará conforme ilustrado na imagem abaixo.
A rede é chamada de 'recorrente' porque realiza a mesma operação em cada quadrado ativado. A rede calculou os pesos das entradas e da saída anterior antes de usar uma função de ativação.
import numpy as np import tensorflow as tf n_inputs = 4 n_neurons = 6 n_timesteps = 2 The data is a sequence of a number from 0 to 9 and divided into three batches of data. ## Data X_batch = np.array([ [[0, 1, 2, 5], [9, 8, 7, 4]], # Batch 1 [[3, 4, 5, 2], [0, 0, 0, 0]], # Batch 2 [[6, 7, 8, 5], [6, 5, 4, 2]], # Batch 3 ])
Podemos construir a rede com um espaço reservado para os dados, o estágio recorrente e a saída.
- Defina o espaço reservado para os dados
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])
Aqui:
- Nenhum: Desconhecido e terá o tamanho do lote
- n_timesteps: Número de vezes que a rede enviará a saída de volta ao neurônio
- n_inputs: Número de entradas por lote
- Defina a rede recorrente
Conforme mencionado na imagem acima, a rede é composta por 6 neurônios. A rede calculará dois produtos escalares:
- Dados de entrada com o primeiro conjunto de pesos (ou seja, 6: igual ao número de neurônios)
- Saída anterior com um segundo conjunto de pesos (ou seja, 6: correspondente ao número de saída)
Observe que, durante o primeiro feedforward, os valores da saída anterior são iguais a zero porque não temos nenhum valor disponível.
O objeto para construir um RNN é tf.contrib.rnn.BasicRNNCell com o argumento num_units para definir o número de entradas
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
Agora que a rede está definida, você pode calcular as saídas e estados
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Este objeto usa um loop interno para multiplicar as matrizes o número apropriado de vezes.
Observe que o neurônio recorrente é uma função de todas as entradas dos intervalos de tempo anteriores. É assim que a rede constrói sua própria memória. As informações do momento anterior podem se propagar no futuro. Esta é a magia da rede neural recorrente
## Define the shape of the tensor X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs]) ## Define the network basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) init = tf.global_variables_initializer() init = tf.global_variables_initializer() with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch}) print(states.eval(feed_dict={X: X_batch})) [[ 0.38941205 -0.9980438 0.99750966 0.7892596 0.9978241 0.9999997 ] [ 0.61096436 0.7255889 0.82977575 -0.88226104 0.29261455 -0.15597084] [ 0.62091285 -0.87023467 0.99729395 -0.58261937 0.9811445 0.99969864]]
Para fins explicativos, imprima os valores do estado anterior. A saída impressa acima mostra a saída do último estado. Agora imprima toda a saída, você pode notar que os estados são a saída anterior de cada lote. Ou seja, a saída anterior contém as informações sobre toda a sequência.e
print(outputs_val) print(outputs_val.shape) [[[-0.75934666 -0.99537754 0.9735819 -0.9722234 -0.14234993 -0.9984044 ] [ 0.99975264 -0.9983206 0.9999993 -1. -0.9997506 -1. ]] [[ 0.97486496 -0.98773265 0.9969686 -0.99950117 -0.7092863 -0.99998885] [ 0.9326837 0.2673438 0.2808514 -0.7535883 -0.43337247 0.5700631 ]] [[ 0.99628735 -0.9998728 0.99999213 -0.99999976 -0.9884324 -1. ] [ 0.99962527 -0.9467421 0.9997403 -0.99999714 -0.99929446 -0.9999795 ]]] (3, 2, 6)
A saída tem o formato de (3, 2, 6):
- 3: Número de lotes
- 2: Número do intervalo de tempo
- 6: Número de neurônios
A otimização de uma rede neural recorrente é idêntica a uma rede neural tradicional. Você verá com mais detalhes como codificar a otimização na próxima parte deste tutorial sobre Rede Neural Recorrente.
Aplicações de RNN
RNN tem múltiplos usos, especialmente quando se trata de prever o futuro. No setor financeiro, o RNN pode ser útil na previsão dos preços das ações ou no sinal da direção do mercado de ações (ou seja, positivo ou negativo).
O RNN é útil para um carro autônomo, pois pode evitar um acidente de carro antecipando a trajetória do veículo.
RNN é amplamente utilizado em análise de texto, legendagem de imagens, análise de sentimento e tradução automática. Por exemplo, pode-se utilizar uma crítica de filme para compreender o sentimento que o espectador percebeu após assistir ao filme. Automatizar essa tarefa é muito útil quando a produtora cinematográfica não tem tempo suficiente para revisar, rotular, consolidar e analisar as resenhas. A máquina pode fazer o trabalho com maior nível de precisão.
Limitações do RNN
Em teoria, o RNN deveria transportar as informações até certo ponto. No entanto, é bastante desafiador propagar todas essas informações quando o intervalo de tempo é muito longo. Quando uma rede tem muitas camadas profundas, ela se torna impossível de treinar. Este problema é denominado: problema de gradiente de desaparecimento. Se você se lembra, a rede neural atualiza o peso usando o algoritmo de gradiente descendente. Os gradientes ficam menores quando a rede progride para as camadas inferiores.
Concluindo, os gradientes permanecem constantes, o que significa que não há espaço para melhorias. O modelo aprende com uma mudança no gradiente; esta mudança afeta a saída da rede. Porém, se a diferença no gradiente for muito pequena (ou seja, os pesos mudam um pouco), a rede não consegue aprender nada e, portanto, a saída. Portanto, uma rede que enfrenta um problema de gradiente evanescente não pode convergir para uma boa solução.
Melhoria LSTM
Para superar o problema potencial de desaparecimento do gradiente enfrentado pelo RNN, três pesquisadores, Hochreiter, Schmidhuber e Bengio melhoraram o RNN com uma arquitetura chamada Long Short-Term Memory (LSTM). Em resumo, o LSMT fornece à rede informações passadas relevantes para tempos mais recentes. A máquina usa uma arquitetura melhor para selecionar e transportar informações posteriormente.
A arquitetura LSTM está disponível no TensorFlow, tf.contrib.rnn.LSTMCell. LSTM está fora do escopo do tutorial. Você pode consultar o oficial documentação para mais informações
RNN em série temporal
Neste tutorial do TensorFlow RNN, você usará um RNN com dados de série temporal. As séries temporais dependem do tempo anterior, o que significa que os valores passados incluem informações relevantes com as quais a rede pode aprender. A ideia por trás da previsão de séries temporais é estimar o valor futuro de uma série, digamos, preço das ações, temperatura, PIB e assim por diante.
A preparação de dados para Keras RNN e séries temporais pode ser um pouco complicada. Em primeiro lugar, o objetivo é prever o próximo valor da série, ou seja, você usará as informações anteriores para estimar o valor em t + 1. O rótulo é igual à sequência de entrada e deslocado um período à frente. Em segundo lugar, o número de entradas é definido como 1, ou seja, uma observação por vez. Por último, o intervalo de tempo é igual à sequência do valor numérico. Por exemplo, se você definir o intervalo de tempo como 10, a sequência de entrada retornará dez vezes consecutivas.
Observe o gráfico abaixo, representamos os dados da série temporal à esquerda e uma sequência de entrada fictícia à direita. Você cria uma função para retornar um conjunto de dados com valor aleatório para cada dia de janeiro de 2001 a dezembro de 2016
# To plot pretty figures %matplotlib inline import matplotlib import matplotlib.pyplot as plt import pandas as pd def create_ts(start = '2001', n = 201, freq = 'M'): rng = pd.date_range(start=start, periods=n, freq=freq) ts = pd.Series(np.random.uniform(-18, 18, size=len(rng)), rng).cumsum() return ts ts= create_ts(start = '2001', n = 192, freq = 'M') ts.tail(5)
saída
2016-08-31 -93.459631 2016-09-30 -95.264791 2016-10-31 -95.551935 2016-11-30 -105.879611 2016-12-31 -123.729319 Freq: M, dtype: float64
ts = create_ts(start = '2001', n = 222) # Left plt.figure(figsize=(11,4)) plt.subplot(121) plt.plot(ts.index, ts) plt.plot(ts.index[90:100], ts[90:100], "b-", linewidth=3, label="A training instance") plt.title("A time series (generated)", fontsize=14) # Right plt.subplot(122) plt.title("A training instance", fontsize=14) plt.plot(ts.index[90:100], ts[90:100], "b-", markersize=8, label="instance") plt.plot(ts.index[91:101], ts[91:101], "bo", markersize=10, label="target", markerfacecolor='red') plt.legend(loc="upper left") plt.xlabel("Time") plt.show()
A parte direita do gráfico mostra todas as séries. Começou em 2001 e termina em 2019. Não faz sentido alimentar todos os dados da rede, em vez disso, é necessário criar um lote de dados com comprimento igual ao intervalo de tempo. Este lote será a variável X. A variável Y é igual a X, mas deslocada em um período (ou seja, você deseja prever t+1).
Ambos os vetores têm o mesmo comprimento. Você pode ver isso na parte direita do gráfico acima. A linha representa os dez valores da entrada X, enquanto os pontos vermelhos são os dez valores do rótulo, Y. Observe que o rótulo começa um período antes de X e termina um período depois.
Crie um RNN para prever séries temporais no TensorFlow
Agora neste treinamento RNN, é hora de construir seu primeiro RNN para prever a série acima. Você precisa especificar alguns hiperparâmetros (os parâmetros do modelo, ou seja, número de neurônios, etc.) para o modelo:
- Número de entrada: 1
- Intervalo de tempo (janelas em série temporal): 10
- Número de neurônios: 120
- Número de saída: 1
Sua rede aprenderá com uma sequência de 10 dias e conterá 120 neurônios recorrentes. Você alimenta o modelo com uma entrada, ou seja, um dia. Sinta-se à vontade para alterar os valores para ver se o modelo melhorou.
Antes de construir o modelo, você precisa dividir o conjunto de dados em um conjunto de treinamento e um conjunto de teste. O conjunto de dados completo possui 222 pontos de dados; você usará os primeiros 201 pontos para treinar o modelo e os últimos 21 pontos para testar seu modelo.
Depois de definir um conjunto de treinamento e teste, você precisa criar um objeto contendo os lotes. Nestes lotes, você tem valores X e valores Y. Lembre-se de que os valores de X estão defasados em um período. Portanto, você usa as primeiras 200 observações e o intervalo de tempo é igual a 10. O objeto X_batches deve conter 20 lotes de tamanho 10*1. O y_batches tem o mesmo formato do objeto X_batches, mas com um ponto à frente.
Passo 1) Crie o trem e teste
Primeiro de tudo, você converte a série em um numpy variedade; em seguida, você define as janelas (ou seja, o número de tempo com o qual a rede aprenderá), o número de entradas, saídas e o tamanho do conjunto de trens, conforme mostrado no exemplo do TensorFlow RNN abaixo.
series = np.array(ts) n_windows = 20 n_input = 1 n_output = 1 size_train = 201
Depois disso, basta dividir o array em dois conjuntos de dados.
## Split data train = series[:size_train] test = series[size_train:] print(train.shape, test.shape) (201,) (21,)
Passo 2) Crie a função para retornar X_batches e y_batches
Para facilitar, você pode criar uma função que retorne dois arrays diferentes, um para X_batches e outro para y_batches.
Vamos escrever uma função RNN TensorFlow para construir os lotes.
Observe que os lotes X estão defasados de um período (assumimos o valor t-1). A saída da função deve ter três dimensões. As primeiras dimensões equivalem ao número de lotes, a segunda ao tamanho das janelas e a última ao número de entradas.
A parte complicada é selecionar os pontos de dados corretamente. Para os pontos de dados X, você escolhe as observações de t = 1 a t = 200, enquanto para o ponto de dados Y, você retorna as observações de t = 2 a 201. Depois de ter os pontos de dados corretos, é simples remodelar as séries.
Para construir o objeto com os lotes, você precisa dividir o conjunto de dados em dez lotes de igual comprimento (ou seja, 20). Você pode usar o método reshape e passar -1 para que a série seja semelhante ao tamanho do lote. O valor 20 é o número de observações por lote e 1 é o número de entradas.
Você precisa fazer o mesmo passo, mas para o rótulo.
Observe que você precisa mudar os dados para o número de vezes que deseja prever. Por exemplo, se você quiser prever um tempo de antecedência, mude a série em 1. Se quiser prever dois dias, mude os dados em 2.
x_data = train[:size_train-1]: Select all the training instance minus one day X_batches = x_data.reshape(-1, windows, input): create the right shape for the batch e.g (10, 20, 1) def create_batches(df, windows, input, output): ## Create X x_data = train[:size_train-1] # Select the data X_batches = x_data.reshape(-1, windows, input) # Reshape the data ## Create y y_data = train[n_output:size_train] y_batches = y_data.reshape(-1, windows, output) return X_batches, y_batches
Agora que a função está definida, você pode chamá-la para criar os lotes conforme mostrado no exemplo RNN abaixo.
X_batches, y_batches = create_batches(df = train, windows = n_windows, input = n_input, output = n_output)
Você pode imprimir a forma para ter certeza de que as dimensões estão corretas.
print(X_batches.shape, y_batches.shape) (10, 20, 1) (10, 20, 1)
Você precisa criar o conjunto de testes com apenas um lote de dados e 20 observações.
Observe que, se você prevê dias após dias, isso significa que o segundo valor previsto será baseado no valor verdadeiro do primeiro dia (t+1) do conjunto de dados de teste. Na verdade, o verdadeiro valor será conhecido.
Se você deseja prever t+2 (ou seja, dois dias antes), você precisa usar o valor previsto t+1; se você pretende prever t+3 (três dias à frente), precisará usar o valor previsto t+1 e t+2. Faz sentido que seja difícil prever com precisão t+n dias à frente.
X_test, y_test = create_batches(df = test, windows = 20,input = 1, output = 1) print(X_test.shape, y_test.shape) (10, 20, 1) (10, 20, 1)
Tudo bem, o tamanho do seu lote está pronto, você pode construir a arquitetura RNN. Lembre-se, você tem 120 neurônios recorrentes.
Passo 3) Construa o modelo
Para criar o modelo, você precisa definir três partes:
- A variável com os tensores
- O RNN
- A perda e otimização
Passo 3.1) Variáveis
Você precisa especificar as variáveis Xey com a forma apropriada. Esta etapa é trivial. O tensor tem a mesma dimensão dos objetos X_batches e y_batches.
Por exemplo, o tensor X é um espaço reservado (confira o tutorial Introdução ao Tensorflow para refrescar sua mente sobre a declaração de variáveis) tem três dimensões:
- Obs: tamanho do lote
- n_windows: Comprimento das janelas. ou seja, o número de vezes que o modelo olha para trás
- n_input: Número de entrada
O resultado é:
tf.placeholder(tf.float32, [None, n_windows, n_input])
## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output])
Passo 3.2) Crie o RNN
Na segunda parte deste exemplo RNN do TensorFlow, você precisa definir a arquitetura da rede. Como antes, você usa o objeto BasicRNNCell e dynamic_rnn do estimador TensorFlow.
## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
A próxima parte é um pouco mais complicada, mas permite cálculos mais rápidos. Você precisa transformar a saída da execução em uma camada densa e depois convertê-la novamente para ter a mesma dimensão da entrada.
stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Passo 3.3) Crie a perda e a otimização
A otimização do modelo depende da tarefa que você está executando. No tutorial anterior sobre CNN, seu objetivo era classificar imagens, neste tutorial da RNN o objetivo é um pouco diferente. Você é solicitado a fazer uma previsão sobre uma variável contínua comparada a uma classe.
Essa diferença é importante porque mudará o problema de otimização. O problema de otimização para uma variável contínua é minimizar o erro quadrático médio. Para construir essas métricas no TF, você pode usar:
- tf.reduce_sum(tf.square(saídas – y))
O restante do código RNN é o mesmo de antes; você usa um otimizador Adam para reduzir a perda (ou seja, MSE):
- tf.train.AdamOptimizer(learning_rate=learning_rate)
- otimizador.minimize(perda)
É isso, você pode embalar tudo e seu modelo está pronto para treinar.
tf.reset_default_graph() r_neuron = 120 ## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) ## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output]) ## 3. Loss + optimization learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer()
Você treinará o modelo usando 1500 épocas e imprimirá a perda a cada 150 iterações. Depois que o modelo for treinado, você avalia o modelo no conjunto de teste e cria um objeto contendo as previsões, conforme mostrado no exemplo de rede neural recorrente abaixo.
iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test}) 0 MSE: 502893.34 150 MSE: 13839.129 300 MSE: 3964.835 450 MSE: 2619.885 600 MSE: 2418.772 750 MSE: 2110.5923 900 MSE: 1887.9644 1050 MSE: 1747.1377 1200 MSE: 1556.3398 1350 MSE: 1384.6113
Por fim, neste tutorial RNN Deep Learning, você pode traçar o valor real da série com o valor previsto. Se o seu modelo for corrigido, os valores previstos deverão ser colocados acima dos valores reais.
Como você pode ver, o modelo pode ser melhorado. Cabe a você alterar os hiperparâmetros como as janelas, o tamanho do lote e o número de neurônios recorrentes.
plt.title("Forecast vs Actual", fontsize=14) plt.plot(pd.Series(np.ravel(y_test)), "bo", markersize=8, label="Actual", color='green') plt.plot(pd.Series(np.ravel(y_pred)), "r.", markersize=8, label="Forecast", color='red') plt.legend(loc="lower left") plt.xlabel("Time") plt.show()
Resumo
Uma rede neural recorrente é uma arquitetura robusta para lidar com séries temporais ou análise de texto. A saída do estado anterior é um feedback para preservar a memória da rede ao longo do tempo ou sequência de palavras.
No TensorFlow, você pode usar os seguintes códigos para treinar uma rede neural recorrente do TensorFlow para séries temporais:
Parâmetros do modelo
n_windows = 20 n_input = 1 n_output = 1 size_train = 201
Defina o modelo
X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Construa a otimização
learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss)
Treine o modelo
init = tf.global_variables_initializer() iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test})