Redes neuronales¶


Proyecto 0: Predicción del precio de una acción¶


Autor: Luis Fernando Apáez Álvarez

Contenido¶

  • Librería yfinance
  • Implementación del modelo

Con base en lo aprendido en la última clase estamos preparados para crear una red neuronal y realizar la predicción de los precios de una acción. Antes de ello será necesario abordar un poco sobre una librería nueva.

Librería yfinance ¶

Esta librería extrae información actualizada de yahoo finance referente a los principales mercados financieros en el mundo. Así, podemos extraer información sobre precios de acciones de diversas compañias. Para acceder a la información de google utilizares el símbolo 'goog'; para el caso de Apple el símbolo es 'aapl'. Puedes chacar más índices en el siguiente Link.

Para extraer información deberemos de utilizar primero el método Ticker(), donde dentro del paréntesis colocaremos la cadena de texto con el símbolo de nuestro interés. Por ejemplo, para google tenemos:

In [31]:
# importación necesaria
import yfinance as yf

# Accedemos a la info de google
goog = yf.Ticker('goog')

Luego, para obtener los precios históricos de las acciones de dicha compañia utilizamos el método history() al cual le pasaremos 3 parámetros:

  • interval: cadena de texto que especifica el período de tiempo en el cual extraeremos los datos. Por ejemplo, podemos especificar un período diario (es decir que obtenemos los precios de la acción de cada día), a un trimestre, a un semestre, entre otros.
  • start: cadena de texto con la fecha inicial a la cual extraeremos la información, donde el formato de la fecha es año-mes-día.
  • end: fecha de fin de la extracción de la información.

Por ejemplo, extraigamos la información de los precios de las acciones de google para todo el 2021

In [37]:
# configuramos el intervalo en 1 dia (1d)
# para obtener los precios diarios
df_goog = goog.history(interval='1d', start='2021-01-01', end='2021-12-31')
df_goog.head()
Out[37]:
Open High Low Close Volume Dividends Stock Splits
Date
2021-01-04 87.876999 88.032501 85.392502 86.412003 38038000 0 0
2021-01-05 86.250000 87.383499 85.900749 87.045998 22906000 0 0
2021-01-06 85.131500 87.400002 84.949997 86.764503 52042000 0 0
2021-01-07 87.002998 89.419998 86.852501 89.362503 45300000 0 0
2021-01-08 89.399002 90.491997 88.676750 90.360497 41012000 0 0

y lo que obtenemos con ello es un dataframe con la siguiente información importante:

  • Date: fecha.
  • Open: precio de apertura de la acción.
  • High: precio más alcanzado en ese día.
  • Low: precio más bajo alcanzado en ese día.
  • Close: precio de cierre.
  • Volumne: volumen de oferta de acciones para ese día.

Veamos un gráfico para Date versus Open:

In [38]:
# resetamos el indice y cambiamos el nombre de la columna resultante
df_goog = df_goog.reset_index().rename(columns={'index': 'Date'})
# graficamos
ax = df_goog.plot(x='Date', y='Open')

Podemos graficar Date versus cada una de las demás variables del dataframe (a excepción de Dividend y Stock Splits):

In [51]:
import seaborn as sns
import matplotlib.pyplot as plt

sns.set_theme(style="darkgrid")

sns.lineplot(x = "Date", y = "Open", data = df_goog, label='Open')
sns.lineplot(x = "Date", y = "Close", data = df_goog, label='Close')
sns.lineplot(x = "Date", y = "Low", data = df_goog, label='Low')
sns.lineplot(x = "Date", y = "High", data = df_goog, label='High')
plt.show()

Implementación del modelo ¶

Ahora realizaremos el modelo de la red neuronal, así como le hicimos en la clase pasada, para realizar la predicción del precio de cierre (Close) de las acciones de google. Dicha predicción será sobre los precios de las acciones de diciembre del 2022.

Ahora bien, para realizar la predicción ocuparemos las variables Open, High y Low como variables predictorias y la variable Clase será la variable de respuesta. Así:

In [55]:
# inicialmente cargamos de nuevo los datos con la informacion
# del primero de enero del 2022 hasta el ultimo dia de noviembre
# de ese agno
df_goog = goog.history(interval='1d', start='2022-01-01', end='2022-11-30')
df_goog.head()
Out[55]:
Open High Low Close Volume Dividends Stock Splits
Date
2022-01-03 144.475494 145.550003 143.502502 145.074493 25214000 0 0.0
2022-01-04 145.550507 146.610001 143.816147 144.416504 22928000 0 0.0
2022-01-05 144.181000 144.298004 137.523499 137.653503 49642000 0 0.0
2022-01-06 137.497498 139.686005 136.763504 137.550995 29050000 0 0.0
2022-01-07 137.904999 138.254745 135.789001 137.004501 19408000 0 0.0
In [57]:
# variables predictorias
X = df_goog.drop(['Dividends', 'Stock Splits', 'Volume', 'Close'], axis=1).values

