Si te gusta Skforecast , ayúdanos dándonos una estrella en GitHub! ⭐️
Más sobre forecasting en: cienciadedatos.net
El Deep Learning es un campo de la inteligencia artificial enfocado en crear modelos basados en redes neuronales que permiten aprender representaciones no lineales de manera jerárquica. Las redes neuronales recurrentes (RNN) son un tipo de arquitectura de deep learning diseñada para trabajar con datos secuenciales, donde la información se propaga a través de conexiones recurrentes, permitiendo a la red aprender dependencias temporales.
Este artículo describe cómo entrenar modelos de redes neuronales recurrentes -específicamente RNN y LSTM- para la predicción de series temporales (forecasting) empleando Python, Keras y Skforecast.
Keras3 proporciona una interfaz sencilla para construir y entrenar modelos de redes neuronales. Gracias a su API de alto nivel, los desarrolladores pueden implementar fácilmente arquitecturas LSTM, aprovechando las ventajas de la eficiencia computacional y la escalabilidad que ofrece el deep learning.
Skforecast permite generalizar de forma sencilla la implementación y uso de modelos de machine learning -entre ellos LSTMs y RNNs- a problemas de forecasting. De esta forma, el usuario puede definir el problema y abstraerse de la arquitectura. Para usuarios avanzados, skforecast también permite ejecutar una arquitectura de deep learning previamente definida.
✎ Nota
Para comprender plenamente este artículo, se presupone cierto conocimiento sobre redes neuronales y deep learning. No obstante, si este no es el caso, y mientras trabajamos en la creación de nuevo material, te proporcionamos algunos enlaces de referencia para comenzar:
Las Redes Neuronales Recurrentes (RNN) son un tipo de redes neuronales diseñadas para procesar datos que siguen un orden secuencial. En las redes neuronales convencionales, como las redes feedforward, la información fluye en una dirección, desde la entrada hasta la salida pasando por las capas ocultas, sin considerar la estructura secuencial de los datos. En cambio, las RNN mantienen estados internos o memorias, lo que les permite recordar información pasada y utilizarla para predecir datos futuros en la secuencia.
La unidad básica de una RNN es la célula recurrente. Esta célula toma dos entradas: la entrada actual y el estado oculto previo. El estado oculto puede entenderse como una "memoria" que retiene información de las iteraciones previas. La entrada actual y el estado oculto anterior se combinan para calcular la salida actual y el nuevo estado oculto. Esta salida se utiliza como entrada para la próxima iteración, junto con la siguiente entrada en la secuencia de datos.
A pesar de los avances que se han conseguido con las arquitecturas RNN, tienen limitaciones para capturar patrones a largo plazo. Es por esto que se han desarrollado variantes como las LSTM (Memorias a Corto y Largo Plazo) y las GRU (Unidades Recurrentes Gated), que abordan estos problemas y permiten retener información a largo plazo de manera más efectiva.
Las redes neuronales Long Short-Term Memory (LSTM) constituyen un tipo especializado de RNNs diseñadas para superar las limitaciones asociados con la captura de dependencias temporales a largo plazo. A diferencia de las RNN tradicionales, las LSTMs incorporan una arquitectura más compleja, introduciendo unidades de memoria y mecanismos de puertas para mejorar la gestión de la información a lo largo del tiempo.
Estructura de las LSTMs
Las LSTMs presentan una estructura modular que consta de tres puertas (gates) fundamentales: la puerta de olvido (forget gate), la puerta de entrada (input gate), y la puerta de salida (output gate). Estas puertas trabajan en conjunto para regular el flujo de información a través de la unidad de memoria, permitiendo un control más preciso sobre qué información retener y cuál olvidar.
Puerta de Olvido (Forget Gate): Regula cuánta información se debe olvidar y cuánta se mantiene, combinando la entrada actual y la salida anterior mediante una función sigmoide.
Puerta de Entrada (Input Gate): Decide cuánta nueva información debe añadirse a la memoria a largo plazo.
Puerta de Salida (Output Gate): Determina cuánta información de la memoria actual se utilizará para la salida final, combinando la entrada actual y la información de la memoria mediante una función sigmoide.
La complejidad de un problema de series temporarles suele estar definida por tres factores clave: primero, decidir qué serie o series temporales utilizar para entrenar el modelo; segundo, determinar qué o cuántas series temporales se quieren predecir; y tercero, definir el número de pasos a futuro que se desea predecir. Estos tres aspectos pueden ser un verdadero desafío al abordar problemas de series temporales.
Las redes neuronales recurrentes, gracias a su ámplia variedad de arquitecturas, permiten modelar los siguientes escenarios:
Problemas 1:1 - Modelar una única serie y predecir esa misma serie (single-serie, single-output)
Problemas N:1 - Modelar una única serie utilizando múltiples series (multi-series, single-output)
Problemas N:M - Modelar múltiples series utilizando múltiples series (multi-series, multiple-outputs)
En todos estos encenarios, la predicción puede realizarse single-step forecasting (un paso a futuro) o multi-step forecasting (múltiples pasos a futuro). En el primer caso, el modelo solo predice un único valor, mientras que en el segundo, el modelo predice múltiples valores a futuro.
En algunas situaciones, puede resultar complicado definir y crear la arquitectura de Deep Learning adecuada para abordar un problema concreto. La librería skforecast dispone de funcionalidades que permiten determinar la arquitectura de Tensorflow adecuada para cada problema, simplificando y acelerando el proceso de modelado para una amplia variedad de problemas. A continuación, se muestra un ejemplo de cómo utilizar skforecast para resolver cada uno de los problemas de series temporales descritos, utilizando redes neuronales recurrentes.
Los datos empleados en este artículo contienen información detallada sobre la calidad del aire en la ciudad de Valencia (España). La colección de datos abarca desde el 1 de enero de 2019 hasta el 31 de diciembre de 2021, proporcionando mediciones horarias de diversos contaminantes atmosféricos, como partículas PM2.5 y PM10, monóxido de carbono (CO), dióxido de nitrógeno (NO2), entre otros. Los datos se han obtenido de plataforma Red de Vigilancia y Control de la Contaminación Atmosférica, 46250054-València - Centre, https://mediambient.gva.es/es/web/calidad-ambiental/datos-historicos.
💡 Tip: Configuración del backend
Desde la versión0.13.0
de Skforecast, se ha añadido soporte para el backend de PyTorch. Puedes configurar el backend exportando la variable de entorno KERAS_BACKEND
o editando tu archivo de configuración local en ~/.keras/keras.json
. Las opciones de backend disponibles son: "tensorflow" y "torch". Ejemplo:
```python
import os
os.environ["KERAS_BACKEND"] = "torch"
import keras
```
⚠ Warning
El backend debe configurarse antes de importar Keras, y no se puede cambiar el backend después de que se haya importado la librería.# Procesado de datos
# ==============================================================================
import os
import pandas as pd
import numpy as np
from skforecast.datasets import fetch_dataset
# Gráficos
# ==============================================================================
import matplotlib.pyplot as plt
from skforecast.plot import set_dark_theme
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
import plotly.offline as poff
pio.templates.default = "seaborn"
poff.init_notebook_mode(connected=True)
# Keras
# ==============================================================================
os.environ["KERAS_BACKEND"] = "tensorflow" # 'tensorflow', 'jax´ or 'torch'
import keras
from keras.optimizers import Adam
from keras.losses import MeanSquaredError
from keras.callbacks import EarlyStopping
if keras.__version__ > "3.0":
if keras.backend.backend() == "tensorflow":
import tensorflow
elif keras.backend.backend() == "torch":
import torch
else:
print("Backend not recognized. Please use 'tensorflow' or 'torch'.")
# Modelado
# ==============================================================================
import skforecast
from skforecast.deep_learning import ForecasterRnn
from skforecast.deep_learning.utils import create_and_compile_model
from sklearn.preprocessing import MinMaxScaler
from skforecast.model_selection import TimeSeriesFold
from skforecast.model_selection import backtesting_forecaster_multiseries
# Configuración warnings
# ==============================================================================
import warnings
warnings.filterwarnings('once')
color = '\033[1m\033[38;5;208m'
print(f"{color}Version skforecast: {skforecast.__version__}")
print(f"{color}Version Keras: {keras.__version__}")
print(f"{color}Using backend: {keras.backend.backend()}")
print(f"{color}Version pandas: {pd.__version__}")
print(f"{color}Version numpy: {np.__version__}")
if keras.__version__ > "3.0":
if keras.backend.backend() == "tensorflow":
print(f"{color}Version tensorflow: {tensorflow.__version__}")
elif keras.backend.backend() == "torch":
print(f"{color}Version torch: {torch.__version__}")
else:
print(f"{color}Version torch: {jax.__version__}")
⚠ Warning
En el momento de escribir este documento,tensorflow
solo es compatible con versiones de numpy
inferiores a 2.0. Si tienes una versión superior, puedes reducirla ejecutando el siguiente comando: pip install numpy==1.26.4
# Descarga y procesado de datos
# ==============================================================================
air_quality = fetch_dataset(name="air_quality_valencia_no_missing")
air_quality.head()
Se verifica que el conjunto de datos tiene un índice de tipo DatetimeIndex
con frecuencia horaria. Si bien no es necesario que los datos tengan este tipo de índice para utilizar skforecast, es más ventajoso para el posterior uso de las predicciones.
# Comprobación de índice y frecuencia
# ==============================================================================
print(f"Tipo de índice: {air_quality.index.dtype}")
print(f"Frecuencia: {air_quality.index.freq}")
Para facilitar el entrenamiento de los modelos, la búsqueda de hiperparámetros óptimos y la evaluación de su capacidad predictiva, los datos se dividen en tres conjuntos separados: entrenamiento, validación y test.
# Split train-validation-test
# ==============================================================================
air_quality = air_quality.loc[:'2021-12-31 23:00:00', :].copy()
end_train = "2021-03-31 23:59:00"
end_validation = "2021-09-30 23:59:00"
air_quality_train = air_quality.loc[:end_train, :].copy()
air_quality_val = air_quality.loc[end_train:end_validation, :].copy()
air_quality_test = air_quality.loc[end_validation:, :].copy()
print(
f"Fechas train : {air_quality_train.index.min()} --- "
f"{air_quality_train.index.max()} (n={len(air_quality_train)})"
)
print(
f"Fechas validation : {air_quality_val.index.min()} --- "
f"{air_quality_val.index.max()} (n={len(air_quality_val)})"
)
print(
f"Fechas test : {air_quality_test.index.min()} --- "
f"{air_quality_test.index.max()} (n={len(air_quality_test)})"
)
# Gráfico de la serie temporal del contaminante pm2.5
# ==============================================================================
set_dark_theme()
fig, ax = plt.subplots(figsize=(7, 3))
air_quality_train["pm2.5"].rolling(100).mean().plot(ax=ax, label="entrenamiento")
air_quality_val["pm2.5"].rolling(100).mean().plot(ax=ax, label="validación")
air_quality_test["pm2.5"].rolling(100).mean().plot(ax=ax, label="test")
ax.set_title("pm2.5")
ax.legend();
Si bien tensorflow-keras facilita el proceso de crear arquitecturas de deep learning, no siempre es trivial determinar las dimensiones que debe tener un modelo LSTM para forecasting ya que estas dependen de cuantas series temporales se estén modelando, cuantas prediciendo y la longitud del horicento de predicción.
Para tratar de mejorar la experiencia del usuario y acelerar el proceso de propotipado, desarollo y puesta en producción, skforecast dispone de la función create_and_compile_model
, con la que, indicando apenas unos pocos argumentos, se infiere la arquitectura y se crea el modelo.
series
: Series temporales que se utilizarán para entrenar el modelo
levels
: Series temporales que se quieren predecir
lags
: Número de pasos de tiempo que se utilizarán para predecir el siguiente valor.
steps
: Número de pasos de tiempo que se quieren predecir.
recurrent_layer
: Tipo de capa recurrente a utilizar. Por defecto, se utiliza una capa LSTM.
recurrent_units
: Número de unidades de la capa recurrente. Por defecto, se utiliza 100. Si se pasa una lista, se creará una capa recurrente por cada elemento de la lista.
dense_units
: Número de unidades de la capa densa. Por defecto, se utiliza 64. Si se pasa una lista, se creará una capa densa por cada elemento de la lista.
optimizer
: Optimizador a utilizar. Por defecto, se utiliza Adam con learning rate de 0.01.
loss
: Función de pérdida a utilizar. Por defecto, se utiliza Mean Squared Error.
✎ Nota
La funcióncreate_and_compile_model
está pensada para facilitar la creación del modelo Tensorflow, sin embargo, usuarios más avanzados pueden crear sus propias arquitecturas siempre y cuado las dimensiones de entrada y salida coincidan con el caso de uso al que se va a aplicar el modelo.
Una vez que el modelo se ha creado y compilado, el siguiente paso es crear una instancia del ForecasterRnn. Esta clase se encarga de añadir al modelo de deep learning todas las funcionalidades necesarias para que pueda utilizarse en problemas de forecasting. Además es compatible con el resto de funcionalidades que ofrece skforecast (backtesting, busqueda de hiperparámetros, ...).
En este priemr escenario, se desea predecir la concentracion de $O_3$ de los próximos 1 y 5 días utilizando únciamente sus propios datos históricos. Se trata por lo tanto de un escenario en el que una única serie temporal se modela utilizando únicamente sus valores pasados. Este problema también se denomina predicción autoregresiva.
En primer lugar, se realiza un pronóstico de un solo paso a futuro. Par ello, se creará un modelo utilizando la función create_and_compile_model
, que se pasa como argumento a la clase ForecasterRnn
.
Este es el ejemplo más sencillo de forecasting con redes neuronales recurrentes. El modelo solo necesita una serie temporal para entrenar y predecir. Por lo tanto, el argumento series
de la función create_and_compile_model
solo necesita una serie temporal, la mismo que se quiere predecir (levels
). Además, como solo se quiere predecir un único valor a futuro, el argumento steps
es igual a 1.
# Creación del modelo
# ==============================================================================
series = ["o3"] # Series temporales que se utilizarán para entrenar el modelo.
levels = ["o3"] # Serie que se quiere predecir
lags = 32 # Valores pasados a utilizar en la predicción
steps = 1 # Pasos a futuro a predecir
# Selección de las series temporales utilizadas
data = air_quality[series].copy()
data_train = air_quality_train[series].copy()
data_val = air_quality_val[series].copy()
data_test = air_quality_test[series].copy()
model = create_and_compile_model(
series=data_train,
levels=levels,
lags=lags,
steps=steps,
recurrent_layer="LSTM",
recurrent_units=4,
dense_units=16,
optimizer=Adam(learning_rate=0.01),
loss=MeanSquaredError()
)
model.summary()
Se utilizará un modelo sencillo, una red LSTM con una única capa recurrente con 4 neuronas y una capa oculta densa de 16 neuronas. La siguiente tabla muestra una descripción detallada de cada capa:
Capa | Tipo | Forma de salida | Parámetros | Descripción |
---|---|---|---|---|
Capa de Entrada (InputLayer) | InputLayer |
(None, 32, 1) |
0 | Esta es la capa de entrada del modelo. Recibe secuencias de longitud 32, correspondiente al número de lags con una dimensión en cada paso de tiempo. |
Capa LSTM (Long Short-Term Memory) | LSTM |
(None, 4) |
96 | La capa LSTM es una capa de memoria a corto y largo plazo que procesa la secuencia de entrada. Tiene 4 unidades LSTM y se conecta a la capa siguiente. |
Primera Capa Densa (Dense) | Dense |
(None, 16) |
80 | Esta es una capa completamente conectada con 16 unidades y utiliza una función de activación por defecto (relu) en la arquitectura proporcionada. |
Segunda Capa Densa (Dense) | Dense |
(None, 1) |
17 | Otra capa densa completamente conectada, esta vez con una sola unidad de salida. También utiliza una función de activación por defecto. |
Capa de Remodelación (Reshape) | Reshape |
(None, 1, 1) |
0 | Esta capa remodela la salida de la capa densa anterior para tener una forma específica (None, 1, 1) . Esta capa no es estrictamente necesaria, pero se incluye para que el módulo sea generalizable a otros problemas de forecasting multi-output. La dimensión de esta capa de salida es (None, pasos_a_futuro_a_predecir, series_a_predecir) . En este caso se tiene steps=1 y levels="o3" , por lo que la dimensión es (None, 1, 1) |
Total de Parámetros y Entrenables | - | - | 193 | Total de Parámetros: 193, Parámetros Entrenables: 193, Parámetros No Entrenables: 0 |
Una vez que el modelo se ha creado y compilado, el siguiente paso es crear una instancia del ForecasterRnn. Esta clase se encarga de añadir al modelo de regresión todas las funcionalidades necesarias para que pueda utilizarse en problemas de forecasting. Además es compatible con el resto de funcionalidades que ofrece skforecast.
El forecaster se crea a partir del modelo y se le pasan los datos de validación para que pueda evaluar el modelo en cada época. Además, se le pasa un objeto MinMaxScaler
para que estandarice los datos de entrada y salida. Este objeto se encargará de escalar los datos de entrada y de desescalar las predicciones.
Por otro lado, los fit_kwargs
son los argumentos que se le pasan al método fit
del modelo. En este caso, se le pasa el número de épocas, el tamaño del batch, los datos de validación y un callback para detener el entrenamiento cuando la pérdida de validación deje de disminuir.
# Creación del forecaster
# ==============================================================================
forecaster = ForecasterRnn(
regressor=model,
levels=levels,
transformer_series=MinMaxScaler(),
fit_kwargs={
"epochs": 10, # Número de épocas para entrenar el modelo.
"batch_size": 32, # Tamaño del batch para entrenar el modelo.
"callbacks": [
EarlyStopping(monitor="val_loss", patience=5)
], # Callback para detener el entrenamiento cuando ya no esté aprendiendo más.
"series_val": data_val, # Datos de validación para el entrenamiento del modelo.
},
)
forecaster
⚠ Warning
El warning indica que el número de lags se ha inferido de la arquitectura del modelo. En este caso, el modelo tiene una capa LSTM con 32 neuronas, por lo que el número de lags es 32. Si se desea utilizar un número diferente de lags, se puede especificar el argumentolags
en la función create_and_compile_model
.
Para omitir el warning, se puede especificar el argumento lags=lags
y steps=steps
en la inicialización del ForecasterRnn
.
# Entrenamiento del forecaster
# ==============================================================================
forecaster.fit(data_train)
# Seguimiento del entrenamiento y overfitting
# ==============================================================================
fig, ax = plt.subplots(figsize=(5, 2.5))
forecaster.plot_history(ax=ax)
En los modelos de deep learning es muy importante controlar el overfitting. Para ello, se utiliza un callback de Keras que detiene el entrenamiento cuando el valor de la función de coste, en los datos de validación, deja de disminuir. En este caso, el callback no llega a detener el entrenamiento, ya que únicamente hemos entrenado con 10 épocas. Si se aumenta el número de épocas, el callback detendrá el entrenamiento cuando la pérdida de validación deje de disminuir.
Por otro lado, otra herramienta muy util es el graficado de la pérdida de entrenamiento y validación en cada época. Esto permite visualizar el comportamiento del modelo y detectar posibles problemas de overfitting.
En el caso de nuestro modelo, se observa que la pérdida de entrenamiento disminuye rápidamente en las primera época, mientras que la pérdida de validación es baja desde la primera época. De esto se deduce lo siguiente:
El modelo no está haciendo overfitting, ya que la pérdida de validación es similar a la de entrenamiento.
El error de validación se calcula una vez se entrena el modelo, por ello el primer valor de la pérdida de validación en la primera época es parecido al de la pérdida de entrenamiento en la segunda época.
Una vez que el forecaster ha sido entrenado, se pueden obtener las predicciones. En este caso, es un único valor ya que solo se ha especificado un paso a futuro (step
).
# Predicción
# ==============================================================================
predictions = forecaster.predict()
predictions
Para obtener una estimación robusta de la capacidad predictiva del modelo, se realiza un proceso de backtesting. El proceso de backtesting consiste en generar una predicción para cada observación del conjunto de test, siguiendo el mismo procedimiento que se seguiría si el modelo estuviese en producción, y finalmente comparar el valor predicho con el valor real.
# Backtesting con datos de test
# ==============================================================================
cv = TimeSeriesFold(
steps=forecaster.max_step,
initial_train_size=len(data.loc[:end_validation, :]), # Datos de entrenamiento + validación
refit=False,
)
metrics, predictions = backtesting_forecaster_multiseries(
forecaster=forecaster,
series=data,
levels=forecaster.levels,
cv=cv,
metric="mean_absolute_error",
verbose=False,
)
# Predicciones de backtesting
# ==============================================================================
predictions
# Gráfico de las predicciones vs valores reales en el conjunto de test
# ==============================================================================
fig = go.Figure()
trace1 = go.Scatter(x=data_test.index, y=data_test['o3'], name="test", mode="lines")
trace2 = go.Scatter(x=predictions.index, y=predictions['o3'], name="predicciones", mode="lines")
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.update_layout(
title="Predicciones vs valores reales en el conjunto de test",
xaxis_title="Date time",
yaxis_title="O3",
width=750,
height=350,
margin=dict(l=20, r=20, t=35, b=20),
legend=dict(
orientation="h",
yanchor="top",
y=1.05,
xanchor="left",
x=0
)
)
fig.show()
# Métricas de backtesting
# ==============================================================================
metrics
# Error en % respecto a la media de la serie
# ==============================================================================
rel_mse = 100 * metrics.loc[0, 'mean_absolute_error'] / np.mean(data["o3"])
print(f"Media de la serie: {np.mean(data['o3']):0.2f}")
print(f"Error (mae) relativo: {rel_mse:0.2f} %")
En este caso, se desea predecir los próximos 5 valores de O3 utilizando únicamente sus datos históricos. Se trata por lo tanto de un escenario en el que múltiples pasos a futuro de una única serie temporal se modela utilizando únicamente sus valores pasados.
Para ello se utilizará una arquitectura similar a la anterior, pero con un mayor número de neuronas en la capa LSTM y en la primera capa densa. Esto permitirá al modelo tener mayor flexibilidad para modelar la serie temporal.
# Creación del modelo
# ==============================================================================
series = ["o3"] # Series temporales que se utilizarán para entrenar el modelo.
levels = ["o3"] # Serie que se quiere predecir
lags = 32 # Valores pasados a utilizar en la predicción
steps = 5 # Pasos a futuro a predecir
model = create_and_compile_model(
series=data_train,
levels=levels,
lags=lags,
steps=steps,
recurrent_layer="LSTM",
recurrent_units=50,
dense_units=32,
optimizer=Adam(learning_rate=0.01),
loss=MeanSquaredError()
)
model.summary()
# Creación del Forecaster
# ==============================================================================
forecaster = ForecasterRnn(
regressor=model,
levels=levels,
steps=steps,
lags=lags,
transformer_series=MinMaxScaler(),
fit_kwargs={
"epochs": 10, # Número de épocas para entrenar el modelo.
"batch_size": 32, # Tamaño del batch para entrenar el modelo.
"callbacks": [
EarlyStopping(monitor="val_loss", patience=5)
], # Callback para detener el entrenamiento cuando ya no esté aprendiendo más.
"series_val": data_val, # Datos de validación para el entrenamiento del modelo.
},
)
✎ Nota
El parámetro `fit_kwargs` es de gran utilidad ya que permite establecer cualquier configuración en el modelo, en este caso de Keras. En el código anterior se define el número de épocas de entrenamiento (10) con un batch size de 32. Se configura un callback de `EarlyStopping` que detiene el entrenamiento cuando la pérdida de validación deja de disminuir durante 5 épocas (`patience=5`). También se pueden cofigurar otros callbacks como `ModelCheckpoint` para guardar el modelo en cada época, o incluso Tensorboard para visualizar la pérdida de entrenamiento y validación en tiempo real.# Entrenamiento del forecaster
# ==============================================================================
forecaster.fit(data_train)
# Seguimiento del entrenamiento y overfitting
# ==============================================================================
fig, ax = plt.subplots(figsize=(5, 2.5))
forecaster.plot_history(ax=ax)
Se intuye que la predicción va a ser de peor calidad que en el caso anterior, ya que el error observado en las distintas épocas es mayor. Esto tiene una explicación sencilla, y es que el modelo tiene que predecir 5 valores en lugar de 1. Por lo tanto, el error de validación es mayor ya que se está calculando la pérdida de 5 valores en lugar de 1.
Se realiza la predicción. En este caso son 5 valores ya que se ha especificado 5 pasos a futuro (step
).
# Predicción
# ==============================================================================
predictions = forecaster.predict()
predictions
También se pueden predecir steps
especificos, siempre y cuando se encuentren dentro del horizonte de predicción definido en el modelo.
# Predicción steps especificos
# ==============================================================================
predictions = forecaster.predict(steps=[1, 3])
predictions
# Backtesting con datos de test
# ==============================================================================
cv = TimeSeriesFold(
steps=forecaster.max_step,
initial_train_size=len(data.loc[:end_validation, :]), # Datos de entrenamiento + validación
refit=False,
)
metrics, predictions = backtesting_forecaster_multiseries(
forecaster=forecaster,
series=data,
levels=forecaster.levels,
cv=cv,
metric="mean_absolute_error",
verbose=False,
)
# Predicciones de backtesting
# ==============================================================================
predictions
# Gráfico de las predicciones vs valores reales en el conjunto de test
# ==============================================================================
fig = go.Figure()
trace1 = go.Scatter(x=data_test.index, y=data_test['o3'], name="test", mode="lines")
trace2 = go.Scatter(x=predictions.index, y=predictions['o3'], name="predicciones", mode="lines")
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.update_layout(
title="Predicciones vs valores reales en el conjunto de test",
xaxis_title="Date time",
yaxis_title="O3",
width=750,
height=350,
margin=dict(l=20, r=20, t=35, b=20),
legend=dict(
orientation="h",
yanchor="top",
y=1.05,
xanchor="left",
x=0
)
)
fig.show()
# Métricas de backtesting
# ==============================================================================
metrics
# Error mse en % respecto a la media de la serie
# ==============================================================================
rel_mse = 100 * metrics.loc[0, 'mean_absolute_error'] / np.mean(data["o3"])
print(f"Media de la serie: {np.mean(data['o3']):0.2f}")
print(f"Error mse relativo: {rel_mse:0.2f} %")
En este caso la predicción es empeora respecto al caso anterior. Esto es de esperar ya que el modelo tiene que predecir 5 valores en lugar de 1.
En este caso se tratará de predecir la misma serie temporal, pero utilizando múltiples series temporales como predictores. Se trata, por lo tanto, de un escenario en el que valores pasados de múltiples series temporales se utilizan predecir una única serie temporal.
Este tipo de aproximaciones son muy útiles cuando se dispone de múltiples series temporales relacionadas entre sí. Por ejemplo, en el caso de la predicción de la temperatura, se pueden utilizar múltiples series temporales como la humedad, la presión atmosférica, la velocidad del viento, etc.
En este tipo de problemas, la arquitectura de la red neuronal es más compleja, se necesita una capa densa recurrente adicional para procesar las múltiples series de entrada. Además, se añade otra capa oculta densa para procesar la salida de la capa recurrente. Como se puede observar, la creación del modelo utilizando skforecast
es muy sencilla, simplemente basta con pasar una lista de enteros al arguento recurrent_units
y dense_units
para crear múltiples capas recurrentes y densas.
# Creación del modelo
# ==============================================================================
# Series temporales utilizadas en el entrenamiento. Tiene que incluir la serie a predecir.
series = ['pm2.5', 'co', 'no', 'no2', 'pm10', 'nox', 'o3', 'veloc.', 'direc.','so2']
levels = ["o3"] # Serie que se quiere predecir
lags = 32 # Valores pasados a utilizar en la predicción
steps = 5 # Pasos a futuro a predecir
# Selección de las series temporales utilizadas
data = air_quality[series].copy()
data_train = air_quality_train[series].copy()
data_val = air_quality_val[series].copy()
data_test = air_quality_test[series].copy()
model = create_and_compile_model(
series=data_train,
levels=levels,
lags=lags,
steps=steps,
recurrent_layer="LSTM",
recurrent_units=[100, 50],
dense_units=[64, 32],
optimizer=Adam(learning_rate=0.01),
loss=MeanSquaredError()
)
model.summary()
Una vez que el modelo se ha creado y compilado, el siguiente paso es crear una instancia del ForecasterRnn. Esta clase se encarga de añadir al modelo de regresión, todas las funcionalidades necesarias para que pueda utilizarse en problemas de forecasting.
# Creación del Forecaster
# ==============================================================================
forecaster = ForecasterRnn(
regressor=model,
levels=levels,
steps=steps,
lags=lags,
transformer_series=MinMaxScaler(),
fit_kwargs={
"epochs": 4, # Número de épocas para entrenar el modelo.
"batch_size": 128, # Tamaño del batch para entrenar el modelo.
"series_val": data_val, # Datos de validación para el entrenamiento del modelo.
},
)
forecaster
# Entrenamiento del Modelo
# ==============================================================================
forecaster.fit(data_train)
# Seguimiento del entrenamiento y overfitting
# ==============================================================================
fig, ax = plt.subplots(figsize=(5, 2.5))
forecaster.plot_history(ax=ax)
# Predicción
# ==============================================================================
predictions = forecaster.predict()
predictions
# Backtesting con datos de test
# ==============================================================================
cv = TimeSeriesFold(
steps=forecaster.max_step,
initial_train_size=len(data.loc[:end_validation, :]), # Datos de entrenamiento + validación
refit=False,
)
metrics, predictions = backtesting_forecaster_multiseries(
forecaster=forecaster,
series=data,
levels=forecaster.levels,
cv=cv,
metric="mean_absolute_error",
verbose=False,
)
# Métricas de error de backtesting
# ==============================================================================
metrics
# Error mse en % respecto a la media de la serie
# ==============================================================================
rel_mse = 100 * metrics.loc[0, 'mean_absolute_error'] / np.mean(data["o3"])
print(f"Media de la serie: {np.mean(data['o3']):0.2f}")
print(f"Error mse relativo: {rel_mse:0.2f} %")
# Predicciones de backtesting
# ==============================================================================
predictions
# Gráfico de las predicciones vs valores reales en el conjunto de test
# ==============================================================================
fig = go.Figure()
trace1 = go.Scatter(x=data_test.index, y=data_test['o3'], name="test", mode="lines")
trace2 = go.Scatter(x=predictions.index, y=predictions['o3'], name="predicciones", mode="lines")
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.update_layout(
title="Predicciones vs valores reales en el conjunto de test",
xaxis_title="Date time",
yaxis_title="O3",
width=750,
height=350,
margin=dict(l=20, r=20, t=35, b=20),
legend=dict(
orientation="h",
yanchor="top",
y=1.05,
xanchor="left",
x=0
)
)
fig.show()