Machine-Learning-Mastery-深度学习时间序列教程-七-

73 阅读1小时+

Machine Learning Mastery 深度学习时间序列教程(七)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何开发用于多元多步空气污染时间序列预测的机器学习模型

原文: machinelearningmastery.com/how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting/

实时世界时间序列预测具有挑战性,其原因不仅限于问题特征,例如具有多个输入变量,需要预测多个时间步骤,以及需要对多个物理站点执行相同类型的预测。

EMC Data Science Global Hackathon 数据集或简称“空气质量预测”数据集描述了多个站点的天气状况,需要预测随后三天的空气质量测量结果。

机器学习算法可以应用于时间序列预测问题,并提供诸如处理具有嘈杂的复杂依赖性的多个输入变量的能力的好处。

在本教程中,您将了解如何开发用于空气污染数据的多步时间序列预测的机器学习模型。

完成本教程后,您将了解:

  • 如何估算缺失值并转换时间序列数据,以便可以通过监督学习算法对其进行建模。
  • 如何开发和评估一套线性算法用于多步时间序列预测。
  • 如何开发和评估一套非线性算法用于多步时间序列预测。

让我们开始吧。

How to Develop Machine Learning Models for Multivariate Multi-Step Air Pollution Time Series Forecasting

如何开发多变量多步空气污染时间序列预测的机器学习模型 照片由 Eric Sc​​hmuttenmaer ,保留一些权利。

教程概述

本教程分为九个部分;他们是:

  1. 问题描述
  2. 模型评估
  3. 机器学习建模
  4. 机器学习数据准备
  5. 模型评估测试线束
  6. 评估线性算法
  7. 评估非线性算法
  8. 调整滞后大小

问题描述

空气质量预测数据集描述了多个地点的天气状况,需要预测随后三天的空气质量测量结果。

具体而言,对于多个站点,每小时提供 8 天的温度,压力,风速和风向等天气观测。目标是预测未来 3 天在多个地点的空气质量测量。预测的提前期不是连续的;相反,必须在 72 小时预测期内预测特定提前期。他们是:

+1, +2, +3, +4, +5, +10, +17, +24, +48, +72

此外,数据集被划分为不相交但连续的数据块,其中 8 天的数据随后是需要预测的 3 天。

并非所有站点或块都可以获得所有观察结果,并且并非所有站点和块都可以使用所有输出变量。必须解决大部分缺失数据。

该数据集被用作 2012 年 Kaggle 网站上短期机器学习竞赛(或黑客马拉松)的基础。

根据从参与者中扣留的真实观察结果评估竞赛的提交,并使用平均绝对误差(MAE)进行评分。提交要求在由于缺少数据而无法预测的情况下指定-1,000,000 的值。实际上,提供了一个插入缺失值的模板,并且要求所有提交都采用(模糊的是什么)。

获胜者在滞留测试集(私人排行榜)上使用随机森林在滞后观察中获得了 0.21058 的 MAE。该帖子中提供了此解决方案的说明:

在本教程中,我们将探索如何为可用作基线的问题开发朴素预测,以确定模型是否具有该问题的技能。

模型评估

在我们评估朴素的预测方法之前,我们必须开发一个测试工具。

这至少包括如何准备数据以及如何评估预测。

加载数据集

第一步是下载数据集并将其加载到内存中。

数据集可以从 Kaggle 网站免费下载。您可能必须创建一个帐户并登录才能下载数据集。

下载整个数据集,例如“_ 将所有 _”下载到您的工作站,并使用名为'AirQualityPrediction'的文件夹解压缩当前工作目录中的存档。

我们的重点将是包含训练数据集的'TrainingData.csv'文件,特别是块中的数据,其中每个块是八个连续的观察日和目标变量。

我们可以使用 Pandas read_csv()函数将数据文件加载到内存中,并在第 0 行指定标题行。

# load dataset
dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0)

我们可以通过'chunkID'变量(列索引 1)对数据进行分组。

首先,让我们获取唯一的块标识符列表。

chunk_ids = unique(values[:, 1])

然后,我们可以收集每个块标识符的所有行,并将它们存储在字典中以便于访问。

chunks = dict()
# sort rows by chunk id
for chunk_id in chunk_ids:
	selection = values[:, chunk_ix] == chunk_id
	chunks[chunk_id] = values[selection, :]

下面定义了一个名为to_chunks()的函数,它接受加载数据的 NumPy 数组,并将chunk_id的字典返回到块的行。

# split the dataset by 'chunkID', return a dict of id to rows
def to_chunks(values, chunk_ix=1):
	chunks = dict()
	# get the unique chunk ids
	chunk_ids = unique(values[:, chunk_ix])
	# group rows by chunk id
	for chunk_id in chunk_ids:
		selection = values[:, chunk_ix] == chunk_id
		chunks[chunk_id] = values[selection, :]
	return chunks

下面列出了加载数据集并将其拆分为块的完整示例。

# load data and split into chunks
from numpy import unique
from pandas import read_csv

# split the dataset by 'chunkID', return a dict of id to rows
def to_chunks(values, chunk_ix=1):
	chunks = dict()
	# get the unique chunk ids
	chunk_ids = unique(values[:, chunk_ix])
	# group rows by chunk id
	for chunk_id in chunk_ids:
		selection = values[:, chunk_ix] == chunk_id
		chunks[chunk_id] = values[selection, :]
	return chunks

# load dataset
dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0)
# group data by chunks
values = dataset.values
chunks = to_chunks(values)
print('Total Chunks: %d' % len(chunks))

运行该示例将打印数据集中的块数。

Total Chunks: 208

数据准备

既然我们知道如何加载数据并将其拆分成块,我们就可以将它们分成训练和测试数据集。

尽管每个块内的实际观测数量可能差异很大,但每个块的每小时观察间隔为 8 天。

我们可以将每个块分成前五天的训练观察和最后三天的测试。

每个观察都有一行称为'position_within_chunk',从 1 到 192(8 天* 24 小时)不等。因此,我们可以将此列中值小于或等于 120(5 * 24)的所有行作为训练数据,将任何大于 120 的值作为测试数据。

此外,任何在训练或测试分割中没有任何观察的块都可以被丢弃,因为不可行。

在使用朴素模型时,我们只对目标变量感兴趣,而不对输入的气象变量感兴趣。因此,我们可以删除输入数据,并使训练和测试数据仅包含每个块的 39 个目标变量,以及块和观察时间内的位置。

下面的split_train_test()函数实现了这种行为;给定一个块的字典,它将每个分成训练和测试块数据。

# split each chunk into train/test sets
def split_train_test(chunks, row_in_chunk_ix=2):
	train, test = list(), list()
	# first 5 days of hourly observations for train
	cut_point = 5 * 24
	# enumerate chunks
	for k,rows in chunks.items():
		# split chunk rows by 'position_within_chunk'
		train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :]
		test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :]
		if len(train_rows) == 0 or len(test_rows) == 0:
			print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape))
			continue
		# store with chunk id, position in chunk, hour and all targets
		indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])]
		train.append(train_rows[:, indices])
		test.append(test_rows[:, indices])
	return train, test

我们不需要整个测试数据集;相反,我们只需要在三天时间内的特定提前期进行观察,特别是提前期:

+1, +2, +3, +4, +5, +10, +17, +24, +48, +72

其中,每个提前期相对于训练期结束。

首先,我们可以将这些提前期放入函数中以便于参考:

# return a list of relative forecast lead times
def get_lead_times():
	return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72]

接下来,我们可以将测试数据集缩减为仅在首选提前期的数据。

我们可以通过查看'position_within_chunk'列并使用提前期作为距离训练数据集末尾的偏移量来实现,例如: 120 + 1,120 +2 等

如果我们在测试集中找到匹配的行,则保存它,否则生成一行 NaN 观测值。

下面的函数to_forecasts()实现了这一点,并为每个块的每个预测提前期返回一行 NumPy 数组。

# convert the rows in a test chunk to forecasts
def to_forecasts(test_chunks, row_in_chunk_ix=1):
	# get lead times
	lead_times = get_lead_times()
	# first 5 days of hourly observations for train
	cut_point = 5 * 24
	forecasts = list()
	# enumerate each chunk
	for rows in test_chunks:
		chunk_id = rows[0, 0]
		# enumerate each lead time
		for tau in lead_times:
			# determine the row in chunk we want for the lead time
			offset = cut_point + tau
			# retrieve data for the lead time using row number in chunk
			row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :]
			# check if we have data
			if len(row_for_tau) == 0:
				# create a mock row [chunk, position, hour] + [nan...]
				row = [chunk_id, offset, nan] + [nan for _ in range(39)]
				forecasts.append(row)
			else:
				# store the forecast row
				forecasts.append(row_for_tau[0])
	return array(forecasts)

我们可以将所有这些组合在一起并将数据集拆分为训练集和测试集,并将结果保存到新文件中。

完整的代码示例如下所示。

# split data into train and test sets
from numpy import unique
from numpy import nan
from numpy import array
from numpy import savetxt
from pandas import read_csv

# split the dataset by 'chunkID', return a dict of id to rows
def to_chunks(values, chunk_ix=1):
	chunks = dict()
	# get the unique chunk ids
	chunk_ids = unique(values[:, chunk_ix])
	# group rows by chunk id
	for chunk_id in chunk_ids:
		selection = values[:, chunk_ix] == chunk_id
		chunks[chunk_id] = values[selection, :]
	return chunks

# split each chunk into train/test sets
def split_train_test(chunks, row_in_chunk_ix=2):
	train, test = list(), list()
	# first 5 days of hourly observations for train
	cut_point = 5 * 24
	# enumerate chunks
	for k,rows in chunks.items():
		# split chunk rows by 'position_within_chunk'
		train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :]
		test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :]
		if len(train_rows) == 0 or len(test_rows) == 0:
			print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape))
			continue
		# store with chunk id, position in chunk, hour and all targets
		indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])]
		train.append(train_rows[:, indices])
		test.append(test_rows[:, indices])
	return train, test

# return a list of relative forecast lead times
def get_lead_times():
	return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72]

# convert the rows in a test chunk to forecasts
def to_forecasts(test_chunks, row_in_chunk_ix=1):
	# get lead times
	lead_times = get_lead_times()
	# first 5 days of hourly observations for train
	cut_point = 5 * 24
	forecasts = list()
	# enumerate each chunk
	for rows in test_chunks:
		chunk_id = rows[0, 0]
		# enumerate each lead time
		for tau in lead_times:
			# determine the row in chunk we want for the lead time
			offset = cut_point + tau
			# retrieve data for the lead time using row number in chunk
			row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :]
			# check if we have data
			if len(row_for_tau) == 0:
				# create a mock row [chunk, position, hour] + [nan...]
				row = [chunk_id, offset, nan] + [nan for _ in range(39)]
				forecasts.append(row)
			else:
				# store the forecast row
				forecasts.append(row_for_tau[0])
	return array(forecasts)