# variable de respuesta
y = df_goog['Close'].values.reshape(-1,1)

Después realizamos la división de los datos

In [58]:
# Importacion necesaria
from sklearn.model_selection import train_test_split

# division de los datos
X_train, X_test, y_train, y_test = train_test_split(X, y, # conjunto completo
                                       test_size=0.25,    # 25% para pruebas
                                       random_state=123)  # semilla aleatoria 

Y traeremos la función modelo() definida en la clase anterior

In [59]:
# importaciones necesarias
from keras.models import Sequential
from keras.layers import Dense
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
import pandas as pd

def modelo(n1, n2, activacion, epocas, boolean):
    """
    n1: número de neuronas en la primer capa
    n2: número de neuronas en la segunda capa
    activacion: función de activación en la última capa
    epocas: número de épocas
    boolean: booleano para determinar el valor de verbose
    """
    # Instanciamos el modelo
    model = Sequential()
    # Capa de entrada de 1 neurona y capa oculta de n1 neuronas.
    # Para la capa oculta utilizamos la funcion de activacion relu
    model.add(Dense(n1, activation='relu', input_shape=(3,)))
    # Capa oculta 2:
    # n2 neuronas y funcion de activacion relu
    model.add(Dense(n2, activation='relu'))
    # Capa de salida con funcion de activacion "activacion"
    model.add(Dense(1, activation=activacion))
    # Compilacion. Utilizaremos ahora el error cuadratico medio
    model.compile(optimizer='adam', loss='mean_squared_error')
    # Entrenamiento
    modelo = model.fit(X_train, y_train, epochs=epocas, verbose = boolean)
    # prediccion
    y_pred = model.predict(X_test)
    # dataframe con la informacion
    df = pd.DataFrame(y_pred).rename(columns = {0: 'y_pred'})
    df['y_test'] = y_test
    df = df.reset_index()
    df['diff'] = abs(df.y_pred - df.y_test)
    df['error'] = (df['diff'] * 100) / df['y_test']
    # estadisticas
    df_stats = df.describe()
    # grafica
    plt.plot(df.index, df.y_pred, color='blue', label='prediccioón')
    plt.plot(df.index, df.y_test, color='red', label='real')
    plt.legend()
    plt.grid()
    plt.show()
    # MSE
    mse = mean_squared_error(y_pred, y_test)
    print(f'mse = {mse}')
    # hacemos que la funcion retorne 4 valores
    #                           |modelo de la red neuronal
    return modelo, df, df_stats, model

Luego, utilizaremos los mismos parámetros (referentes a los de la clase anterior) para la función definida antes

In [60]:
modelo1, df1, df_stats1, model = modelo(40, 25, 'relu', 13, True)
df_stats1
Epoch 1/13
6/6 [==============================] - 0s 1ms/step - loss: 11172.4160
Epoch 2/13
6/6 [==============================] - 0s 2ms/step - loss: 8778.2803
Epoch 3/13
6/6 [==============================] - 0s 2ms/step - loss: 6640.5557
Epoch 4/13
6/6 [==============================] - 0s 1ms/step - loss: 4760.3193
Epoch 5/13
6/6 [==============================] - 0s 2ms/step - loss: 3164.4153
Epoch 6/13
6/6 [==============================] - 0s 2ms/step - loss: 1876.5985
Epoch 7/13
6/6 [==============================] - 0s 2ms/step - loss: 919.3132
Epoch 8/13
6/6 [==============================] - 0s 2ms/step - loss: 282.8372
Epoch 9/13
6/6 [==============================] - 0s 2ms/step - loss: 33.2030
Epoch 10/13
6/6 [==============================] - 0s 1ms/step - loss: 16.0118
Epoch 11/13
6/6 [==============================] - 0s 2ms/step - loss: 50.3454
Epoch 12/13
6/6 [==============================] - 0s 2ms/step - loss: 45.8178
Epoch 13/13
6/6 [==============================] - 0s 2ms/step - loss: 20.2811
2/2 [==============================] - 0s 2ms/step
mse = 8.290976731999825
Out[60]:
index y_pred y_test diff error
count 58.000000 58.000000 58.000000 58.000000 58.000000
mean 28.500000 120.414383 117.967120 2.458504 2.113276
std 16.886879 16.744480 16.533656 1.512003 1.337491
min 0.000000 87.110794 83.489998 0.009026 0.007565
25% 14.250000 107.735193 105.547503 1.434198 1.224794
50% 28.500000 119.347202 116.684498 2.241558 1.858940
75% 42.750000 136.508518 135.368870 3.769310 3.229663
max 57.000000 152.891418 148.036499 6.004059 5.293557

