Predicción del precio de las acciones con LSTM

El aprendizaje automático ha encontrado sus aplicaciones en muchos campos interesantes a lo largo de estos años. Domar la bolsa es uno de ellos. Llevaba tiempo pensando en darle una oportunidad; sobre todo para afianzar mis conocimientos de trabajo con LSTMs. Y finalmente he terminado el proyecto y bastante emocionado de compartir mi experiencia.

Motivación y público objetivo

Escribiré sobre mi experiencia en una serie de blogs. El propósito de esta serie no es explicar los conceptos básicos de LSTM o de aprendizaje automático. Por lo tanto, voy a asumir que el lector ha comenzado su viaje con el Aprendizaje Automático y tiene los fundamentos como Python, la familiaridad con SkLearn, Keras, LSTM etc. La razón es que ya hay excelentes artículos sobre temas como «¿Cómo funcionan las LSTM?» por personas que están mucho más cualificadas para explicar las matemáticas que hay detrás. Pero compartiré los enlaces a dichos artículos, siempre que considere que pueden faltar conocimientos de fondo. Aunque hay muchos artículos que te dicen cómo predecir los precios de las acciones dado un conjunto de datos, la mayoría de los autores no revelan/explican cómo llegaron a esa configuración particular para una red neuronal o cómo seleccionaron ese conjunto particular de hiperparámetros. Así que el verdadero propósito de este artículo es compartir esos pasos, mis errores y algunos pasos que me resultaron muy útiles. Como tal, este artículo no se limita al problema de la predicción del precio de las acciones.

Aquí están las cosas que veremos :

  1. Lectura y análisis de datos. (Pandas)
  2. Normalizar los datos. (SkLearn)
  3. Convertir los datos en series temporales y problema de aprendizaje supervisado.
  4. Crear el modelo (Keras)
  5. Ajustar el modelo (en el próximo artículo)
  6. Entrenar, predecir y visualizar el resultado.
  7. Consejos &herramientas que me resultaron muy útiles (último artículo de la serie)

Tenga en cuenta que este primer artículo habla de los pasos de preprocesamiento y las terminologías de LSTM. Si estás bastante seguro de estos pasos, puedes pasar al siguiente artículo.

¡Comencemos!

Lectura y análisis de los datos

Para este artículo utilizaré los datos históricos del precio de las acciones de GE. Usted puede encontrar los datos en mi sitio kaggle aquí. No recuerdo la fuente de los datos ya que los había descargado hace tiempo. Podemos leer los datos en el marco como se muestra a continuación:

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

Como se puede ver hay alrededor de 14060 elementos, cada uno de los cuales representa los atributos del mercado de valores de un día para la empresa. Veamos cómo se ve en un gráfico :

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 los precios -Apertura, Cierre, Mínimo, Máximo- no varían demasiado entre sí, excepto por ligeras caídas ocasionales en el precio mínimo.

Ahora vamos a ver el gráfico del volumen :

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

Huh. ¿Has visto algo interesante? Hay un gran aumento en el número de transacciones alrededor del día 12000 en la línea de tiempo, que coincide con la repentina caída del precio de las acciones. Tal vez podemos volver a esa fecha en particular y desenterrar viejos artículos de noticias para encontrar lo que causó.

Ahora vamos a ver si tenemos algún valor null/Nan que preocuparse. Resulta que no tenemos ningún valor nulo.

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

Normalizando los datos

Los datos no están normalizados y el rango de cada columna varía, especialmente el Volumen. Normalizar los datos ayuda al algoritmo a converger, es decir, a encontrar el mínimo local/global de forma eficiente. Voy a utilizar MinMaxScaler de Sci-kit Learn. Pero antes tenemos que dividir el conjunto de datos en conjuntos de datos de entrenamiento y de prueba. También voy a convertir el DataFrame a ndarray en el proceso.

Convertir los datos en series de tiempo y el problema de aprendizaje supervisado

Esto es bastante importante y algo complicado. Aquí es donde se necesita el conocimiento LSTM. Me gustaría dar una breve descripción de los conceptos clave que se necesitan aquí, pero recomiendo encarecidamente la lectura del blog de Andre karpathy aquí, que se considera uno de los mejores recursos en LSTM por ahí y esto. O puedes ver el video de Andrew Ng también (que por cierto menciona el blog de Andre también).

LSTMs consumen la entrada en formato ; un array de 3 dimensiones.

  • El tamaño del lote dice cuántas muestras de entrada quieres que tu red neuronal vea antes de actualizar los pesos. Digamos que tiene 100 muestras (conjunto de datos de entrada) y quiere actualizar los pesos cada vez que su NN ha visto una entrada. En ese caso el tamaño del lote sería 1 y el número total de lotes sería 100. De la misma manera, si quiere que su red actualice los pesos después de haber visto todas las muestras, el tamaño del lote sería 100 y el número de lotes sería 1. Resulta que el uso de un tamaño de lote muy pequeño reduce la velocidad de entrenamiento y, por otro lado, el uso de un tamaño de lote demasiado grande (como todo el conjunto de datos) reduce la capacidad de los modelos para generalizar a diferentes datos y también consume más memoria. Pero se necesitan menos pasos para encontrar el mínimo de la función objetivo. Así que hay que probar varios valores en los datos y encontrar el punto óptimo. Es un tema bastante amplio. Vamos a ver cómo buscar estos de manera algo más inteligente en el próximo artículo.
  • Pasos de tiempo definen cuántas unidades hacia atrás en el tiempo que desea que su red para ver. Por ejemplo, si estás trabajando en un problema de predicción de caracteres donde tienes un corpus de texto para entrenar y decides alimentar tu red con 6 caracteres a la vez. En nuestro caso, utilizaremos 60 como paso de tiempo, es decir, miraremos 2 meses de datos para predecir el precio del día siguiente. Más sobre esto más tarde.
  • Características es el número de atributos utilizados para representar cada paso de tiempo. Considere el ejemplo de predicción de caracteres anterior, y suponga que utiliza un vector codificado de un solo disparo de tamaño 100 para representar cada carácter. Entonces el tamaño de la característica aquí es 100.

