Introdução às redes LSTM: Prevendo valor de ações na bolsa

Tarcisio Balbi
Datarisk.io
Published in
9 min readOct 6, 2021

Entendendo como as redes neurais Long-Short Term Memory podem nos ajudar a prever séries temporais com um exemplo prático em Python.

Photo by Markus Spiske on Unsplash

Antes de começarmos a conversar sobre modelos, redes neurais, Python e toda essa parte técnica, precisamos entender o que são as séries temporais e porquê estamos interessados em prevê-las.

Imagine que na sua varanda tenha um termômetro e que todos os dias, às 9 horas da manhã, você afira a temperatura e anote esse valor. Ao final do primeiro ano, você terá um conjunto de dados que contém temperaturas para cada ponto no tempo.

Assim são as séries temporais, cada entrada representa um valor em um determinado instante, que pode ser: um mês, um dia, uma hora e por ai vai. Essas quebras temporais são o que chamamos granularidade.

Outros exemplos dessas estruturas são:

  • O preço do combustível;
  • A quantidade de gás carbônico na atmosfera;
  • O preço de abertura ou fechamento de ações na bolsa de valores.

Desde que sejam aferidos com uma granularidade regular.

Pelos exemplos podemos perceber que variáveis muito importantes podem ser representadas por séries temporais, não é mesmo? Mas elas ficam ainda mais interessantes, pois podemos utilizá-las para estimar futuros valores para esses dados.

Como sempre, em Ciência de Dados, não existe um modelo ou técnica mágica que funcione em todas as situações e a previsão de séries temporais não foge a essa regra.

Existem diferentes métodos desenvolvidos para esse fim. Dentre eles, temos os modelos estatísticos como auto regressivos (AR), de média móvel (MA) e combinações desses dois, como é o caso do ARMA e ARIMA.

Esses modelos podem funcionar muito bem para séries mais curtas - sem grandes sazonalidades. Contudo, no caso de períodos mais longos de observação, como é o exemplo da temperatura aferida diariamente, grandes sazonalidades podem dificultar sua utilização ou deixá-los muito complexos.

Isso acontece, pois a auto regressão utiliza um polinômio cujas variáveis são os dados das observações anteriores, como descrito abaixo:

x(t) = b0 + b1*x(t-1) + b2*x(t-2) ... bn*x(t-n)

Dessa forma, quanto maior a janela que queremos considerar, maior o polinômio. Para levar em conta os últimos 3 meses em cada previsão, por exemplo, seria necessária uma função de 91 termos!

Além disso, cada vez que a janela se deslocar, perderemos completamente a informação mais antiga, sendo ela útil ou não.

Para aqueles que quiserem entender um pouco mais sobre as diferentes técnicas, fica a dica do livro: Forecasting: Principles and Practice, dos autores Rob J. Hyndman e George Athanasopoulos, que pode ser acessado de maneira on-line e gratuitamente.

Para séries longas, métodos baseados em redes neurais se tornam bastante interessantes, como é o caso das redes neurais recorrentes e suas variações.

Por isso, hoje vamos focar nas redes neurais artificiais do tipo Long-Short Term Memory, ou LSTM - para os mais íntimos. Mas antes, precisamos passar por alguns conceitos mais básicos.

Redes Neurais Artificiais

As redes neurais artificiais (RNA) recebem esse nome, pois foram concebidas como uma tentativa de reproduzir as capacidades de aprendizado e adaptação do cérebro humano.

Da mesma forma que nosso cérebro, elas são compostas por neurônios que se ligam uns aos outros formando, de fato, uma rede. A figura abaixo mostra uma RNA genérica simplificada, onde cada círculo representa um neurônio.

Fonte e Autor: Dake, Mysid, CC BY 1.0 <https://commons.wikimedia.org/wiki/File:Neural_network.svg>, via Wikimedia Commons

No caso das RNAs padrão, cada neurônio é a composição de uma operação linear e uma não linear. Na prática, eles recebem um ou mais sinais, multiplicam essa entrada por um peso, acrescentam um valor conhecido como bias. Em seguida, esse novo valor passa por uma função não linear, conhecida como função de ativação.

Esse tipo de estrutura de rede pode ser utilizado em problemas de regressão, como: prever o preço de uma casa (saída) - baseado nas características dela (entradas) - ano de construção, área total, número de quartos e etc.

Durante o treinamento de uma rede, os valores de pesos e bias são iniciados com valores aleatórios e ajustados por um processo de otimização que leva em conta o desempenho da rede em prever corretamente a saída desejada, um processo que recebe o nome de backpropagation.

Para aqueles que quiserem se aprofundar no tema e na matemática por trás dessas estruturas, recomendo a leitura do artigo Neural Networks no site da IBM.