# load dataset
dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0)
# group data by chunks
values = dataset.values
chunks = to_chunks(values)
# split into train/test
train, test = split_train_test(chunks)
# flatten training chunks to rows
train_rows = array([row for rows in train for row in rows])
# print(train_rows.shape)
print('Train Rows: %s' % str(train_rows.shape))
# reduce train to forecast lead times only
test_rows = to_forecasts(test)
print('Test Rows: %s' % str(test_rows.shape))
# save datasets
savetxt('AirQualityPrediction/naive_train.csv', train_rows, delimiter=',')
savetxt('AirQualityPrediction/naive_test.csv', test_rows, delimiter=',')

运行该示例首先评论了从数据集中移除了块 69 以获得不足的数据。

然后我们可以看到每个训练和测试集中有 42 列,一个用于块 ID,块内位置,一天中的小时和 39 个训练变量。

我们还可以看到测试数据集的显着缩小版本,其中行仅在预测前置时间。

新的训练和测试数据集分别保存在'naive_train.csv'和'naive_test.csv'文件中。

>dropping chunk=69: train=(0, 95), test=(28, 95)
Train Rows: (23514, 42)
Test Rows: (2070, 42)

预测评估

一旦做出预测,就需要对它们进行评估。

在评估预测时,使用更简单的格式会很有帮助。例如,我们将使用 [chunk] [变量] [时间] 的三维结构,其中变量是从 0 到 38 的目标变量数,time 是从 0 到 9 的提前期索引。

模型有望以这种格式做出预测。

我们还可以重新构建测试数据集以使此数据集进行比较。下面的prepare_test_forecasts()函数实现了这一点。

# convert the test dataset in chunks to [chunk][variable][time] format
def prepare_test_forecasts(test_chunks):
	predictions = list()
	# enumerate chunks to forecast
	for rows in test_chunks:
		# enumerate targets for chunk
		chunk_predictions = list()
		for j in range(3, rows.shape[1]):
			yhat = rows[:, j]
			chunk_predictions.append(yhat)
		chunk_predictions = array(chunk_predictions)
		predictions.append(chunk_predictions)
	return array(predictions)

我们将使用平均绝对误差或 MAE 来评估模型。这是在竞争中使用的度量,并且在给定目标变量的非高斯分布的情况下是合理的选择。

如果提前期不包含测试集中的数据(例如NaN),则不会计算该预测的错误。如果提前期确实在测试集中有数据但预测中没有数据,那么观察的全部大小将被视为错误。最后,如果测试集具有观察值并做出预测,则绝对差值将被记录为误差。

calculate_error()函数实现这些规则并返回给定预测的错误。

# calculate the error between an actual and predicted value
def calculate_error(actual, predicted):
	# give the full actual value if predicted is nan
	if isnan(predicted):
		return abs(actual)
	# calculate abs difference
	return abs(actual - predicted)

错误在所有块和所有提前期之间求和,然后取平均值。

将计算总体 MAE,但我们还将计算每个预测提前期的 MAE。这通常有助于模型选择,因为某些模型在不同的提前期可能会有不同的表现。

下面的 evaluate_forecasts()函数实现了这一点,计算了 [chunk] [variable] [time] 格式中提供的预测和期望值的 MAE 和每个引导时间 MAE。

# evaluate a forecast in the format [chunk][variable][time]
def evaluate_forecasts(predictions, testset):
	lead_times = get_lead_times()
	total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))]
	total_c, times_c = 0, [0 for _ in range(len(lead_times))]
	# enumerate test chunks
	for i in range(len(test_chunks)):
		# convert to forecasts
		actual = testset[i]
		predicted = predictions[i]
		# enumerate target variables
		for j in range(predicted.shape[0]):
			# enumerate lead times
			for k in range(len(lead_times)):
				# skip if actual in nan
				if isnan(actual[j, k]):
					continue
				# calculate error
				error = calculate_error(actual[j, k], predicted[j, k])
				# update statistics
				total_mae += error
				times_mae[k] += error
				total_c += 1
				times_c[k] += 1
	# normalize summed absolute errors
	total_mae /= total_c
	times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))]
	return total_mae, times_mae

一旦我们对模型进行评估,我们就可以呈现它。

下面的summarize_error()函数首先打印模型表现的一行摘要,然后创建每个预测提前期的 MAE 图。

# summarize scores
def summarize_error(name, total_mae, times_mae):
	# print summary
	lead_times = get_lead_times()
	formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))]
	s_scores = ', '.join(formatted)
	print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores))
	# plot summary
	pyplot.plot([str(x) for x in lead_times], times_mae, marker='.')
	pyplot.show()

我们现在准备开始探索朴素预测方法的表现。

机器学习建模

问题可以通过机器学习来建模。

大多数机器学习模型并不直接支持随时间推移的观测概念。相反,必须将滞后观察视为输入要素才能做出预测。

这是时间序列预测的机器学习算法的一个好处。具体来说,他们能够支持大量的输入功能。这些可能是一个或多个输入时间序列的滞后观察。

用于经典方法的时间序列预测的机器学习算法的其他一般好处包括:

  • 能够支持变量之间关系中的噪声特征和噪声。
  • 能够处理不相关的功能。
  • 能够支持变量之间的复杂关系。

该数据集面临的挑战是需要进行多步预测。机器学习方法有两种主要方法可用于进行多步预测;他们是:

  • 直接。开发了一个单独的模型来预测每个预测提前期。
  • 递归。开发单一模型以进行一步预测,并且递归地使用该模型,其中先前预测被用作输入以预测随后的提前期。

递归方法在预测短的连续交付时间块时是有意义的,而直接方法在预测不连续的交付周期时可能更有意义。直接方法可能更适合空气污染预测问题,因为我们有兴趣预测三天内 10 个连续和不连续交付时间的混合。

数据集有 39 个目标变量,我们根据预测的提前期为每个目标变量开发一个模型。这意味着我们需要(39 * 10)390 个机器学习模型。

使用机器学习算法进行时间序列预测的关键是输入数据的选择。我们可以考虑三个主要的数据来源,它们可以用作输入并映射到目标变量的每个预测提前期;他们是:

  • 单变量数据,例如来自正在预测的目标变量的滞后观察。
  • 多变量数据,例如来自其他变量(天气和目标)的滞后观测。
  • 元数据,例如有关预测日期或时间的数据。

可以从所有块中提取数据,提供丰富的数据集,用于学习从输入到目标预测提前期的映射。

39 个目标变量实际上包含 14 个站点的 12 个变量。

由于提供数据的方式,建模的默认方法是将每个变量站点视为独立的。可以通过变量折叠数据,并为多个站点的变量使用相同的模型。

一些变量被故意贴错标签(例如,不同的数据使用具有相同标识符的变量)。然而,也许这些错误标记的变量可以从多站点模型中识别和排除。

机器学习数据准备

在我们探索此数据集的机器学习模型之前,我们必须以能够适合模型的方式准备数据。

这需要两个数据准备步骤:

  • 处理缺失数据。
  • 准备输入输出模式。

目前,我们将关注 39 个目标变量并忽略气象和元数据。

处理缺失数据

对于 39 个目标变量,块由五小时或更少的小时观察组成。

许多块没有全部五天的数据,并且没有任何块具有所有 39 个目标变量的数据。

在块没有目标变量数据的情况下,不需要预测。

如果一个块确实有一些目标变量的数据,但不是所有五天的值,那么该系列中将存在空白。这些间隙可能需要几个小时到一天的观察时间,有时甚至更长。

处理这些差距的三种候选战略如下:

  • 忽略差距。
  • 无间隙地使用数据。
  • 填补空白。

我们可以忽略这些差距。这样做的一个问题是,在将数据分成输入和输出时,数据不会是连续的。在训练模型时,输入将不一致,但可能意味着最后 n 小时的数据或数据分布在最后n天。这种不一致将使学习从输入到输出的映射非常嘈杂,并且可能比模型更难以实现。

我们只能使用无间隙的数据。这是一个不错的选择。风险在于我们可能没有足够或足够的数据来适应模型。

最后,我们可以填补空白。这称为数据插补,有许多策略可用于填补空白。可能表现良好的三种方法包括:

  • 保持最后观察到的值向前(线性)。
  • 使用块中一天中的小时的中值。
  • 使用跨块的一小时的中值。

在本教程中,我们将使用后一种方法,并通过使用跨块的时间的中位数来填补空白。经过一点点测试后,这种方法似乎可以产生更多的训练样本和更好的模型表现。

对于给定变量,可能缺少由缺失行定义的观察值。具体地,每个观察具有'position_within_chunk'。我们期望训练数据集中的每个块有 120 个观察值,其中“positions_within_chunk”从 1 到 120 包含。

因此,我们可以为每个变量创建一个 120NaN值的数组,使用'positions_within_chunk'值标记块中的所有观察值,剩下的任何值都将被标记为NaN。然后我们可以绘制每个变量并寻找差距。

下面的variable_to_series()函数将获取目标变量的块和给定列索引的行,并将为变量返回一系列 120 个时间步长,所有可用数据都标记为来自块。

# layout a variable with breaks in the data for missing positions
def variable_to_series(chunk_train, col_ix, n_steps=5*24):
	# lay out whole series
	data = [nan for _ in range(n_steps)]
	# mark all available data
	for i in range(len(chunk_train)):
		# get position in chunk
		position = int(chunk_train[i, 1] - 1)
		# store data
		data[position] = chunk_train[i, col_ix]
	return data

我们需要为每个块计算一个小时的并行序列,我们可以使用它来为块中的每个变量输入小时特定数据。

给定一系列部分填充的小时,下面的interpolate_hours()函数将填充一天中缺少的小时数。它通过找到第一个标记的小时,然后向前计数,填写一天中的小时,然后向后执行相同的操作来完成此操作。

# interpolate series of hours (in place) in 24 hour time
def interpolate_hours(hours):
	# find the first hour
	ix = -1
	for i in range(len(hours)):
		if not isnan(hours[i]):
			ix = i
			break
	# fill-forward
	hour = hours[ix]
	for i in range(ix+1, len(hours)):
		# increment hour
		hour += 1
		# check for a fill
		if isnan(hours[i]):
			hours[i] = hour % 24
	# fill-backward
	hour = hours[ix]
	for i in range(ix-1, -1, -1):
		# decrement hour
		hour -= 1
		# check for a fill
		if isnan(hours[i]):
			hours[i] = hour % 24

我们可以调用相同的variable_to_series()函数(上面)来创建具有缺失值的系列小时(列索引 2),然后调用interpolate_hours()来填补空白。

# prepare sequence of hours for the chunk
hours = variable_to_series(rows, 2)
# interpolate hours
interpolate_hours(hours)

然后我们可以将时间传递给可以使用它的任何 impute 函数。

我们现在可以尝试在同一系列中使用相同小时填充值中的缺失值。具体来说,我们将在系列中找到所有具有相同小时的行并计算中值。

下面的impute_missing()获取块中的所有行,准备好的块的一天中的小时数,以及具有变量的缺失值和变量的列索引的系列。

