Redes Neuronales Recurrentes (LSTM)

Pronósticos de índice AAPL

RNN
LSTM
Tensorflow
Python
Author

Juan Isaula

Published

May 23, 2023

Red de Memoria de Corto y Largo Plazo (LSTM)

Las redes LSTM (Long Short-Term Memory) son un tipo especial de redes neuronales recurrentes diseñadas con celdas de memoria que mantienen su estado a largo plazo. El principal objetivo de este tipo de redes es la solución del desvanecimiento del gradiente experimentado en las redes recurrentes. Globalmente, el flujo computacional de LSTM se ve de la siguiente manera:

Flujo computacional de LSTM

Las redes neuronales recurrentes pasan solo un estado oculto \(h_t\) a través de cada iteración. Pero LSTM pasa dos vectores: \(h_t-\)estado oculto (memoria a corto plazo) y \(c_t-\)estado celular (memoria a largo plazo).

Las salidas de la celda LSTM se calculan a traves de las fórmulas que se muestran a continuación:

\[\begin{eqnarray} i_t &=& \sigma(w_{ii}x_t + b_ii + w_{hi}h_{(t-1)} + b_{hi})\\[0.2cm] f_t &=& \sigma(w_{if}x_t + b_{if} + w_{hj}h_{(t-1)} + b_{hf})\\[0.2cm] g_t &=& \tanh(w_{ig}x_t + b_{ig} + w_{hg}h_{(t-1)} + b_{hn})\\[0.2cm] o_t &=& \sigma(w_{io}x_t + b_{io} + w_{ho}h_{(t-1)} + b_{ho})\\[0.2cm] c_t &=& f_t \circ c_{t-1} + i_t\circ g_t\\[0.2cm] h_t &=& o_t \circ \tanh(c_t) \end{eqnarray}\]

donde:

  • \(\sigma\) es la función sigmoidea

  • \(\circ\) es el producto de Hadamard, que es:

    \[ \begin{bmatrix} a_1 \\ a_2 \\ a_3 \end{bmatrix} \circ \begin{bmatrix} b_1 \\ b_2 \\ b_3 \end{bmatrix} = \begin{bmatrix} a_1b_1\\ a_2b_2\\ a_3b_3 \end{bmatrix} \]

Variables

  1. \(i_t\) (puerta de entrada) es la variable que se utiliza para actualizar el estado de la celda \(c_t\). El estado previamente oculto \(c_t\) y la entrada secuencial actual \(x_t\) se dan como entrada a una función sigmoidea. Si la salida está cerca a uno, más importante es la información.

  2. \(f_t\) (puerta de olvido) es la variable que decide que información debe olvidarse en el estado de celda \(c_t\). El estadp previamente oculto \(h_t\) y la entrada de secuencia \(x_t\) se dan como entradas a una función sigmoidea. Si la salida \(f_t\) está cerca de cero, entonces la información puede olvidarse, mientras que si la salida esta cerca de uno, la información debe almacenarse.

  3. \(g_t\) representa información importante potencialmente nueva para el estado celular \(c_t\).

  4. \(c_t\) (estado celular) es una suma de:

    • Estado de celada anterior \(c_{t-1}\) con alguna información olvidada \(f_t\).

    • Nueva información de \(g_t\).

  1. \(o_t\) (puerta de salida) es la variable para actualizar el estado oculto \(h_t\).

  2. \(h_t\) (estado oculto) es el siguiente estado oculto que se calcula seleccionando la información importante \(o_t\) del estado de celda \(c_t\).

La siguiente figura muestra el gráfico computacional de la celda LSTM:

Gráfico computacional de LSTM

La red LSTM tiene los siguientes parámetros, que se ajustan durante el entrenamiento:

  • \(w_{ii}, w_{hi}, w_{if}, w_{hf}, w_{ig}, w_{hg}, w_{io}, w_{ho}\) - Pesos

  • \(b_{ii}, b_{hi}, b_{if}, b_{hf}, b_{ig}, b_{io}, b_{ho}\) - Sesgos

Los modelos LSTM son lo suficientemente potentes como para aprender los comportamientos pasados más importantes y comprender si esos comportamientos pasados son características importantes para hacer predicciones futuras. Hay varias aplicaciones en las que las LSTM se utilizan mucho. Aplicaciones como reconocimiento de voz, composición musical, reconocimiento de escritura a mano.

Particularmente considero que las LSTM es como un modelo que tiene su propia memoria y que puede comportarse como un humano inteligente en la toma de desiciones.

