Recherche de site Web

Comment griller les méthodes naïves de recherche pour la prévision de séries chronologiques univariées


Les méthodes de prévision simples consistent à utiliser naïvement la dernière observation comme prédiction ou comme moyenne d’observations antérieures.

Il est important d'évaluer les performances de méthodes de prévision simples sur des problèmes de prévision de séries chronologiques univariées avant d'utiliser des méthodes plus sophistiquées, car leurs performances fournissent une limite inférieure et un point de comparaison qui peuvent être utilisés pour déterminer si un modèle est compétent ou non pour un domaine donné. problème.

Bien que simples, des méthodes telles que les stratégies de prévision naïve et moyenne peuvent être adaptées à un problème spécifique en termes de choix de l'observation antérieure à conserver ou du nombre d'observations antérieures à moyenner. Souvent, le réglage des hyperparamètres de ces stratégies simples peut fournir une limite inférieure plus robuste et défendable sur les performances du modèle, ainsi que des résultats surprenants qui peuvent éclairer le choix et la configuration de méthodes plus sophistiquées.

Dans ce didacticiel, vous découvrirez comment développer un cadre à partir de zéro pour des stratégies simples de recherche de grille, naïves et de moyenne, pour la prévision de séries chronologiques avec des données univariées.

Après avoir terminé ce tutoriel, vous saurez :

  • Comment développer un cadre pour la recherche de modèles simples sur grille à partir de zéro en utilisant la validation progressive.
  • Comment rechercher sur une grille des hyperparamètres de modèles simples pour les données de séries chronologiques quotidiennes sur les naissances.
  • Comment rechercher sur une grille des hyperparamètres de modèles simples pour des données de séries chronologiques mensuelles sur les ventes de shampoing, 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 avril 2019 : mise à jour des liens vers les ensembles de données.
  • Mise à jour en février 2020 : Correction d'une faute de frappe dans la sélection de la saisonnalité dans les deux derniers cas.

Présentation du didacticiel

Ce didacticiel est divisé en six parties ; ils sont:

  1. Stratégies de prévision simples
  2. Développer un cadre de recherche de grille
  3. Étude de cas 1 : Aucune tendance ni saisonnalité
  4. Étude de cas 2 : Tendance
  5. Étude de cas 3 : Saisonnalité
  6. Étude de cas 4 : Tendance et saisonnalité

Stratégies de prévision simples

Il est important et utile de tester des stratégies de prévision simples avant de tester des modèles plus complexes.

Les stratégies de prévision simples sont celles qui supposent peu ou rien sur la nature du problème de prévision et qui sont rapides à mettre en œuvre et à calculer.

Les résultats peuvent être utilisés comme référence en matière de performances et comme point de comparaison. Si un modèle peut être plus performant qu’une simple stratégie de prévision, alors on peut dire qu’il est habile.

Les stratégies de prévision simples comportent deux thèmes principaux : ils sont:

  • Naïf, ou en utilisant directement les valeurs d'observations.
  • Moyenne, ou en utilisant une statistique calculée sur des observations précédentes.

Examinons de plus près ces deux stratégies.

Stratégie de prévision naïve

Une prévision naïve consiste à utiliser l’observation précédente directement comme prévision sans aucun changement.

On l'appelle souvent prévision de persistance car l'observation antérieure est persistante.

Cette approche simple peut être légèrement ajustée pour les données saisonnières. Dans ce cas, l’observation effectuée au même moment dans le cycle précédent peut être conservée.

Cela peut être généralisé en testant chaque décalage possible dans les données historiques qui pourraient être utilisées pour conserver une valeur pour une prévision.

Par exemple, étant donné la série :

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Nous pourrions conserver la dernière observation (indice relatif -1) comme valeur 9 ou conserver l'avant-dernière observation précédente (indice relatif -2) comme 8, et ainsi de suite.

Stratégie de prévision moyenne

Un cran au-dessus des prévisions naïves est la stratégie consistant à faire la moyenne des valeurs antérieures.

Toutes les observations antérieures sont collectées et moyennées, soit en utilisant la moyenne, soit en utilisant la médiane, sans autre traitement des données.

Dans certains cas, nous souhaiterons peut-être raccourcir l’historique utilisé dans le calcul de la moyenne aux dernières observations.