它首先检查系列是否全部缺失数据,如果是这种情况则立即返回,因为不能执行任何插补。然后,它会在系列的时间步骤中进行枚举,当它检测到没有数据的时间步长时,它会收集序列中所有行,并使用相同小时的数据并计算中值。

# impute missing data
def impute_missing(train_chunks, rows, hours, series, col_ix):
	# impute missing using the median value for hour in all series
	imputed = list()
	for i in range(len(series)):
		if isnan(series[i]):
			# collect all rows across all chunks for the hour
			all_rows = list()
			for rows in train_chunks:
				[all_rows.append(row) for row in rows[rows[:,2]==hours[i]]]
			# calculate the central tendency for target
			all_rows = array(all_rows)
			# fill with median value
			value = nanmedian(all_rows[:, col_ix])
			if isnan(value):
				value = 0.0
			imputed.append(value)
		else:
			imputed.append(series[i])
	return imputed

监督表示

我们需要将每个目标变量的系列变换为具有输入和输出的行,以便我们可以适应有监督的机器学习算法。

具体来说,我们有一个系列,如:

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

当使用 2 个滞后变量预测+1 的前置时间时,我们将系列分为输入(X)和输出(y)模式,如下所示:

X,			y
1, 2,		3
2, 3,		4
3, 4,		5
4, 5,		6
5, 6,		7
6, 7,		8
7, 8,		9

这首先要求我们选择一些滞后观察值作为输入。没有正确的答案;相反,测试不同的数字并查看哪些有效是一个好主意。

然后,我们必须将系列分为 10 个预测提前期中的每一个的监督学习格式。例如,使用 2 个滞后观察预测+24 可能如下所示:

X,			y
1, 2,		24

然后对 39 个目标变量中的每一个重复该过程。

然后,可以跨块来聚集为每个目标变量的每个提前期准备的模式,以提供模型的训练数据集。

我们还必须准备一个测试数据集。也就是说,为每个块的每个目标变量输入数据(X),以便我们可以将其用作输入来预测测试数据集中的提前期。如果我们选择滞后为 2,则测试数据集将包含每个块的每个目标变量的最后两个观察值。非常直截了当。

我们可以从定义一个函数开始,该函数将为给定的完整(插补)系列创建输入输出模式。

下面的supervised_for_lead_time()函数将采用一系列滞后观察作为输入,预测前置时间做出预测,然后返回从该系列中抽取的输入/输出行列表。

# created input/output patterns from a sequence
def supervised_for_lead_time(series, n_lag, lead_time):
	samples = list()
	# enumerate observations and create input/output patterns
	for i in range(n_lag, len(series)):
		end_ix = i + (lead_time - 1)
		# check if can create a pattern
		if end_ix >= len(series):
			break
		# retrieve input and output
		start_ix = i - n_lag
		row = series[start_ix:i] + [series[end_ix]]
		samples.append(row)
	return samples

理解这件作品很重要。

我们可以测试此函数并探索不同数量的滞后变量并预测小型测试数据集的提前期。

下面是一个完整的示例,它生成一系列 20 个整数并创建一个具有两个输入滞后的系列,并预测+6 前置时间。

# test supervised to input/output patterns
from numpy import array

# created input/output patterns from a sequence
def supervised_for_lead_time(series, n_lag, lead_time):
	data = list()
	# enumerate observations and create input/output patterns
	for i in range(n_lag, len(series)):
		end_ix = i + (lead_time - 1)
		# check if can create a pattern
		if end_ix >= len(series):
			break
		# retrieve input and output
		start_ix = i - n_lag
		row = series[start_ix:i] + [series[end_ix]]
		data.append(row)
	return array(data)

# define test dataset
data = [x for x in range(20)]
# convert to supervised format
result = supervised_for_lead_time(data, 2, 6)
# display result
print(result)

运行该示例将打印显示滞后观察结果及其相关预测提前期的结果模式。

尝试使用此示例来熟悉此数据转换,因为它是使用机器学习算法建模时间序列的关键。

[[ 0  1  7]
 [ 1  2  8]
 [ 2  3  9]
 [ 3  4 10]
 [ 4  5 11]
 [ 5  6 12]
 [ 6  7 13]
 [ 7  8 14]
 [ 8  9 15]
 [ 9 10 16]
 [10 11 17]
 [11 12 18]
 [12 13 19]]

我们现在可以为给定目标变量系列的每个预测提前期调用 supervised_for_lead_time()

下面的target_to_supervised()函数实现了这个功能。首先,将目标变量转换为系列,并使用上一节中开发的函数进行估算。然后为每个目标提前期创建训练样本。还创建了目标变量的测试样本。

然后,为该目标变量返回每个预测提前期的训练数据和测试输入数据。

# create supervised learning data for each lead time for this target
def target_to_supervised(chunks, rows, hours, col_ix, n_lag):
	train_lead_times = list()
	# get series
	series = variable_to_series(rows, col_ix)
	if not has_data(series):
		return None, [nan for _ in range(n_lag)]
	# impute
	imputed = impute_missing(chunks, rows, hours, series, col_ix)
	# prepare test sample for chunk-variable
	test_sample = array(imputed[-n_lag:])
	# enumerate lead times
	lead_times = get_lead_times()
	for lead_time in lead_times:
		# make input/output data from series
		train_samples = supervised_for_lead_time(imputed, n_lag, lead_time)
		train_lead_times.append(train_samples)
	return train_lead_times, test_sample

我们有件;我们现在需要定义驱动数据准备过程的函数。

此功能可构建训练和测试数据集。

该方法是枚举每个目标变量,并从所有块中收集每个提前期的训练数据。同时,我们在对测试数据集做出预测时收集所需的样本作为输入。

结果是具有维度 [var] [提前期] [样本] 的训练数据集,其中最终维度是目标变量的预测提前期的训练样本行。该函数还返回具有维度 [chunk] [var] [样本] 的测试数据集,其中最终维度是用于对块的目标变量做出预测的输入数据。

下面的data_prep()函数实现了这种行为,并将块格式的数据和指定数量的滞后观察值用作输入。

# prepare training [var][lead time][sample] and test [chunk][var][sample]
def data_prep(chunks, n_lag, n_vars=39):
	lead_times = get_lead_times()
	train_data = [[list() for _ in range(len(lead_times))] for _ in range(n_vars)]
	test_data = [[list() for _ in range(n_vars)] for _ in range(len(chunks))]
	# enumerate targets for chunk
	for var in range(n_vars):
		# convert target number into column number
		col_ix = 3 + var
		# enumerate chunks to forecast
		for c_id in range(len(chunks)):
			rows = chunks[c_id]
			# prepare sequence of hours for the chunk
			hours = variable_to_series(rows, 2)
			# interpolate hours
			interpolate_hours(hours)
			# check for no data
			if not has_data(rows[:, col_ix]):
				continue
			# convert series into training data for each lead time
			train, test_sample = target_to_supervised(chunks, rows, hours, col_ix, n_lag)
			# store test sample for this var-chunk
			test_data[c_id][var] = test_sample
			if train is not None:
				# store samples per lead time
				for lead_time in range(len(lead_times)):
					# add all rows to the existing list of rows
					train_data[var][lead_time].extend(train[lead_time])
		# convert all rows for each var-lead time to a numpy array
		for lead_time in range(len(lead_times)):
			train_data[var][lead_time] = array(train_data[var][lead_time])
	return array(train_data), array(test_data)

完整的例子

我们可以将所有内容组合在一起,并使用监督学习格式为机器学习算法准备训练和测试数据集。

在预测每个预测提前期时,我们将使用先前 12 小时的滞后观测作为输入。

然后,生成的训练和测试数据集将保存为二进制 NumPy 数组。

下面列出了完整的示例。

# prepare data
from numpy import loadtxt
from numpy import nan
from numpy import isnan
from numpy import count_nonzero
from numpy import unique
from numpy import array
from numpy import nanmedian
from numpy import save

# split the dataset by 'chunkID', return a list of chunks
def to_chunks(values, chunk_ix=0):
	chunks = list()
	# get the unique chunk ids
	chunk_ids = unique(values[:, chunk_ix])
	# group rows by chunk id
	for chunk_id in chunk_ids:
		selection = values[:, chunk_ix] == chunk_id
		chunks.append(values[selection, :])
	return chunks

# return a list of relative forecast lead times
def get_lead_times():
	return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72]

# interpolate series of hours (in place) in 24 hour time
def interpolate_hours(hours):
	# find the first hour
	ix = -1
	for i in range(len(hours)):
		if not isnan(hours[i]):
			ix = i
			break
	# fill-forward
	hour = hours[ix]
	for i in range(ix+1, len(hours)):
		# increment hour
		hour += 1
		# check for a fill
		if isnan(hours[i]):
			hours[i] = hour % 24
	# fill-backward
	hour = hours[ix]
	for i in range(ix-1, -1, -1):
		# decrement hour
		hour -= 1
		# check for a fill
		if isnan(hours[i]):
			hours[i] = hour % 24

# return true if the array has any non-nan values
def has_data(data):
	return count_nonzero(isnan(data)) < len(data)

# impute missing data
def impute_missing(train_chunks, rows, hours, series, col_ix):
	# impute missing using the median value for hour in all series
	imputed = list()
	for i in range(len(series)):
		if isnan(series[i]):
			# collect all rows across all chunks for the hour
			all_rows = list()
			for rows in train_chunks:
				[all_rows.append(row) for row in rows[rows[:,2]==hours[i]]]
			# calculate the central tendency for target
			all_rows = array(all_rows)
			# fill with median value
			value = nanmedian(all_rows[:, col_ix])
			if isnan(value):
				value = 0.0
			imputed.append(value)
		else:
			imputed.append(series[i])
	return imputed

# layout a variable with breaks in the data for missing positions
def variable_to_series(chunk_train, col_ix, n_steps=5*24):
	# lay out whole series
	data = [nan for _ in range(n_steps)]
	# mark all available data
	for i in range(len(chunk_train)):
		# get position in chunk
		position = int(chunk_train[i, 1] - 1)
		# store data
		data[position] = chunk_train[i, col_ix]
	return data

# created input/output patterns from a sequence
def supervised_for_lead_time(series, n_lag, lead_time):
	samples = list()
	# enumerate observations and create input/output patterns
	for i in range(n_lag, len(series)):
		end_ix = i + (lead_time - 1)
		# check if can create a pattern
		if end_ix >= len(series):
			break
		# retrieve input and output
		start_ix = i - n_lag
		row = series[start_ix:i] + [series[end_ix]]
		samples.append(row)
	return samples

# create supervised learning data for each lead time for this target
def target_to_supervised(chunks, rows, hours, col_ix, n_lag):
	train_lead_times = list()
	# get series
	series = variable_to_series(rows, col_ix)
	if not has_data(series):
		return None, [nan for _ in range(n_lag)]
	# impute
	imputed = impute_missing(chunks, rows, hours, series, col_ix)
	# prepare test sample for chunk-variable
	test_sample = array(imputed[-n_lag:])
	# enumerate lead times
	lead_times = get_lead_times()
	for lead_time in lead_times:
		# make input/output data from series
		train_samples = supervised_for_lead_time(imputed, n_lag, lead_time)
		train_lead_times.append(train_samples)
	return train_lead_times, test_sample

