Comment effectuer une recherche en grille avec un triple lissage exponentiel pour la prévision de séries chronologiques en Python
Le lissage exponentiel est une méthode de prévision de séries chronologiques pour les données univariées qui peut être étendue pour prendre en charge les données avec une tendance systématique ou une composante saisonnière.
Il est courant d'utiliser un processus d'optimisation pour trouver les hyperparamètres du modèle qui aboutissent au modèle de lissage exponentiel avec les meilleures performances pour un ensemble de données de série chronologique donné. Cette pratique s'applique uniquement aux coefficients utilisés par le modèle pour décrire la structure exponentielle du niveau, de la tendance et de la saisonnalité.
Il est également possible d'optimiser automatiquement d'autres hyperparamètres d'un modèle de lissage exponentiel, comme par exemple s'il faut ou non modéliser la tendance et la composante saisonnière et si oui, s'il faut les modéliser par une méthode additive ou multiplicative.
Dans ce didacticiel, vous découvrirez comment développer un cadre de recherche sur grille de tous les hyperparamètres du modèle de lissage exponentiel pour la prévision de séries chronologiques univariées.
Après avoir terminé ce tutoriel, vous saurez :
- Comment développer un cadre pour la recherche de modèles ETS dans une grille à partir de zéro en utilisant la validation progressive.
- Comment rechercher sur une grille les hyperparamètres du modèle ETS pour les données de séries chronologiques quotidiennes pour les naissances féminines.
- Comment rechercher dans une grille les hyperparamètres du modèle ETS pour obtenir des données chronologiques mensuelles sur les ventes de shampoings, les ventes de voitures et la température.
Démarrez votre projet avec mon nouveau livre Deep Learning for Time Series Forecasting, comprenant des tutoriels pas à pas et les fichiers code source Python pour tous les exemples.
Commençons.
- Mise à jour en octobre 2018 : mise à jour de l'ajustement des modèles ETS pour utiliser le tableau NumPy afin de résoudre les problèmes de tendance/saisonnalité multiplicative (merci Amit Amola).
- Mise à jour en avril 2019 : mise à jour du lien vers l'ensemble de données.
Présentation du didacticiel
Ce didacticiel est divisé en six parties ; ils sont:
- Lissage exponentiel pour la prévision de séries chronologiques
- Développer un cadre de recherche de grille
- Étude de cas 1 : Aucune tendance ni saisonnalité
- Étude de cas 2 : Tendance
- Étude de cas 3 : Saisonnalité
- Étude de cas 4 : Tendance et saisonnalité
Lissage exponentiel pour la prévision de séries chronologiques
Le lissage exponentiel est une méthode de prévision de séries chronologiques pour les données univariées.
Les méthodes de séries chronologiques telles que la famille de méthodes Box-Jenkins ARIMA développent un modèle dans lequel la prédiction est une somme linéaire pondérée d'observations ou de décalages passés récents.
Les méthodes de prévision par lissage exponentiel sont similaires dans le sens où une prédiction est une somme pondérée des observations passées, mais le modèle utilise explicitement un poids décroissant exponentiellement pour les observations passées.
Plus précisément, les observations passées sont pondérées avec un rapport géométriquement décroissant.
Les prévisions produites à l'aide de méthodes de lissage exponentiel sont des moyennes pondérées des observations passées, les poids décroissant de façon exponentielle à mesure que les observations vieillissent. Autrement dit, plus l’observation est récente, plus le poids associé est élevé.
— Page 171, Prévisions : principes et pratiques, 2013.
Les méthodes de lissage exponentiel peuvent être considérées comme des pairs et une alternative à la classe de méthodes Box-Jenkins ARIMA populaire pour la prévision de séries chronologiques.
Collectivement, les méthodes sont parfois appelées modèles ETS, faisant référence à la modélisation explicite de l'Erreur, de la Tendance et de la Saisonnalité.
Il existe trois types de lissage exponentiel ; ils sont:
- Single Exponential Smoothing, ou SES, pour les données univariées sans tendance ni saisonnalité.
- Double lissage exponentiel pour les données univariées avec prise en charge des tendances.
- Triple Exponential Smoothing, ou Holt-Winters Exponential Smoothing, avec prise en charge des tendances et de la saisonnalité.
Un modèle de lissage exponentiel triple englobe le lissage exponentiel simple et double par la configuration de la nature de la tendance (additif, multiplicatif ou aucun) et la nature de la saisonnalité (additif, multiplicatif ou aucun), ainsi que tout amortissement de la tendance. s'orienter.
Développer un cadre de recherche de grille
Dans cette section, nous développerons un cadre pour la recherche de grille d'hyperparamètres de modèle de lissage exponentiel pour un problème de prévision de séries chronologiques univariées donné.
Nous utiliserons l'implémentation du lissage exponentiel Holt-Winters fourni par la bibliothèque statsmodels.
Ce modèle comporte des hyperparamètres qui contrôlent la nature de l'exponentielle effectuée pour la série, la tendance et la saisonnalité, en particulier :
- smoothing_level (alpha) : le coefficient de lissage du niveau.
- smoothing_slope (bêta) : le coefficient de lissage de la tendance.
- smoothing_seasonal (gamma) : le coefficient de lissage pour la composante saisonnière.
- damping_slope (phi) : le coefficient de la tendance amortie.
Ces quatre hyperparamètres peuvent être spécifiés lors de la définition du modèle. S'ils ne sont pas spécifiés, la bibliothèque ajustera automatiquement le modèle et trouvera les valeurs optimales pour ces hyperparamètres (par exemple optimized=True).
Il existe d'autres hyperparamètres que le modèle ne réglera pas automatiquement et que vous souhaiterez peut-être spécifier ; ils sont:
- trend : type de composant de tendance, sous la forme « add » pour additif ou « mul » pour multiplicatif. La modélisation de la tendance peut être désactivée en la définissant sur Aucun.
- amorti : indique si le composant de tendance doit être amorti ou non, soit True soit False.
- saisonnier : type de composant saisonnier, sous la forme « add » pour additif ou « mul » pour multiplicatif. La modélisation du composant saisonnier peut être désactivée en le définissant sur Aucun.
- seasonal_periods : nombre de pas de temps dans une période saisonnière, par ex. 12 pour 12 mois dans une structure saisonnière annuelle.
- use_boxcox : s'il faut ou non effectuer une transformation de puissance de la série (Vrai/Faux) ou spécifier le lambda pour la transformation.
Si vous en savez suffisamment sur votre problème pour spécifier un ou plusieurs de ces paramètres, vous devez les spécifier. Sinon, vous pouvez essayer de rechercher ces paramètres dans une grille.
Nous pouvons commencer par définir une fonction qui adaptera un modèle avec une configuration donnée et effectuer une prévision en une étape.
Le exp_smoothing_forecast() ci-dessous implémente ce comportement.
La fonction prend un tableau ou une liste d'observations antérieures contiguës et une liste de paramètres de configuration utilisés pour configurer le modèle.
Les paramètres de configuration dans l'ordre sont : le type de tendance, le type d'amortissement, le type de saisonnalité, la période saisonnière, l'utilisation ou non d'une transformation Box-Cox et la suppression ou non du biais lors de l'ajustement du modèle.
# one-step Holt Winter's Exponential Smoothing forecast
def exp_smoothing_forecast(history, config):
t,d,s,p,b,r = config
# define model model
history = array(history)
model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p)
# fit model
model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r)
# make one step forecast
yhat = model_fit.predict(len(history), len(history))
return yhat[0]
Ensuite, nous devons créer certaines fonctions pour ajuster et évaluer un modèle à plusieurs reprises via une validation progressive, notamment en divisant un ensemble de données en ensembles d'entraînement et de test et en évaluant les prévisions en une étape.
Nous pouvons diviser une liste ou un tableau NumPy de données en utilisant une tranche étant donné une taille spécifiée de division, par ex. le nombre de pas de temps à utiliser à partir des données de l'ensemble de test.
La fonction train_test_split() ci-dessous implémente cela pour un ensemble de données fourni et un nombre spécifié de pas de temps à utiliser dans l'ensemble de test.
# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
return data[:-n_test], data[-n_test:]
Une fois les prévisions établies pour chaque étape de l'ensemble de données de test, elles doivent être comparées à l'ensemble de test afin de calculer un score d'erreur.
Il existe de nombreux scores d’erreurs populaires pour la prévision de séries chronologiques. Dans ce cas, nous utiliserons l'erreur quadratique moyenne (RMSE), mais vous pouvez la modifier selon votre mesure préférée, par ex. MAPE, MAE, etc.
La fonction measure_rmse() ci-dessous calculera le RMSE à partir d'une liste de valeurs réelles (l'ensemble de test) et prédites.
# root mean squared error or rmse
def measure_rmse(actual, predicted):
return sqrt(mean_squared_error(actual, predicted))
Nous pouvons maintenant implémenter le schéma de validation walk-forward. Il s'agit d'une approche standard pour évaluer un modèle de prévision de séries chronologiques qui respecte l'ordre temporel des observations.
Tout d'abord, un ensemble de données de série chronologique univariée fourni est divisé en ensembles d'entraînement et de test à l'aide de la fonction train_test_split(). Ensuite, le nombre d'observations dans l'ensemble de test est énuméré. Pour chacun, nous adaptons un modèle à l’ensemble de l’historique et effectuons une prévision en une étape. La véritable observation du pas de temps est ensuite ajoutée à l’historique et le processus est répété. La fonction exp_smoothing_forecast() est appelée afin d'ajuster un modèle et de faire une prédiction. Enfin, un score d'erreur est calculé en comparant toutes les prévisions en une étape à l'ensemble de tests réel en appelant la fonction measure_rmse().
La fonction walk_forward_validation() ci-dessous implémente cela, en prenant une série temporelle univariée, un certain nombre de pas de temps à utiliser dans l'ensemble de test et un ensemble de configurations de modèle.
# walk-forward validation for univariate data
def walk_forward_validation(data, n_test, cfg):
predictions = list()
# split dataset
train, test = train_test_split(data, n_test)
# seed history with training dataset
history = [x for x in train]
# step over each time-step in the test set
for i in range(len(test)):
# fit model and make forecast for history
yhat = exp_smoothing_forecast(history, cfg)
# store forecast in list of predictions
predictions.append(yhat)
# add actual observation to history for the next loop
history.append(test[i])
# estimate prediction error
error = measure_rmse(test, predictions)
return error
Si vous souhaitez effectuer des prédictions en plusieurs étapes, vous pouvez modifier l'appel à predict() dans la fonction exp_smoothing_forecast() et également modifier le calcul de l'erreur dans la predict(). Fonctionmeasure_rmse().
Nous pouvons appeler walk_forward_validation() à plusieurs reprises avec différentes listes de configurations de modèle.
Un problème possible est que certaines combinaisons de configurations de modèle peuvent ne pas être appelées pour le modèle et lèveront une exception, par ex. spécifiant certains aspects de la structure saisonnière dans les données, mais pas tous.
De plus, certains modèles peuvent également émettre des avertissements sur certaines données, par ex. à partir des bibliothèques d'algèbre linéaire appelées par la bibliothèque statsmodels.
Nous pouvons intercepter les exceptions et ignorer les avertissements pendant la recherche dans la grille en encapsulant tous les appels à walk_forward_validation() avec un try-sauf et un bloc pour ignorer les avertissements. Nous pouvons également ajouter la prise en charge du débogage pour désactiver ces protections au cas où nous voudrions voir ce qui se passe réellement. Enfin, si une erreur se produit, nous pouvons renvoyer un résultat Aucun ; sinon, nous pouvons imprimer des informations sur la compétence de chaque modèle évalué. Ceci est utile lorsqu’un grand nombre de modèles sont évalués.
La fonction score_model() ci-dessous implémente cela et renvoie un tuple de (clé et résultat), où la clé est une version chaîne de la configuration du modèle testé.
# score a model, return None on failure
def score_model(data, n_test, cfg, debug=False):
result = None
# convert config to a key
key = str(cfg)
# show all warnings and fail on exception if debugging
if debug:
result = walk_forward_validation(data, n_test, cfg)
else:
# one failure during model validation suggests an unstable config
try:
# never show warnings when grid searching, too noisy
with catch_warnings():
filterwarnings("ignore")
result = walk_forward_validation(data, n_test, cfg)
except:
error = None
# check for an interesting result
if result is not None:
print(' > Model[%s] %.3f' % (key, result))
return (key, result)
Ensuite, nous avons besoin d'une boucle pour tester une liste de différentes configurations de modèles.
Il s'agit de la fonction principale qui pilote le processus de recherche de grille et appellera la fonction score_model() pour chaque configuration de modèle.
Nous pouvons considérablement accélérer le processus de recherche de grille en évaluant les configurations des modèles en parallèle. Une façon de procéder consiste à utiliser la bibliothèque Joblib.
Nous pouvons définir un objet Parallèle avec le nombre de cœurs à utiliser et le définir sur le nombre de cœurs CPU détectés dans votre matériel.
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
Nous pouvons ensuite créer une liste de tâches à exécuter en parallèle, qui sera un appel à la fonction score_model() pour chaque configuration de modèle dont nous disposons.
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
Enfin, on peut utiliser l'objet Parallel pour exécuter la liste des tâches en parallèle.
scores = executor(tasks)
C'est ça.
Nous pouvons également fournir une version non parallèle d'évaluation de toutes les configurations de modèle au cas où nous souhaiterions déboguer quelque chose.
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
Le résultat de l'évaluation d'une liste de configurations sera une liste de tuples, chacun avec un nom qui résume une configuration de modèle spécifique et l'erreur du modèle évalué avec cette configuration comme RMSE ou Aucun s'il y a eu une erreur.
Nous pouvons filtrer tous les scores avec Aucun.
scores = [r for r in scores if r[1] != None]
Nous pouvons ensuite trier tous les tuples de la liste par score dans l'ordre croissant (les meilleurs sont en premier), puis renvoyer cette liste de scores pour examen.
La fonction grid_search() ci-dessous implémente ce comportement étant donné un ensemble de données de séries chronologiques univariées, une liste de configurations de modèle (liste de listes) et le nombre de pas de temps à utiliser dans l'ensemble de test. Un argument parallèle facultatif permet d’activer ou de désactiver l’évaluation des modèles sur tous les cœurs, et est activé par défaut.
# grid search configs
def grid_search(data, cfg_list, n_test, parallel=True):
scores = None
if parallel:
# execute configs in parallel
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
scores = executor(tasks)
else:
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
# remove empty results
scores = [r for r in scores if r[1] != None]
# sort configs by error, asc
scores.sort(key=lambda tup: tup[1])
return scores
Nous avons presque terminé.
Il ne reste plus qu'à définir une liste de configurations de modèles à essayer pour un ensemble de données.
Nous pouvons définir cela de manière générique. Le seul paramètre que nous pouvons vouloir spécifier est la périodicité de la composante saisonnière de la série, si elle existe. Par défaut, nous ne supposerons aucune composante saisonnière.
La fonction exp_smoothing_configs() ci-dessous créera une liste de configurations de modèle à évaluer.
Une liste facultative de périodes saisonnières peut être spécifiée, et vous pouvez même modifier la fonction pour spécifier d'autres éléments que vous connaissez peut-être sur votre série chronologique.
En théorie, il existe 72 configurations de modèle possibles à évaluer, mais en pratique, beaucoup ne seront pas valides et entraîneront une erreur que nous capterons et ignorerons.
# create a set of exponential smoothing configs to try
def exp_smoothing_configs(seasonal=[None]):
models = list()
# define config lists
t_params = ['add', 'mul', None]
d_params = [True, False]
s_params = ['add', 'mul', None]
p_params = seasonal
b_params = [True, False]
r_params = [True, False]
# create config instances
for t in t_params:
for d in d_params:
for s in s_params:
for p in p_params:
for b in b_params:
for r in r_params:
cfg = [t,d,s,p,b,r]
models.append(cfg)
return models
Nous disposons désormais d'un cadre pour la recherche sur grille des hyperparamètres du modèle de lissage exponentiel triple via une validation progressive en une étape.
Il est générique et fonctionnera pour toute série temporelle univariée en mémoire fournie sous forme de liste ou de tableau NumPy.
Nous pouvons nous assurer que tous les éléments fonctionnent ensemble en le testant sur un ensemble de données artificiel en 10 étapes.
L’exemple complet est répertorié ci-dessous.
# grid search holt winter's exponential smoothing
from math import sqrt
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error
from numpy import array
# one-step Holt Winter’s Exponential Smoothing forecast
def exp_smoothing_forecast(history, config):
t,d,s,p,b,r = config
# define model
history = array(history)
model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p)
# fit model
model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r)
# make one step forecast
yhat = model_fit.predict(len(history), len(history))
return yhat[0]
# root mean squared error or rmse
def measure_rmse(actual, predicted):
return sqrt(mean_squared_error(actual, predicted))
# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
return data[:-n_test], data[-n_test:]
# walk-forward validation for univariate data
def walk_forward_validation(data, n_test, cfg):
predictions = list()
# split dataset
train, test = train_test_split(data, n_test)
# seed history with training dataset
history = [x for x in train]
# step over each time-step in the test set
for i in range(len(test)):
# fit model and make forecast for history
yhat = exp_smoothing_forecast(history, cfg)
# store forecast in list of predictions
predictions.append(yhat)
# add actual observation to history for the next loop
history.append(test[i])
# estimate prediction error
error = measure_rmse(test, predictions)
return error
# score a model, return None on failure
def score_model(data, n_test, cfg, debug=False):
result = None
# convert config to a key
key = str(cfg)
# show all warnings and fail on exception if debugging
if debug:
result = walk_forward_validation(data, n_test, cfg)
else:
# one failure during model validation suggests an unstable config
try:
# never show warnings when grid searching, too noisy
with catch_warnings():
filterwarnings("ignore")
result = walk_forward_validation(data, n_test, cfg)
except:
error = None
# check for an interesting result
if result is not None:
print(' > Model[%s] %.3f' % (key, result))
return (key, result)
# grid search configs
def grid_search(data, cfg_list, n_test, parallel=True):
scores = None
if parallel:
# execute configs in parallel
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
scores = executor(tasks)
else:
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
# remove empty results
scores = [r for r in scores if r[1] != None]
# sort configs by error, asc
scores.sort(key=lambda tup: tup[1])
return scores
# create a set of exponential smoothing configs to try
def exp_smoothing_configs(seasonal=[None]):
models = list()
# define config lists
t_params = ['add', 'mul', None]
d_params = [True, False]
s_params = ['add', 'mul', None]
p_params = seasonal
b_params = [True, False]
r_params = [True, False]
# create config instances
for t in t_params:
for d in d_params:
for s in s_params:
for p in p_params:
for b in b_params:
for r in r_params:
cfg = [t,d,s,p,b,r]
models.append(cfg)
return models
if __name__ == '__main__':
# define dataset
data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0]
print(data)
# data split
n_test = 4
# model configs
cfg_list = exp_smoothing_configs()
# grid search
scores = grid_search(data, cfg_list, n_test)
print('done')
# list top 3 configs
for cfg, error in scores[:3]:
print(cfg, error)
L’exécution de l’exemple imprime d’abord l’ensemble de données de série chronologique artificiel.
Ensuite, les configurations du modèle et leurs erreurs sont signalées au fur et à mesure de leur évaluation.
Enfin, les configurations et les erreurs des trois principales configurations sont signalées.
[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0]
> Model[[None, False, None, None, True, True]] 1.380
> Model[[None, False, None, None, True, False]] 10.000
> Model[[None, False, None, None, False, True]] 2.563
> Model[[None, False, None, None, False, False]] 10.000
done
[None, False, None, None, True, True] 1.379824445857423
[None, False, None, None, False, True] 2.5628662672606612
[None, False, None, None, False, False] 10.0
Nous ne rapportons pas les paramètres du modèle optimisés par le modèle lui-même. On suppose que vous pouvez obtenir à nouveau le même résultat en spécifiant des hyperparamètres plus larges et en permettant à la bibliothèque de trouver les mêmes paramètres internes.
Vous pouvez accéder à ces paramètres internes en réajustant un modèle autonome avec la même configuration et en imprimant le contenu de l'attribut « params » sur l'ajustement du modèle ; Par exemple:
print(model_fit.params)
Maintenant que nous disposons d'un cadre robuste pour la recherche de grilles d'hyperparamètres du modèle ETS, testons-le sur une suite d'ensembles de données de séries chronologiques univariées standard.
Les ensembles de données ont été choisis à des fins de démonstration ; Je ne dis pas qu'un modèle ETS est la meilleure approche pour chaque ensemble de données, et peut-être qu'un SARIMA ou autre serait plus approprié dans certains cas.
Étude de cas 1 : Aucune tendance ni saisonnalité
L’ensemble de données « naissances quotidiennes des femmes » résume le total quotidien des naissances féminines en Californie, aux États-Unis, en 1959.
L'ensemble de données n'a pas de tendance évidente ni de composante saisonnière.
Téléchargez l'ensemble de données directement à partir d'ici :
- naissances-totales-quotidiennes-de-femmes.csv
Enregistrez le fichier sous le nom de fichier « daily-total-female-births.csv » dans votre répertoire de travail actuel.
Nous pouvons charger cet ensemble de données sous forme de série Pandas en utilisant la fonction read_csv().
series = read_csv('daily-total-female-births.csv', header=0, index_col=0)
L'ensemble de données comporte un an, soit 365 observations. Nous utiliserons les 200 premiers pour la formation et les 165 restants comme ensemble de test.
L’exemple complet de grille recherchant le problème de prévision de séries chronologiques univariées féminines quotidiennes est répertorié ci-dessous.
# grid search ets models for daily female births
from math import sqrt
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error
from pandas import read_csv
from numpy import array
# one-step Holt Winter’s Exponential Smoothing forecast
def exp_smoothing_forecast(history, config):
t,d,s,p,b,r = config
# define model
history = array(history)
model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p)
# fit model
model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r)
# make one step forecast
yhat = model_fit.predict(len(history), len(history))
return yhat[0]
# root mean squared error or rmse
def measure_rmse(actual, predicted):
return sqrt(mean_squared_error(actual, predicted))
# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
return data[:-n_test], data[-n_test:]
# walk-forward validation for univariate data
def walk_forward_validation(data, n_test, cfg):
predictions = list()
# split dataset
train, test = train_test_split(data, n_test)
# seed history with training dataset
history = [x for x in train]
# step over each time-step in the test set
for i in range(len(test)):
# fit model and make forecast for history
yhat = exp_smoothing_forecast(history, cfg)
# store forecast in list of predictions
predictions.append(yhat)
# add actual observation to history for the next loop
history.append(test[i])
# estimate prediction error
error = measure_rmse(test, predictions)
return error
# score a model, return None on failure
def score_model(data, n_test, cfg, debug=False):
result = None
# convert config to a key
key = str(cfg)
# show all warnings and fail on exception if debugging
if debug:
result = walk_forward_validation(data, n_test, cfg)
else:
# one failure during model validation suggests an unstable config
try:
# never show warnings when grid searching, too noisy
with catch_warnings():
filterwarnings("ignore")
result = walk_forward_validation(data, n_test, cfg)
except:
error = None
# check for an interesting result
if result is not None:
print(' > Model[%s] %.3f' % (key, result))
return (key, result)
# grid search configs
def grid_search(data, cfg_list, n_test, parallel=True):
scores = None
if parallel:
# execute configs in parallel
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
scores = executor(tasks)
else:
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
# remove empty results
scores = [r for r in scores if r[1] != None]
# sort configs by error, asc
scores.sort(key=lambda tup: tup[1])
return scores
# create a set of exponential smoothing configs to try
def exp_smoothing_configs(seasonal=[None]):
models = list()
# define config lists
t_params = ['add', 'mul', None]
d_params = [True, False]
s_params = ['add', 'mul', None]
p_params = seasonal
b_params = [True, False]
r_params = [True, False]
# create config instances
for t in t_params:
for d in d_params:
for s in s_params:
for p in p_params:
for b in b_params:
for r in r_params:
cfg = [t,d,s,p,b,r]
models.append(cfg)
return models
if __name__ == '__main__':
# load dataset
series = read_csv('daily-total-female-births.csv', header=0, index_col=0)
data = series.values
# data split
n_test = 165
# model configs
cfg_list = exp_smoothing_configs()
# grid search
scores = grid_search(data[:,0], cfg_list, n_test)
print('done')
# list top 3 configs
for cfg, error in scores[:3]:
print(cfg, error)
L'exécution de l'exemple peut prendre quelques minutes, car l'installation de chaque modèle ETS peut prendre environ une minute sur du matériel moderne.
Les configurations de modèle et le RMSE sont imprimés au fur et à mesure que les modèles sont évalués. Les trois principales configurations de modèle et leurs erreurs sont signalées à la fin de l'analyse.
Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.
On voit que le meilleur résultat était un RMSE d’environ 6,96 naissances avec la configuration suivante :
- Tendance : multiplicative
- Amorti : Faux
- Saisonnier : Aucun
- Périodes saisonnières : Aucune
- Transformation Box-Cox : vrai
- Supprimer le biais : Vrai
Ce qui est surprenant, c’est qu’un modèle qui supposait une tendance multiplicative a obtenu de meilleurs résultats qu’un autre qui ne l’a pas fait.
Nous ne saurions pas que c’est le cas si nous n’éliminions pas les hypothèses et les modèles de recherche sur grille.
> Model[['add', False, None, None, True, True]] 7.081
> Model[['add', False, None, None, True, False]] 7.113
> Model[['add', False, None, None, False, True]] 7.112
> Model[['add', False, None, None, False, False]] 7.115
> Model[['add', True, None, None, True, True]] 7.118
> Model[['add', True, None, None, True, False]] 7.170
> Model[['add', True, None, None, False, True]] 7.113
> Model[['add', True, None, None, False, False]] 7.126
> Model[['mul', True, None, None, True, True]] 7.118
> Model[['mul', True, None, None, True, False]] 7.170
> Model[['mul', True, None, None, False, True]] 7.113
> Model[['mul', True, None, None, False, False]] 7.126
> Model[['mul', False, None, None, True, True]] 6.961
> Model[['mul', False, None, None, True, False]] 6.985
> Model[[None, False, None, None, True, True]] 7.169
> Model[[None, False, None, None, True, False]] 7.212
> Model[[None, False, None, None, False, True]] 7.117
> Model[[None, False, None, None, False, False]] 7.126
done
['mul', False, None, None, True, True] 6.960703917145126
['mul', False, None, None, True, False] 6.984513598720297
['add', False, None, None, True, True] 7.081359856193836
Étude de cas 2 : Tendance
L’ensemble de données « shampoing » résume les ventes mensuelles de shampoing sur une période de trois ans.
L'ensemble de données contient une tendance évidente mais aucune composante saisonnière évidente.
Téléchargez l'ensemble de données directement à partir d'ici :
- shampooing.csv
Enregistrez le fichier sous le nom de fichier « shampooing.csv » dans votre répertoire de travail actuel.
Nous pouvons charger cet ensemble de données sous forme de série Pandas en utilisant la fonction read_csv().
# parse dates
def custom_parser(x):
return datetime.strptime('195'+x, '%Y-%m')
# load dataset
series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser)
L'ensemble de données compte trois années, soit 36 observations. Nous utiliserons les 24 premiers pour la formation et les 12 restants comme ensemble de test.
L’exemple complet de grille recherchant le problème de prévision de séries chronologiques univariées des ventes de shampooing est répertorié ci-dessous.
# grid search ets models for monthly shampoo sales
from math import sqrt
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error
from pandas import read_csv
from numpy import array
# one-step Holt Winter’s Exponential Smoothing forecast
def exp_smoothing_forecast(history, config):
t,d,s,p,b,r = config
# define model
history = array(history)
model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p)
# fit model
model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r)
# make one step forecast
yhat = model_fit.predict(len(history), len(history))
return yhat[0]
# root mean squared error or rmse
def measure_rmse(actual, predicted):
return sqrt(mean_squared_error(actual, predicted))
# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
return data[:-n_test], data[-n_test:]
# walk-forward validation for univariate data
def walk_forward_validation(data, n_test, cfg):
predictions = list()
# split dataset
train, test = train_test_split(data, n_test)
# seed history with training dataset
history = [x for x in train]
# step over each time-step in the test set
for i in range(len(test)):
# fit model and make forecast for history
yhat = exp_smoothing_forecast(history, cfg)
# store forecast in list of predictions
predictions.append(yhat)
# add actual observation to history for the next loop
history.append(test[i])
# estimate prediction error
error = measure_rmse(test, predictions)
return error
# score a model, return None on failure
def score_model(data, n_test, cfg, debug=False):
result = None
# convert config to a key
key = str(cfg)
# show all warnings and fail on exception if debugging
if debug:
result = walk_forward_validation(data, n_test, cfg)
else:
# one failure during model validation suggests an unstable config
try:
# never show warnings when grid searching, too noisy
with catch_warnings():
filterwarnings("ignore")
result = walk_forward_validation(data, n_test, cfg)
except:
error = None
# check for an interesting result
if result is not None:
print(' > Model[%s] %.3f' % (key, result))
return (key, result)
# grid search configs
def grid_search(data, cfg_list, n_test, parallel=True):
scores = None
if parallel:
# execute configs in parallel
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
scores = executor(tasks)
else:
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
# remove empty results
scores = [r for r in scores if r[1] != None]
# sort configs by error, asc
scores.sort(key=lambda tup: tup[1])
return scores
# create a set of exponential smoothing configs to try
def exp_smoothing_configs(seasonal=[None]):
models = list()
# define config lists
t_params = ['add', 'mul', None]
d_params = [True, False]
s_params = ['add', 'mul', None]
p_params = seasonal
b_params = [True, False]
r_params = [True, False]
# create config instances
for t in t_params:
for d in d_params:
for s in s_params:
for p in p_params:
for b in b_params:
for r in r_params:
cfg = [t,d,s,p,b,r]
models.append(cfg)
return models
if __name__ == '__main__':
# load dataset
series = read_csv('shampoo.csv', header=0, index_col=0)
data = series.values
# data split
n_test = 12
# model configs
cfg_list = exp_smoothing_configs()
# grid search
scores = grid_search(data[:,0], cfg_list, n_test)
print('done')
# list top 3 configs
for cfg, error in scores[:3]:
print(cfg, error)
L’exécution de l’exemple est rapide étant donné qu’il existe un petit nombre d’observations.
Les configurations des modèles et le RMSE sont imprimés au fur et à mesure que les modèles sont évalués. Les trois principales configurations de modèles et leurs erreurs sont signalées à la fin de l'exécution.
Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.
On constate que le meilleur résultat était un RMSE d'environ 83,74 ventes avec la configuration suivante :
- Tendance : multiplicative
- Amorti : Faux
- Saisonnier : Aucun
- Périodes saisonnières : Aucune
- Transformation Box-Cox : Faux
- Supprimer le biais : Faux
> Model[['add', False, None, None, False, True]] 106.431
> Model[['add', False, None, None, False, False]] 104.874
> Model[['add', True, None, None, False, False]] 103.069
> Model[['add', True, None, None, False, True]] 97.918
> Model[['mul', True, None, None, False, True]] 95.337
> Model[['mul', True, None, None, False, False]] 102.152
> Model[['mul', False, None, None, False, True]] 86.406
> Model[['mul', False, None, None, False, False]] 83.747
> Model[[None, False, None, None, False, True]] 99.416
> Model[[None, False, None, None, False, False]] 108.031
done
['mul', False, None, None, False, False] 83.74666940175238
['mul', False, None, None, False, True] 86.40648953786152
['mul', True, None, None, False, True] 95.33737598817238
Étude de cas 3 : Saisonnalité
L'ensemble de données « températures moyennes mensuelles » résume les températures mensuelles moyennes de l'air au château de Nottingham, en Angleterre, de 1920 à 1939, en degrés Fahrenheit.
L'ensemble de données comporte une composante saisonnière évidente et aucune tendance évidente.
Téléchargez l'ensemble de données directement à partir d'ici :
- température-moyenne-mensuelle.csv
Enregistrez le fichier sous le nom de fichier « monthly-mean-temp.csv » dans votre répertoire de travail actuel.
Nous pouvons charger cet ensemble de données sous forme de série Pandas en utilisant la fonction read_csv().
series = read_csv('monthly-mean-temp.csv', header=0, index_col=0)
L'ensemble de données compte 20 ans, soit 240 observations.
Nous réduirons l'ensemble de données aux cinq dernières années de données (60 observations) afin d'accélérer le processus d'évaluation du modèle et utiliserons la dernière année, soit 12 observations, pour l'ensemble de test.
# trim dataset to 5 years
data = data[-(5*12):]
La période de la composante saisonnière est d'environ un an, soit 12 observations.
Nous l'utiliserons comme période saisonnière dans l'appel à la fonction exp_smoothing_configs() lors de la préparation des configurations du modèle.
# model configs
cfg_list = exp_smoothing_configs(seasonal=[0, 12])
L’exemple complet de grille recherchant le problème de prévision des séries chronologiques de températures moyennes mensuelles est répertorié ci-dessous.
# grid search ets hyperparameters for monthly mean temp dataset
from math import sqrt
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error
from pandas import read_csv
from numpy import array
# one-step Holt Winter’s Exponential Smoothing forecast
def exp_smoothing_forecast(history, config):
t,d,s,p,b,r = config
# define model
history = array(history)
model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p)
# fit model
model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r)
# make one step forecast
yhat = model_fit.predict(len(history), len(history))
return yhat[0]
# root mean squared error or rmse
def measure_rmse(actual, predicted):
return sqrt(mean_squared_error(actual, predicted))
# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
return data[:-n_test], data[-n_test:]
# walk-forward validation for univariate data
def walk_forward_validation(data, n_test, cfg):
predictions = list()
# split dataset
train, test = train_test_split(data, n_test)
# seed history with training dataset
history = [x for x in train]
# step over each time-step in the test set
for i in range(len(test)):
# fit model and make forecast for history
yhat = exp_smoothing_forecast(history, cfg)
# store forecast in list of predictions
predictions.append(yhat)
# add actual observation to history for the next loop
history.append(test[i])
# estimate prediction error
error = measure_rmse(test, predictions)
return error
# score a model, return None on failure
def score_model(data, n_test, cfg, debug=False):
result = None
# convert config to a key
key = str(cfg)
# show all warnings and fail on exception if debugging
if debug:
result = walk_forward_validation(data, n_test, cfg)
else:
# one failure during model validation suggests an unstable config
try:
# never show warnings when grid searching, too noisy
with catch_warnings():
filterwarnings("ignore")
result = walk_forward_validation(data, n_test, cfg)
except:
error = None
# check for an interesting result
if result is not None:
print(' > Model[%s] %.3f' % (key, result))
return (key, result)
# grid search configs
def grid_search(data, cfg_list, n_test, parallel=True):
scores = None
if parallel:
# execute configs in parallel
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
scores = executor(tasks)
else:
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
# remove empty results
scores = [r for r in scores if r[1] != None]
# sort configs by error, asc
scores.sort(key=lambda tup: tup[1])
return scores
# create a set of exponential smoothing configs to try
def exp_smoothing_configs(seasonal=[None]):
models = list()
# define config lists
t_params = ['add', 'mul', None]
d_params = [True, False]
s_params = ['add', 'mul', None]
p_params = seasonal
b_params = [True, False]
r_params = [True, False]
# create config instances
for t in t_params:
for d in d_params:
for s in s_params:
for p in p_params:
for b in b_params:
for r in r_params:
cfg = [t,d,s,p,b,r]
models.append(cfg)
return models
if __name__ == '__main__':
# load dataset
series = read_csv('monthly-mean-temp.csv', header=0, index_col=0)
data = series.values
# trim dataset to 5 years
data = data[-(5*12):]
# data split
n_test = 12
# model configs
cfg_list = exp_smoothing_configs(seasonal=[0,12])
# grid search
scores = grid_search(data[:,0], cfg_list, n_test)
print('done')
# list top 3 configs
for cfg, error in scores[:3]:
print(cfg, error)
L'exécution de l'exemple est relativement lente étant donné la grande quantité de données.
Les configurations des modèles et le RMSE sont imprimés au fur et à mesure que les modèles sont évalués. Les trois principales configurations de modèles et leurs erreurs sont signalées à la fin de l'exécution.
Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.
On voit que le meilleur résultat était un RMSE d’environ 1,50 degrés avec la configuration suivante :
- Tendance : Aucune
- Amorti : Faux
- Saisonnier : Additif
- Périodes saisonnières : 12
- Transformation Box-Cox : Faux
- Supprimer le biais : Faux
> Model[['add', True, 'mul', 12, True, False]] 1.659
> Model[['add', True, 'mul', 12, True, True]] 1.663
> Model[['add', True, 'mul', 12, False, True]] 1.603
> Model[['add', True, 'mul', 12, False, False]] 1.609
> Model[['mul', False, None, 0, True, True]] 4.920
> Model[['mul', False, None, 0, True, False]] 4.881
> Model[['mul', False, None, 0, False, True]] 4.838
> Model[['mul', False, None, 0, False, False]] 4.813
> Model[['add', True, 'add', 12, False, True]] 1.568
> Model[['mul', False, None, 12, True, True]] 4.920
> Model[['add', True, 'add', 12, False, False]] 1.555
> Model[['add', True, 'add', 12, True, False]] 1.638
> Model[['add', True, 'add', 12, True, True]] 1.646
> Model[['mul', False, None, 12, True, False]] 4.881
> Model[['mul', False, None, 12, False, True]] 4.838
> Model[['mul', False, None, 12, False, False]] 4.813
> Model[['add', True, None, 0, True, True]] 4.654
> Model[[None, False, 'add', 12, True, True]] 1.508
> Model[['add', True, None, 0, True, False]] 4.597
> Model[['add', True, None, 0, False, True]] 4.800
> Model[[None, False, 'add', 12, True, False]] 1.507
> Model[['add', True, None, 0, False, False]] 4.760
> Model[[None, False, 'add', 12, False, True]] 1.502
> Model[['add', True, None, 12, True, True]] 4.654
> Model[[None, False, 'add', 12, False, False]] 1.502
> Model[['add', True, None, 12, True, False]] 4.597
> Model[[None, False, 'mul', 12, True, True]] 1.507
> Model[['add', True, None, 12, False, True]] 4.800
> Model[[None, False, 'mul', 12, True, False]] 1.507
> Model[['add', True, None, 12, False, False]] 4.760
> Model[[None, False, 'mul', 12, False, True]] 1.502
> Model[['add', False, 'add', 12, True, True]] 1.859
> Model[[None, False, 'mul', 12, False, False]] 1.502
> Model[[None, False, None, 0, True, True]] 5.188
> Model[[None, False, None, 0, True, False]] 5.143
> Model[[None, False, None, 0, False, True]] 5.187
> Model[[None, False, None, 0, False, False]] 5.143
> Model[[None, False, None, 12, True, True]] 5.188
> Model[[None, False, None, 12, True, False]] 5.143
> Model[[None, False, None, 12, False, True]] 5.187
> Model[[None, False, None, 12, False, False]] 5.143
> Model[['add', False, 'add', 12, True, False]] 1.825
> Model[['add', False, 'add', 12, False, True]] 1.706
> Model[['add', False, 'add', 12, False, False]] 1.710
> Model[['add', False, 'mul', 12, True, True]] 1.882
> Model[['add', False, 'mul', 12, True, False]] 1.739
> Model[['add', False, 'mul', 12, False, True]] 1.580
> Model[['add', False, 'mul', 12, False, False]] 1.581
> Model[['add', False, None, 0, True, True]] 4.980
> Model[['add', False, None, 0, True, False]] 4.900
> Model[['add', False, None, 0, False, True]] 5.203
> Model[['add', False, None, 0, False, False]] 5.151
> Model[['add', False, None, 12, True, True]] 4.980
> Model[['add', False, None, 12, True, False]] 4.900
> Model[['add', False, None, 12, False, True]] 5.203
> Model[['add', False, None, 12, False, False]] 5.151
> Model[['mul', True, 'add', 12, True, True]] 19.353
> Model[['mul', True, 'add', 12, True, False]] 9.807
> Model[['mul', True, 'add', 12, False, True]] 11.696
> Model[['mul', True, 'add', 12, False, False]] 2.847
> Model[['mul', True, None, 0, True, True]] 4.607
> Model[['mul', True, None, 0, True, False]] 4.570
> Model[['mul', True, None, 0, False, True]] 4.630
> Model[['mul', True, None, 0, False, False]] 4.596
> Model[['mul', True, None, 12, True, True]] 4.607
> Model[['mul', True, None, 12, True, False]] 4.570
> Model[['mul', True, None, 12, False, True]] 4.630
> Model[['mul', True, None, 12, False, False]] 4.593
> Model[['mul', False, 'add', 12, True, True]] 4.230
> Model[['mul', False, 'add', 12, True, False]] 4.157
> Model[['mul', False, 'add', 12, False, True]] 1.538
> Model[['mul', False, 'add', 12, False, False]] 1.520
done
[None, False, 'add', 12, False, False] 1.5015527325330889
[None, False, 'add', 12, False, True] 1.5015531225114707
[None, False, 'mul', 12, False, False] 1.501561363221282
Étude de cas 4 : Tendance et saisonnalité
L'ensemble de données « Ventes mensuelles de voitures » résume les ventes mensuelles de voitures au Québec, Canada entre 1960 et 1968.
L'ensemble de données a une tendance évidente et une composante saisonnière.
Téléchargez l'ensemble de données directement à partir d'ici :
- ventes-de-voitures-mensuelles.csv
Enregistrez le fichier sous le nom de fichier « monthly-car-sales.csv » dans votre répertoire de travail actuel.
Nous pouvons charger cet ensemble de données sous forme de série Pandas en utilisant la fonction read_csv().
series = read_csv('monthly-car-sales.csv', header=0, index_col=0)
L'ensemble de données compte neuf années, soit 108 observations. Nous utiliserons la dernière année, soit 12 observations, comme ensemble de tests.
La durée de la composante saisonnière pourrait être de six mois ou de 12 mois. Nous essaierons les deux comme période saisonnière dans l'appel à la fonction exp_smoothing_configs() lors de la préparation des configurations du modèle.
# model configs
cfg_list = exp_smoothing_configs(seasonal=[0,6,12])
L'exemple de grille complet recherchant le problème de prévision des séries chronologiques de ventes mensuelles de voitures est répertorié ci-dessous.
# grid search ets models for monthly car sales
from math import sqrt
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error
from pandas import read_csv
from numpy import array
# one-step Holt Winter’s Exponential Smoothing forecast
def exp_smoothing_forecast(history, config):
t,d,s,p,b,r = config
# define model
history = array(history)
model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p)
# fit model
model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r)
# make one step forecast
yhat = model_fit.predict(len(history), len(history))
return yhat[0]
# root mean squared error or rmse
def measure_rmse(actual, predicted):
return sqrt(mean_squared_error(actual, predicted))
# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
return data[:-n_test], data[-n_test:]
# walk-forward validation for univariate data
def walk_forward_validation(data, n_test, cfg):
predictions = list()
# split dataset
train, test = train_test_split(data, n_test)
# seed history with training dataset
history = [x for x in train]
# step over each time-step in the test set
for i in range(len(test)):
# fit model and make forecast for history
yhat = exp_smoothing_forecast(history, cfg)
# store forecast in list of predictions
predictions.append(yhat)
# add actual observation to history for the next loop
history.append(test[i])
# estimate prediction error
error = measure_rmse(test, predictions)
return error
# score a model, return None on failure
def score_model(data, n_test, cfg, debug=False):
result = None
# convert config to a key
key = str(cfg)
# show all warnings and fail on exception if debugging
if debug:
result = walk_forward_validation(data, n_test, cfg)
else:
# one failure during model validation suggests an unstable config
try:
# never show warnings when grid searching, too noisy
with catch_warnings():
filterwarnings("ignore")
result = walk_forward_validation(data, n_test, cfg)
except:
error = None
# check for an interesting result
if result is not None:
print(' > Model[%s] %.3f' % (key, result))
return (key, result)
# grid search configs
def grid_search(data, cfg_list, n_test, parallel=True):
scores = None
if parallel:
# execute configs in parallel
executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
scores = executor(tasks)
else:
scores = [score_model(data, n_test, cfg) for cfg in cfg_list]
# remove empty results
scores = [r for r in scores if r[1] != None]
# sort configs by error, asc
scores.sort(key=lambda tup: tup[1])
return scores
# create a set of exponential smoothing configs to try
def exp_smoothing_configs(seasonal=[None]):
models = list()
# define config lists
t_params = ['add', 'mul', None]
d_params = [True, False]
s_params = ['add', 'mul', None]
p_params = seasonal
b_params = [True, False]
r_params = [True, False]
# create config instances
for t in t_params:
for d in d_params:
for s in s_params:
for p in p_params:
for b in b_params:
for r in r_params:
cfg = [t,d,s,p,b,r]
models.append(cfg)
return models
if __name__ == '__main__':
# load dataset
series = read_csv('monthly-car-sales.csv', header=0, index_col=0)
data = series.values
# data split
n_test = 12
# model configs
cfg_list = exp_smoothing_configs(seasonal=[0,6,12])
# grid search
scores = grid_search(data[:,0], cfg_list, n_test)
print('done')
# list top 3 configs
for cfg, error in scores[:3]:
print(cfg, error)
L'exécution de l'exemple est lente étant donné la grande quantité de données.
Les configurations des modèles et le RMSE sont imprimés au fur et à mesure que les modèles sont évalués. Les trois principales configurations de modèles et leurs erreurs sont signalées à la fin de l'exécution.
Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.
On constate que le meilleur résultat était un RMSE d'environ 1 672 ventes avec la configuration suivante :
- Tendance : Additif
- Amorti : Faux
- Saisonnier : Additif
- Périodes saisonnières : 12
- Transformation Box-Cox : Faux
- Supprimer le biais : Vrai
C’est un peu surprenant, car j’aurais pensé qu’un modèle saisonnier sur six mois serait l’approche privilégiée.
> Model[['add', True, 'add', 6, False, True]] 3240.433
> Model[['add', True, 'add', 6, False, False]] 3226.384
> Model[['add', True, 'add', 6, True, False]] 2836.535
> Model[['add', True, 'add', 6, True, True]] 2784.852
> Model[['add', True, 'add', 12, False, False]] 1696.173
> Model[['add', True, 'add', 12, False, True]] 1721.746
> Model[[None, False, 'add', 6, True, True]] 3204.874
> Model[['add', True, 'add', 12, True, False]] 2064.937
> Model[['add', True, 'add', 12, True, True]] 2098.844
> Model[[None, False, 'add', 6, True, False]] 3190.972
> Model[[None, False, 'add', 6, False, True]] 3147.623
> Model[[None, False, 'add', 6, False, False]] 3126.527
> Model[[None, False, 'add', 12, True, True]] 1834.910
> Model[[None, False, 'add', 12, True, False]] 1872.081
> Model[[None, False, 'add', 12, False, True]] 1736.264
> Model[[None, False, 'add', 12, False, False]] 1807.325
> Model[[None, False, 'mul', 6, True, True]] 2993.566
> Model[[None, False, 'mul', 6, True, False]] 2979.123
> Model[[None, False, 'mul', 6, False, True]] 3025.876
> Model[[None, False, 'mul', 6, False, False]] 3009.999
> Model[['add', True, 'mul', 6, True, True]] 2956.728
> Model[[None, False, 'mul', 12, True, True]] 1972.547
> Model[[None, False, 'mul', 12, True, False]] 1989.234
> Model[[None, False, 'mul', 12, False, True]] 1925.010
> Model[[None, False, 'mul', 12, False, False]] 1941.217
> Model[[None, False, None, 0, True, True]] 3801.741
> Model[[None, False, None, 0, True, False]] 3783.966
> Model[[None, False, None, 0, False, True]] 3801.560
> Model[[None, False, None, 0, False, False]] 3783.966
> Model[[None, False, None, 6, True, True]] 3801.741
> Model[[None, False, None, 6, True, False]] 3783.966
> Model[[None, False, None, 6, False, True]] 3801.560
> Model[[None, False, None, 6, False, False]] 3783.966
> Model[[None, False, None, 12, True, True]] 3801.741
> Model[[None, False, None, 12, True, False]] 3783.966
> Model[[None, False, None, 12, False, True]] 3801.560
> Model[[None, False, None, 12, False, False]] 3783.966
> Model[['add', True, 'mul', 6, True, False]] 2932.827
> Model[['mul', True, 'mul', 12, True, True]] 1953.405
> Model[['add', True, 'mul', 6, False, True]] 2997.259
> Model[['mul', True, 'mul', 12, True, False]] 1960.242
> Model[['add', True, 'mul', 6, False, False]] 2979.248
> Model[['mul', True, 'mul', 12, False, True]] 1907.792
> Model[['add', True, 'mul', 12, True, True]] 1972.550
> Model[['add', True, 'mul', 12, True, False]] 1989.236
> Model[['mul', True, None, 0, True, True]] 3951.024
> Model[['mul', True, None, 0, True, False]] 3930.394
> Model[['mul', True, None, 0, False, True]] 3947.281
> Model[['mul', True, None, 0, False, False]] 3926.082
> Model[['mul', True, None, 6, True, True]] 3951.026
> Model[['mul', True, None, 6, True, False]] 3930.389
> Model[['mul', True, None, 6, False, True]] 3946.654
> Model[['mul', True, None, 6, False, False]] 3926.026
> Model[['mul', True, None, 12, True, True]] 3951.027
> Model[['mul', True, None, 12, True, False]] 3930.368
> Model[['mul', True, None, 12, False, True]] 3942.037
> Model[['mul', True, None, 12, False, False]] 3920.756
> Model[['add', True, 'mul', 12, False, True]] 1750.480
> Model[['mul', False, 'add', 6, True, False]] 5043.557
> Model[['mul', False, 'add', 6, False, True]] 7425.711
> Model[['mul', False, 'add', 6, False, False]] 7448.455
> Model[['mul', False, 'add', 12, True, True]] 2160.794
> Model[['mul', False, 'add', 12, True, False]] 2346.478
> Model[['mul', False, 'add', 12, False, True]] 16303.868
> Model[['mul', False, 'add', 12, False, False]] 10268.636
> Model[['mul', False, 'mul', 12, True, True]] 3012.036
> Model[['mul', False, 'mul', 12, True, False]] 3005.824
> Model[['add', True, 'mul', 12, False, False]] 1774.636
> Model[['mul', False, 'mul', 12, False, True]] 14676.476
> Model[['add', True, None, 0, True, True]] 3935.674
> Model[['mul', False, 'mul', 12, False, False]] 13988.754
> Model[['mul', False, None, 0, True, True]] 3804.906
> Model[['mul', False, None, 0, True, False]] 3805.342
> Model[['mul', False, None, 0, False, True]] 3778.444
> Model[['mul', False, None, 0, False, False]] 3798.003
> Model[['mul', False, None, 6, True, True]] 3804.906
> Model[['mul', False, None, 6, True, False]] 3805.342
> Model[['mul', False, None, 6, False, True]] 3778.456
> Model[['mul', False, None, 6, False, False]] 3798.007
> Model[['add', True, None, 0, True, False]] 3915.499
> Model[['mul', False, None, 12, True, True]] 3804.906
> Model[['mul', False, None, 12, True, False]] 3805.342
> Model[['mul', False, None, 12, False, True]] 3778.457
> Model[['mul', False, None, 12, False, False]] 3797.989
> Model[['add', True, None, 0, False, True]] 3924.442
> Model[['add', True, None, 0, False, False]] 3905.627
> Model[['add', True, None, 6, True, True]] 3935.658
> Model[['add', True, None, 6, True, False]] 3913.420
> Model[['add', True, None, 6, False, True]] 3924.287
> Model[['add', True, None, 6, False, False]] 3913.618
> Model[['add', True, None, 12, True, True]] 3935.673
> Model[['add', True, None, 12, True, False]] 3913.428
> Model[['add', True, None, 12, False, True]] 3924.487
> Model[['add', True, None, 12, False, False]] 3913.529
> Model[['add', False, 'add', 6, True, True]] 3220.532
> Model[['add', False, 'add', 6, True, False]] 3199.766
> Model[['add', False, 'add', 6, False, True]] 3243.478
> Model[['add', False, 'add', 6, False, False]] 3226.955
> Model[['add', False, 'add', 12, True, True]] 1833.481
> Model[['add', False, 'add', 12, True, False]] 1833.511
> Model[['add', False, 'add', 12, False, True]] 1672.554
> Model[['add', False, 'add', 12, False, False]] 1680.845
> Model[['add', False, 'mul', 6, True, True]] 3014.447
> Model[['add', False, 'mul', 6, True, False]] 3016.207
> Model[['add', False, 'mul', 6, False, True]] 3025.870
> Model[['add', False, 'mul', 6, False, False]] 3010.015
> Model[['add', False, 'mul', 12, True, True]] 1982.087
> Model[['add', False, 'mul', 12, True, False]] 1981.089
> Model[['add', False, 'mul', 12, False, True]] 1898.045
> Model[['add', False, 'mul', 12, False, False]] 1894.397
> Model[['add', False, None, 0, True, True]] 3815.765
> Model[['add', False, None, 0, True, False]] 3813.234
> Model[['add', False, None, 0, False, True]] 3805.649
> Model[['add', False, None, 0, False, False]] 3809.864
> Model[['add', False, None, 6, True, True]] 3815.765
> Model[['add', False, None, 6, True, False]] 3813.234
> Model[['add', False, None, 6, False, True]] 3805.619
> Model[['add', False, None, 6, False, False]] 3809.846
> Model[['add', False, None, 12, True, True]] 3815.765
> Model[['add', False, None, 12, True, False]] 3813.234
> Model[['add', False, None, 12, False, True]] 3805.638
> Model[['add', False, None, 12, False, False]] 3809.837
> Model[['mul', True, 'add', 6, True, False]] 4099.032
> Model[['mul', True, 'add', 6, False, True]] 3818.567
> Model[['mul', True, 'add', 6, False, False]] 3745.142
> Model[['mul', True, 'add', 12, True, True]] 2203.354
> Model[['mul', True, 'add', 12, True, False]] 2284.172
> Model[['mul', True, 'add', 12, False, True]] 2842.605
> Model[['mul', True, 'add', 12, False, False]] 2086.899
done
['add', False, 'add', 12, False, True] 1672.5539372356582
['add', False, 'add', 12, False, False] 1680.845043013083
['add', True, 'add', 12, False, False] 1696.1734099400082
Rallonges
Cette section répertorie quelques idées pour étendre le didacticiel que vous souhaiterez peut-être explorer.
- Transformations de données. Mettez à jour le framework pour prendre en charge les transformations de données configurables telles que la normalisation et la standardisation.
- Prévisions de tracé. Mettez à jour le cadre pour réajuster un modèle avec la meilleure configuration et prévoir l'intégralité de l'ensemble de données de test, puis tracez la prévision par rapport aux observations réelles de l'ensemble de test.
- Régler la quantité d'historique. Mettez à jour le cadre pour ajuster la quantité de données historiques utilisées pour ajuster le modèle (par exemple dans le cas des 10 années de données de température maximale).
Si vous explorez l’une de ces extensions, j’aimerais le savoir.
Lectures complémentaires
Cette section fournit plus de ressources sur le sujet si vous souhaitez approfondir.
Livres
- Chapitre 7 Lissage exponentiel, Prévisions : principes et pratiques, 2013.
- Article 6.4. Introduction à l'analyse des séries chronologiques, Engineering Statistics Handbook, 2012.
- Prévision pratique de séries chronologiques avec R, 2016.
Apis
- API statsmodels.tsa.holtwinters.ExponentialSmoothing
- API statsmodels.tsa.holtwinters.HoltWintersResults
- Joblib : exécuter des fonctions Python en tant que tâches de pipeline
Articles
- Lissage exponentiel sur Wikipédia
Résumé
Dans ce didacticiel, vous avez découvert comment développer un cadre de recherche sur grille de tous les hyperparamètres du modèle de lissage exponentiel pour la prévision de séries temporelles univariées.
Concrètement, vous avez appris :
- Comment développer un cadre pour la recherche de modèles ETS dans une grille à partir de zéro en utilisant la validation progressive.
- Comment rechercher sur une grille les hyperparamètres du modèle ETS pour les données de séries chronologiques quotidiennes sur les naissances.
- Comment rechercher sur une grille les hyperparamètres du modèle ETS pour obtenir des données chronologiques mensuelles sur les ventes de shampoings, les ventes de voitures et la température.
Avez-vous des questions ?
Posez vos questions dans les commentaires ci-dessous et je ferai de mon mieux pour y répondre.