Nous pouvons généraliser cela au cas où l’on teste chaque ensemble possible d’observations n préalables à inclure dans le calcul de la moyenne.

Par exemple, étant donné la série :

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Nous pourrions faire la moyenne de la dernière observation (9), des deux dernières observations (8, 9), et ainsi de suite.

Dans le cas de données saisonnières, nous pouvons vouloir faire la moyenne des n dernières observations antérieures au même moment du cycle que l'heure prévue.

Par exemple, étant donné la série avec un cycle en 3 étapes :

[1, 2, 3, 1, 2, 3, 1, 2, 3]

Nous pourrions utiliser une taille de fenêtre de 3 et faire la moyenne de la dernière observation (-3 ou 1), des deux dernières observations (-3 ou 1 et -(3 * 2) ou 1), et ainsi de suite.

Développer un cadre de recherche de grille

Dans cette section, nous développerons un cadre de recherche sur grille pour les deux stratégies de prévision simples décrites dans la section précédente, à savoir les stratégies naïve et moyenne.

Nous pouvons commencer par mettre en œuvre une stratégie de prévision naïve.

Pour un ensemble de données donné d'observations historiques, nous pouvons conserver n'importe quelle valeur dans cet historique, c'est-à-dire de l'observation précédente à l'index -1 à la première observation de l'historique à -(len(data)).

La fonction naive_forecast() ci-dessous implémente la stratégie de prévision naïve pour un décalage donné de 1 à la longueur de l'ensemble de données.

# one-step naive forecast
def naive_forecast(history, n):
	return history[-n]

Nous pouvons tester cette fonction sur un petit ensemble de données artificiel.

# one-step naive forecast
def naive_forecast(history, n):
	return history[-n]

# 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)
# test naive forecast
for i in range(1, len(data)+1):
	print(naive_forecast(data, i))

L’exécution de l’exemple imprime d’abord l’ensemble de données artificiel, puis la prévision naïve pour chaque décalage dans l’ensemble de données historiques.

[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0]
100.0
90.0
80.0
70.0
60.0
50.0
40.0
30.0
20.0
10.0

Nous pouvons maintenant envisager de développer une fonction pour la stratégie de prévision moyenne.

La moyenne des n dernières observations est simple ; Par exemple:

from numpy import mean
result = mean(history[-n:])

Nous pouvons également vouloir tester la médiane dans les cas où la distribution des observations est non gaussienne.

from numpy import median
result = median(history[-n:])

La fonction average_forecast() ci-dessous implémente cela en prenant les données historiques et un tableau ou tuple de configuration qui spécifie le nombre de valeurs antérieures à moyenner sous forme d'entier, et une chaîne qui décrit la manière de calculer la moyenne ( 'moyenne' ou 'médiane').

# one-step average forecast
def average_forecast(history, config):
	n, avg_type = config
	# mean of last n values
	if avg_type is 'mean':
		return mean(history[-n:])
	# median of last n values
	return median(history[-n:])

L'exemple complet sur un petit ensemble de données artificiel est répertorié ci-dessous.

from numpy import mean
from numpy import median

# one-step average forecast
def average_forecast(history, config):
	n, avg_type = config
	# mean of last n values
	if avg_type is 'mean':
		return mean(history[-n:])
	# median of last n values
	return median(history[-n:])

# 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)
# test naive forecast
for i in range(1, len(data)+1):
	print(average_forecast(data, (i, 'mean')))

L’exécution de l’exemple prévoit la valeur suivante de la série comme valeur moyenne des sous-ensembles contigus d’observations antérieures de -1 à -10 inclusivement.

[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0]
100.0
95.0
90.0
85.0
80.0
75.0
70.0
65.0
60.0
55.0

Nous pouvons mettre à jour la fonction pour prendre en charge la moyenne sur les données saisonnières, en respectant le décalage saisonnier.

Un argument de décalage peut être ajouté à la fonction qui, lorsqu'il n'est pas défini sur 1, déterminera le nombre d'observations antérieures à compter à rebours avant de collecter les valeurs à inclure dans la moyenne.

Par exemple, si n=1 et offset=3, alors la moyenne est calculée à partir de la valeur unique à n*offset ou 1*3=-3. Si n=2 et offset=3, alors la moyenne est calculée à partir des valeurs à 1*3 ou -3 et 2*3 ou -6.