# prepare training [var][lead time][sample] and test [chunk][var][sample]
def data_prep(chunks, n_lag, n_vars=39):
	lead_times = get_lead_times()
	train_data = [[list() for _ in range(len(lead_times))] for _ in range(n_vars)]
	test_data = [[list() for _ in range(n_vars)] for _ in range(len(chunks))]
	# enumerate targets for chunk
	for var in range(n_vars):
		# convert target number into column number
		col_ix = 3 + var
		# enumerate chunks to forecast
		for c_id in range(len(chunks)):
			rows = chunks[c_id]
			# prepare sequence of hours for the chunk
			hours = variable_to_series(rows, 2)
			# interpolate hours
			interpolate_hours(hours)
			# check for no data
			if not has_data(rows[:, col_ix]):
				continue
			# convert series into training data for each lead time
			train, test_sample = target_to_supervised(chunks, rows, hours, col_ix, n_lag)
			# store test sample for this var-chunk
			test_data[c_id][var] = test_sample
			if train is not None:
				# store samples per lead time
				for lead_time in range(len(lead_times)):
					# add all rows to the existing list of rows
					train_data[var][lead_time].extend(train[lead_time])
		# convert all rows for each var-lead time to a numpy array
		for lead_time in range(len(lead_times)):
			train_data[var][lead_time] = array(train_data[var][lead_time])
	return array(train_data), array(test_data)

# load dataset
train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',')
test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',')
# group data by chunks
train_chunks = to_chunks(train)
test_chunks = to_chunks(test)
# convert training data into supervised learning data
n_lag = 12
train_data, test_data = data_prep(train_chunks, n_lag)
print(train_data.shape, test_data.shape)
# save train and test sets to file
save('AirQualityPrediction/supervised_train.npy', train_data)
save('AirQualityPrediction/supervised_test.npy', test_data)

运行示例可能需要一分钟。

结果是包含训练和测试数据集的两个二进制文件,我们可以在以下部分加载这些文件,以便训练和评估问题的机器学习算法。

模型评估测试线束

在我们开始评估算法之前,我们需要更多的测试工具元素。

首先,我们需要能够在训练数据上使用 scikit-learn 模型。下面的fit_model()函数将复制模型配置,并使其适合所提供的训练数据。我们需要适应每个配置模型的许多(360)版本,因此这个函数将被调用很多。

# fit a single model
def fit_model(model, X, y):
	# clone the model configuration
	local_model = clone(model)
	# fit the model
	local_model.fit(X, y)
	return local_model

接下来,我们需要为每个变量拟合一个模型并预测提前期组合。

我们可以通过首先通过变量枚举训练数据集,然后通过提前期来完成此操作。然后我们可以拟合模型并将其存储在具有相同结构的列表列表中,具体为: [var] [time] [model]

下面的fit_models()函数实现了这一点。

# fit one model for each variable and each forecast lead time [var][time][model]
def fit_models(model, train):
	# prepare structure for saving models
	models = [[list() for _ in range(train.shape[1])] for _ in range(train.shape[0])]
	# enumerate vars
	for i in range(train.shape[0]):
		# enumerate lead times
		for j in range(train.shape[1]):
			# get data
			data = train[i, j]
			X, y = data[:, :-1], data[:, -1]
			# fit model
			local_model = fit_model(model, X, y)
			models[i][j].append(local_model)
	return models

拟合模型是缓慢的部分,可以从并行化中受益,例如使用 Joblib 库。这是一个扩展。

一旦模型适合,它们就可用于对测试数据集做出预测。

准备好的测试数据集首先按块组织,然后按目标变量组织。预测很快,首先要检查是否可以做出预测(我们有输入数据),如果是,则使用适当的模型作为目标变量。然后,将使用每个直接模型预测变量的 10 个预测前置时间中的每一个。

下面的make_predictions()函数实现了这一点,将模型列表列表和加载的测试数据集作为参数,并返回结构 _[chunks] [var] [time]的预测数组 _。

# return forecasts as [chunks][var][time]
def make_predictions(models, test):
	lead_times = get_lead_times()
	predictions = list()
	# enumerate chunks
	for i in range(test.shape[0]):
		# enumerate variables
		chunk_predictions = list()
		for j in range(test.shape[1]):
			# get the input pattern for this chunk and target
			pattern = test[i,j]
			# assume a nan forecast
			forecasts = array([nan for _ in range(len(lead_times))])
			# check we can make a forecast
			if has_data(pattern):
				pattern = pattern.reshape((1, len(pattern)))
				# forecast each lead time
				forecasts = list()
				for k in range(len(lead_times)):
					yhat = models[j][k][0].predict(pattern)
					forecasts.append(yhat[0])
				forecasts = array(forecasts)
			# save forecasts fore each lead time for this variable
			chunk_predictions.append(forecasts)
		# save forecasts for this chunk
		chunk_predictions = array(chunk_predictions)
		predictions.append(chunk_predictions)
	return array(predictions)

我们需要一个要评估的模型列表。

我们可以定义一个通用的get_models()函数,该函数负责定义映射到已配置的 scikit-learn 模型对象的模型名称字典。

# prepare a list of ml models
def get_models(models=dict()):
	# ...
	return models

最后,我们需要一个功能来推动模型评估过程。

给定模型字典,枚举模型,首先在训练数据上拟合模型矩阵,预测测试数据集,评估预测,并总结结果。

下面的evaluate_models()函数实现了这一点。

# evaluate a suite of models
def evaluate_models(models, train, test, actual):
	for name, model in models.items():
		# fit models
		fits = fit_models(model, train)
		# make predictions
		predictions = make_predictions(fits, test)
		# evaluate forecast
		total_mae, _ = evaluate_forecasts(predictions, actual)
		# summarize forecast
		summarize_error(name, total_mae)

我们现在拥有评估机器学习模型所需的一切。

评估线性算法

在本节中,我们将检查一套线性机器学习算法。

线性算法是假设输出是输入变量的线性函数的算法。这很像 ARIMA 等经典时间序列预测模型的假设。

现场检查意味着评估一套模型,以便大致了解哪些有效。我们感兴趣的是任何超过简单自回归模型 AR(2)的模型,其实现的 MAE 误差约为 0.487。

我们将使用默认配置测试八种线性机器学习算法;特别:

  • 线性回归
  • 套索线性回归
  • 岭回归
  • 弹性网络回归
  • 胡贝尔回归
  • Lasso Lars 线性回归
  • 被动攻击性回归
  • 随机梯度下降回归

我们可以在get_models()函数中定义这些模型。

# prepare a list of ml models
def get_models(models=dict()):
	# linear models
	models['lr'] = LinearRegression()
	models['lasso'] = Lasso()
	models['ridge'] = Ridge()
	models['en'] = ElasticNet()
	models['huber'] = HuberRegressor()
	models['llars'] = LassoLars()
	models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3)
	models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3)
	print('Defined %d models' % len(models))
	return models

完整的代码示例如下所示。

# evaluate linear algorithms
from numpy import load
from numpy import loadtxt
from numpy import nan
from numpy import isnan
from numpy import count_nonzero
from numpy import unique
from numpy import array
from sklearn.base import clone
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Lasso
from sklearn.linear_model import Ridge
from sklearn.linear_model import ElasticNet
from sklearn.linear_model import HuberRegressor
from sklearn.linear_model import LassoLars
from sklearn.linear_model import PassiveAggressiveRegressor
from sklearn.linear_model import SGDRegressor

# split the dataset by 'chunkID', return a list of chunks
def to_chunks(values, chunk_ix=0):
	chunks = list()
	# get the unique chunk ids
	chunk_ids = unique(values[:, chunk_ix])
	# group rows by chunk id
	for chunk_id in chunk_ids:
		selection = values[:, chunk_ix] == chunk_id
		chunks.append(values[selection, :])
	return chunks

# return true if the array has any non-nan values
def has_data(data):
	return count_nonzero(isnan(data)) < len(data)

# return a list of relative forecast lead times
def get_lead_times():
	return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72]

# fit a single model
def fit_model(model, X, y):
	# clone the model configuration
	local_model = clone(model)
	# fit the model
	local_model.fit(X, y)
	return local_model

# fit one model for each variable and each forecast lead time [var][time][model]
def fit_models(model, train):
	# prepare structure for saving models
	models = [[list() for _ in range(train.shape[1])] for _ in range(train.shape[0])]
	# enumerate vars
	for i in range(train.shape[0]):
		# enumerate lead times
		for j in range(train.shape[1]):
			# get data
			data = train[i, j]
			X, y = data[:, :-1], data[:, -1]
			# fit model
			local_model = fit_model(model, X, y)
			models[i][j].append(local_model)
	return models

# return forecasts as [chunks][var][time]
def make_predictions(models, test):
	lead_times = get_lead_times()
	predictions = list()
	# enumerate chunks
	for i in range(test.shape[0]):
		# enumerate variables
		chunk_predictions = list()
		for j in range(test.shape[1]):
			# get the input pattern for this chunk and target
			pattern = test[i,j]
			# assume a nan forecast
			forecasts = array([nan for _ in range(len(lead_times))])
			# check we can make a forecast
			if has_data(pattern):
				pattern = pattern.reshape((1, len(pattern)))
				# forecast each lead time
				forecasts = list()
				for k in range(len(lead_times)):
					yhat = models[j][k][0].predict(pattern)
					forecasts.append(yhat[0])
				forecasts = array(forecasts)
			# save forecasts for each lead time for this variable
			chunk_predictions.append(forecasts)
		# save forecasts for this chunk
		chunk_predictions = array(chunk_predictions)
		predictions.append(chunk_predictions)
	return array(predictions)

# convert the test dataset in chunks to [chunk][variable][time] format
def prepare_test_forecasts(test_chunks):
	predictions = list()
	# enumerate chunks to forecast
	for rows in test_chunks:
		# enumerate targets for chunk
		chunk_predictions = list()
		for j in range(3, rows.shape[1]):
			yhat = rows[:, j]
			chunk_predictions.append(yhat)
		chunk_predictions = array(chunk_predictions)
		predictions.append(chunk_predictions)
	return array(predictions)

# calculate the error between an actual and predicted value
def calculate_error(actual, predicted):
	# give the full actual value if predicted is nan
	if isnan(predicted):
		return abs(actual)
	# calculate abs difference
	return abs(actual - predicted)