Redes Neurais Recorrentes

Agora já temos uma ideia do que são redes neurais artificiais, mas antes de chegar nas LSTM, temos que dar um passo atrás e entender o que são as redes neurais recorrentes.

Em uma rede neural padrão, cada entrada é considerada de forma independente para realizar uma predição, o que funciona bem para muitos casos de regressão e classificação. Porém, quando a ideia é prever dados que tenham uma tendência relacionada ao tempo, elas não são suficientes. Nesse ponto entram as redes recorrentes.

Redes neurais recorrentes (RNN) são um tipo especial de RNA no qual as conexões dos neurônios formam uma sequência temporal, uma espécie de memória interna, que as permite levar em conta esse comportamento temporal em suas previsões.

Nesse tipo de estrutura, a saída do neurônio anterior é utilizada pelo próximo para produzir a nova previsão, ou seja, as entradas deixam de ser independentes. A ideia fica mais clara com uma imagem:

Autor e Fonte: fdeloche, CC BY-SA 4.0 <https://commons.wikimedia.org/wiki/File:Recurrent_neural_network_unfold.svg>, via Wikimedia Commons

A principal limitação dessas redes é o “tempo” que elas se lembram de algo, sendo extremamente úteis para prever dados em contextos curtos. Porém, quando falamos de séries longas, como vendas de protetor solar, por exemplo, onde sazonalidades são fortes durante o ano, elas podem se tornar muito grandes e complexas.

Redes LSTM

Para resolver esse problema foram propostas as redes do tipo LSTM. A principal diferença entre as duas está na forma com que as entradas são tratadas.

Enquanto com as RNN normais cada nova informação é passada por uma função para obtenção do estado seguinte, as LSTM possuem uma estrutura mais complexa. Elas são formadas por 3 portas (gates), cada um recebe informações de diferentes prazos no tempo, selecionando o que deve ou não ser “esquecido” pela rede.

No fim do post, deixo duas dicas de leitura relacionadas ao tema para aqueles que quiserem se aprofundar mais no funcionamento das LSTM e de cada uma de suas portas.

Mão na Massa

Agora que sabemos o que são series temporais e entendemos o que são as redes neurais do tipo LSTM, vamos fazer um modelo simples de previsão do preço de abertura de ações da Petrobras.

Para simplificar as coisas, vamos tentar prever o comportamento dos preços com um dia de antecedência, ou seja, estamos preocupados em descobrir se as ações vão cair ou não no dia seguinte.

Antes de mais nada, precisamos dos dados para treinar e validar nosso modelo. O histórico de preços de ações pode ser consultado e baixado no site do Yahoo Finanças, basta acessar, procurar pela empresa, escolher o período e baixar. Uma alternativa é utilizar a biblioteca yfinance, que simplifica bastante esse processo.

É importante que deixe pelo menos o último mês separado para validar o modelo.

Importamos os pacotes necessários e os dados de treino e validação.

# Importamos as bibliotecas necessárias
from keras.models import Sequential
from keras.layers import Dense, Dropout, LSTM
from sklearn.preprocessing import MinMaxScaler
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Importamos os dados de treinamento, que estão em .csv
base = pd.read_csv('petr4_treinamento.csv')
# Queremos remover eventuais valores nulos. Nessa base eles não são # muitos.
base = base.dropna()
#Selecionamos apenas os valores da coluna de abertura.
base_treinamento = base.iloc[:, 1:2].values
#Vamos normalizar os valores para a faixa de 0 a 1. Isso ajuda no treinamento da rede.
normalizador = MinMaxScaler(feature_range=(0,1))
base_treinamento_normalizada = normalizador.fit_transform(base_treinamento)

O passo seguinte é deixar os dados estruturados de maneira correta para o treinamento da rede. Aqui, devemos definir qual será a janela temporal que vamos observar para fazer cada previsão, que nesse caso foi de 90 dias. Isso quer dizer que vamos observar os últimos 90 dias para prever o seguinte. Isso significa que, se houver algum comportamento sazonal dentro desses três meses, seremos capazes de aprendê-lo.

previsores = []
preco_real = []
# Variamos i a partir de 90, pois esse é o tamanho da janela que # estamos observando.
for i in range(90, 1242):
previsores.append(base_treinamento_normalizada[i-90:i, 0])
preco_real.append(base_treinamento_normalizada[i, 0])
# Transformamos os previsores e os preços reais em um array com formato correto para o treinamento na rede.
previsores, preco_real = np.array(previsores), np.array(preco_real)
previsores = np.reshape(previsores, (previsores.shape[0], previsores.shape[1], 1))

Como vamos prever sempre com um dia de antecedência, podemos simplesmente separa-los com janelas de 90 dias, variando de 1 em 1 dia.