Nous pouvons également ajouter une protection pour déclencher une exception lorsqu'une configuration saisonnière (n * offset) s'étend au-delà de la fin des observations historiques.

La fonction mise à jour est répertoriée ci-dessous.

# one-step average forecast
def average_forecast(history, config):
	n, offset, avg_type = config
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# mean of last n values
	if avg_type is 'mean':
		return mean(values)
	# median of last n values
	return median(values)

Nous pouvons tester cette fonction sur un petit ensemble de données artificiel avec un cycle saisonnier.

L’exemple complet est répertorié ci-dessous.

from numpy import mean
from numpy import median

# one-step average forecast
def average_forecast(history, config):
	n, offset, avg_type = config
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# mean of last n values
	if avg_type is 'mean':
		return mean(values)
	# median of last n values
	return median(values)

# define dataset
data = [10.0, 20.0, 30.0, 10.0, 20.0, 30.0, 10.0, 20.0, 30.0]
print(data)
# test naive forecast
for i in [1, 2, 3]:
	print(average_forecast(data, (i, 3, 'mean')))

L'exécution de l'exemple calcule les valeurs moyennes de [10], [10, 10] et [10, 10, 10].

[10.0, 20.0, 30.0, 10.0, 20.0, 30.0, 10.0, 20.0, 30.0]
10.0
10.0
10.0

Il est possible de combiner les stratégies de prévision naïve et moyenne dans la même fonction.

Il existe un petit chevauchement entre les méthodes, en particulier le décalage n- dans l'historique qui est utilisé soit pour conserver les valeurs, soit pour déterminer le nombre de valeurs à moyenner.

Il est utile que les deux stratégies soient prises en charge par une seule fonction afin que nous puissions tester une suite de configurations pour les deux stratégies à la fois dans le cadre d'une recherche plus large sur une grille de modèles simples.

La fonction simple_forecast() ci-dessous combine les deux stratégies en une seule fonction.

# one-step simple forecast
def simple_forecast(history, config):
	n, offset, avg_type = config
	# persist value, ignore other config
	if avg_type == 'persist':
		return history[-n]
	# collect values to average
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# check if we can average
	if len(values) < 2:
		raise Exception('Cannot calculate average')
	# mean of last n values
	if avg_type == 'mean':
		return mean(values)
	# median of last n values
	return median(values)

Ensuite, nous devons créer certaines fonctions pour ajuster et évaluer un modèle de manière répétée via une validation progressive, y compris la division d'un ensemble de données en entraîner et tester des ensembles et évaluer les prévisions en une étape.

Nous pouvons diviser une liste ou NumPy tableau 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 tests afin de calculer un score d'erreur.

Il existe de nombreux scores d'erreur courants pour temps prévisions de séries. 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 désormais 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 train et des ensembles de tests à l'aide de train_test_split() fonction . 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 pour le pas de temps est ensuite ajoutée à l'historique, et le le processus est répété. Le simple_forecast() La fonction 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 tableau de configuration du 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 = simple_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 en predict() dans le simple_forecast() et modifiez également le calcul de l'erreur dans la fonction measure_rmse() fonction.

Nous pouvons appeler walk_forward_validation() à plusieurs reprises avec différentes listes de configurations de modèles.

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.

Nous pouvons intercepter les exceptions et ignorer les avertissements lors de 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 un support de débogage pour désactiver ces protections dans le cas où nous voulons 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 lorsque 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 régler sur le nombre de scores 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 était une erreur.

Nous pouvons filtrer tous les scores définis sur 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 (décalage), si elle existe. Par défaut, nous ne supposerons aucune composante saisonnière.

La fonction simple_configs() ci-dessous créera une liste de configurations de modèle à évaluer.

La fonction nécessite uniquement la longueur maximale des données historiques comme argument et éventuellement la périodicité de toute composante saisonnière, qui est par défaut 1 (pas de composante saisonnière).

# create a set of simple configs to try
def simple_configs(max_length, offsets=[1]):
	configs = list()
	for i in range(1, max_length+1):
		for o in offsets:
			for t in ['persist', 'mean', 'median']:
				cfg = [i, o, t]
				configs.append(cfg)
	return configs

Nous disposons désormais d'un cadre pour la recherche sur grille d'hyperparamètres de modèles simples 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 simple forecasts
from math import sqrt
from numpy import mean
from numpy import median
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from sklearn.metrics import mean_squared_error