# evaluate a forecast in the format [chunk][variable][time]
def evaluate_forecasts(predictions, testset):
	lead_times = get_lead_times()
	total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))]
	total_c, times_c = 0, [0 for _ in range(len(lead_times))]
	# enumerate test chunks
	for i in range(len(test_chunks)):
		# convert to forecasts
		actual = testset[i]
		predicted = predictions[i]
		# enumerate target variables
		for j in range(predicted.shape[0]):
			# enumerate lead times
			for k in range(len(lead_times)):
				# skip if actual in nan
				if isnan(actual[j, k]):
					continue
				# calculate error
				error = calculate_error(actual[j, k], predicted[j, k])
				# update statistics
				total_mae += error
				times_mae[k] += error
				total_c += 1
				times_c[k] += 1
	# normalize summed absolute errors
	total_mae /= total_c
	times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))]
	return total_mae, times_mae

# summarize scores
def summarize_error(name, total_mae):
	print('%s: %.3f MAE' % (name, total_mae))

# prepare a list of ml models
def get_models(models=dict()):
	# linear models
	models['lr'] = LinearRegression()
	models['lasso'] = Lasso()
	models['ridge'] = Ridge()
	models['en'] = ElasticNet()
	models['huber'] = HuberRegressor()
	models['llars'] = LassoLars()
	models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3)
	models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3)
	print('Defined %d models' % len(models))
	return models

# evaluate a suite of models
def evaluate_models(models, train, test, actual):
	for name, model in models.items():
		# fit models
		fits = fit_models(model, train)
		# make predictions
		predictions = make_predictions(fits, test)
		# evaluate forecast
		total_mae, _ = evaluate_forecasts(predictions, actual)
		# summarize forecast
		summarize_error(name, total_mae)

# load supervised datasets
train = load('AirQualityPrediction/supervised_train.npy')
test = load('AirQualityPrediction/supervised_test.npy')
print(train.shape, test.shape)
# load test chunks for validation
testset = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',')
test_chunks = to_chunks(testset)
actual = prepare_test_forecasts(test_chunks)
# prepare list of models
models = get_models()
# evaluate models
evaluate_models(models, train, test, actual)

运行该示例为每个评估的算法打印 MAE。

我们可以看到,与简单的 AR 模型相比,许多算法显示出技能,实现了低于 0.487 的 MAE。

Huber 回归似乎表现最佳(使用默认配置),实现了 0.434 的 MAE。

这很有趣,因为 Huber 回归稳健回归与 Huber 损失,是一种设计为对训练数据集中的异常值具有鲁棒性的方法。这可能表明其他方法可以通过更多的数据准备(例如标准化和/或异常值去除)来表现更好。

lr: 0.454 MAE
lasso: 0.624 MAE
ridge: 0.454 MAE
en: 0.595 MAE
huber: 0.434 MAE
llars: 0.631 MAE
pa: 0.833 MAE
sgd: 0.457 MAE

非线性算法

我们可以使用相同的框架来评估一套非线性和集成机器学习算法的表现。

特别:

非线性算法

  • k-最近邻居
  • 分类和回归树
  • 额外的树
  • 支持向量回归

集成算法

  • Adaboost 的
  • 袋装决策树
  • 随机森林
  • 额外的树木
  • 梯度增压机

下面的get_models()函数定义了这九个模型。

# prepare a list of ml models
def get_models(models=dict()):
	# non-linear models
	models['knn'] = KNeighborsRegressor(n_neighbors=7)
	models['cart'] = DecisionTreeRegressor()
	models['extra'] = ExtraTreeRegressor()
	models['svmr'] = SVR()
	# # ensemble models
	n_trees = 100
	models['ada'] = AdaBoostRegressor(n_estimators=n_trees)
	models['bag'] = BaggingRegressor(n_estimators=n_trees)
	models['rf'] = RandomForestRegressor(n_estimators=n_trees)
	models['et'] = ExtraTreesRegressor(n_estimators=n_trees)
	models['gbm'] = GradientBoostingRegressor(n_estimators=n_trees)
	print('Defined %d models' % len(models))
	return models

完整的代码清单如下。

# spot check nonlinear algorithms
from numpy import load
from numpy import loadtxt
from numpy import nan
from numpy import isnan
from numpy import count_nonzero
from numpy import unique
from numpy import array
from sklearn.base import clone
from sklearn.neighbors import KNeighborsRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.tree import ExtraTreeRegressor
from sklearn.svm import SVR
from sklearn.ensemble import AdaBoostRegressor
from sklearn.ensemble import BaggingRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.ensemble import GradientBoostingRegressor

# split the dataset by 'chunkID', return a list of chunks
def to_chunks(values, chunk_ix=0):
	chunks = list()
	# get the unique chunk ids
	chunk_ids = unique(values[:, chunk_ix])
	# group rows by chunk id
	for chunk_id in chunk_ids:
		selection = values[:, chunk_ix] == chunk_id
		chunks.append(values[selection, :])
	return chunks

# return true if the array has any non-nan values
def has_data(data):
	return count_nonzero(isnan(data)) < len(data)

# return a list of relative forecast lead times
def get_lead_times():
	return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72]

# fit a single model
def fit_model(model, X, y):
	# clone the model configuration
	local_model = clone(model)
	# fit the model
	local_model.fit(X, y)
	return local_model

# fit one model for each variable and each forecast lead time [var][time][model]
def fit_models(model, train):
	# prepare structure for saving models
	models = [[list() for _ in range(train.shape[1])] for _ in range(train.shape[0])]
	# enumerate vars
	for i in range(train.shape[0]):
		# enumerate lead times
		for j in range(train.shape[1]):
			# get data
			data = train[i, j]
			X, y = data[:, :-1], data[:, -1]
			# fit model
			local_model = fit_model(model, X, y)
			models[i][j].append(local_model)
	return models

# return forecasts as [chunks][var][time]
def make_predictions(models, test):
	lead_times = get_lead_times()
	predictions = list()
	# enumerate chunks
	for i in range(test.shape[0]):
		# enumerate variables
		chunk_predictions = list()
		for j in range(test.shape[1]):
			# get the input pattern for this chunk and target
			pattern = test[i,j]
			# assume a nan forecast
			forecasts = array([nan for _ in range(len(lead_times))])
			# check we can make a forecast
			if has_data(pattern):
				pattern = pattern.reshape((1, len(pattern)))
				# forecast each lead time
				forecasts = list()
				for k in range(len(lead_times)):
					yhat = models[j][k][0].predict(pattern)
					forecasts.append(yhat[0])
				forecasts = array(forecasts)
			# save forecasts for each lead time for this variable
			chunk_predictions.append(forecasts)
		# save forecasts for this chunk
		chunk_predictions = array(chunk_predictions)
		predictions.append(chunk_predictions)
	return array(predictions)

# convert the test dataset in chunks to [chunk][variable][time] format
def prepare_test_forecasts(test_chunks):
	predictions = list()
	# enumerate chunks to forecast
	for rows in test_chunks:
		# enumerate targets for chunk
		chunk_predictions = list()
		for j in range(3, rows.shape[1]):
			yhat = rows[:, j]
			chunk_predictions.append(yhat)
		chunk_predictions = array(chunk_predictions)
		predictions.append(chunk_predictions)
	return array(predictions)

# calculate the error between an actual and predicted value
def calculate_error(actual, predicted):
	# give the full actual value if predicted is nan
	if isnan(predicted):
		return abs(actual)
	# calculate abs difference
	return abs(actual - predicted)

# evaluate a forecast in the format [chunk][variable][time]
def evaluate_forecasts(predictions, testset):
	lead_times = get_lead_times()
	total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))]
	total_c, times_c = 0, [0 for _ in range(len(lead_times))]
	# enumerate test chunks
	for i in range(len(test_chunks)):
		# convert to forecasts
		actual = testset[i]
		predicted = predictions[i]
		# enumerate target variables
		for j in range(predicted.shape[0]):
			# enumerate lead times
			for k in range(len(lead_times)):
				# skip if actual in nan
				if isnan(actual[j, k]):
					continue
				# calculate error
				error = calculate_error(actual[j, k], predicted[j, k])
				# update statistics
				total_mae += error
				times_mae[k] += error
				total_c += 1
				times_c[k] += 1
	# normalize summed absolute errors
	total_mae /= total_c
	times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))]
	return total_mae, times_mae

# summarize scores
def summarize_error(name, total_mae):
	print('%s: %.3f MAE' % (name, total_mae))

# prepare a list of ml models
def get_models(models=dict()):
	# non-linear models
	models['knn'] = KNeighborsRegressor(n_neighbors=7)
	models['cart'] = DecisionTreeRegressor()
	models['extra'] = ExtraTreeRegressor()
	models['svmr'] = SVR()
	# # ensemble models
	n_trees = 100
	models['ada'] = AdaBoostRegressor(n_estimators=n_trees)
	models['bag'] = BaggingRegressor(n_estimators=n_trees)
	models['rf'] = RandomForestRegressor(n_estimators=n_trees)
	models['et'] = ExtraTreesRegressor(n_estimators=n_trees)
	models['gbm'] = GradientBoostingRegressor(n_estimators=n_trees)
	print('Defined %d models' % len(models))
	return models

# evaluate a suite of models
def evaluate_models(models, train, test, actual):
	for name, model in models.items():
		# fit models
		fits = fit_models(model, train)
		# make predictions
		predictions = make_predictions(fits, test)
		# evaluate forecast
		total_mae, _ = evaluate_forecasts(predictions, actual)
		# summarize forecast
		summarize_error(name, total_mae)

# load supervised datasets
train = load('AirQualityPrediction/supervised_train.npy')
test = load('AirQualityPrediction/supervised_test.npy')
print(train.shape, test.shape)
# load test chunks for validation
testset = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',')
test_chunks = to_chunks(testset)
actual = prepare_test_forecasts(test_chunks)
# prepare list of models
models = get_models()
# evaluate models
evaluate_models(models, train, test, actual)

运行该示例,我们可以看到许多算法与自回归算法的基线相比表现良好,尽管在上一节中没有一个表现与 Huber 回归一样好。

支持向量回归和可能的梯度增强机器可能值得进一步研究分别达到 0.437 和 0.450 的 MAE。

knn: 0.484 MAE
cart: 0.631 MAE
extra: 0.630 MAE
svmr: 0.437 MAE
ada: 0.717 MAE
bag: 0.471 MAE
rf: 0.470 MAE
et: 0.469 MAE
gbm: 0.450 MAE

调整滞后大小

在之前的抽查实验中,滞后观察的数量被任意固定为 12。

我们可以改变滞后观察的数量并评估对 MAE 的影响。一些算法可能需要更多或更少的先前观察,但是一般趋势可能跨越算法。

准备具有一系列不同数量的滞后观察的监督学习数据集并拟合并评估每个观察数据的 HuberRegressor。

我试验了以下数量的滞后观察:

[1, 3, 6, 12, 24, 36, 48]

结果如下:

1:	0.451
3:	0.445
6:	0.441
12:	0.434
24:	0.423
36:	0.422
48:	0.439

下面提供了这些结果的图表。

Line Plot of Number of Lag Observations vs MAE for Huber Regression

Huber 回归的滞后观测数与 MAE 的线图

随着滞后观测数量的增加,我们可以看到整体 MAE 降低的总趋势,至少到一个点,之后误差再次开始上升。