Pronósticos para el índice de AAPL utilizando una red LSTM

A continuación realizaremos un ejercicio donde intentamos pronósticar el precio de cierre (Close) del índice de AAPL, la data la puede encontrar dando click en prices-split-adjusted.csv .

Comenzamos importando las librerías a utilizar.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import datetime
import math, time
import itertools
from sklearn import preprocessing
import datetime
from operator import itemgetter
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
from math import sqrt
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers import LSTM
from keras.models import load_model
import keras
import h5py
import requests
import os
import warnings
warnings.filterwarnings('ignore')
C:\Users\juani\AppData\Local\Temp\ipykernel_18008\2001173417.py:4: FutureWarning:

The pandas.datetime class is deprecated and will be removed from pandas in a future version. Import from datetime module instead.

Cargamos el DataSet y lo almacenamos en la variable df y data_df. La data almacenada en data_df es la que utilizaremos para realizar todo nuestro estudio.

df = pd.read_csv("prices-split-adjusted.csv", index_col = 0)
data_df = pd.read_csv("prices-split-adjusted.csv", index_col = 0)
data_df.head()
symbol open close low high volume
date
2016-01-05 WLTW 123.430000 125.839996 122.309998 126.250000 2163600.0
2016-01-06 WLTW 125.239998 119.980003 119.940002 125.540001 2386400.0
2016-01-07 WLTW 116.379997 114.949997 114.930000 119.739998 2489500.0
2016-01-08 WLTW 115.480003 116.620003 113.500000 117.440002 2006300.0
2016-01-11 WLTW 117.010002 114.970001 114.089996 117.330002 1408600.0

Este bloque de código fue de mucha utilidad, dado que aquí filtramos de nuestro dataset unicamente la información para el indice de AAPL y visualizamos la data.

data_df = data_df[data_df.symbol == 'AAPL']
data_df.drop(['symbol'],1,inplace=True)
data_df.head()
open close low high volume
date
2010-01-04 30.490000 30.572857 30.340000 30.642857 123432400.0
2010-01-05 30.657143 30.625713 30.464285 30.798571 150476200.0
2010-01-06 30.625713 30.138571 30.107143 30.747143 138040000.0
2010-01-07 30.250000 30.082857 29.864286 30.285715 119282800.0
2010-01-08 30.042856 30.282858 29.865715 30.285715 111902700.0

Preliminarmente con este bloque de código podemos ver como se comporta la serie correspondiente al precio de cierre de las acciones de AAPL.

plt.figure(figsize=(15, 5));
plt.subplot(1,2,1);
plt.plot(df[df.symbol == 'EQIX'].close.values, color='green', label='close')
plt.title('Indice de AAPL')
plt.xlabel('días')
plt.ylabel('precio de cierre (Close)')
plt.legend(loc='best')
<matplotlib.legend.Legend at 0x236b99436a0>

Necesitamos manipular nuestro campo de fecha para poder manipularlas como lo que son en realidad (fechas).

data_df['date'] = data_df.index
data_df.head()
open close low high volume date
date
2010-01-04 30.490000 30.572857 30.340000 30.642857 123432400.0 2010-01-04
2010-01-05 30.657143 30.625713 30.464285 30.798571 150476200.0 2010-01-05
2010-01-06 30.625713 30.138571 30.107143 30.747143 138040000.0 2010-01-06
2010-01-07 30.250000 30.082857 29.864286 30.285715 119282800.0 2010-01-07
2010-01-08 30.042856 30.282858 29.865715 30.285715 111902700.0 2010-01-08
data_df['date'] = pd.to_datetime(data_df['date'])

Transformamos los datos con MinMaxScaler() para que se distribuyan normal estándar, recuerde que esto es con media cero y varianza 1.

min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
dataset = min_max_scaler.fit_transform(data_df['close'].values.reshape(-1, 1))

Dividimos la data en datos de entrenamiento (train), tomando el 70% de los datos para entrenar nuestro modelo y un 20% para prueba (test).

train_size = int(len(dataset) * 0.7)
test_size = len(dataset) - train_size
train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:]
print(len(train), len(test))
1233 529
# convertir una matriz de valores en una matriz de conjunto de datos
def create_dataset(dataset, look_back=15):
    dataX, dataY = [], []
    for i in range(len(dataset)-look_back-1):
        a = dataset[i:(i+look_back), 0]
        dataX.append(a)
        dataY.append(dataset[i + look_back, 0])
    return np.array(dataX), np.array(dataY)