Neste instante, vamos estruturar nossa rede. Cada neurônio de uma LSTM é bastante complexo e implementar tudo do zero é inviável, por isso vamos usar a biblioteca Keras, que usa de base o TensorFlow. Nela, já temos diversos tipos de camadas implementadas, inclusive a LSTM.

# Adicionamos também camadas de Dropout para prevenir ovefittingregressor = Sequential()
regressor.add(LSTM(units = 100, return_sequences = True, input_shape = (previsores.shape[1], 1)))
regressor.add(Dropout(0.3))
regressor.add(LSTM(units = 50, return_sequences = True))
regressor.add(Dropout(0.3))
regressor.add(LSTM(units = 50, return_sequences = True))
regressor.add(Dropout(0.3))
regressor.add(LSTM(units = 50))
regressor.add(Dropout(0.3))
regressor.add(Dense(units = 1, activation = 'linear'))# Aqui definimos o otimizador, a função de perda e métricas de # avaliação do modelo durante o treinamento.
regressor.compile(optimizer = 'rmsprop', loss = 'mean_squared_error',
metrics = ['mean_absolute_error'])

Para treinar o modelo, muitos parâmetros podem ser ajustados, como número de camadas ocultas, de neurônios por camada oculta e de épocas, além da taxa de aprendizado e otimizador. Como é um processo que consome tempo, vou deixar aqui os que já funcionaram bem para mim:

# Por fim, damos início ao treinamento. A taxa de aprendizado foi a padrão do otimizador,0.001.
regressor.fit(previsores, preco_real, epochs = 150, batch_size = 32)

O modelo foi treinado, só nos resta testá-lo. Para isso, vamos importar os dados de validação e prepará-los - como fizemos com os de treino. A partir daí, só precisamos realizar as predições.

Caso quiséssemos prever mais dias a frente, seria necessário adicionar a previsão de cada um deles aos previsores do próximo de forma iterativa, ao invés dessa separação de 90 em 90. O problema de fazer isso é que propagamos o erro a cada nova previsão. Então manteremos o exemplo simples, prevendo com apenas 1 dia de antecedência.

# Importamos os dados de teste
base_teste = pd.read_csv('petr4_teste.csv')
preco_real_teste = base_teste.iloc[:, 1:2].values
# Precisamos das últimas 90 entradas dos dados de treino para começar os testes. Então concatenamos as duas bases.
base_completa = pd.concat((base['Open'], base_teste['Open']), axis = 0)
# Agora extraímos da base completa apenas a porção que precisamos e # aplicamos o mesmo normalizador.
entradas = base_completa[(len(base_treinamento) - 90):].values
entradas = entradas.reshape(-1, 1)
entradas = normalizador.transform(entradas)
# Aplicamos a mesma estruturação e antes, separando de 90 em 90 # dias, movendo um dia por vez.
X_teste = []
for i in range(90, 112):
X_teste.append(entradas[i-90:i, 0])

X_teste = np.array(X_teste)
X_teste = np.reshape(X_teste, (X_teste.shape[0], X_teste.shape[1], 1))
previsoes = regressor.predict(X_teste)
previsoes = normalizador.inverse_transform(previsoes)

O resultado de nossas previsões pode ser comparado visualmente com os valores reais no gráfico abaixo:

Como podemos ver, o modelo consegue acompanhar bem as tendências de subida e descida dos preços, nos dando uma boa ideia de como será a abertura no dia seguinte.

Claro que essa foi uma abordagem bem básica e didática, só pudemos ver aqui a ponta do iceberg.

Se estivermos falando de um modelo para aplicação real, a validação visual não é suficiente, precisamos empregar técnicas mais robustas, como as validações cruzadas dos tipos Janela Deslizante e Janela Expansível. Deixo aqui a dica do artigo Don’t Use K-fold Validation for Time Series Forecasting, que aborda o tema com foco na previsão de series temporais.

Conclusão

A previsão de séries temporais e o uso de redes neurais recorrentes são assuntos bem mais profundos, todavia, espero que tenha conseguido iluminar os primeiros passos de quem quer mergulhar de cabeça nesse mundo, e despertar interesse em quem caiu aqui de paraquedas.

Como prometido, deixo aqui dois artigos que podem complementar e reforçar o que vimos até aqui:

Por hoje é isso e muito obrigado a você que leu até aqui!

Dúvidas, sugestões e criticas construtivas são sempre bem-vindas!

Quer acompanhar outros temas relacionados à Inteligência Artificial?

Confira nossos posts aqui, aproveite também e dê uma passada em nosso site e veja como solucionamos problemas do mundo real com Machine Learning.

--

--