结果表明,至少对于 HuberRegressor 算法,36 个滞后观察可能是实现 MAE 为 0.422 的良好配置。

扩展

本节列出了一些扩展您可能希望探索的教程的想法。

  • 数据准备。探索简单数据准备(如标准化或统计异常值删除)是否可以提高模型表现。
  • 工程特征。探索工程特征(如预测时段的中值)是否可以提高模型表现
  • 气象变量。探索在模型中添加滞后气象变量是否可以提高表现。
  • 跨站点模型。探索组合相同类型的目标变量以及跨站点重用模型是否会提高表现。
  • 算法调整。探索调整一些表现更好的算法的超参数是否可以带来表现改进。

如果你探索任何这些扩展,我很想知道。

进一步阅读

如果您希望深入了解,本节将提供有关该主题的更多资源。

摘要

在本教程中,您了解了如何为空气污染数据的多步时间序列预测开发机器学习模型。

具体来说,你学到了:

  • 如何估算缺失值并转换时间序列数据,以便可以通过监督学习算法对其进行建模。
  • 如何开发和评估一套线性算法用于多步时间序列预测。
  • 如何开发和评估一套非线性算法用于多步时间序列预测。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

如何开发用于时间序列预测的多层感知机模型

原文: machinelearningmastery.com/how-to-develop-multilayer-perceptron-models-for-time-series-forecasting/

多层感知机(简称 MLP)可应用于时间序列预测。

使用 MLP 进行时间序列预测的一个挑战是准备数据。具体而言,必须将滞后观察平坦化为特征向量。

在本教程中,您将了解如何针对一系列标准时间序列预测问题开发一套 MLP 模型。

本教程的目的是为每种类型的时间序列问题提供每个模型的独立示例,作为模板,您可以根据特定的时间序列预测问题进行复制和调整。

在本教程中,您将了解如何针对一系列标准时间序列预测问题开发一套多层感知机模型。

完成本教程后,您将了解:

  • 如何开发单变量时间序列预测的 MLP 模型。
  • 如何开发多元时间序列预测的 MLP 模型。
  • 如何开发 MLP 模型进行多步时间序列预测。

让我们开始吧。

How to Develop Multilayer Perceptron Models for Time Series Forecasting

如何开发用于时间序列预测的多层感知机模型 照片由土地管理局,保留一些权利。

教程概述

本教程分为四个部分;他们是:

  1. 单变量 MLP 模型
  2. 多变量 MLP 模型
  3. 多步 MLP 模型
  4. 多变量多步 MLP 模型

单变量 MLP 模型

多层感知机(简称 MLP)可用于模拟单变量时间序列预测问题。

单变量时间序列是由具有时间顺序的单个观察序列组成的数据集,并且需要模型来从过去的一系列观察中学习以预测序列中的下一个值。

本节分为两部分;他们是:

  1. 数据准备
  2. MLP 模型

数据准备

在对单变量系列进行建模之前,必须准备好它。

MLP 模型将学习将过去观察序列作为输入映射到输出观察的函数。因此,必须将观察序列转换为模型可以从中学习的多个示例。

考虑给定的单变量序列:

[10, 20, 30, 40, 50, 60, 70, 80, 90]

我们可以将序列划分为多个称为样本的输入/输出模式,其中三个时间步长用作输入,一个时间步长用作正在学习的一步预测的输出。

X,				y
10, 20, 30		40
20, 30, 40		50
30, 40, 50		60
...

下面的split_sequence()函数实现了这种行为,并将给定的单变量序列分成多个样本,其中每个样本具有指定的时间步长,输出是单个时间步长。

# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
	X, y = list(), list()
	for i in range(len(sequence)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the sequence
		if end_ix > len(sequence)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

我们可以在上面的小型人为数据集上演示这个功能。

下面列出了完整的示例。

# univariate data preparation
from numpy import array

# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
	X, y = list(), list()
	for i in range(len(sequence)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the sequence
		if end_ix > len(sequence)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
# choose a number of time steps
n_steps = 3
# split into samples
X, y = split_sequence(raw_seq, n_steps)
# summarize the data
for i in range(len(X)):
	print(X[i], y[i])

运行该示例将单变量系列分成六个样本,其中每个样本具有三个输入时间步长和一个输出时间步长。

[10 20 30] 40
[20 30 40] 50
[30 40 50] 60
[40 50 60] 70
[50 60 70] 80
[60 70 80] 90

现在我们已经知道如何准备用于建模的单变量系列,让我们看看开发一个可以学习输入到输出的映射的 MLP 模型。

MLP 模型

简单的 MLP 模型具有单个隐藏的节点层,以及用于做出预测的输出层。

我们可以如下定义用于单变量时间序列预测的 MLP。

# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_steps))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

定义中重要的是输入的形状;这就是模型所期望的每个样本在时间步数方面的输入。

输入的时间步数是我们在准备数据集时选择的数字,作为split_sequence()函数的参数。

每个样本的输入维度在第一个隐藏层定义的input_dim参数中指定。从技术上讲,模型将每个时间步骤视为单独的特征而不是单独的时间步骤。

我们几乎总是有多个样本,因此,模型将期望训练数据的输入组件具有尺寸或形状:

[samples, features]

我们在上一节中的split_sequence()函数输出X,形状 [样本,特征] 准备用于建模。

使用随机梯度下降的有效 Adam 版本拟合该模型,并使用均方误差或'mse',损失函数进行优化。

定义模型后,我们可以将其放在训练数据集上。

# fit model
model.fit(X, y, epochs=2000, verbose=0)

在模型拟合后,我们可以使用它来做出预测。

我们可以通过提供输入来预测序列中的下一个值:

[70, 80, 90]

并期望模型预测如下:

[100]

模型期望输入形状为 [样本,特征] 为二维,因此,我们必须在做出预测之前重新整形单个输入样本,例如,对于 1 个样本,形状为[1,3]和 3 个时间步骤用作输入功能。

# demonstrate prediction
x_input = array([70, 80, 90])
x_input = x_input.reshape((1, n_steps))
yhat = model.predict(x_input, verbose=0)

我们可以将所有这些结合在一起并演示如何为单变量时间序列预测开发 MLP 并进行单一预测。

# univariate mlp example
from numpy import array
from keras.models import Sequential
from keras.layers import Dense

# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
	X, y = list(), list()
	for i in range(len(sequence)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the sequence
		if end_ix > len(sequence)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
# choose a number of time steps
n_steps = 3
# split into samples
X, y = split_sequence(raw_seq, n_steps)
# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_steps))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([70, 80, 90])
x_input = x_input.reshape((1, n_steps))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行该示例准备数据,拟合模型并做出预测。

鉴于算法的随机性,您的结果可能会有所不同;尝试运行几次这个例子。

我们可以看到模型预测序列中的下一个值。

[[100.0109]]

多变量 MLP 模型

多变量时间序列数据是指每个时间步长有多个观察值的数据。

对于多变量时间序列数据,我们可能需要两种主要模型;他们是:

  1. 多输入系列。
  2. 多个并联系列。

让我们依次看看每一个。

多输入系列

问题可能有两个或更多并行输入时间序列和输出时间序列,这取决于输入时间序列。

输入时间序列是平行的,因为每个系列在同一时间步骤都有观察。

我们可以通过两个并行输入时间序列的简单示例来演示这一点,其中输出序列是输入序列的简单添加。

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])

我们可以将这三个数据数组重新整形为单个数据集,其中每一行都是一个时间步,每列是一个单独的时间序列。这是将并行时间序列存储在 CSV 文件中的标准方法。

# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))

下面列出了完整的示例。

# multivariate data preparation
from numpy import array
from numpy import hstack
# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
print(dataset)

运行该示例将打印数据集,每个时间步长为一行,两个输入和一个输出并行时间序列分别为一列。

[[ 10  15  25]
 [ 20  25  45]
 [ 30  35  65]
 [ 40  45  85]
 [ 50  55 105]
 [ 60  65 125]
 [ 70  75 145]
 [ 80  85 165]
 [ 90  95 185]]

与单变量时间序列一样,我们必须将这些数据组织成具有输入和输出样本的样本。

我们需要将数据分成样本,保持两个输入序列的观察顺序。

如果我们选择三个输入时间步长,那么第一个样本将如下所示:

输入:

10, 15
20, 25
30, 35

输出:

65

也就是说,每个并行系列的前三个时间步长被提供作为模型的输入,并且模型将其与第三时间步骤的输出系列中的值相关联,在这种情况下为 65。

我们可以看到,在将时间序列转换为输入/输出样本以训练模型时,我们将不得不从输出时间序列中丢弃一些值,其中我们在先前时间步骤中没有输入时间序列中的值。反过来,选择输入时间步数的大小将对使用多少训练数据产生重要影响。

我们可以定义一个名为split_sequences()的函数,该函数将采用数据集,因为我们已经为时间步长和行定义了并行序列和返回输入/输出样本的列。

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

我们可以使用每个输入时间序列的三个时间步长作为输入在我们的数据集上测试此函数。

下面列出了完整的示例。

# multivariate data preparation
from numpy import array
from numpy import hstack

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
print(X.shape, y.shape)
# summarize the data
for i in range(len(X)):
	print(X[i], y[i])

首先运行该示例将打印 X 和 y 组件的形状。

我们可以看到 X 组件具有三维结构。

第一个维度是样本数,在本例中为 7.第二个维度是每个样本的时间步数,在这种情况下为 3,即为函数指定的值。最后,最后一个维度指定并行时间序列的数量或变量的数量,在这种情况下,两个并行序列为 2。

然后我们可以看到每个样本的输入和输出都被打印出来,显示了两个输入序列中每个样本的三个时间步长以及每个样本的相关输出。

(7, 3, 2) (7,)

[[10 15]
 [20 25]
 [30 35]] 65
[[20 25]
 [30 35]
 [40 45]] 85
[[30 35]
 [40 45]
 [50 55]] 105
[[40 45]
 [50 55]
 [60 65]] 125
[[50 55]
 [60 65]
 [70 75]] 145
[[60 65]
 [70 75]
 [80 85]] 165
[[70 75]
 [80 85]
 [90 95]] 185

在我们可以在这些数据上拟合 MLP 之前,我们必须平整输入样本的形状。

MLP 要求每个样本的输入部分的形状是向量。使用多变量输入,我们将有多个向量,每个时间步长一个。

我们可以展平每个输入样本的时间结构,以便:

[[10 15]
[20 25]
[30 35]]

变为:

[10, 15, 20, 25, 30, 35]

首先,我们可以计算每个输入向量的长度,作为时间步数乘以要素数或时间序列数。然后我们可以使用此向量大小来重塑输入。

# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))

我们现在可以为多变量输入定义 MLP 模型,其中向量长度用于输入维度参数。

# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_input))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

在做出预测时,模型需要两个输入时间序列的三个时间步长。

我们可以预测输出系列中的下一个值,证明输入值:

80,	 85
90,	 95
100, 105

具有 3 个时间步长和 2 个变量的 1 个样本的形状将是[1,3,2]。我们必须再次将其重塑为 1 个样本,其中包含 6 个元素的向量或[1,6]

我们希望序列中的下一个值为 100 + 105 或 205。

# demonstrate prediction
x_input = array([[80, 85], [90, 95], [100, 105]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)

下面列出了完整的示例。

# multivariate mlp example
from numpy import array
from numpy import hstack
from keras.models import Sequential
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))
# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_input))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([[80, 85], [90, 95], [100, 105]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行该示例准备数据,拟合模型并做出预测。

