Previsão do preço do estoque com LSTM

Aprendizagem da máquina encontrou suas aplicações em muitos campos interessantes ao longo destes anos. O mercado de ações domesticadas é uma delas. Eu estava pensando em tentar há algum tempo; principalmente para solidificar meu conhecimento sobre LSTMs. E finalmente eu terminei o projeto e bastante animado para compartilhar minha experiência.

Motivação e Público Alvo

Eu vou escrever sobre minha experiência em uma série de blogs. O propósito desta série não é explicar os conceitos básicos de LSTM ou Machine Learning. Portanto, vou assumir que o leitor começou sua jornada com Machine Learning e tem os conceitos básicos como Python, familiaridade com SkLearn, Keras, LSTM etc. A razão é que já existem excelentes artigos sobre tópicos como “Como funcionam as LSTMs?” por pessoas que são muito mais qualificadas para explicar as matemáticas por trás disso. Mas eu estarei compartilhando links para tais artigos, onde quer que eu sinta que falte conhecimento de fundo. Embora haja muitos artigos por aí para lhe dizer como prever os preços das ações dado um conjunto de dados, a maioria dos autores não revela/explica como eles chegaram a essa configuração específica para uma Rede Neural ou como eles selecionaram aquele conjunto particular de Hiperparâmetros. Então o verdadeiro propósito deste artigo é compartilhar tais passos, meus erros e alguns passos que eu achei muito úteis. Como tal, este artigo não se limita ao problema de Previsão de Preço de Estoque.

Aqui estão as coisas que vamos ver :

  1. Leitura e análise de dados. (Pandas)
  2. Normalizar os dados. (SkLearn)
  3. Converter dados para séries temporais e problema de aprendizagem supervisionado.
  4. Criar modelo (Keras)
  5. Ajustar o modelo (no próximo artigo)
  6. Treinar, prever e visualizar o resultado.
  7. Dicas & Ferramentas que achei muito úteis (último artigo da série)

Por favor note que este primeiro artigo fala sobre os passos e terminologias de pré-processamento da LSTM. Se você está bastante confiante sobre esses passos, você pode pular para o próximo artigo.

Vamos começar!

Lendo e analisando os dados

I estará usando os dados históricos do preço das ações da GE para este post. Você pode encontrar os dados no meu site de kaggle aqui. Eu não me lembro da fonte dos dados desde que eu tinha feito o download há muito tempo atrás. Podemos ler os dados em frames como mostrado abaixo :

df_ge = pd.read_csv(os.path.join(INPUT_PATH, "us.ge.txt"), engine='python')
df_ge.tail()

Como você pode ver existem cerca de 14060 itens, cada um representando um dia de atributos da bolsa de valores para a empresa. Vejamos como fica numa parcela :

from matplotlib import pyplot as pltplt.figure()
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.title('GE stock price history')
plt.ylabel('Price (USD)')
plt.xlabel('Days')
plt.legend(, loc='upper left')
plt.show()

Parece que os preços – Aberto, Fechado, Baixo, Alto – não variam muito uns dos outros, excepto para pequenas quedas ocasionais no preço Baixo.

Agora vamos ver o gráfico para o volume :

plt.figure()
plt.plot(df_ge)
plt.title('GE stock volume history')
plt.ylabel('Volume')
plt.xlabel('Days')
plt.show()

Huh. Você viu algo interessante? Há um grande aumento no número de transações em torno do 12000º dia na linha do tempo, que coincide com a queda repentina do preço das ações. Talvez possamos voltar a essa data em particular e desenterrar notícias antigas para encontrar o que a causou.

Agora vamos ver se temos algum valor nulo/Não com que nos preocupar. Acontece que não temos valores nulos. Ótimo!

print("checking if any null values are present\n", df_ge.isna().sum())

Normalizando os dados

Os dados não são normalizados e o intervalo para cada coluna varia, especialmente o Volume. A normalização dos dados ajuda o algoritmo a convergir, ou seja, a encontrar o mínimo local/global de forma eficiente. Vou usar o MinMaxScaler do Sci-kit Learn. Mas antes disso temos que dividir o conjunto de dados em conjuntos de dados de treinamento e teste. Também vou converter o DataFrame para ndarray no processo.

Converter dados para séries temporais e problema de aprendizagem supervisionada

É muito importante e um pouco complicado. Aqui é onde o conhecimento LSTM é necessário. Eu daria uma breve descrição dos conceitos-chave que são necessários aqui, mas eu recomendo fortemente a leitura do blog do André Karpathy aqui, que é considerado um dos melhores recursos em LSTM lá fora e isto. Ou você também pode assistir ao vídeo do Andrew Ng (que a propósito menciona o blog do André também).

LSTMs consomem input no formato ; um array tridimensional.

  • Batch Size diz quantas amostras de input você quer que sua Rede Neural veja antes de atualizar os pesos. Então vamos dizer que você tem 100 amostras (conjunto de dados de entrada) e quer atualizar os pesos toda vez que seu NN tiver visto uma entrada. Nesse caso, o tamanho do lote seria 1 e o número total de lotes seria 100. Como sábio, se você quisesse que sua rede atualizasse os pesos depois que ela tivesse visto todas as amostras, o tamanho do lote seria 100 e o número de lotes seria 1. Como acontece com o uso de lotes muito pequenos, reduz a velocidade do treinamento e, por outro lado, o uso de lotes muito grandes (como todo o conjunto de dados) reduz a capacidade dos modelos de generalizar para dados diferentes e também consome mais memória. Mas são necessários menos passos para encontrar os mínimos para a sua função objetiva. Então você tem que experimentar vários valores nos seus dados e encontrar o sweet spot. É um tópico bastante importante. Veremos como pesquisá-los de forma um pouco mais inteligente no próximo artigo.
  • Time Steps define quantas unidades de volta no tempo você quer que sua rede veja. Por exemplo, se você estava trabalhando em um problema de predição de caracteres onde você tem um corpo de texto para treinar e decide alimentar sua rede com 6 caracteres de cada vez. Então o seu passo de tempo é 6. No nosso caso estaremos usando 60 como passo de tempo, ou seja, vamos analisar 2 meses de dados para prever o preço dos próximos dias. Mais sobre isto mais tarde.
  • Características é o número de atributos usados para representar cada passo de tempo. Considere o exemplo de predição de caracteres acima, e assuma que você usa um vetor codificado de tamanho 100 para representar cada caractere. Então o tamanho do recurso aqui é 100.