Ahora que tenemos algunas terminologías aclaradas fuera del camino, vamos a convertir nuestros datos de stock en un formato adecuado. Supongamos, para simplificar, que elegimos 3 como nuestro paso de tiempo (queremos que nuestra red mire hacia atrás en 3 días de datos para predecir el precio en el cuarto día) entonces formaríamos nuestro conjunto de datos así:

Las muestras 0 a 2 serían nuestra primera entrada y el precio de cierre de la muestra 3 sería su correspondiente valor de salida; ambos encerrados en el rectángulo verde. Del mismo modo, las muestras 1 a 3 serían nuestra segunda entrada y el precio de cierre de la muestra 4 sería el valor de salida; representado por el rectángulo azul. Y así sucesivamente. Así que hasta ahora tenemos una matriz de forma (3, 5), siendo 3 el paso de tiempo y 5 el número de características. Ahora piense cuántos pares de entrada-salida son posibles en la imagen anterior. 4.

También mezcle el tamaño del lote con esto. Supongamos que elegimos un tamaño de lote de 2. Entonces el par de entrada-salida 1 (rectángulo verde) y el par 2 (rectángulo azul) constituirían el lote uno. Y así sucesivamente. Aquí está el fragmento de código python para hacer esto:

‘y_col_index’ es el índice de su columna de salida. Ahora supongamos que después de convertir los datos en formato de aprendizaje supervisado, como se muestra arriba, usted tiene 41 muestras en su conjunto de datos de entrenamiento, pero su tamaño de lote es 20, entonces usted tendrá que recortar su conjunto de entrenamiento para eliminar las muestras impares que quedan fuera. Buscaré una forma mejor de evitar esto, pero por ahora esto es lo que he hecho:

Ahora usando las funciones anteriores vamos a formar nuestros conjuntos de datos de entrenamiento, validación y prueba

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)

Ahora que nuestros datos están listos podemos concentrarnos en la construcción del modelo.

Creación del modelo

Vamos a utilizar LSTM para esta tarea, que es una variación de la red neuronal recurrente. Crear el modelo LSTM es tan sencillo como esto:

Ahora que tienes tu modelo compilado y listo para ser entrenado, entrénalo como se muestra a continuación. Si te preguntas qué valores utilizar para parámetros como las épocas, el tamaño del lote, etc., no te preocupes, veremos cómo resolverlos en el próximo artículo.

El entrenamiento de este modelo (con los hiperparámetros bien ajustados) dio un mejor error de 3,27e-4 y un mejor error de validación de 3,7e-4. Aquí es lo que la pérdida de formación frente a la pérdida de validación parecía:

Error de formación frente a error de validación

Así es como la predicción parecía con el modelo anterior:

Predicción vs datos reales

He descubierto que esta configuración para LSTM es la que mejor funciona de todas las combinaciones que he probado (para este conjunto de datos), ¡y he probado más de 100! Así que la pregunta es: ¿cómo encontrar la arquitectura perfecta (o, en casi todos los casos, casi perfecta) para tu red neuronal? Esto nos lleva a nuestra siguiente e importante sección, que continuará en el próximo artículo.

Puede encontrar todos los programas completos en mi perfil de Github aquí.

NOTA: Una humilde petición a los lectores – Todos ustedes son bienvenidos a conectarse conmigo en LinkedIn o Twitter, pero si usted tiene una pregunta con respecto a mis blogs por favor, publíquelo en la sección de comentarios del blog respectivo en lugar de la mensajería personal, de modo que si alguien más tiene la misma consulta, lo encontraría aquí mismo, y no tendría que explicarlo individualmente. No obstante, puedes enviarme personalmente las consultas que no estén relacionadas con los blogs o con la tecnología en general. Gracias 🙂

Actualización 13/4/19

  1. Ha llegado a mi conocimiento, desde que he escrito este artículo, que mi modelo utilizado para este blog puede haber sido sobreajustado. Aunque no lo he confirmado, es probable. Así que, por favor, tened cuidado al implementarlo en vuestros proyectos. Usted podría probar cosas como menor epochs, red más pequeña, más dropout etc.
  2. He utilizado la activación Sigmoid para la última capa que puede sufrir de la limitación de no ser capaz de predecir un precio mayor que ‘max’ precio en el conjunto de datos. Usted podría intentar la activación ‘Lineal’ para la última capa para resolver esto.
  3. Corregí un error tipográfico en la sección «convertir los datos a series de tiempo».

Gracias a los lectores por llamar mi atención.

Actualización 21/1/2020

Deja una respuesta

Tu dirección de correo electrónico no será publicada.