[[205.04436]]

还有另一种更精细的方法来模拟问题。

每个输入序列可以由单独的 MLP 处理,并且可以在对输出序列做出预测之前组合这些子模型中的每一个的输出。

我们可以将其称为多头输入 MLP 模型。根据正在建模的问题的具体情况,它可以提供更大的灵活性或更好的表现。

可以使用 Keras 功能 API 在 Keras 中定义此类型的模型。

首先,我们可以将第一个输入模型定义为 MLP,其输入层需要具有n_steps特征的向量。

# first input model
visible1 = Input(shape=(n_steps,))
dense1 = Dense(100, activation='relu')(visible1)

我们可以以相同的方式定义第二个输入子模型。

# second input model
visible2 = Input(shape=(n_steps,))
dense2 = Dense(100, activation='relu')(visible2)

既然已经定义了两个输入子模型,我们可以将每个模型的输出合并为一个长向量,可以在对输出序列做出预测之前对其进行解释。

# merge input models
merge = concatenate([dense1, dense2])
output = Dense(1)(merge)

然后我们可以将输入和输出联系在一起。

model = Model(inputs=[visible1, visible2], outputs=output)

下图提供了该模型外观的示意图,包括每层输入和输出的形状。

Plot of Multi-Headed MLP for Multivariate Time Series Forecasting

多元时间序列预测的多头 MLP 图

此模型要求输入作为两个元素的列表提供,其中列表中的每个元素包含一个子模型的数据。

为了实现这一点,我们可以将 3D 输入数据分成两个独立的输入数据数组:即从一个形状为[7,3,2]的数组到两个形状为[7,3]的 2D 数组

# separate input data
X1 = X[:, :, 0]
X2 = X[:, :, 1]

然后可以提供这些数据以适合模型。

# fit model
model.fit([X1, X2], y, epochs=2000, verbose=0)

类似地,我们必须在进行单个一步预测时将单个样本的数据准备为两个单独的二维数组。

x_input = array([[80, 85], [90, 95], [100, 105]])
x1 = x_input[:, 0].reshape((1, n_steps))
x2 = x_input[:, 1].reshape((1, n_steps))

我们可以将所有这些结合在一起;下面列出了完整的示例。