# one-step simple forecast
def simple_forecast(history, config):
	n, offset, avg_type = config
	# persist value, ignore other config
	if avg_type == 'persist':
		return history[-n]
	# collect values to average
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# check if we can average
	if len(values) < 2:
		raise Exception('Cannot calculate average')
	# mean of last n values
	if avg_type == 'mean':
		return mean(values)
	# median of last n values
	return median(values)

# 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 = simple_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 simple configs to try
def simple_configs(max_length, offsets=[1]):
	configs = list()
	for i in range(1, max_length+1):
		for o in offsets:
			for t in ['persist', 'mean', 'median']:
				cfg = [i, o, t]
				configs.append(cfg)
	return configs

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
	max_length = len(data) - n_test
	cfg_list = simple_configs(max_length)
	# 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.

Nous pouvons voir que le modèle de persistance avec une configuration de 1 (par exemple conserver la dernière observation) obtient les meilleures performances des modèles simples testés, comme on pouvait s'y attendre.

[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0]

> Model[[1, 1, 'persist']] 10.000
> Model[[2, 1, 'persist']] 20.000
> Model[[2, 1, 'mean']] 15.000
> Model[[2, 1, 'median']] 15.000
> Model[[3, 1, 'persist']] 30.000
> Model[[4, 1, 'persist']] 40.000
> Model[[5, 1, 'persist']] 50.000
> Model[[5, 1, 'mean']] 30.000
> Model[[3, 1, 'mean']] 20.000
> Model[[4, 1, 'median']] 25.000
> Model[[6, 1, 'persist']] 60.000
> Model[[4, 1, 'mean']] 25.000
> Model[[3, 1, 'median']] 20.000
> Model[[6, 1, 'mean']] 35.000
> Model[[5, 1, 'median']] 30.000
> Model[[6, 1, 'median']] 35.000
done

[1, 1, 'persist'] 10.0
[2, 1, 'mean'] 15.0
[2, 1, 'median'] 15.0

Maintenant que nous disposons d'un cadre robuste pour la recherche de grilles d'hyperparamètres de modèles simples, testons-le sur une suite d'ensembles de données de séries chronologiques univariées standard.

Les résultats démontrés sur chaque ensemble de données fournissent une base de performances qui peut être utilisée pour comparer des méthodes plus sophistiquées, telles que SARIMA, ETS et même des méthodes d'apprentissage automatique.

É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 simple forecast for daily female births
from math import sqrt
from numpy import mean
from numpy import median
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from sklearn.metrics import mean_squared_error
from pandas import read_csv

# one-step simple forecast
def simple_forecast(history, config):
	n, offset, avg_type = config
	# persist value, ignore other config
	if avg_type == 'persist':
		return history[-n]
	# collect values to average
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# check if we can average
	if len(values) < 2:
		raise Exception('Cannot calculate average')
	# mean of last n values
	if avg_type == 'mean':
		return mean(values)
	# median of last n values
	return median(values)

# 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 = simple_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 simple configs to try
def simple_configs(max_length, offsets=[1]):
	configs = list()
	for i in range(1, max_length+1):
		for o in offsets:
			for t in ['persist', 'mean', 'median']:
				cfg = [i, o, t]
				configs.append(cfg)
	return configs

if __name__ == '__main__':
	# define dataset
	series = read_csv('daily-total-female-births.csv', header=0, index_col=0)
	data = series.values
	print(data)
	# data split
	n_test = 165
	# model configs
	max_length = len(data) - n_test
	cfg_list = simple_configs(max_length)
	# 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 les configurations du modèle et les 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.

On voit que le meilleur résultat était un RMSE d’environ 6,93 naissances avec la configuration suivante :

  • Stratégie : moyenne
  • n : 22
  • fonction : moyenne()

C'est surprenant étant donné l'absence de tendance ou de saisonnalité, je me serais attendu à ce qu'une persistance de -1 ou une moyenne de l'ensemble des données historiques aboutisse aux meilleures performances.

...
> Model[[186, 1, 'mean']] 7.523
> Model[[200, 1, 'median']] 7.681
> Model[[186, 1, 'median']] 7.691
> Model[[187, 1, 'persist']] 11.137
> Model[[187, 1, 'mean']] 7.527
done

