Búsqueda de sitios web

Cómo realizar búsquedas en cuadrículas con métodos ingenuos para el pronóstico de series temporales univariadas


Los métodos de pronóstico simples incluyen el uso ingenuo de la última observación como predicción o un promedio de las observaciones anteriores.

Es importante evaluar el desempeño de métodos de pronóstico simples en problemas de pronóstico de series de tiempo univariadas antes de usar métodos más sofisticados, ya que su desempeño proporciona un límite inferior y un punto de comparación que se puede usar para determinar si un modelo tiene habilidad o no para un período determinado. problema.

Aunque simples, métodos como las estrategias de pronóstico ingenuo y promedio pueden ajustarse a un problema específico en términos de la elección de qué observación previa persistir o cuántas observaciones previas promediar. A menudo, ajustar los hiperparámetros de estas estrategias simples puede proporcionar un límite inferior más sólido y defendible en el rendimiento del modelo, así como resultados sorprendentes que pueden informar la elección y configuración de métodos más sofisticados.

En este tutorial, descubrirá cómo desarrollar un marco desde cero para búsqueda de cuadrículas, estrategias simples e ingenuas de promediación para pronósticos de series temporales con datos univariados.

Después de completar este tutorial, sabrá:

  • Cómo desarrollar un marco para la búsqueda de cuadrículas en modelos simples desde cero mediante validación directa.
  • Cómo realizar una búsqueda en cuadrícula de hiperparámetros de modelos simples para datos de series temporales diarias de nacimientos.
  • Cómo realizar una búsqueda en cuadrícula de hiperparámetros de modelos simples para datos de series temporales mensuales de ventas de champú, ventas de automóviles y temperatura.

Pon en marcha tu proyecto con mi nuevo libro Aprendizaje profundo para la previsión de series temporales, que incluye tutoriales paso a paso y los archivos de código fuente de Python. para todos los ejemplos.

Empecemos.

  • Actualizado en abril de 2019: se actualizaron los enlaces a los conjuntos de datos.
  • Actualizado en febrero/2020: Se corrigió error tipográfico en la selección de la estacionalidad en los dos últimos casos.

Descripción general del tutorial

Este tutorial se divide en seis partes; ellos son:

  1. Estrategias simples de pronóstico
  2. Desarrollar un marco de búsqueda de cuadrícula
  3. Estudio de caso 1: Sin tendencia ni estacionalidad
  4. Estudio de caso 2: Tendencia
  5. Estudio de caso 3: estacionalidad
  6. Estudio de caso 4: Tendencia y estacionalidad

Estrategias simples de pronóstico

Es importante y útil probar estrategias de pronóstico simples antes de probar modelos más complejos.

Las estrategias de pronóstico simples son aquellas que asumen poco o nada sobre la naturaleza del problema de pronóstico y son rápidas de implementar y calcular.

Los resultados se pueden utilizar como punto de referencia en el rendimiento y como punto de comparación. Si un modelo puede funcionar mejor que una estrategia de pronóstico simple, entonces se puede decir que es hábil.

Hay dos temas principales para las estrategias de pronóstico simples; ellos son:

  • Ingenuo, o utilizando valores de observaciones directamente.
  • Promedio, o utilizando una estadística calculada a partir de observaciones anteriores.

Echemos un vistazo más de cerca a ambas estrategias.

Estrategia de pronóstico ingenua

Un pronóstico ingenuo implica utilizar la observación anterior directamente como pronóstico sin ningún cambio.

A menudo se le denomina pronóstico de persistencia, ya que persiste la observación anterior.

Este sencillo enfoque puede ajustarse ligeramente a los datos estacionales. En este caso, la observación en el mismo momento en el ciclo anterior puede persistir.

Esto se puede generalizar aún más para probar cada posible compensación en los datos históricos que podrían usarse para conservar un valor para un pronóstico.

Por ejemplo, dada la serie:

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

Podríamos conservar la última observación (índice relativo -1) como el valor 9 o conservar la penúltima observación anterior (índice relativo -2) como 8, y así sucesivamente.