# multivariate mlp example
from numpy import array
from numpy import hstack
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers.merge import concatenate

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
# separate input data
X1 = X[:, :, 0]
X2 = X[:, :, 1]
# first input model
visible1 = Input(shape=(n_steps,))
dense1 = Dense(100, activation='relu')(visible1)
# second input model
visible2 = Input(shape=(n_steps,))
dense2 = Dense(100, activation='relu')(visible2)
# merge input models
merge = concatenate([dense1, dense2])
output = Dense(1)(merge)
model = Model(inputs=[visible1, visible2], outputs=output)
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit([X1, X2], y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([[80, 85], [90, 95], [100, 105]])
x1 = x_input[:, 0].reshape((1, n_steps))
x2 = x_input[:, 1].reshape((1, n_steps))
yhat = model.predict([x1, x2], verbose=0)
print(yhat)

运行该示例准备数据,拟合模型并做出预测。

[[206.05022]]

多个并联系列

另一个时间序列问题是存在多个并行时间序列并且必须为每个时间序列预测值的情况。

例如,给定上一节的数据:

[[ 10  15  25]
 [ 20  25  45]
 [ 30  35  65]
 [ 40  45  85]
 [ 50  55 105]
 [ 60  65 125]
 [ 70  75 145]
 [ 80  85 165]
 [ 90  95 185]]

我们可能想要预测下一个时间步的三个时间序列中的每一个的值。

这可以称为多变量预测。

同样,必须将数据分成输入/输出样本以训练模型。

该数据集的第一个示例是:

输入:

10, 15, 25
20, 25, 45
30, 35, 65

输出:

40, 45, 85

下面的split_sequences()函数将分割多个并行时间序列,其中时间步长为行,每列一个系列为所需的输入/输出形状。

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

我们可以在人为的问题上证明这一点;下面列出了完整的示例。

# multivariate output data prep
from numpy import array
from numpy import hstack

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
print(X.shape, y.shape)
# summarize the data
for i in range(len(X)):
	print(X[i], y[i])

首先运行该实例打印制备的Xy组分的形状。

X的形状是三维的,包括样品的数量(6),每个样品选择的时间步数(3),以及平行时间序列或特征的数量(3)。

y的形状是二维的,正如我们对样本数(6)和每个样本预测的时间变量数(3)所预期的那样。

然后,打印每个样本,显示每个样本的输入和输出分量。

(6, 3, 3) (6, 3)

[[10 15 25]
 [20 25 45]
 [30 35 65]] [40 45 85]
[[20 25 45]
 [30 35 65]
 [40 45 85]] [ 50  55 105]
[[ 30  35  65]
 [ 40  45  85]
 [ 50  55 105]] [ 60  65 125]
[[ 40  45  85]
 [ 50  55 105]
 [ 60  65 125]] [ 70  75 145]
[[ 50  55 105]
 [ 60  65 125]
 [ 70  75 145]] [ 80  85 165]
[[ 60  65 125]
 [ 70  75 145]
 [ 80  85 165]] [ 90  95 185]

我们现在准备在这些数据上安装 MLP 模型。

与之前的多变量输入情况一样,我们必须将输入数据样本的三维结构展平为[_ 样本,特征 _]的二维结构,其中滞后观察被模型视为特征。

# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))

模型输出将是一个向量,三个不同时间序列中的每一个都有一个元素。

n_output = y.shape[1]

我们现在可以定义我们的模型,使用输入层的展平向量长度和做出预测时的向量长度作为向量长度。

# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_input))
model.add(Dense(n_output))
model.compile(optimizer='adam', loss='mse')

我们可以通过为每个系列提供三个时间步长的输入来预测三个并行系列中的每一个的下一个值。

70, 75, 145
80, 85, 165
90, 95, 185

用于进行单个预测的输入的形状必须是 1 个样本,3 个时间步长和 3 个特征,或者[1,3,3]。同样,我们可以将其展平为[1,6]以满足模型的期望。

我们希望向量输出为:

[100, 105, 205]
# demonstrate prediction
x_input = array([[70,75,145], [80,85,165], [90,95,185]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)

我们可以将所有这些结合在一起并演示下面的多变量输出时间序列预测的 MLP。

# multivariate output mlp example
from numpy import array
from numpy import hstack
from keras.models import Sequential
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))
n_output = y.shape[1]
# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_input))
model.add(Dense(n_output))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([[70,75,145], [80,85,165], [90,95,185]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行该示例准备数据,拟合模型并做出预测。

[[100.95039 107.541306 206.81033 ]]

与多输入系列一样,还有另一种更精细的方法来模拟问题。

每个输出系列都可以由单独的输出 MLP 模型处理。

我们可以将其称为多输出 MLP 模型。根据正在建模的问题的具体情况,它可以提供更大的灵活性或更好的表现。

可以使用 Keras 功能 API 在 Keras 中定义此类型的模型。

首先,我们可以将输入模型定义为 MLP,其输入层需要平坦的特征向量。

# define model
visible = Input(shape=(n_input,))
dense = Dense(100, activation='relu')(visible)

然后,我们可以为我们希望预测的三个系列中的每一个定义一个输出层,其中每个输出子模型将预测单个时间步长。

# define output 1
output1 = Dense(1)(dense)
# define output 2
output2 = Dense(1)(dense)
# define output 2
output3 = Dense(1)(dense)

然后,我们可以将输入和输出层组合到一个模型中。

# tie together
model = Model(inputs=visible, outputs=[output1, output2, output3])
model.compile(optimizer='adam', loss='mse')

为了使模型架构清晰,下面的示意图清楚地显示了模型的三个独立输出层以及每个层的输入和输出形状。

Plot of Multi-Output MLP for Multivariate Time Series Forecasting

多元时间序列预测的多输出 MLP 图

在训练模型时,每个样本需要三个独立的输出数组。

我们可以通过将具有形状[7,3]的输出训练数据转换为具有形状[7,1]的三个数组来实现这一点。

# separate output
y1 = y[:, 0].reshape((y.shape[0], 1))
y2 = y[:, 1].reshape((y.shape[0], 1))
y3 = y[:, 2].reshape((y.shape[0], 1))

可以在训练期间将这些数组提供给模型。

# fit model
model.fit(X, [y1,y2,y3], epochs=2000, verbose=0)

将所有这些结合在一起,下面列出了完整的示例。

# multivariate output mlp example
from numpy import array
from numpy import hstack
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps
		# check if we are beyond the dataset
		if end_ix > len(sequences)-1:
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))
# separate output
y1 = y[:, 0].reshape((y.shape[0], 1))
y2 = y[:, 1].reshape((y.shape[0], 1))
y3 = y[:, 2].reshape((y.shape[0], 1))
# define model
visible = Input(shape=(n_input,))
dense = Dense(100, activation='relu')(visible)
# define output 1
output1 = Dense(1)(dense)
# define output 2
output2 = Dense(1)(dense)
# define output 2
output3 = Dense(1)(dense)
# tie together
model = Model(inputs=visible, outputs=[output1, output2, output3])
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, [y1,y2,y3], epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([[70,75,145], [80,85,165], [90,95,185]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行该示例准备数据,拟合模型并做出预测。

[array([[100.86121]], dtype=float32),
array([[105.14738]], dtype=float32),
array([[205.97507]], dtype=float32)]

多步 MLP 模型

实际上,MLP 模型在预测表示不同输出变量的向量输出(如前例中所示)或表示一个变量的多个时间步长的向量输出方面几乎没有差别。

然而,训练数据的编制方式存在细微而重要的差异。在本节中,我们将演示使用向量模型开发多步预测模型的情况。

在我们查看模型的细节之前,让我们首先看一下多步预测的数据准备。

数据准备

与一步预测一样,用于多步时间序列预测的时间序列必须分为带有输入和输出组件的样本。

输入和输出组件都将包含多个时间步长,并且可以具有或不具有相同数量的步骤。

例如,给定单变量时间序列:

[10, 20, 30, 40, 50, 60, 70, 80, 90]

我们可以使用最后三个时间步作为输入并预测接下来的两个时间步。

第一个样本如下:

输入:

[10, 20, 30]

输出:

[40, 50]

下面的split_sequence()函数实现了这种行为,并将给定的单变量时间序列分割为具有指定数量的输入和输出时间步长的样本。

# split a univariate sequence into samples
def split_sequence(sequence, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequence)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out
		# check if we are beyond the sequence
		if out_end_ix > len(sequence):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

我们可以在小型设计数据集上演示此功能。

下面列出了完整的示例。

# multi-step data preparation
from numpy import array

# split a univariate sequence into samples
def split_sequence(sequence, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequence)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out
		# check if we are beyond the sequence
		if out_end_ix > len(sequence):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
# choose a number of time steps
n_steps_in, n_steps_out = 3, 2
# split into samples
X, y = split_sequence(raw_seq, n_steps_in, n_steps_out)
# summarize the data
for i in range(len(X)):
	print(X[i], y[i])

运行该示例将单变量系列拆分为输入和输出时间步骤,并打印每个系列的输入和输出组件。

[10 20 30] [40 50]
[20 30 40] [50 60]
[30 40 50] [60 70]
[40 50 60] [70 80]
[50 60 70] [80 90]

既然我们知道如何为多步预测准备数据,那么让我们看一下可以学习这种映射的 MLP 模型。

向量输出模型

MLP 可以直接输出向量,可以解释为多步预测。

在前一节中看到这种方法是每个输出时间序列的一个时间步骤被预测为向量。

通过n_steps_inn_steps_out变量中指定的输入和输出步数,我们可以定义一个多步骤时间序列预测模型。

# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_steps_in))
model.add(Dense(n_steps_out))
model.compile(optimizer='adam', loss='mse')

该模型可以对单个样本做出预测。我们可以通过提供输入来预测数据集末尾之后的下两个步骤:

[70, 80, 90]

我们希望预测的输出为:

[100, 110]

正如模型所预期的那样,做出预测时输入数据的单个样本的形状对于输入和单个特征的 1 个样本和 3 个时间步长(特征)必须是[1,3]。

# demonstrate prediction
x_input = array([70, 80, 90])
x_input = x_input.reshape((1, n_steps_in))
yhat = model.predict(x_input, verbose=0)

将所有这些结合在一起,下面列出了具有单变量时间序列的多步骤预测的 MLP。

# univariate multi-step vector-output mlp example
from numpy import array
from keras.models import Sequential
from keras.layers import Dense

# split a univariate sequence into samples
def split_sequence(sequence, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequence)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out
		# check if we are beyond the sequence
		if out_end_ix > len(sequence):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
# choose a number of time steps
n_steps_in, n_steps_out = 3, 2
# split into samples
X, y = split_sequence(raw_seq, n_steps_in, n_steps_out)
# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_steps_in))
model.add(Dense(n_steps_out))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([70, 80, 90])
x_input = x_input.reshape((1, n_steps_in))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行示例预测并打印序列中的后两个时间步骤。

[[102.572365 113.88405 ]]

多变量多步 MLP 模型

在前面的部分中,我们研究了单变量,多变量和多步骤时间序列预测。

可以混合和匹配到目前为止针对不同问题呈现的不同类型的 MLP 模型。这也适用于涉及多变量和多步预测的时间序列预测问题,但它可能更具挑战性,特别是在准备数据和定义模型的输入和输出的形状时。

在本节中,我们将以多变量多步骤时间序列预测的数据准备和建模的简短示例作为模板来缓解这一挑战,具体来说:

  1. 多输入多步输出。
  2. 多个并行输入和多步输出。

也许最大的绊脚石是准备数据,所以这是我们关注的重点。

多输入多步输出

存在多变量时间序列预测问题,其中输出序列是分开的但取决于输入时间序列,并且输出序列需要多个时间步长。

例如,考虑前一部分的多变量时间序列:

[[ 10  15  25]
 [ 20  25  45]
 [ 30  35  65]
 [ 40  45  85]
 [ 50  55 105]
 [ 60  65 125]
 [ 70  75 145]
 [ 80  85 165]
 [ 90  95 185]]

我们可以使用两个输入时间序列中的每一个的三个先前时间步骤来预测输出时间序列的两个时间步长。

输入:

10, 15
20, 25
30, 35

输出:

65
85

下面的split_sequences()函数实现了这种行为。

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out-1
		# check if we are beyond the dataset
		if out_end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

我们可以在我们设计的数据集上证明这一点。下面列出了完整的示例。

# multivariate multi-step data preparation
from numpy import array
from numpy import hstack

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out-1
		# check if we are beyond the dataset
		if out_end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps_in, n_steps_out = 3, 2
# convert into input/output
X, y = split_sequences(dataset, n_steps_in, n_steps_out)
print(X.shape, y.shape)
# summarize the data
for i in range(len(X)):
	print(X[i], y[i])

首先运行该示例打印准备好的训练数据的形状。

我们可以看到样本的输入部分的形状是三维的,由六个样本组成,具有三个时间步长和两个输入时间序列的两个变量。

样本的输出部分对于六个样本是二维的,并且每个样本的两个时间步长是预测的。

然后打印制备的样品以确认数据是按照我们指定的方式制备的。

(6, 3, 2) (6, 2)

[[10 15]
 [20 25]
 [30 35]] [65 85]
[[20 25]
 [30 35]
 [40 45]] [ 85 105]
[[30 35]
 [40 45]
 [50 55]] [105 125]
[[40 45]
 [50 55]
 [60 65]] [125 145]
[[50 55]
 [60 65]
 [70 75]] [145 165]
[[60 65]
 [70 75]
 [80 85]] [165 185]

我们现在可以使用向量输出开发用于多步预测的 MLP 模型。

下面列出了完整的示例。

# multivariate multi-step mlp example
from numpy import array
from numpy import hstack
from keras.models import Sequential
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out-1
		# check if we are beyond the dataset
		if out_end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps_in, n_steps_out = 3, 2
# convert into input/output
X, y = split_sequences(dataset, n_steps_in, n_steps_out)
# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))
# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_input))
model.add(Dense(n_steps_out))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([[70, 75], [80, 85], [90, 95]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行该示例适合模型并预测输出序列的下两个时间步骤超出数据集。

我们希望接下来的两个步骤是[185,205]。

这是一个具有挑战性的问题框架,数据非常少,模型的任意配置版本也很接近。

[[186.53822 208.41725]]

多个并行输入和多步输出

并行时间序列的问题可能需要预测每个时间序列的多个时间步长。

例如,考虑前一部分的多变量时间序列:

[[ 10  15  25]
 [ 20  25  45]
 [ 30  35  65]
 [ 40  45  85]
 [ 50  55 105]
 [ 60  65 125]
 [ 70  75 145]
 [ 80  85 165]
 [ 90  95 185]]

我们可以使用三个时间序列中的每一个的最后三个步骤作为模型的输入,并预测三个时间序列中的每一个的下一个时间步长作为输出。

训练数据集中的第一个样本如下。

输入:

10, 15, 25
20, 25, 45
30, 35, 65

输出:

40, 45, 85
50, 55, 105

下面的split_sequences()函数实现了这种行为。

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out
		# check if we are beyond the dataset
		if out_end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

我们可以在小型设计数据集上演示此功能。

下面列出了完整的示例。

# multivariate multi-step data preparation
from numpy import array
from numpy import hstack

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out
		# check if we are beyond the dataset
		if out_end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps_in, n_steps_out = 3, 2
# convert into input/output
X, y = split_sequences(dataset, n_steps_in, n_steps_out)
print(X.shape, y.shape)
# summarize the data
for i in range(len(X)):
	print(X[i], y[i])

首先运行该示例打印准备好的训练数据集的形状。

我们可以看到数据集的输入(X)和输出(Y)元素分别对于样本数,时间步长和变量或并行时间序列是三维的。 。

然后将每个系列的输入和输出元素并排打印,以便我们可以确认数据是按照我们的预期准备的。

(5, 3, 3) (5, 2, 3)

[[10 15 25]
 [20 25 45]
 [30 35 65]] [[ 40  45  85]
 [ 50  55 105]]
[[20 25 45]
 [30 35 65]
 [40 45 85]] [[ 50  55 105]
 [ 60  65 125]]
[[ 30  35  65]
 [ 40  45  85]
 [ 50  55 105]] [[ 60  65 125]
 [ 70  75 145]]
[[ 40  45  85]
 [ 50  55 105]
 [ 60  65 125]] [[ 70  75 145]
 [ 80  85 165]]
[[ 50  55 105]
 [ 60  65 125]
 [ 70  75 145]] [[ 80  85 165]
 [ 90  95 185]]

我们现在可以开发 MLP 模型来进行多变量多步预测。

除了展平输入数据的形状之外,正如我们在先前的例子中所做的那样,我们还必须平整输出数据的三维结构。这是因为 MLP 模型只能采用向量输入和输出。

# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))
# flatten output
n_output = y.shape[1] * y.shape[2]
y = y.reshape((y.shape[0], n_output))

下面列出了完整的示例。

# multivariate multi-step mlp example
from numpy import array
from numpy import hstack
from keras.models import Sequential
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
	X, y = list(), list()
	for i in range(len(sequences)):
		# find the end of this pattern
		end_ix = i + n_steps_in
		out_end_ix = end_ix + n_steps_out
		# check if we are beyond the dataset
		if out_end_ix > len(sequences):
			break
		# gather input and output parts of the pattern
		seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :]
		X.append(seq_x)
		y.append(seq_y)
	return array(X), array(y)

# define input sequence
in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90])
in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95])
out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))])
# convert to [rows, columns] structure
in_seq1 = in_seq1.reshape((len(in_seq1), 1))
in_seq2 = in_seq2.reshape((len(in_seq2), 1))
out_seq = out_seq.reshape((len(out_seq), 1))
# horizontally stack columns
dataset = hstack((in_seq1, in_seq2, out_seq))
# choose a number of time steps
n_steps_in, n_steps_out = 3, 2
# convert into input/output
X, y = split_sequences(dataset, n_steps_in, n_steps_out)
# flatten input
n_input = X.shape[1] * X.shape[2]
X = X.reshape((X.shape[0], n_input))
# flatten output
n_output = y.shape[1] * y.shape[2]
y = y.reshape((y.shape[0], n_output))
# define model
model = Sequential()
model.add(Dense(100, activation='relu', input_dim=n_input))
model.add(Dense(n_output))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=2000, verbose=0)
# demonstrate prediction
x_input = array([[60, 65, 125], [70, 75, 145], [80, 85, 165]])
x_input = x_input.reshape((1, n_input))
yhat = model.predict(x_input, verbose=0)
print(yhat)

运行该示例适合模型并预测超出数据集末尾的下两个时间步的三个时间步中的每一个的值。

我们希望这些系列和时间步骤的值如下:

90, 95, 185
100, 105, 205

我们可以看到模型预测合理地接近预期值。

[[ 91.28376 96.567 188.37575 100.54482 107.9219 208.108 ]

摘要

在本教程中,您了解了如何针对一系列标准时间序列预测问题开发一套多层感知机或 MLP 模型。

具体来说,你学到了:

  • 如何开发单变量时间序列预测的 MLP 模型。
  • 如何开发多元时间序列预测的 MLP 模型。
  • 如何开发 MLP 模型进行多步时间序列预测。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。