[22, 1, 'mean'] 6.930411499775709
[23, 1, 'mean'] 6.932293117115201
[21, 1, 'mean'] 6.951918385845375

É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 « shampoo.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 simple forecast for monthly shampoo sales
from math import sqrt
from numpy import mean
from numpy import median
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from sklearn.metrics import mean_squared_error
from pandas import read_csv
from pandas import datetime

# one-step simple forecast
def simple_forecast(history, config):
	n, offset, avg_type = config
	# persist value, ignore other config
	if avg_type == 'persist':
		return history[-n]
	# collect values to average
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# check if we can average
	if len(values) < 2:
		raise Exception('Cannot calculate average')
	# mean of last n values
	if avg_type == 'mean':
		return mean(values)
	# median of last n values
	return median(values)

# 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 = simple_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 simple configs to try
def simple_configs(max_length, offsets=[1]):
	configs = list()
	for i in range(1, max_length+1):
		for o in offsets:
			for t in ['persist', 'mean', 'median']:
				cfg = [i, o, t]
				configs.append(cfg)
	return configs

# parse dates
def custom_parser(x):
	return datetime.strptime('195'+x, '%Y-%m')

if __name__ == '__main__':
	# load dataset
	series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser)
	data = series.values
	print(data.shape)
	# data split
	n_test = 12
	# model configs
	max_length = len(data) - n_test
	cfg_list = simple_configs(max_length)
	# 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 les configurations et les 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.

Nous pouvons constater que le meilleur résultat était un RMSE d’environ 95,69 ventes avec la configuration suivante :

  • Stratégie : Persister
  • n : 2

Ceci est surprenant car la structure tendancielle des données suggère que conserver la valeur précédente (-1) serait la meilleure approche, plutôt que de conserver l’avant-dernière valeur.

...
> Model[[23, 1, 'mean']] 209.782
> Model[[23, 1, 'median']] 221.863
> Model[[24, 1, 'persist']] 305.635
> Model[[24, 1, 'mean']] 213.466
> Model[[24, 1, 'median']] 226.061
done

[2, 1, 'persist'] 95.69454007413378
[2, 1, 'mean'] 96.01140340258198
[2, 1, 'median'] 96.01140340258198

É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 ou 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 simple_configs() lors de la préparation des configurations du modèle.