Estrategia de pronóstico promedio

Un paso por encima del pronóstico ingenuo es la estrategia de promediar los valores anteriores.

Todas las observaciones anteriores se recopilan y promedian, ya sea utilizando la media o la mediana, sin ningún otro tratamiento para los datos.

En algunos casos, es posible que deseemos acortar el historial utilizado en el cálculo promedio a las últimas observaciones.

Podemos generalizar esto al caso de probar cada conjunto posible de n observaciones previas que se incluirán en el cálculo promedio.

Por ejemplo, dada la serie:

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

Podríamos promediar la última observación (9), las dos últimas observaciones (8, 9), y así sucesivamente.

En el caso de datos estacionales, es posible que deseemos promediar las últimas n observaciones anteriores al mismo tiempo del ciclo que el tiempo que se pronostica.

Por ejemplo, dada la serie con un ciclo de 3 pasos:

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

Podríamos usar un tamaño de ventana de 3 y promediar la última observación (-3 o 1), las dos últimas observaciones (-3 o 1 y -(3 * 2) o 1), y así sucesivamente.

Desarrollar un marco de búsqueda de cuadrícula

En esta sección, desarrollaremos un marco para la búsqueda en cuadrícula de las dos estrategias de pronóstico simples descritas en la sección anterior, a saber, las estrategias ingenua y promedio.

Podemos comenzar implementando una estrategia de pronóstico ingenua.

Para un conjunto de datos dado de observaciones históricas, podemos conservar cualquier valor en ese historial, es decir, desde la observación anterior en el índice -1 hasta la primera observación del historial en -(len(data)).

La función naive_forecast() a continuación implementa la estrategia de pronóstico ingenuo para un desplazamiento determinado de 1 a la longitud del conjunto de datos.

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

Podemos probar esta función en un pequeño conjunto de datos artificial.

# 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))

Al ejecutar el ejemplo, primero se imprime el conjunto de datos artificial y luego el pronóstico ingenuo para cada compensación en el conjunto de datos histórico.