x_train, y_train = create_dataset(train, look_back=15)
x_test, y_test = create_dataset(test, look_back=15)
print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)
(1217, 15)
(1217,)
(513, 15)
(513,)
x_train = np.reshape(x_train, (x_train.shape[0], 1, x_train.shape[1]))
x_test = np.reshape(x_test, (x_test.shape[0], 1, x_test.shape[1]))

Comenzamos a crear y entrenar nuestro modelo LSTM.

look_back = 15
model = Sequential()
model.add(LSTM(20, input_shape=(1, look_back)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(x_train, y_train, epochs=90, batch_size=8, verbose=2)
Epoch 1/90
153/153 - 2s - loss: 0.0174 - 2s/epoch - 16ms/step
Epoch 2/90
153/153 - 0s - loss: 5.3418e-04 - 273ms/epoch - 2ms/step
Epoch 3/90
153/153 - 0s - loss: 4.4450e-04 - 278ms/epoch - 2ms/step
Epoch 4/90
153/153 - 0s - loss: 4.2874e-04 - 277ms/epoch - 2ms/step
Epoch 5/90
153/153 - 0s - loss: 4.0739e-04 - 268ms/epoch - 2ms/step
Epoch 6/90
153/153 - 0s - loss: 3.8709e-04 - 284ms/epoch - 2ms/step
Epoch 7/90
153/153 - 0s - loss: 3.7214e-04 - 273ms/epoch - 2ms/step
Epoch 8/90
153/153 - 0s - loss: 3.6078e-04 - 276ms/epoch - 2ms/step
Epoch 9/90
153/153 - 0s - loss: 3.5206e-04 - 290ms/epoch - 2ms/step
Epoch 10/90
153/153 - 0s - loss: 3.2790e-04 - 269ms/epoch - 2ms/step
Epoch 11/90
153/153 - 0s - loss: 3.0376e-04 - 288ms/epoch - 2ms/step
Epoch 12/90
153/153 - 0s - loss: 3.0369e-04 - 272ms/epoch - 2ms/step
Epoch 13/90
153/153 - 0s - loss: 3.1673e-04 - 272ms/epoch - 2ms/step
Epoch 14/90
153/153 - 0s - loss: 2.6855e-04 - 288ms/epoch - 2ms/step
Epoch 15/90
153/153 - 0s - loss: 2.6134e-04 - 274ms/epoch - 2ms/step
Epoch 16/90
153/153 - 0s - loss: 2.7831e-04 - 266ms/epoch - 2ms/step
Epoch 17/90
153/153 - 0s - loss: 2.3250e-04 - 280ms/epoch - 2ms/step
Epoch 18/90
153/153 - 0s - loss: 2.4377e-04 - 277ms/epoch - 2ms/step
Epoch 19/90
153/153 - 0s - loss: 2.1988e-04 - 300ms/epoch - 2ms/step
Epoch 20/90
153/153 - 0s - loss: 2.1443e-04 - 309ms/epoch - 2ms/step
Epoch 21/90
153/153 - 0s - loss: 2.0727e-04 - 308ms/epoch - 2ms/step
Epoch 22/90
153/153 - 0s - loss: 2.1768e-04 - 303ms/epoch - 2ms/step
Epoch 23/90
153/153 - 0s - loss: 1.9675e-04 - 301ms/epoch - 2ms/step
Epoch 24/90
153/153 - 0s - loss: 1.8817e-04 - 298ms/epoch - 2ms/step
Epoch 25/90
153/153 - 0s - loss: 2.0940e-04 - 302ms/epoch - 2ms/step
Epoch 26/90
153/153 - 0s - loss: 1.8725e-04 - 273ms/epoch - 2ms/step
Epoch 27/90
153/153 - 0s - loss: 1.9996e-04 - 268ms/epoch - 2ms/step
Epoch 28/90
153/153 - 0s - loss: 1.8666e-04 - 269ms/epoch - 2ms/step
Epoch 29/90
153/153 - 0s - loss: 2.0690e-04 - 289ms/epoch - 2ms/step
Epoch 30/90
153/153 - 0s - loss: 1.8579e-04 - 290ms/epoch - 2ms/step
Epoch 31/90
153/153 - 0s - loss: 1.8400e-04 - 327ms/epoch - 2ms/step
Epoch 32/90
153/153 - 0s - loss: 2.0776e-04 - 370ms/epoch - 2ms/step
Epoch 33/90
153/153 - 0s - loss: 1.7955e-04 - 285ms/epoch - 2ms/step
Epoch 34/90
153/153 - 0s - loss: 1.8656e-04 - 283ms/epoch - 2ms/step
Epoch 35/90
153/153 - 0s - loss: 1.7452e-04 - 363ms/epoch - 2ms/step
Epoch 36/90
153/153 - 0s - loss: 1.8671e-04 - 291ms/epoch - 2ms/step
Epoch 37/90
153/153 - 0s - loss: 2.0337e-04 - 276ms/epoch - 2ms/step
Epoch 38/90
153/153 - 0s - loss: 1.8612e-04 - 290ms/epoch - 2ms/step
Epoch 39/90
153/153 - 0s - loss: 1.7868e-04 - 287ms/epoch - 2ms/step
Epoch 40/90
153/153 - 0s - loss: 1.6047e-04 - 288ms/epoch - 2ms/step
Epoch 41/90
153/153 - 0s - loss: 1.7351e-04 - 274ms/epoch - 2ms/step
Epoch 42/90
153/153 - 0s - loss: 1.7513e-04 - 277ms/epoch - 2ms/step
Epoch 43/90
153/153 - 0s - loss: 1.6558e-04 - 286ms/epoch - 2ms/step
Epoch 44/90
153/153 - 0s - loss: 1.6894e-04 - 273ms/epoch - 2ms/step
Epoch 45/90
153/153 - 0s - loss: 1.9839e-04 - 270ms/epoch - 2ms/step
Epoch 46/90
153/153 - 0s - loss: 1.6972e-04 - 283ms/epoch - 2ms/step
Epoch 47/90
153/153 - 0s - loss: 1.9237e-04 - 287ms/epoch - 2ms/step
Epoch 48/90
153/153 - 0s - loss: 1.7812e-04 - 276ms/epoch - 2ms/step
Epoch 49/90
153/153 - 0s - loss: 1.6258e-04 - 283ms/epoch - 2ms/step
Epoch 50/90
153/153 - 0s - loss: 1.5556e-04 - 296ms/epoch - 2ms/step
Epoch 51/90
153/153 - 0s - loss: 1.5876e-04 - 279ms/epoch - 2ms/step
Epoch 52/90
153/153 - 0s - loss: 1.5916e-04 - 266ms/epoch - 2ms/step
Epoch 53/90
153/153 - 0s - loss: 1.7933e-04 - 363ms/epoch - 2ms/step
Epoch 54/90
153/153 - 0s - loss: 1.6076e-04 - 298ms/epoch - 2ms/step
Epoch 55/90
153/153 - 0s - loss: 1.5508e-04 - 276ms/epoch - 2ms/step
Epoch 56/90
153/153 - 0s - loss: 2.0915e-04 - 270ms/epoch - 2ms/step
Epoch 57/90
153/153 - 0s - loss: 1.6386e-04 - 340ms/epoch - 2ms/step
Epoch 58/90
153/153 - 0s - loss: 1.8083e-04 - 276ms/epoch - 2ms/step
Epoch 59/90
153/153 - 0s - loss: 1.5268e-04 - 274ms/epoch - 2ms/step
Epoch 60/90
153/153 - 0s - loss: 1.4961e-04 - 275ms/epoch - 2ms/step
Epoch 61/90
153/153 - 0s - loss: 1.8783e-04 - 271ms/epoch - 2ms/step
Epoch 62/90
153/153 - 0s - loss: 1.4447e-04 - 280ms/epoch - 2ms/step
Epoch 63/90
153/153 - 0s - loss: 1.4833e-04 - 275ms/epoch - 2ms/step
Epoch 64/90
153/153 - 0s - loss: 1.5035e-04 - 270ms/epoch - 2ms/step
Epoch 65/90
153/153 - 0s - loss: 1.6430e-04 - 272ms/epoch - 2ms/step
Epoch 66/90
153/153 - 0s - loss: 1.5268e-04 - 280ms/epoch - 2ms/step
Epoch 67/90
153/153 - 0s - loss: 1.6926e-04 - 282ms/epoch - 2ms/step
Epoch 68/90
153/153 - 0s - loss: 1.4859e-04 - 270ms/epoch - 2ms/step
Epoch 69/90
153/153 - 0s - loss: 1.6119e-04 - 285ms/epoch - 2ms/step
Epoch 70/90
153/153 - 0s - loss: 1.6663e-04 - 262ms/epoch - 2ms/step
Epoch 71/90
153/153 - 0s - loss: 1.6530e-04 - 280ms/epoch - 2ms/step
Epoch 72/90
153/153 - 0s - loss: 1.7051e-04 - 285ms/epoch - 2ms/step
Epoch 73/90
153/153 - 0s - loss: 1.6448e-04 - 275ms/epoch - 2ms/step
Epoch 74/90
153/153 - 0s - loss: 1.6769e-04 - 278ms/epoch - 2ms/step
Epoch 75/90
153/153 - 0s - loss: 1.6086e-04 - 271ms/epoch - 2ms/step
Epoch 76/90
153/153 - 0s - loss: 1.6478e-04 - 279ms/epoch - 2ms/step
Epoch 77/90
153/153 - 0s - loss: 1.5804e-04 - 263ms/epoch - 2ms/step
Epoch 78/90
153/153 - 0s - loss: 1.5207e-04 - 334ms/epoch - 2ms/step
Epoch 79/90
153/153 - 0s - loss: 1.3981e-04 - 295ms/epoch - 2ms/step
Epoch 80/90
153/153 - 0s - loss: 1.3907e-04 - 287ms/epoch - 2ms/step
Epoch 81/90
153/153 - 0s - loss: 1.5976e-04 - 307ms/epoch - 2ms/step
Epoch 82/90
153/153 - 0s - loss: 1.6238e-04 - 339ms/epoch - 2ms/step
Epoch 83/90
153/153 - 0s - loss: 1.4658e-04 - 497ms/epoch - 3ms/step
Epoch 84/90
153/153 - 0s - loss: 1.6490e-04 - 310ms/epoch - 2ms/step
Epoch 85/90
153/153 - 0s - loss: 1.5613e-04 - 283ms/epoch - 2ms/step
Epoch 86/90
153/153 - 0s - loss: 1.5913e-04 - 302ms/epoch - 2ms/step
Epoch 87/90
153/153 - 0s - loss: 1.5089e-04 - 288ms/epoch - 2ms/step
Epoch 88/90
153/153 - 0s - loss: 1.6357e-04 - 276ms/epoch - 2ms/step
Epoch 89/90
153/153 - 0s - loss: 1.4136e-04 - 271ms/epoch - 2ms/step
Epoch 90/90
153/153 - 0s - loss: 1.4603e-04 - 275ms/epoch - 2ms/step
<keras.callbacks.History at 0x236b9801000>

Evaluamos nuestro modelo, utilizando la métrica del RMSE para los datos de train y test.

trainPredict = model.predict(x_train)
testPredict = model.predict(x_test)
# Invertimos las predicciones, dado que las habiamos transformado con MinMaxScaler
trainPredict = min_max_scaler.inverse_transform(trainPredict)
trainY = min_max_scaler.inverse_transform([y_train])
testPredict = min_max_scaler.inverse_transform(testPredict)
testY = min_max_scaler.inverse_transform([y_test])
# calculamos el RMSE
trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0]))
print('Train Score: %.2f RMSE' % (trainScore))
testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0]))
print('Test Score: %.2f RMSE' % (testScore))
 1/39 [..............................] - ETA: 13s39/39 [==============================] - 0s 1ms/step
 1/17 [>.............................] - ETA: 0s17/17 [==============================] - 0s 1ms/step
Train Score: 1.20 RMSE
Test Score: 1.91 RMSE

Finalmente, observamos nuestro resultados gráficamente

trainPredictPlot = np.empty_like(dataset)
trainPredictPlot[:, :] = np.nan
trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict

testPredictPlot = np.empty_like(dataset)
testPredictPlot[:, :] = np.nan
testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict

plt.plot(min_max_scaler.inverse_transform(dataset), label = "Precio Historico")
plt.plot(trainPredictPlot, label = "Datos Entrenamiento")
plt.plot(testPredictPlot, label = "Predicción de Precio")
plt.legend()
plt.show()

Note que nuestros resultados son muy buenos, sin embargo, podrían mejorarse, quizas aplicando más capas ocultas a nuestra red, recuerde que solo utilizamos 20. Este podría ser un buen ejercicio para que usted intente mejorar estos resultados.

Recuerda que puedes comentar este post, agradeceria que lo hagas ya sea para alguna sugerencia u observación. Saludos espero hayas conocido un poco sobre este tipo de Red Neuronal en particular y su potente poder predectivo.