# model configs
cfg_list = simple_configs(max_length, offsets=[1,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 simple forecast for monthly mean temperature
from math import sqrt
from numpy import mean
from numpy import median
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from sklearn.metrics import mean_squared_error
from pandas import read_csv

# one-step simple forecast
def simple_forecast(history, config):
	n, offset, avg_type = config
	# persist value, ignore other config
	if avg_type == 'persist':
		return history[-n]
	# collect values to average
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# check if we can average
	if len(values) < 2:
		raise Exception('Cannot calculate average')
	# mean of last n values
	if avg_type == 'mean':
		return mean(values)
	# median of last n values
	return median(values)

# 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 = simple_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 simple configs to try
def simple_configs(max_length, offsets=[1]):
	configs = list()
	for i in range(1, max_length+1):
		for o in offsets:
			for t in ['persist', 'mean', 'median']:
				cfg = [i, o, t]
				configs.append(cfg)
	return configs

if __name__ == '__main__':
	# define dataset
	series = read_csv('monthly-mean-temp.csv', header=0, index_col=0)
	data = series.values
	print(data)
	# data split
	n_test = 12
	# model configs
	max_length = len(data) - n_test
	cfg_list = simple_configs(max_length, offsets=[1,12])
	# 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 les configurations du modèle et les 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.

Nous pouvons voir que le meilleur résultat était un RMSE d'environ 1,501 degrés avec la configuration suivante :

  • Stratégie : Moyenne
  • n : 4
  • décalage : 12
  • fonction : moyenne()

Cette découverte n’est pas trop surprenante. Compte tenu de la structure saisonnière des données, nous nous attendrions à ce qu'une fonction des dernières observations à des moments antérieurs du cycle annuel soit efficace.

...
> Model[[227, 12, 'persist']] 5.365
> Model[[228, 1, 'persist']] 2.818
> Model[[228, 1, 'mean']] 8.258
> Model[[228, 1, 'median']] 8.361
> Model[[228, 12, 'persist']] 2.818
done
[4, 12, 'mean'] 1.5015616870445234
[8, 12, 'mean'] 1.5794579766489512
[13, 12, 'mean'] 1.586186052546763

É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 9 années, soit 108 observations. Nous utiliserons la dernière année ou 12 observations comme ensemble de tests.

La durée de la composante saisonnière pourrait être de 12 mois. Nous allons essayer cela comme période saisonnière dans l'appel à la fonction simple_configs() lors de la préparation des configurations du modèle.

# model configs
 cfg_list = simple_configs(max_length, offsets=[1,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 simple forecast for monthly car sales
from math import sqrt
from numpy import mean
from numpy import median
from multiprocessing import cpu_count
from joblib import Parallel
from joblib import delayed
from warnings import catch_warnings
from warnings import filterwarnings
from sklearn.metrics import mean_squared_error
from pandas import read_csv

# one-step simple forecast
def simple_forecast(history, config):
	n, offset, avg_type = config
	# persist value, ignore other config
	if avg_type == 'persist':
		return history[-n]
	# collect values to average
	values = list()
	if offset == 1:
		values = history[-n:]
	else:
		# skip bad configs
		if n*offset > len(history):
			raise Exception('Config beyond end of data: %d %d' % (n,offset))
		# try and collect n values using offset
		for i in range(1, n+1):
			ix = i * offset
			values.append(history[-ix])
	# check if we can average
	if len(values) < 2:
		raise Exception('Cannot calculate average')
	# mean of last n values
	if avg_type == 'mean':
		return mean(values)
	# median of last n values
	return median(values)

# 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 = simple_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 simple configs to try
def simple_configs(max_length, offsets=[1]):
	configs = list()
	for i in range(1, max_length+1):
		for o in offsets:
			for t in ['persist', 'mean', 'median']:
				cfg = [i, o, t]
				configs.append(cfg)
	return configs

if __name__ == '__main__':
	# define dataset
	series = read_csv('monthly-car-sales.csv', header=0, index_col=0)
	data = series.values
	print(data)
	# data split
	n_test = 12
	# model configs
	max_length = len(data) - n_test
	cfg_list = simple_configs(max_length, offsets=[1,12])
	# 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 les configurations du modèle et les 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.

Nous pouvons constater que le meilleur résultat était un RMSE d'environ 1841,155 ventes avec la configuration suivante :

  • Stratégie : Moyenne
  • n : 3
  • décalage : 12
  • fonction : médiane()

Il n’est pas surprenant que le modèle choisi soit fonction des dernières observations au même moment dans les cycles précédents, même si l’utilisation de la médiane au lieu de la moyenne n’a peut-être pas été immédiatement évidente et que les résultats ont été bien meilleurs que la moyenne.

...
> Model[[79, 1, 'median']] 5124.113
> Model[[91, 12, 'persist']] 9580.149
> Model[[79, 12, 'persist']] 8641.529
> Model[[92, 1, 'persist']] 9830.921
> Model[[92, 1, 'mean']] 5148.126
done
[3, 12, 'median'] 1841.1559321976688
[3, 12, 'mean'] 2115.198495632485
[4, 12, 'median'] 2184.37708988932

Rallonges

Cette section répertorie quelques idées pour étendre le didacticiel que vous souhaiterez peut-être explorer.

  • 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.
  • Méthode de dérive. Implémentez la méthode de dérive pour les prévisions simples et comparez les résultats aux méthodes moyennes et naïves.
  • Un autre ensemble de données. Appliquez le cadre développé à un problème de série chronologique univariée supplémentaire.

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.

  • Prévisions, Wikipédia
  • Joblib : exécuter des fonctions Python en tant que tâches de pipeline

Résumé

Dans ce didacticiel, vous avez découvert comment développer un cadre à partir de zéro pour des stratégies simples de recherche de grille, naïves et de moyenne, pour la prévision de séries chronologiques avec des données univariées.

Concrètement, vous avez appris :

  • Comment développer un cadre pour la recherche de modèles simples sur grille à partir de zéro en utilisant la validation progressive.
  • Comment rechercher sur une grille des hyperparamètres de modèles simples pour les données de séries chronologiques quotidiennes sur les naissances.
  • Comment rechercher sur une grille des hyperparamètres de modèles simples pour des données de séries chronologiques mensuelles sur les ventes de shampoing, 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.

Articles connexes