[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

Ahora podemos considerar el desarrollo de una función para la estrategia de pronóstico promedio.

Promediar las últimas n observaciones es sencillo; Por ejemplo:

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

También es posible que deseemos probar la mediana en aquellos casos en los que la distribución de las observaciones no sea gaussiana.

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

La función average_forecast() a continuación implementa esto tomando los datos históricos y una matriz o tupla de configuración que especifica el número de valores anteriores a promediar como un número entero, y una cadena que describe la forma de calcular el promedio ( 'media' o 'mediana').

# 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:])

A continuación se enumera el ejemplo completo de un pequeño conjunto de datos artificial.

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')))

Al ejecutar el ejemplo se pronostica el siguiente valor de la serie como el valor medio de subconjuntos contiguos de observaciones anteriores de -1 a -10, inclusive.

[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

Podemos actualizar la función para admitir el promedio de datos estacionales, respetando la compensación estacional.

Se puede agregar un argumento de compensación a la función que, cuando no se establece en 1, determinará el número de observaciones anteriores hacia atrás para contar antes de recopilar valores para incluir en el promedio.

Por ejemplo, si n=1 y desplazamiento=3, entonces el promedio se calcula a partir del valor único en n*desplazamiento o 1*3=-3. Si n=2 y offset=3, entonces el promedio se calcula a partir de los valores en 1*3 o -3 y 2*3 o -6.

También podemos agregar cierta protección para generar una excepción cuando una configuración estacional (n * compensación) se extiende más allá del final de las observaciones históricas.

La función actualizada se enumera a continuación.

# 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)

Podemos probar esta función en un pequeño conjunto de datos artificial con un ciclo estacional.

El ejemplo completo se enumera a continuación.

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')))

Al ejecutar el ejemplo se calculan los valores medios de [10], [10, 10] y [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

Es posible combinar las estrategias de pronóstico ingenua y promedio en la misma función.

Hay una pequeña superposición entre los métodos, específicamente el n-offset en el historial que se utiliza para conservar valores o determinar el número de valores a promediar.

Es útil tener ambas estrategias respaldadas por una función para que podamos probar un conjunto de configuraciones para ambas estrategias a la vez como parte de una búsqueda de cuadrícula más amplia de modelos simples.

La siguiente función simple_forecast() combina ambas estrategias en una sola función.

# 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)

A continuación, necesitamos crear algunas funciones para ajustar y evaluar un modelo repetidamente mediante validación directa, incluida la división de un conjunto de datos en entrenar y conjuntos de prueba y evaluación de pronósticos de un solo paso.

Podemos dividir una lista o NumPymatriz de datos utilizando un segmento dado un tamaño específico de división, p. ej. el número de pasos de tiempo que se utilizarán a partir de los datos del conjunto de prueba.

La función train_test_split() siguiente implementa esto para un conjunto de datos proporcionado y un número específico de pasos de tiempo para usar en el conjunto de prueba.

# split a univariate dataset into train/test sets
def train_test_split(data, n_test):
	return data[:-n_test], data[-n_test:]

Después de realizar pronósticos para cada paso en el conjunto de datos de prueba, es necesario compararlos con el conjunto de prueba para calcular una puntuación de error.

Existen muchas puntuaciones de error populares para tiempo pronóstico de series. En este caso, usaremos la raíz del error cuadrático medio (RMSE), pero puede cambiar esto a su medida preferida, p. MAPE, MAE, etc.

La función measure_rmse() a continuación calculará el RMSE dada una lista de valores reales (el conjunto de prueba) y pronosticados.

# root mean squared error or rmse
def measure_rmse(actual, predicted):
	return sqrt(mean_squared_error(actual, predicted))

Ahora podemos implementar el esquema de validación directa. Este es un enfoque estándar para evaluar un modelo de pronóstico de series temporales que respeta el orden temporal de las observaciones.

Primero, un conjunto de datos de series temporales univariadas proporcionado se divide en tren y conjuntos de prueba usando train_test_split() función. Luego se enumera el número de observaciones en el conjunto de prueba. Para cada uno ajustamos un modelo en toda la historia y hacemos un pronóstico de un paso. La observación verdadera para el paso de tiempo se agrega al historial, y el Se repite el proceso. El simple_forecast() La función se llama para ajustar un modelo y hacer una predicción. Finalmente, se calcula una puntuación de error comparando todos los pronósticos de un paso con el conjunto de pruebas real llamando a la función measure_rmse().

La función walk_forward_validation() a continuación implementa esto, tomando una serie de tiempo univariada, una cantidad de pasos de tiempo para usar en el conjunto de prueba y una matriz. de configuración del modelo.

# 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 está interesado en realizar predicciones de varios pasos, puede cambiar la llamada a predict() en el simple_forecast() y también cambiar el cálculo del error en measure_rmse() Función .

Podemos llamar a walk_forward_validation() repetidamente con diferentes listas de configuraciones de modelos.

Un posible problema es que algunas combinaciones de configuraciones de modelo pueden no ser invocadas para el modelo y generarán una excepción.

Podemos detectar excepciones e ignorar advertencias durante la búsqueda en la cuadrícula ajustando todas las llamadas a walk_forward_validation() con un try-except y un bloque para ignorar las advertencias. También podemos agregar soporte de depuración para desactivar estas protecciones en caso de que queramos ver qué está pasando realmente. Finalmente, si ocurre un error, podemos devolver un resultado Ninguno; de lo contrario, podemos imprimir alguna información sobre la habilidad de cada modelo evaluado. Esto resulta útil cuando se evalúa una gran cantidad de modelos.

La función score_model() siguiente implementa esto y devuelve una tupla de (clave y resultado), donde la clave es una versión de cadena de la configuración del modelo probado.

# 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)

A continuación, necesitamos un bucle para probar una lista de diferentes configuraciones de modelos.

Esta es la función principal que impulsa el proceso de búsqueda de la cuadrícula y llamará a la función score_model() para cada configuración del modelo.

Podemos acelerar drásticamente el proceso de búsqueda de la red evaluando las configuraciones del modelo en paralelo. Una forma de hacerlo es utilizar la biblioteca Joblib.

Podemos definir un objeto paralelo con la cantidad de núcleos a usar y configurarlo según la cantidad de puntuaciones detectadas en su hardware.

executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing')

Luego podemos crear una lista de tareas para ejecutar en paralelo, que será una llamada a la función score_model() para cada configuración de modelo que tengamos.

tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)

Finalmente, podemos usar el objeto Parallel para ejecutar la lista de tareas en paralelo.

scores = executor(tasks)

Eso es todo.

También podemos proporcionar una versión no paralela para evaluar todas las configuraciones del modelo en caso de que queramos depurar algo.

scores = [score_model(data, n_test, cfg) for cfg in cfg_list]

El resultado de evaluar una lista de configuraciones será una lista de tuplas, cada una con un nombre que resume una configuración de modelo específica y el error del modelo evaluado con esa configuración como RMSE o Ninguno si hay fue un error.

Podemos filtrar todas las puntuaciones establecidas en Ninguna.

scores = [r for r in scores if r[1] != None]

Luego podemos ordenar todas las tuplas de la lista por puntuación en orden ascendente (las mejores son las primeras) y luego devolver esta lista de puntuaciones para su revisión.

La siguiente función grid_search() implementa este comportamiento dado un conjunto de datos de series temporales univariadas, una lista de configuraciones de modelo (lista de listas) y la cantidad de pasos de tiempo que se usarán en el conjunto de prueba. Un argumento paralelo opcional permite activar o desactivar la evaluación de modelos en todos los núcleos y está activado de forma predeterminada.

# 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

Ya casi hemos terminado.

Lo único que queda por hacer es definir una lista de configuraciones de modelo para probar en un conjunto de datos.

Podemos definir esto genéricamente. El único parámetro que quizás queramos especificar es la periodicidad del componente estacional de la serie (compensación), si existe. De forma predeterminada, no asumiremos ningún componente estacional.

La función simple_configs() a continuación creará una lista de configuraciones del modelo para evaluar.

La función solo requiere la longitud máxima de los datos históricos como argumento y, opcionalmente, la periodicidad de cualquier componente estacional, cuyo valor predeterminado es 1 (sin componente estacional).

# 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

Ahora tenemos un marco para la búsqueda en cuadrícula de hiperparámetros de modelos simples mediante una validación directa de un solo paso.

Es genérico y funcionará para cualquier serie temporal univariada en memoria proporcionada como una lista o matriz NumPy.

Podemos asegurarnos de que todas las piezas funcionen juntas probándolo en un conjunto de datos artificial de 10 pasos.

El ejemplo completo se enumera a continuación.

# 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)

Al ejecutar el ejemplo, primero se imprime el conjunto de datos de serie temporal artificial.

A continuación, se informan las configuraciones del modelo y sus errores a medida que se evalúan.

Finalmente, se informan las configuraciones y el error de las tres configuraciones principales.

Podemos ver que el modelo de persistencia con una configuración de 1 (por ejemplo, persistir en la última observación) logra el mejor rendimiento de los modelos simples probados, como era de esperar.

[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

Ahora que tenemos un marco sólido para la búsqueda de hiperparámetros de modelos simples en cuadrículas, probémoslo en un conjunto de conjuntos de datos de series temporales univariadas estándar.

Los resultados demostrados en cada conjunto de datos proporcionan una base de rendimiento que se puede utilizar para comparar métodos más sofisticados, como SARIMA, ETS e incluso métodos de aprendizaje automático.

Estudio de caso 1: Sin tendencia ni estacionalidad

El conjunto de datos sobre “nacimientos femeninos diarios” resume el total diario de nacimientos femeninos en California, EE. UU., en 1959.

El conjunto de datos no tiene ninguna tendencia obvia ni componente estacional.

Descargue el conjunto de datos directamente desde aquí:

  • total-diario-nacimientos-femeninos.csv

Guarde el archivo con el nombre 'daily-total-female-births.csv' en su directorio de trabajo actual.

Podemos cargar este conjunto de datos como una serie de Pandas usando la función read_csv().

series = read_csv('daily-total-female-births.csv', header=0, index_col=0)

El conjunto de datos tiene un año o 365 observaciones. Usaremos los primeros 200 para entrenamiento y los 165 restantes como conjunto de prueba.

A continuación se muestra el ejemplo completo de cuadrícula que busca el problema de pronóstico de series temporales univariadas femeninas diarias.

# 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)

Al ejecutar el ejemplo se imprimen las configuraciones del modelo y el RMSE se imprime a medida que se evalúan los modelos.

Las tres configuraciones principales del modelo y su error se informan al final de la ejecución.

Podemos ver que el mejor resultado fue un RMSE de unos 6,93 nacimientos con la siguiente configuración:

  • Estrategia: Promedio
  • n: 22
  • función: media()

Esto es sorprendente dada la falta de tendencia o estacionalidad; hubiera esperado que una persistencia de -1 o un promedio de todo el conjunto de datos históricos resultara en el mejor rendimiento.

...
> 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

Estudio de caso 2: Tendencia

El conjunto de datos sobre "champú" resume las ventas mensuales de champú durante un período de tres años.

El conjunto de datos contiene una tendencia obvia pero ningún componente estacional obvio.

Descargue el conjunto de datos directamente desde aquí:

  • champú.csv

Guarde el archivo con el nombre de archivo 'shampoo.csv' en su directorio de trabajo actual.

Podemos cargar este conjunto de datos como una serie de Pandas usando la función 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)

El conjunto de datos tiene tres años, o 36 observaciones. Usaremos los primeros 24 para entrenamiento y los 12 restantes como conjunto de prueba.

A continuación se muestra la cuadrícula de ejemplo completa que busca el problema de pronóstico de series temporales univariadas de ventas de champú.

# 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)

Al ejecutar el ejemplo se imprimen las configuraciones y el RMSE se imprime a medida que se evalúan los modelos.

Las tres configuraciones principales del modelo y su error se informan al final de la ejecución.

Podemos ver que el mejor resultado fue un RMSE de unas 95,69 ventas con la siguiente configuración:

  • Estrategia: persistir
  • n: 2

Esto es sorprendente ya que la estructura de tendencia de los datos sugeriría que persistir en el valor anterior (-1) sería el mejor enfoque, en lugar de persistir en el penúltimo valor.

...
> 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

Estudio de caso 3: estacionalidad

El conjunto de datos de "temperaturas medias mensuales" resume las temperaturas medias mensuales del aire en el castillo de Nottingham, Inglaterra, de 1920 a 1939 en grados Fahrenheit.

El conjunto de datos tiene un componente estacional obvio y ninguna tendencia obvia.

Descargue el conjunto de datos directamente desde aquí:

  • temperatura-media-mensual.csv

Guarde el archivo con el nombre de archivo 'monthly-mean-temp.csv' en su directorio de trabajo actual.

Podemos cargar este conjunto de datos como una serie de Pandas usando la función read_csv().

series = read_csv('monthly-mean-temp.csv', header=0, index_col=0)

El conjunto de datos tiene 20 años o 240 observaciones. Recortaremos el conjunto de datos a los últimos cinco años de datos (60 observaciones) para acelerar el proceso de evaluación del modelo y utilizaremos el último año o 12 observaciones para el conjunto de prueba.

# trim dataset to 5 years
data = data[-(5*12):]

El período del componente estacional es de aproximadamente un año, o 12 observaciones. Usaremos esto como el período estacional en la llamada a la función simple_configs() al preparar las configuraciones del modelo.

# model configs
cfg_list = simple_configs(max_length, offsets=[1,12])

A continuación se enumera el ejemplo completo de cuadrícula que busca el problema de pronóstico de series temporales de temperatura media mensual.

# 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)

Al ejecutar el ejemplo se imprimen las configuraciones del modelo y el RMSE se imprime a medida que se evalúan los modelos.

Las tres configuraciones principales del modelo y su error se informan al final de la ejecución.

Podemos ver que el mejor resultado fue un RMSE de unos 1.501 grados con la siguiente configuración:

  • Estrategia: Promedio
  • n: 4
  • compensación: 12
  • función: media()

Este hallazgo no es demasiado sorprendente. Dada la estructura estacional de los datos, esperaríamos que una función de las últimas observaciones en puntos anteriores del ciclo anual fuera efectiva.

...
> 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

Estudio de caso 4: Tendencia y estacionalidad

El conjunto de datos de "ventas mensuales de automóviles" resume las ventas mensuales de automóviles en Quebec, Canadá, entre 1960 y 1968.

El conjunto de datos tiene una tendencia obvia y un componente estacional.

Descargue el conjunto de datos directamente desde aquí:

  • ventas-mensuales-de-autos.csv

Guarde el archivo con el nombre 'monthly-car-sales.csv' en su directorio de trabajo actual.

Podemos cargar este conjunto de datos como una serie de Pandas usando la función read_csv().

series = read_csv('monthly-car-sales.csv', header=0, index_col=0)

El conjunto de datos tiene 9 años o 108 observaciones. Usaremos el último año o 12 observaciones como conjunto de prueba.

El período del componente estacional podría ser de 12 meses. Intentaremos esto como el período estacional en la llamada a la función simple_configs() al preparar las configuraciones del modelo.

# model configs
 cfg_list = simple_configs(max_length, offsets=[1,12])

A continuación se muestra el ejemplo completo de cuadrícula que busca el problema de pronóstico de series temporales de ventas mensuales de automóviles.

# 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)

Al ejecutar el ejemplo se imprimen las configuraciones del modelo y el RMSE se imprime a medida que se evalúan los modelos.

Las tres configuraciones principales del modelo y su error se informan al final de la ejecución.

Podemos ver que el mejor resultado fue un RMSE de aproximadamente 1841.155 ventas con la siguiente configuración:

  • Estrategia: Promedio
  • n: 3
  • compensación: 12
  • función: mediana()

No es sorprendente que el modelo elegido sea función de las últimas observaciones en el mismo punto en ciclos anteriores, aunque el uso de la mediana en lugar de la media puede no haber sido inmediatamente obvio y los resultados fueron mucho mejores que la media.

...
> 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

Extensiones

Esta sección enumera algunas ideas para ampliar el tutorial que quizás desee explorar.

  • Previsión de la trama. Actualice el marco para reajustar un modelo con la mejor configuración y pronosticar todo el conjunto de datos de prueba, luego trace el pronóstico en comparación con las observaciones reales en el conjunto de prueba.
  • Método de deriva. Implemente el método de deriva para pronósticos simples y compare los resultados con los métodos promedio e ingenuo.
  • Otro conjunto de datos. Aplique el marco desarrollado a un problema adicional de series de tiempo univariadas.

Si explora alguna de estas extensiones, me encantaría saberlo.

Lectura adicional

Esta sección proporciona más recursos sobre el tema si desea profundizar más.

  • Previsión, Wikipedia
  • Joblib: ejecutar funciones de Python como trabajos de canalización

Resumen

En este tutorial, descubrió cómo desarrollar un marco desde cero para buscar estrategias simples e ingenuas de promediación en cuadrículas para el pronóstico de series de tiempo con datos univariados.

Específicamente, aprendiste:

  • Cómo desarrollar un marco para la búsqueda de cuadrículas en modelos simples desde cero mediante validación directa.
  • Cómo realizar una búsqueda en cuadrícula de hiperparámetros de modelos simples para datos de series temporales diarias de nacimientos.
  • Cómo realizar una búsqueda en cuadrícula de hiperparámetros de modelos simples para datos de series temporales mensuales de ventas de champú, ventas de automóviles y temperatura.

¿Tiene alguna pregunta?
Haga sus preguntas en los comentarios a continuación y haré todo lo posible para responder.

Artículos relacionados