Agora temos algumas terminologias claras fora do caminho, vamos converter nossos dados de estoque em um formato adequado. Vamos supor, para simplificar, que escolhemos 3 como passo do nosso tempo (queremos que a nossa rede olhe para trás em 3 dias de dados para prever o preço no 4º dia), então formaríamos o nosso conjunto de dados assim:

Amostras 0 a 2 seriam o nosso primeiro input e Preço fechado da amostra 3 seria o seu valor de saída correspondente; ambos fechados por um rectângulo verde. Similarmente as amostras 1 a 3 seriam o nosso segundo input e o preço Fechar da amostra 4 seria o seu valor de saída; ambos fechados por um retângulo azul. E assim por diante. Assim até agora temos uma matriz de forma (3, 5), sendo 3 o passo do tempo e 5 o número de características. Agora pense em quantos pares de input-output são possíveis na imagem acima? 4.

Misturar também o tamanho do lote com isto. Vamos assumir que escolhemos o tamanho do lote 2. Então o par input-output 1 (rectângulo verde) e o par 2 (rectângulo azul) constituiriam o lote 1. E assim por diante. Aqui está o código python para fazer isto:

‘y_col_index’ é o índice da sua coluna de saída. Agora suponha que depois de converter os dados em formato de aprendizagem supervisionada, como mostrado acima, você tem 41 amostras no seu conjunto de dados de treinamento, mas o tamanho do seu lote é 20, então você terá que aparar o seu conjunto de treinamento para remover as amostras ímpares deixadas de fora. Vou procurar uma maneira melhor de contornar isso, mas por enquanto é isso que eu fiz:

Agora usando as funções acima permite formar nossos conjuntos de dados de treinamento, validação e teste

x_t, y_t = build_timeseries(x_train, 3)
x_t = trim_dataset(x_t, BATCH_SIZE)
y_t = trim_dataset(y_t, BATCH_SIZE)
x_temp, y_temp = build_timeseries(x_test, 3)
x_val, x_test_t = np.split(trim_dataset(x_temp, BATCH_SIZE),2)
y_val, y_test_t = np.split(trim_dataset(y_temp, BATCH_SIZE),2)

Agora nossos dados estão prontos, podemos nos concentrar na construção do modelo.

Criando o modelo

Para essa tarefa, que é uma variação da Rede Neural Recorrente, vamos usar LSTM. Criar o modelo LSTM é tão simples quanto isto:

Agora que você tenha seu modelo compilado e pronto para ser treinado, treine-o como mostrado abaixo. Se você está se perguntando sobre quais valores usar para parâmetros como épocas, tamanho de lote, etc., não se preocupe, veremos como descobrir esses no próximo artigo.

Treinar este modelo (com hiperparâmetros afinados) deu melhor erro de 3.27e-4 e melhor erro de validação de 3.7e-4. Eis como ficou a perda de treinamento vs perda de validação:

Erro de treinamento vs erro de validação

Eis como ficou a previsão com o modelo acima:

previsão vs dados reais

Concluí que esta configuração para LSTM funciona da melhor de todas as combinações que tentei (para este conjunto de dados), e tentei mais de 100! Então a questão é como você aterrissa na arquitetura perfeita (ou em quase todos os casos, perto da perfeição) para a sua rede neural? Isso nos leva à nossa próxima e importante seção, a ser continuada no próximo artigo.

Você pode encontrar todos os programas completos no meu perfil Github aqui.

NOTE: Um humilde pedido aos leitores – Todos vocês são bem-vindos para se conectarem comigo no LinkedIn ou Twitter, mas se você tiver uma consulta sobre meus blogs, por favor poste-a na seção de comentários do respectivo blog ao invés de mensagens pessoais, para que, se alguém tiver a mesma consulta, ele mesmo a encontre aqui, e eu não teria que explicá-la individualmente. No entanto, você ainda é bem-vindo para enviar consultas não relacionadas a blogs ou consultas técnicas em geral, para mim pessoalmente. Obrigado 🙂

UPDATE 13/4/19

  1. Chegou ao meu conhecimento, desde que escrevi este artigo, que o meu modelo usado para este blog pode ter sido adaptado em demasia. Embora eu não o tenha confirmado, é provável que tenha sido. Portanto, por favor tenha cuidado ao implementar isto em seus projetos. Você poderia experimentar coisas como menos épocas, rede menor, mais desistência etc.
  2. I have used Sigmoid activation for last layer which may suffer from limitation of not being able to predict a price greater than ‘max’ price in dataet. Você poderia tentar a ativação ‘Linear’ para a última camada para resolver isso.
  3. Fixar um erro de digitação na seção “conversão de dados para série temporal”.

Escrevo os agradecimentos aos leitores por trazerem estes para minha atenção.

UPDATE 21/1/2020

Deixe uma resposta

O seu endereço de email não será publicado.