Notamos que el modelo obtenido es bastante bueno. Intentaremos mejorar el modelo anterior aumentando el número de épocas

In [64]:
modelo2, df2, df_stats2, model2 = modelo(40, 25, 'relu', 20, False)
df_stats2
2/2 [==============================] - 0s 2ms/step
mse = 1.780634913191095
Out[64]:
index y_pred y_test diff error
count 58.000000 58.000000 58.000000 58.000000 58.000000
mean 28.500000 117.840652 117.967120 1.091763 0.942146
std 16.886879 16.403910 16.533656 0.773962 0.673513
min 0.000000 85.007462 83.489998 0.006187 0.005394
25% 14.250000 105.638510 105.547503 0.378515 0.354370
50% 28.500000 116.582172 116.684498 0.947289 0.838506
75% 42.750000 133.619869 135.368870 1.592861 1.362022
max 57.000000 148.566010 148.036499 2.639015 2.273301

con lo que sí conseguimos una gran mejora en el modelo. Luego, realizaremos la predicción de los precios de las acciones para diciembre (hasta el 6 de diciembre), para ello

In [17]:
# extraemos la informacion 
data2 = goog.history(interval='1d', start='2022-11-30', end='2022-12-06')
data2
Out[17]:
Open High Low Close Volume Dividends Stock Splits
Date
2022-11-30 141.399994 148.720001 140.550003 148.029999 111224400 0 0
2022-12-01 148.210007 149.130005 146.610001 148.309998 71250400 0 0
2022-12-02 145.960007 148.000000 145.649994 147.809998 65421400 0 0
2022-12-05 147.770004 150.919998 145.770004 146.630005 68826400 0 0
In [65]:
# convertimos esos valores en un array
X_new = data2.drop(['Volume', 'Dividends', 'Stock Splits', 'Close'], axis=1).values

# hacemos las predicciones a partir del segundo modelo obtenido
y_pred = model2.predict(X_new)
1/1 [==============================] - 0s 21ms/step

Después, comparamos los valores reales con los predichos:

In [66]:
df_eval = pd.DataFrame(y_pred).rename(columns={0:'val_pred'})
df_eval['val_real'] = data2['Close'].values
df_eval
Out[66]:
val_pred val_real
0 142.344238 148.029999
1 147.913437 148.309998
2 146.598312 147.809998
3 147.499329 146.630005

Podemos observar el porcentaje de error para cada predicción:

In [67]:
# valor absoluto de la diferencia entre el precio
# real y el predicho
df_eval['diff'] = abs(df_eval.val_real - df_eval.val_pred) 

# calculo del porcentaje de error
df_eval['error (%)'] = (df_eval['diff'] * 100) / df_eval['val_real']
df_eval
Out[67]:
val_pred val_real diff error (%)
0 142.344238 148.029999 5.685760 3.840952
1 147.913437 148.309998 0.396561 0.267386
2 146.598312 147.809998 1.211685 0.819759
3 147.499329 146.630005 0.869324 0.592869

Veamos que los errores cometidos no son muy grandes.

Finalmente, veamos el ajuste de los datos que la red neuronal hace, es decir, realicemos la predicción sobre el precio para el conjunto de datos de entrenamiento y prueba y comparemos.

In [75]:
# Conjunto de datos 
X_data = df_goog[['Open', 'Low', 'High']].values
y_data = df_goog['Close'].values.reshape(-1,1)

# prediccion
y_pred = model2.predict(X_data)
8/8 [==============================] - 0s 1ms/step

Graficamos el gráfico de líneas de los datos originales y contrastamos con los datos predichos

In [77]:
# dataframe auxiliar
df_aux = pd.DataFrame(y_pred).rename(columns={0:'val_pred'})
df_aux['val_real'] = y_data
df_aux
Out[77]:
val_pred val_real
0 145.598526 145.074493
1 146.572693 144.416504
2 144.005722 137.653503
3 139.325851 137.550995
4 138.420609 137.004501
... ... ...
224 97.266556 97.330002
225 98.877586 98.820000
226 99.110260 97.599998
227 97.892242 96.250000
228 96.503128 95.440002

229 rows × 2 columns

In [79]:
df_aux = df_aux.reset_index()

sns.lineplot(x = "index", y = "val_real", data = df_aux, label='Real')
sns.lineplot(x = "index", y = "val_pred", data = df_aux, label='Predicción')
plt.show()

vemos que el ajuste hecho por la red neuronal es muy bueno y se acerca bastante al comportamiento real de los precios.