深度学习神经网络能够自动学习和从原始数据中提取特征。
神经网络的这一特性可用于时间序列预测问题,其中模型可以直接在原始观测数据上开发,而无需直接使用归一化和标准化来缩放数据,也无需通过差分使数据平稳。
令人印象深刻的是,与朴素模型和经过调整的 SARIMA 模型相比,简单的深度学习神经网络模型能够在具有趋势和季节性成分的单变量时间序列预测问题上,在未经预处理的情况下做出熟练的预测。
在本教程中,您将学习如何开发一套用于单变量时间序列预测的深度学习模型。
完成本教程后,您将了解:
- 如何使用滚动验证(walk-forward validation)开发稳健的测试工具,以评估神经网络模型的性能。
- 如何开发和评估用于时间序列预测的简单多层感知器和卷积神经网络。
- 如何开发和评估用于时间序列预测的 LSTM、CNN-LSTM 和 ConvLSTM 神经网络模型。
通过我的新书《时间序列深度学习》**开启您的项目**,包括所有示例的*逐步教程*和*Python 源代码*文件。
让我们开始吧。
- 2019 年 4 月更新:更新了数据集链接。

如何开发单变量时间序列预测的深度学习模型
图片由 Nathaniel McQueen 拍摄,保留部分权利。
教程概述
本教程分为五个部分;它们是:
- 问题描述
- 模型评估测试工具
- 多层感知器模型
- 卷积神经网络模型
- 循环神经网络模型
问题描述
“月度汽车销量”数据集总结了1960年至1968年间加拿大魁北克省的月度汽车销量。
直接从这里下载数据集
将文件保存为“_monthly-car-sales.csv_”在当前工作目录中。
我们可以使用 *read_csv()* 函数将此数据集加载为 Pandas 序列。
1 2 |
# 加载 series = read_csv('monthly-car-sales.csv', header=0, index_col=0) |
加载后,我们可以总结数据集的形状,以确定观测值的数量。
1 2 |
# 总结形状 print(series.shape) |
然后我们可以创建序列的折线图,以了解序列的结构。
1 2 3 |
# 绘图 pyplot.plot(series) pyplot.show() |
我们可以将所有这些结合起来;完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 |
# 加载并绘制数据集 from pandas import read_csv from matplotlib import pyplot # 加载 series = read_csv('monthly-car-sales.csv', header=0, index_col=0) # 总结形状 print(series.shape) # 绘图 pyplot.plot(series) pyplot.show() |
运行示例首先打印数据集的形状。
1 |
(108, 1) |
该数据集是月度的,有九年,即108个观测值。在我们的测试中,我们将使用最后一年,即12个观测值,作为测试集。
创建了一个折线图。数据集具有明显的趋势和季节性成分。季节性成分的周期可能是六个月或十二个月。

月度汽车销量的折线图
从之前的实验中我们知道,一个朴素模型可以通过取预测月份前三年观测值的中位数来达到1841.155的均方根误差(RMSE);例如:
1 |
yhat = median(-12, -24, -36) |
其中负索引指的是序列中相对于预测月份历史数据末尾的观测值。
从先前的实验中我们知道,SARIMA 模型在 SARIMA(0, 0, 0),(1, 1, 0),12 的配置下可以达到 1551.842 的 RMSE,其中未指定趋势元素,并计算了周期为 12 的季节性差分,并使用了单季节的 AR 模型。
朴素模型的性能为被认为是熟练模型的模型提供了下限。任何在最近12个月内预测性能低于1841.155的模型都具有技能。
SARIMA模型的性能提供了衡量问题上好模型的标准。任何在最近12个月内预测性能低于1551.842的模型都应优于SARIMA模型。
现在我们已经定义了问题和模型技能的期望,接下来可以着手定义测试工具。
时间序列深度学习需要帮助吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
模型评估测试工具
在本节中,我们将开发一个测试工具,用于开发和评估不同类型的神经网络模型进行单变量时间序列预测。
本节分为以下几个部分
- 训练-测试集分割
- 序列作为监督学习
- 逐时验证
- 重复评估
- 总结绩效
- 实例演示
训练-测试集分割
第一步是将加载的序列分割成训练集和测试集。
我们将使用前八年(96个观测值)进行训练,最后12个观测值作为测试集。
下面的`train_test_split()`函数将分割序列,将原始观测值和用于测试集的观测值数量作为参数。
1 2 3 |
# 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] |
序列作为监督学习
接下来,我们需要能够将单变量观测序列构建为监督学习问题,以便训练神经网络模型。
将序列构架为监督学习意味着数据需要被分成多个示例,模型可以从中学习并进行泛化。
每个样本必须同时包含输入和输出部分。
输入部分将是之前的一些观测值,例如三年或 36 个时间步长。
输出部分将是下个月的总销售额,因为我们有兴趣开发一个模型来进行一步预测。
我们可以使用 Pandas DataFrame 上的 shift() 函数来实现这一点。它允许我们将一列向下(时间向前)或向后(时间向后)移动。我们可以将序列作为数据列,然后创建该列的多个副本,向前或向后移动,以创建我们所需的包含输入和输出元素的样本。
当序列向下移动时,会引入 NaN 值,因为我们没有超出序列开头的值。
例如,定义为一列的序列
1 2 3 4 5 |
(t) 1 2 3 4 |
可以提前作为一列进行移位并插入
1 2 3 4 5 6 |
(t-1), (t) 空值, 1 1, 2 2, 3 3, 4 4 空值 |
我们可以看到,在第二行,值 1 作为先前时间步的观测值提供给输入,而 2 是序列中的下一个值,可以被模型预测或学习以在 1 作为输入时进行预测。
带有`NaN`值的行可以被删除。
下面的 `series_to_supervised()` 函数实现了这种行为,允许您为每个样本指定输入中使用的滞后观测值数量以及输出中使用的数量。它还将删除包含 `NaN` 值的行,因为这些行不能用于训练或测试模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values |
逐时验证
时间序列预测模型可以通过滚动验证在测试集上进行评估。
滚动验证是一种方法,模型在测试数据集中一次对每个观测值进行预测。在对测试数据集中的某个时间步长进行预测后,该预测的真实观测值将添加到测试数据集中,并提供给模型。
对于简单的模型,可以在进行后续预测之前,用观测值重新拟合模型。对于更复杂的模型,如神经网络,鉴于计算成本更高,则不重新拟合。
然而,时间步长的真实观测值可以作为输入的一部分,用于对下一个时间步长进行预测。
首先,数据集被分割成训练集和测试集。我们将调用 `train_test_split()` 函数来执行此分割,并传入预先指定的用于测试数据的观测值数量。
模型将在给定配置的训练数据集上拟合一次。
我们将定义一个通用的 `model_fit()` 函数来执行此操作,该函数稍后可以填充我们可能感兴趣的特定神经网络类型。该函数接受训练数据集和模型配置,并返回已拟合的模型,准备好进行预测。
1 2 3 |
# 拟合模型 def model_fit(train, config): return None |
测试数据集的每个时间步长都被枚举。使用拟合模型进行预测。
同样,我们将定义一个名为`model_predict()`的通用函数,它接受拟合模型、历史数据和模型配置,并进行一次单步预测。
1 2 3 |
# 使用预训练模型进行预测 def model_predict(model, history, config): return 0.0 |
预测结果被添加到预测列表中,测试集中的真实观测值被添加到观测值列表中,该列表以训练数据集的所有观测值作为种子。该列表在滚动验证的每个步骤中建立,允许模型使用最新历史进行一步预测。
然后可以将所有预测与测试集中的真实值进行比较,并计算误差度量。
我们将计算预测值与真实值之间的均方根误差 (RMSE)。
RMSE的计算方法是预测值与实际值之间平方差的平均值的平方根。下面的`measure_rmse()`函数使用`mean_squared_error()` scikit-learn函数首先计算均方误差(MSE),然后再计算平方根来实现此功能。
1 2 3 |
# 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) |
下面列出了将所有这些联系起来的完整 `walk_forward_validation()` 函数。
它接受数据集、用作测试集的观测值数量以及模型的配置,并返回模型在测试集上的性能的 RMSE。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error |
重复评估
神经网络模型是随机的。
这意味着,在相同的模型配置和相同的训练数据集下,每次训练模型都会产生一组不同的内部权重,从而导致不同的性能。
这是一个优点,它允许模型具有适应性,并为复杂问题找到高性能配置。
在评估模型性能和选择最终用于预测的模型时,这也是一个问题。
为了解决模型评估问题,我们将通过滚动验证多次评估模型配置,并将误差报告为每次评估的平均误差。
对于大型神经网络,这并非总是可行,可能只适用于可在几分钟或几小时内拟合的小型网络。
下面的 `repeat_evaluate()` 函数实现了这一点,并允许将重复次数指定为可选参数,默认为 30 次,并返回模型性能分数列表:在本例中是 RMSE 值。
1 2 3 4 5 |
# 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] return scores |
总结绩效
最后,我们需要从多次重复中总结模型的性能。
我们将首先使用统计摘要,特别是均值和标准差来总结性能。
我们还将使用箱线图绘制模型性能分数的分布,以帮助了解性能的范围。
下面的 `summarize_scores()` 函数实现了此功能,它接收被评估模型的名称和每次重复评估的分数列表,打印摘要并显示图表。
1 2 3 4 5 6 7 8 |
# 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() |
实例演示
现在我们已经定义了测试工具的各个元素,我们可以将它们整合起来,并定义一个简单的持久化模型。
具体来说,我们将计算相对于预测时间的一组先前观测值的中位数。
我们不需要拟合模型,因此 `model_fit()` 函数将简单地返回 `None`。
1 2 3 |
# 拟合模型 def model_fit(train, config): return None |
我们将使用配置来定义相对于预测时间的先前观测值中的索引偏移量列表,这些偏移量将用作预测。例如,12 将使用 12 个月前(-12)相对于预测时间的观测值。
1 2 |
# 定义配置 config = [12, 24, 36] |
`model_predict()`函数可以实现为使用此配置收集观测值,然后返回这些观测值的中位数。
1 2 3 4 5 6 |
# 使用预训练模型进行预测 def model_predict(model, history, config): values = list() for offset in config: values.append(history[-offset]) return median(values) |
下面列出了使用带有简单持久性模型框架的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# 持久性 from math import sqrt from numpy import mean from numpy import std from pandas import DataFrame 从 pandas 导入 concat from pandas import read_csv from sklearn.metrics import mean_squared_error from matplotlib import pyplot # 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] # 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values # 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) # 差异数据集 def difference(data, interval): return [data[i] - data[i -interval] for i in range(interval, len(data))] # 拟合模型 def model_fit(train, config): return None # 使用预训练模型进行预测 def model_predict(model, history, config): values = list() for offset in config: values.append(history[-offset]) return median(values) # 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error # 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] 返回 分数 # 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() series = read_csv('monthly-car-sales.csv', header=0, index_col=0) data = series.values # 数据分割 n_test = 12 # 定义配置 config = [12, 24, 36] # 网格搜索 scores = repeat_evaluate(data, config, n_test) # 总结得分 summarize_scores('persistence', scores) |
运行示例将打印使用滚动验证在最后12个月数据上评估的模型RMSE。
该模型被评估了 30 次,尽管由于模型没有随机元素,每次得分都相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
> 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 > 1841.156 持久性:1841.156 RMSE(+/- 0.000) |
我们可以看到模型的RMSE为1841,这提供了一个性能的下限,我们可以据此评估模型在问题上是否熟练。

汽车销售预测持久性RMSE的箱线图
现在我们有了一个健壮的测试工具,我们可以用它来评估一系列神经网络模型。
多层感知器模型
我们将评估的第一个网络是多层感知器,简称 MLP。
这是一个简单的前馈神经网络模型,在考虑更复杂的模型之前应该先对其进行评估。
MLP 可用于时间序列预测,通过获取多个先前时间步的观测值(称为滞后观测值),并将其用作输入特征,然后预测这些观测值的一个或多个时间步。
这正是上一节中 `series_to_supervised()` 函数提供的问题框架。
因此,训练数据集是一个样本列表,其中每个样本都包含预测时间之前几个月的观测值,预测结果是序列中的下个月。例如:
1 2 3 4 5 |
X, y 第一月,第二月,第三月,第四月 第二月,第三月,第四月,第五月 第三月,第四月,第五月,第六月 ... |
该模型将尝试对这些样本进行泛化,以便当提供超出模型已知范围的新样本时,它可以预测一些有用的东西;例如
1 2 |
X, y 第四月,第五月,第六月,??? |
我们将使用 Keras 深度学习库实现一个简单的 MLP。
该模型将有一个输入层,包含一些先前的观测值。这可以通过定义第一个隐藏层时使用 `input_dim` 参数来指定。模型将有一个包含一些节点的单个隐藏层,然后是一个单一的输出层。
我们将在隐藏层上使用修正线性激活函数,因为它表现良好。我们将在输出层上使用线性激活函数(默认),因为我们正在预测一个连续值。
网络的损失函数将是均方误差损失(MSE),我们将使用高效的Adam随机梯度下降变体来训练网络。
1 2 3 4 5 |
# 定义模型 model = Sequential() model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') |
模型将进行一定数量的训练周期(对训练数据的暴露次数),并且可以指定批次大小来定义每个周期内权重更新的频率。
下面列出了用于在训练数据集上拟合 MLP 模型的 `model_fit()` 函数。
函数期望配置是一个包含以下配置超参数的列表
- n_input:用作模型输入的滞后观测值数量。
- n_nodes:隐藏层中使用的节点数量。
- n_epochs:模型暴露于整个训练数据集的次数。
- n_batch:每个 epoch 内,权重更新后的样本数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 拟合模型 def model_fit(train, config): # 解包配置 n_input, n_nodes, n_epochs, n_batch = config # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] # 定义模型 model = Sequential() model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model |
使用已拟合的 MLP 模型进行预测与调用 `predict()` 函数并传入进行预测所需的一个样本的输入值一样简单。
1 |
yhat = model.predict(x_input, verbose=0) |
为了在已知数据范围之外进行预测,这需要将最后 n 个已知观测值作为一个数组,并将其用作输入。
`predict()` 函数期望在进行预测时提供一个或多个输入样本,因此提供单个样本需要数组的形状为 `[1, n_input]`,其中 `n_input` 是模型期望的输入时间步长数量。
同样,`predict()` 函数返回一个预测数组,每个输入样本一个。如果只有一个预测,则会有一个包含一个值的数组。
下面的 `model_predict()` 函数实现了这种行为,它将模型、先前的观测值和模型配置作为参数,形成一个输入样本并进行一步预测,然后返回该预测。
1 2 3 4 5 6 7 8 9 |
# 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_input, _, _, _ = config # 准备数据 x_input = array(history[-n_input:]).reshape(1, n_input) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] |
现在我们拥有一切所需,可以在月度汽车销售数据集上评估 MLP 模型。
对模型超参数进行了一次简单的网格搜索,并选择了下面的配置。这可能不是最佳配置,但这是找到的最好配置。
- n_input: 24 (例如 24 个月)
- 节点数: 500
- 纪元数: 100
- 批次大小: 100
此配置可以定义为列表
1 2 |
# 定义配置 config = [24, 500, 100, 100] |
请注意,当训练数据被构建为监督学习问题时,只有 72 个样本可用于训练模型。
使用 72 或更大的批次大小意味着模型正在使用批次梯度下降而不是小批量梯度下降进行训练。这通常用于小型数据集,意味着权重更新和梯度计算在每个 epoch 结束时执行,而不是在每个 epoch 内多次执行。
完整的代码示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# 评估mlp from math import sqrt from numpy import array from numpy import mean from numpy import std from pandas import DataFrame 从 pandas 导入 concat from pandas import read_csv from sklearn.metrics import mean_squared_error from keras.models import Sequential from keras.layers import Dense from matplotlib import pyplot # 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] # 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values # 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) # 拟合模型 def model_fit(train, config): # 解包配置 n_input, n_nodes, n_epochs, n_batch = config # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] # 定义模型 model = Sequential() model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model # 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_input, _, _, _ = config # 准备数据 x_input = array(history[-n_input:]).reshape(1, n_input) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] # 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error # 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] 返回 分数 # 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() series = read_csv('monthly-car-sales.csv', header=0, index_col=0) data = series.values # 数据分割 n_test = 12 # 定义配置 config = [24, 500, 100, 100] # 网格搜索 scores = repeat_evaluate(data, config, n_test) # 总结得分 summarize_scores('mlp', scores) |
运行示例将打印模型30次重复评估的RMSE。
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
运行结束时,报告了平均和标准差 RMSE,约为 1,526 辆汽车销售。
我们可以看到,平均而言,所选配置的性能优于朴素模型(1841.155)和 SARIMA 模型(1551.842)。
考虑到模型直接在原始数据上操作,没有进行缩放或使数据平稳,这令人印象深刻。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
> 1629.203 > 1642.219 > 1472.483 > 1662.055 > 1452.480 > 1465.535 > 1116.253 > 1682.667 > 1642.626 > 1700.183 > 1444.481 > 1673.217 > 1602.342 > 1655.895 > 1319.387 > 1591.972 > 1592.574 > 1361.607 > 1450.348 > 1314.529 > 1549.505 > 1569.750 > 1427.897 > 1478.926 > 1474.990 > 1458.993 > 1643.383 > 1457.925 > 1558.934 > 1708.278 MLP: 1526.688 RMSE (+/- 134.789) |
创建了一个RMSE分数的箱线图,以总结模型性能的分布。
这有助于理解分数的分布。我们可以看到,尽管模型的平均性能令人印象深刻,但分布范围很大。标准差略高于134个销售额,这意味着最差情况下的模型运行(误差与平均误差相差2或3个标准差)可能比朴素模型更差。
使用 MLP 模型的一个挑战在于如何利用更高的技能并最小化模型在多次运行中的方差。
这个问题普遍适用于神经网络。您可以使用许多策略,但也许最简单的方法是简单地在所有可用数据上训练多个最终模型,并在进行预测时将它们用于集成,例如,预测是 10 到 30 个模型的平均值。

多层感知器 RMSE 汽车销量预测的箱线图
卷积神经网络模型
卷积神经网络(CNN)是一种为二维图像数据开发的神经网络,尽管它们也可以用于一维数据,如文本序列和时间序列。
当在一维数据上操作时,CNN 会遍历一系列滞后观测值,并学习提取与预测相关的特征。
我们将定义一个具有两个卷积层的CNN,用于从输入序列中提取特征。每个卷积层都将具有可配置的过滤器数量和核大小,并使用修正线性激活函数。过滤器数量决定了加权输入被读取和投影的并行场的数量。核大小定义了网络沿输入序列读取时,每个快照内读取的时间步长数量。
1 2 |
model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, 1))) model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu')) |
在卷积层之后使用最大池化层,将加权输入特征提炼为最显著的特征,将输入大小减少1/4。池化后的输入被展平为一长向量,然后进行解释并用于进行一步预测。
1 2 3 |
model.add(MaxPooling1D(pool_size=2)) model.add(Flatten()) model.add(Dense(1)) |
CNN模型期望输入数据以多个样本的形式呈现,其中每个样本具有多个输入时间步,这与上一节中的MLP相同。
一个区别是 CNN 可以支持每个时间步的多个特征或观测类型,这些特征或观测类型被解释为图像的通道。我们每个时间步只有一个特征,因此输入数据所需的三维形状将是 `[n_samples, n_input, 1]`。
1 |
train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) |
下面列出了用于在训练数据集上拟合 CNN 模型的 `model_fit()` 函数。
模型接受以下五个配置参数列表
- n_input:用作模型输入的滞后观测值数量。
- n_filters:并行滤波器的数量。
- n_kernel:在输入序列每次读取中考虑的时间步长数量。
- n_epochs:模型暴露于整个训练数据集的次数。
- n_batch:每个 epoch 内,权重更新后的样本数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 拟合模型 def model_fit(train, config): # 解包配置 n_input, n_filters, n_kernel, n_epochs, n_batch = config # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) # 定义模型 model = Sequential() model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, 1))) model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu')) model.add(MaxPooling1D(pool_size=2)) model.add(Flatten()) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model |
使用已拟合的 CNN 模型进行预测与上一节中使用已拟合的 MLP 模型进行预测非常相似。
唯一的区别在于,我们需要指定在每个时间步观察到的特征数量,在本例中为 1。因此,当进行单步预测时,输入数组的形状必须为
1 |
[1, 输入数量, 1] |
下面的 `model_predict()` 函数实现了这种行为。
1 2 3 4 5 6 7 8 9 |
# 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_input, _, _, _, _ = config # 准备数据 x_input = array(history[-n_input:]).reshape((1, n_input, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] |
对模型超参数进行了一次简单的网格搜索,并选择了下面的配置。这并非最佳配置,但已是找到的最佳配置。
选定的配置如下
- n_input: 36 (例如 3 年或 3 * 12)
- 过滤器数量: 256
- 核大小: 3
- 纪元数: 100
- n_batch: 100 (即批量梯度下降)
这可以指定为列表,如下所示
1 2 |
# 定义配置 config = [36, 256, 3, 100, 100] |
将所有这些联系在一起,完整的示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# 评估CNN from math import sqrt from numpy import array from numpy import mean from numpy import std from pandas import DataFrame 从 pandas 导入 concat from pandas import read_csv from sklearn.metrics import mean_squared_error from keras.models import Sequential from keras.layers import Dense from keras.layers import Flatten from keras.layers.convolutional import Conv1D from keras.layers.convolutional import MaxPooling1D from matplotlib import pyplot # 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] # 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values # 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) # 拟合模型 def model_fit(train, config): # 解包配置 n_input, n_filters, n_kernel, n_epochs, n_batch = config # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) # 定义模型 model = Sequential() model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, 1))) model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu')) model.add(MaxPooling1D(pool_size=2)) model.add(Flatten()) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model # 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_input, _, _, _, _ = config # 准备数据 x_input = array(history[-n_input:]).reshape((1, n_input, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] # 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error # 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] 返回 分数 # 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() series = read_csv('monthly-car-sales.csv', header=0, index_col=0) data = series.values # 数据分割 n_test = 12 # 定义配置 config = [36, 256, 3, 100, 100] # 网格搜索 scores = repeat_evaluate(data, config, n_test) # 总结得分 summarize_scores('cnn', scores) |
运行示例首先打印模型每次重复评估的RMSE。
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
运行结束时,我们可以看到模型确实具有技能,平均 RMSE 达到 1,524.067,这优于朴素模型、SARIMA 模型,甚至上一节中的 MLP 模型。
考虑到模型直接在原始数据上操作,没有进行缩放或使数据平稳,这令人印象深刻。
分数的标准差很大,约为 57 销量,但仅为上一节 MLP 模型观察到的方差的三分之一。我们有一定信心,在最坏情况下(3 个标准差),模型的 RMSE 将保持低于(优于)朴素模型的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
> 1551.031 > 1495.743 > 1449.408 > 1526.017 > 1466.118 > 1566.535 > 1649.204 > 1455.782 > 1574.214 > 1541.790 > 1489.140 > 1506.035 > 1513.197 > 1530.714 > 1511.328 > 1471.518 > 1555.596 > 1552.026 > 1531.727 > 1472.978 > 1620.242 > 1424.153 > 1456.393 > 1581.114 > 1539.286 > 1489.795 > 1652.620 > 1537.349 > 1443.777 > 1567.179 CNN:1524.067 RMSE (+/- 57.148) |
创建了一个分数的箱线图,以帮助理解各次运行中误差的分布。
我们可以看到,误差的分布确实偏向于较大的误差值,正如我们所预期的那样,尽管图中上方的胡须(在这种情况下,不是异常值的最大误差)的 RMSE 仍然限制在 1,650 销量。

卷积神经网络 RMSE 汽车销量预测的箱线图
循环神经网络模型
循环神经网络(RNN)是一类神经网络,它利用网络在前一步的输出作为输入,试图自动学习序列数据。
长短期记忆(LSTM)网络是一种 RNN,其实现解决了在序列数据上训练 RNN 导致稳定模型的一般困难。它通过学习内部门的权重来实现这一点,这些内部门控制着每个节点内部的循环连接。
尽管是为序列数据开发的,但 LSTM 尚未证明在时间序列预测问题上有效,其中输出是近期观测值的函数,例如自回归型预测问题,如汽车销售数据集。
尽管如此,我们可以开发 LSTM 模型来解决自回归问题,并将其作为与其他神经网络模型进行比较的参考点。
在本节中,我们将探讨用于单变量时间序列预测的 LSTM 模型的三种变体;它们是:
- LSTM:原汁原味的 LSTM 网络。
- CNN-LSTM:一个学习输入特征的 CNN 网络和一个解释这些特征的 LSTM。
- ConvLSTM:CNN 和 LSTM 的结合,其中 LSTM 单元使用 CNN 的卷积过程读取输入数据。
LSTM
LSTM 神经网络可用于单变量时间序列预测。
作为一个 RNN,它将逐个时间步读取输入序列。LSTM 具有内部记忆,使其能够在读取给定输入序列的步骤时积累内部状态。
序列结束时,隐藏 LSTM 单元层中的每个节点都将输出一个值。这个值向量总结了 LSTM 从输入序列中学习或提取的内容。这可以由全连接层进行解释,然后进行最终预测。
1 2 3 4 5 6 |
# 定义模型 model = Sequential() model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, 1))) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') |
与 CNN 类似,LSTM 可以在每个时间步支持多个变量或特征。由于汽车销售数据集在每个时间步只有一个值,我们可以将其固定为 1,无论是在 `input_shape` 参数 `[n_input, 1]` 中定义网络输入时,还是在定义输入样本的形状时。
1 |
train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) |
与不逐个时间步读取序列数据的 MLP 和 CNN 不同,如果数据是平稳的,LSTM 的表现会更好。这意味着执行差分操作以消除趋势和季节性结构。
对于汽车销售数据集,我们可以通过进行季节性调整使数据平稳,即从每个观测值中减去一年前的值。
1 |
adjusted = value - value[-12] |
这可以对整个训练数据集系统地执行。这也意味着必须丢弃第一年的观测值,因为我们没有前一年的数据可供它们进行差分。
下面的 `difference()` 函数将使用给定的偏移量(称为差分阶数,例如 12 代表一年前的月份)对给定的数据集进行差分。
1 2 3 |
# 差异数据集 def difference(data, interval): return [data[i] - data[i -interval] for i in range(interval, len(data))] |
我们可以将差分阶数设置为模型的超参数,并且仅当提供的值不为零时才执行该操作。
下面提供了用于拟合 LSTM 模型的 `model_fit()` 函数。
模型期望一个包含五个模型超参数的列表;它们是:
- n_input:用作模型输入的滞后观测值数量。
- n_nodes:隐藏层中使用的 LSTM 单元数量。
- n_epochs:模型暴露于整个训练数据集的次数。
- n_batch:每个 epoch 内,权重更新后的样本数量。
- n_diff:差分阶数,如果未使用则为 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 拟合模型 def model_fit(train, config): # 解包配置 n_input, n_nodes, n_epochs, n_batch, n_diff = config # 准备数据 if n_diff > 0: train = difference(train, n_diff) data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) # 定义模型 model = Sequential() model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, 1))) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model |
使用 LSTM 模型进行预测与使用 CNN 模型进行预测相同。
单个输入必须具有样本、时间步和特征的三维结构,在这种情况下,我们只有一个样本和一个特征:`[1, n_input, 1]`。
如果执行了差分操作,我们必须在模型做出预测后将减去的值加回来。我们还必须在构建用于预测的单个输入之前对历史数据进行差分。
下面的 `model_predict()` 函数实现了这种行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_input, _, _, _, n_diff = config # 准备数据 correction = 0.0 if n_diff > 0: correction = history[-n_diff] history = difference(history, n_diff) x_input = array(history[-n_input:]).reshape((1, n_input, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return correction + yhat[0] |
对模型超参数进行了一次简单的网格搜索,并选择了下面的配置。这并非最佳配置,但已是找到的最佳配置。
选定的配置如下
- n_input: 36 (即 3 年或 3 * 12)
- 节点数: 50
- 纪元数: 100
- n_batch: 100 (即批量梯度下降)
- n_diff: 12 (即季节性差异)
这可以指定为列表
1 2 |
# 定义配置 config = [36, 50, 100, 100, 12] |
将所有这些联系在一起,完整的示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# 评估LSTM from math import sqrt from numpy import array from numpy import mean from numpy import std from pandas import DataFrame 从 pandas 导入 concat from pandas import read_csv from sklearn.metrics import mean_squared_error from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from matplotlib import pyplot # 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] # 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values # 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) # 差异数据集 def difference(data, interval): return [data[i] - data[i -interval] for i in range(interval, len(data))] # 拟合模型 def model_fit(train, config): # 解包配置 n_input, n_nodes, n_epochs, n_batch, n_diff = config # 准备数据 if n_diff > 0: train = difference(train, n_diff) data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) # 定义模型 model = Sequential() model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, 1))) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model # 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_input, _, _, _, n_diff = config # 准备数据 correction = 0.0 if n_diff > 0: correction = history[-n_diff] history = difference(history, n_diff) x_input = array(history[-n_input:]).reshape((1, n_input, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return correction + yhat[0] # 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error # 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] 返回 分数 # 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() series = read_csv('monthly-car-sales.csv', header=0, index_col=0) data = series.values # 数据分割 n_test = 12 # 定义配置 config = [36, 50, 100, 100, 12] # 网格搜索 scores = repeat_evaluate(data, config, n_test) # 总结得分 summarize_scores('lstm', scores) |
运行示例,我们可以看到模型每次重复评估的 RMSE。
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
运行结束时,我们可以看到平均 RMSE 约为 2,109,这比朴素模型差。这表明所选模型不熟练,而且在与前几节中用于寻找模型配置相同的资源下,这是能找到的最好结果。
这进一步证明(尽管证据薄弱)LSTM,至少是单独使用,可能不适合自回归型序列预测问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
> 2129.480 > 2169.109 > 2078.290 > 2257.222 > 2014.911 > 2197.283 > 2028.176 > 2110.718 > 2100.388 > 2157.271 > 1940.103 > 2086.588 > 1986.696 > 2168.784 > 2188.813 > 2086.759 > 2128.095 > 2126.467 > 2077.463 > 2057.679 > 2209.818 > 2067.082 > 1983.346 > 2157.749 > 2145.071 > 2266.130 > 2105.043 > 2128.549 > 1952.002 > 2188.287 LSTM:2109.779 RMSE(+/- 81.373) |
还创建了一个箱线图,总结了 RMSE 分数的分布。
即使是模型的基本情况也未能达到朴素模型的性能。

长短期记忆神经网络 RMSE 汽车销量预测的箱线图
CNN LSTM
我们已经看到 CNN 模型能够自动学习并从原始序列数据中提取特征,而无需进行缩放或差分。
我们可以将这种能力与 LSTM 结合起来,其中 CNN 模型应用于输入数据的子序列,这些子序列的结果共同形成一个提取特征的时间序列,可以由 LSTM 模型进行解释。
这种由 CNN 模型读取多个子序列并由 LSTM 进行解释的组合称为 CNN-LSTM 模型。
该模型要求每个输入序列(例如 36 个月)被分成多个子序列,每个子序列由 CNN 模型读取(例如 3 个子序列,每个子序列 12 个时间步)。按年份划分子序列可能是有意义的,但这只是一个假设,也可以使用其他划分方式,例如 6 个子序列,每个子序列 6 个时间步。因此,这种划分通过 `n_seq` 和 `n_steps`(分别表示子序列的数量和每个子序列的步数)参数化。
1 |
train_x = train_x.reshape((train_x.shape[0], n_seq, n_steps, 1)) |
每个样本的滞后观测值数量简单地为 (n_seq * n_steps)。
现在这是一个四维输入数组,其维度为
1 |
[样本数, 子序列数, 时间步长, 特征数] |
相同的 CNN 模型必须应用于每个输入子序列。
我们可以通过将整个 CNN 模型包装在 `TimeDistributed` 层包装器中来实现这一点。
1 2 3 4 5 |
model = Sequential() model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1)))) model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu'))) model.add(TimeDistributed(MaxPooling1D(pool_size=2))) model.add(TimeDistributed(Flatten())) |
CNN 子模型的一次应用输出将是一个向量。子模型对每个输入子序列的输出将是一个时间序列的解释,可以由 LSTM 模型进行解释。这之后可以是一个全连接层来解释 LSTM 的结果,最后是一个输出层来做一步预测。
1 2 3 |
model.add(LSTM(n_nodes, activation='relu')) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) |
下面列出了完整的 `model_fit()` 函数。
模型期望一个包含七个超参数的列表;它们是:
- n_seq:样本中的子序列数量。
- n_steps:每个子序列中的时间步数。
- n_filters:并行滤波器的数量。
- n_kernel:在输入序列每次读取中考虑的时间步长数量。
- n_nodes:隐藏层中使用的 LSTM 单元数量。
- n_epochs:模型暴露于整个训练数据集的次数。
- n_batch:每个 epoch 内,权重更新后的样本数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 拟合模型 def model_fit(train, config): # 解包配置 n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config n_input = n_seq * n_steps # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], n_seq, n_steps, 1)) # 定义模型 model = Sequential() model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1)))) model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu'))) model.add(TimeDistributed(MaxPooling1D(pool_size=2))) model.add(TimeDistributed(Flatten())) model.add(LSTM(n_nodes, activation='relu')) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model |
使用拟合模型进行预测与LSTM或CNN大致相同,只是需要将每个样本分成具有给定时间步长的子序列。
1 2 |
# 准备数据 x_input = array(history[-n_input:]).reshape((1, n_seq, n_steps, 1)) |
更新后的 model_predict() 函数如下所示。
1 2 3 4 5 6 7 8 9 10 |
# 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_seq, n_steps, _, _, _, _, _ = config n_input = n_seq * n_steps # 准备数据 x_input = array(history[-n_input:]).reshape((1, n_seq, n_steps, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] |
对模型超参数进行了简单的网格搜索,并选择了以下配置。这可能不是最优配置,但它是目前发现的最佳配置。
- n_seq: 3(即3年)
- n_steps: 12(即1年的月份)
- 过滤器数量: 64
- 核大小: 3
- 节点数: 100
- 纪元数: 200
- n_batch: 100 (即批量梯度下降)
我们可以将配置定义为一个列表;例如
1 2 |
# 定义配置 config = [3, 12, 64, 3, 100, 200, 100] |
下面列出了评估CNN-LSTM模型预测单变量月度汽车销量的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# 评估 CNN LSTM from math import sqrt from numpy import array from numpy import mean from numpy import std from pandas import DataFrame 从 pandas 导入 concat from pandas import read_csv from sklearn.metrics import mean_squared_error from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from keras.layers import TimeDistributed from keras.layers import Flatten from keras.layers.convolutional import Conv1D from keras.layers.convolutional import MaxPooling1D from matplotlib import pyplot # 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] # 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values # 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) # 拟合模型 def model_fit(train, config): # 解包配置 n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config n_input = n_seq * n_steps # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], n_seq, n_steps, 1)) # 定义模型 model = Sequential() model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1)))) model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu'))) model.add(TimeDistributed(MaxPooling1D(pool_size=2))) model.add(TimeDistributed(Flatten())) model.add(LSTM(n_nodes, activation='relu')) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model # 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_seq, n_steps, _, _, _, _, _ = config n_input = n_seq * n_steps # 准备数据 x_input = array(history[-n_input:]).reshape((1, n_seq, n_steps, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] # 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error # 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] 返回 分数 # 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() series = read_csv('monthly-car-sales.csv', header=0, index_col=0) data = series.values # 数据分割 n_test = 12 # 定义配置 config = [3, 12, 64, 3, 100, 200, 100] # 网格搜索 scores = repeat_evaluate(data, config, n_test) # 总结得分 summarize_scores('cnn-lstm', scores) |
运行示例会打印模型每次重复评估的RMSE。
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
最终平均RMSE报告约为1,626,低于朴素模型,但仍高于SARIMA模型。这个分数的标准差也非常大,表明所选配置可能不如独立的CNN模型稳定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
> 1543.533 > 1421.895 > 1467.927 > 1441.125 > 1750.995 > 1321.498 > 1571.657 > 1845.298 > 1621.589 > 1425.065 > 1675.232 > 1807.288 > 2922.295 > 1391.861 > 1626.655 > 1633.177 > 1667.572 > 1577.285 > 1590.235 > 1557.385 > 1784.982 > 1664.839 > 1741.729 > 1437.992 > 1772.076 > 1289.794 > 1685.976 > 1498.123 > 1618.627 > 1448.361 cnn-lstm: 1626.735 RMSE (+/- 279.850) |
还创建了一个箱线图,总结了 RMSE 分数的分布。
该图显示了一个性能非常差的单一异常值,销售额略低于3,000。

CNN-LSTM RMSE预测汽车销量的箱线图
ConvLSTM
可以在每个LSTM单元内作为输入序列读取的一部分执行卷积操作。
这意味着,LSTM不是一次读取一个步骤的序列,而是使用卷积过程(类似于CNN)一次读取一个观测块或子序列。
这与先使用LSTM读取和提取特征并使用LSTM解释结果不同;这是在每个时间步长作为LSTM的一部分执行CNN操作。
这种类型的模型称为卷积LSTM,简称ConvLSTM。它在Keras中作为ConvLSTM2D层提供,用于2D数据。我们可以通过假设我们有一行多列来配置它以用于1D序列数据。
与CNN-LSTM一样,输入数据被分成子序列,其中每个子序列具有固定数量的时间步长,尽管我们还必须指定每个子序列中的行数,在本例中固定为1。
1 |
train_x = train_x.reshape((train_x.shape[0], n_seq, 1, n_steps, 1)) |
形状是五维的,维度为
1 |
[样本、子序列、行、列、特征] |
与CNN一样,ConvLSTM层允许我们指定在读取输入序列时使用的过滤器图数量和核大小。
1 |
model.add(ConvLSTM2D(filters=n_filters, kernel_size=(1,n_kernel), activation='relu', input_shape=(n_seq, 1, n_steps, 1))) |
该层的输出是过滤器图序列,在解释并后面跟一个输出层之前必须先展平。
该模型需要七个超参数列表,与CNN-LSTM相同;它们是
- n_seq:样本中的子序列数量。
- n_steps:每个子序列中的时间步数。
- n_filters:并行滤波器的数量。
- n_kernel:在输入序列每次读取中考虑的时间步长数量。
- n_nodes:隐藏层中使用的 LSTM 单元数量。
- n_epochs:模型暴露于整个训练数据集的次数。
- n_batch:每个 epoch 内,权重更新后的样本数量。
实现所有这些的 model_fit() 函数如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 拟合模型 def model_fit(train, config): # 解包配置 n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config n_input = n_seq * n_steps # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], n_seq, 1, n_steps, 1)) # 定义模型 model = Sequential() model.add(ConvLSTM2D(filters=n_filters, kernel_size=(1,n_kernel), activation='relu', input_shape=(n_seq, 1, n_steps, 1))) model.add(Flatten()) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model |
使用拟合模型进行预测的方式与CNN-LSTM相同,只是增加了我们固定为1的行维度。
1 2 |
# 准备数据 x_input = array(history[-n_input:]).reshape((1, n_seq, 1, n_steps, 1)) |
下面列出了用于进行单步预测的 model_predict() 函数。
1 2 3 4 5 6 7 8 9 10 |
# 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_seq, n_steps, _, _, _, _, _ = config n_input = n_seq * n_steps # 准备数据 x_input = array(history[-n_input:]).reshape((1, n_seq, 1, n_steps, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] |
对模型超参数进行了简单的网格搜索,并选择了以下配置。
这可能不是最优配置,但它是目前发现的最佳配置。
- n_seq: 3(即3年)
- n_steps: 12(即1年的月份)
- 过滤器数量: 256
- 核大小: 3
- 节点数: 200
- 纪元数: 200
- n_batch: 100 (即批量梯度下降)
我们可以将配置定义为一个列表;例如
1 2 |
# 定义配置 config = [3, 12, 256, 3, 200, 200, 100] |
我们可以将所有这些联系起来。下面列出了用于月度汽车销量数据集单步预测的ConvLSTM模型的完整代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# 评估 convlstm from math import sqrt from numpy import array from numpy import mean from numpy import std from pandas import DataFrame 从 pandas 导入 concat from pandas import read_csv from sklearn.metrics import mean_squared_error from keras.models import Sequential from keras.layers import Dense from keras.layers import Flatten from keras.layers import ConvLSTM2D from matplotlib import pyplot # 将单变量数据集拆分为训练/测试集 def train_test_split(data, n_test): return data[:-n_test], data[-n_test:] # 将列表转换为监督学习格式 def series_to_supervised(data, n_in=1, n_out=1): df = DataFrame(data) cols = list() # 输入序列 (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 预测序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) # 将它们组合在一起 agg = concat(cols, axis=1) # 删除包含 NaN 值的行 agg.dropna(inplace=True) return agg.values # 均方根误差或 RMSE def measure_rmse(actual, predicted): return sqrt(mean_squared_error(actual, predicted)) # 差异数据集 def difference(data, interval): return [data[i] - data[i -interval] for i in range(interval, len(data))] # 拟合模型 def model_fit(train, config): # 解包配置 n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config n_input = n_seq * n_steps # 准备数据 data = series_to_supervised(train, n_in=n_input) train_x, train_y = data[:, :-1], data[:, -1] train_x = train_x.reshape((train_x.shape[0], n_seq, 1, n_steps, 1)) # 定义模型 model = Sequential() model.add(ConvLSTM2D(filters=n_filters, kernel_size=(1,n_kernel), activation='relu', input_shape=(n_seq, 1, n_steps, 1))) model.add(Flatten()) model.add(Dense(n_nodes, activation='relu')) model.add(Dense(1)) model.compile(loss='mse', optimizer='adam') # 拟合 model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) return model # 使用预训练模型进行预测 def model_predict(model, history, config): # 解包配置 n_seq, n_steps, _, _, _, _, _ = config n_input = n_seq * n_steps # 准备数据 x_input = array(history[-n_input:]).reshape((1, n_seq, 1, n_steps, 1)) # 预测 yhat = model.predict(x_input, verbose=0) return yhat[0] # 单变量数据的滚动预测验证 def walk_forward_validation(data, n_test, cfg): predictions = list() # 拆分数据集 train, test = train_test_split(data, n_test) # 拟合模型 model = model_fit(train, cfg) # 用训练数据集初始化历史数据 history = [x for x in train] # 遍历测试集中的每个时间步 for i in range(len(test)): # 拟合模型并对历史数据进行预测 yhat = model_predict(model, history, cfg) # 将预测结果存储在预测列表中 predictions.append(yhat) # 将实际观测值添加到历史数据中以进行下一次循环 history.append(test[i]) # 估计预测误差 error = measure_rmse(test, predictions) print(' > %.3f' % error) return error # 重复评估配置 def repeat_evaluate(data, config, n_test, n_repeats=30): # 拟合并评估模型 n 次 scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] 返回 分数 # 总结模型性能 def summarize_scores(name, scores): # 打印摘要 scores_m, score_std = mean(scores), std(scores) print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) # 箱线图 pyplot.boxplot(scores) pyplot.show() series = read_csv('monthly-car-sales.csv', header=0, index_col=0) data = series.values # 数据分割 n_test = 12 # 定义配置 config = [3, 12, 256, 3, 200, 200, 100] # 网格搜索 scores = repeat_evaluate(data, config, n_test) # 总结得分 summarize_scores('convlstm', scores) |
运行示例会打印模型每次重复评估的RMSE。
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
最终平均RMSE报告约为1,660,低于朴素模型,但仍高于SARIMA模型。
这个结果可能与CNN-LSTM模型相当。这个分数的标准差也非常大,表明所选配置可能不如独立的CNN模型稳定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
> 1825.246 > 1862.674 > 1684.313 > 1310.448 > 2109.668 > 1507.912 > 1431.118 > 1442.692 > 1400.548 > 1732.381 > 1523.824 > 1611.898 > 1805.970 > 1616.015 > 1649.466 > 1521.884 > 2025.655 > 1622.886 > 2536.448 > 1526.532 > 1866.631 > 1562.625 > 1491.386 > 1506.270 > 1843.981 > 1653.084 > 1650.430 > 1291.353 > 1558.616 > 1653.231 convlstm: 1660.840 RMSE (+/- 248.826) |
还创建了一个箱线图,总结了RMSE分数的分布。

ConvLSTM RMSE预测汽车销量的箱线图
扩展
本节列出了一些您可能希望探索的扩展本教程的想法。
- 数据准备。探索数据准备(例如归一化、标准化和/或差分)是否可以提高任何模型的性能。
- 网格搜索超参数。对一个模型的超参数执行网格搜索,看看是否可以进一步提高性能。
- 学习曲线诊断。对一个模型进行一次拟合,并审查数据集的训练集和验证集上的学习曲线,然后使用学习曲线的诊断来进一步调整模型超参数,以提高模型性能。
- 历史大小。探索一个模型不同数量的历史数据(滞后输入),看看是否可以进一步提高模型性能
- 降低最终模型的方差。探索一种或多种策略来降低其中一个神经网络模型的方差。
- 向前推移时更新。探索作为向前验证的一部分重新拟合或更新神经网络模型是否可以进一步提高模型性能。
- 更多参数化。探索为某个模型添加更多模型参数化,例如使用额外的层。
如果您探索了这些扩展中的任何一个,我很想知道。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
- pandas.DataFrame.shift API
- sklearn.metrics.mean_squared_error API
- matplotlib.pyplot.boxplot API
- Keras 序列模型 API
总结
在本教程中,您了解了如何开发一套用于单变量时间序列预测的深度学习模型。
具体来说,你学到了:
- 如何使用滚动验证(walk-forward validation)开发稳健的测试工具,以评估神经网络模型的性能。
- 如何开发和评估用于时间序列预测的简单多层感知器和卷积神经网络。
- 如何开发和评估用于时间序列预测的 LSTM、CNN-LSTM 和 ConvLSTM 神经网络模型。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
你好。安迪。乌克兰。基辅。
在你的CNN LSTM(完整代码模型)示例中,我遇到了错误。
文件“C:\Users\User\Dropbox\DeepLearning3\realmodproj\project\educationNew\sub2\step_005.py”,第89行,在model_fit中
model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1))))
文件“C:\Users\User\AppData\Local\Programs\Python\Python36\lib\site-packages\keras\models.py”,第454行,在add中
raise ValueError('The first layer in a '
ValueError: Sequential模型中的第一层必须有
input_shape
或batch_input_shape
参数。你能解释一下原因吗?
谢谢你。
我相信你需要将Keras更新到2.2.4或更高版本。
使用 TensorFlow 后端。
1.8.0
我建议使用TensorFlow 1.11.0或更高版本。
安迪,基辅
也许我应该用?
model.add(TimeDistributed( Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1) ) , input_shape=(None,n_steps,1) ) )
当然,这是Keras旧版本所必需的。
那么,简而言之,RNN在处理时间序列预测时效果不佳吗?
没错,在大多数情况下,它们在单变量数据上的表现会被线性方法超越。
另请参阅此
https://machinelearning.org.cn/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting/
感谢这篇文章
我有一个单变量时间序列分类问题。数据来自加速度计传感器(振动分析)。只有X轴。采样率为2.5 KHz,我需要实时分类。
您对这个时间序列的分类有什么建议?我想区分多个带标签的类别(故障)。
你的哪些文章可以帮助我?我读了所有的文章,但其中许多不是单变量的,而这篇关于单变量的文章是关于预测而不是分类的。
非常感谢。
听起来像是时间序列分类。
你可以看看HAR文章,例如
https://machinelearning.org.cn/how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification/
嗨,Jason,感谢你所有精彩的机器学习教程!
我是时间序列预测的新手,但我从您的文章中学到了很多。
我有一个问题,我试图预测每天售出的门票数量,但我想在实际日期前60天进行预测。我有3年的每日售票历史数据。
在我的数据上测试上述模型,持久性模型得分最高(我认为),RMSE为4.041,下一个最好的是MLP,RMSE为7.501(+/- 0.589)。
您对如何进行数据预测有什么建议吗?
再次感谢您的出色工作!
干得不错。
是的,你到底有什么问题?
嗨,
假设我有一个演出,我想在演出前60天预测这60天内售出的门票数量(t+60)。
如果我将60天看作一个时间段,预测将不考虑季节性影响,而季节性影响对售票量有很大影响。因此,我认为我需要将这60天中的每一天都用作一个时间段,以获得更准确的预测。
所以,我想预测60天期间每天售出的票数,但我想在60天期间开始之前知道预测。模型的输出是一个包含60个元素的列表,表示每天的预期票数销售[0,1,7,5,3…,3]
我的问题是选择这种预测的正确方法。您有什么建议吗?
这听起来是一个非常具有挑战性的问题。
我们无法知道最佳方法。您必须通过使用一系列不同方法的实验来发现最佳方法。
这个可能会有帮助
https://machinelearning.org.cn/how-to-develop-a-skilful-time-series-forecasting-model/
你的博客很棒
谢谢。
感谢您的代码和解释。我正在处理单变量时间序列数据进行预测。我有60个数据点,基本上是5年的月度数据。如果我想预测去年或最近12个月,我的n_test会是12吗?我还在想如何查看模型做出的最后12个预测,以便我可以绘制实际值和预测值。
谢谢。
听起来你想做多步预测。我在这里给出了一些例子
https://machinelearning.org.cn/start-here/#deep_learning_time_series
非常感谢您发表了有价值的文章!!!!
谢谢!~~~
哦,我有一个小问题。为什么RMSE分数在重复评估时每次都会改变,尽管配置或超参数没有调整?
好问题,我在这里回答
https://machinelearning.org.cn/faq/single-faq/why-do-i-get-different-results-each-time-i-run-the-code
不客气,我很高兴它有所帮助。
是否可以使用“集成方法”机器学习(例如bagging,boosting…)进行预测?
如果是,何时以及如何使用?
你能给我一个使用集成方法进行时间序列预测的例子吗?
在此先感谢您
是的,你可以从这里开始
https://machinelearning.org.cn/start-here/#deep_learning_time_series
还有这里
https://machinelearning.org.cn/start-here/#better
谢谢先生
我不认为那是为了时间序列预测!
感谢您的这些解释。
我们如何为预测添加置信区间。
我想你指的是预测区间
https://machinelearning.org.cn/prediction-intervals-for-machine-learning/
嗨,Jason,非常感谢你的这篇文章,它对我帮助很大。你能附上你使用朴素方法和SARIMA对月度销售数据集进行预测的文章链接吗?我搜索了你的文章但没有找到。谢谢!
你可以使用页面顶部的搜索框,例如“naive bayes”或“sarima”。
嗨,Jason,非常感谢您的上次回复。我正在使用您的模型来拟合一种产品的每周总需求量的时间序列数据,我发现CNN-LSTM的RMSE变化很大。有时我得到了一个非常好的结果,其中RSME是MLP模型的一半,有时我得到了一个一般般的结果,有时我得到了一个非常差的结果,其中RSME比我从朴素预测中得到的差得多。您能告诉我一些避免这种情况并只获得好结果的方法吗,比如如何为CNN-LSTM选择参数?谢谢!
很好的问题。
你可以尝试一些正则化方法,也许可以增加模型的容量。
我在这里给出例子
https://machinelearning.org.cn/start-here/#better
嗨,Jason,这真是一个很棒的教程,谢谢!
我有一个关于深度学习神经网络模型中的“向前推移验证”的问题。
我理解了在模型中进行“向前推移验证”的概念,当你想要进行一步预测时,你会将预测与实际测试值进行比较,并使用测试集中的真实已知窗口更新你的窗口,以预测下一个周期。
对于深度学习模型,我有点缺乏理解。正如我所看到的,更复杂的模型,如神经网络,不会以“典型”的“向前推移验证”方式重新拟合模型,因为计算复杂。
那么,你的MLP代码或CNN代码中的“向前推移验证”到底意味着什么?你是否使用真实观测值作为输入层的一部分来预测下一个时间步?
我们使用与处理其他模型相同的向前推移验证过程。
MLP代码中拟合模型时的X-train和y_train意味着什么?这可能是我问题的答案,即你用预测的y值连续更新X-train?
我们可以选择在每次新的观测值出现时(例如每个步骤)重新拟合模型,也可以不重新拟合。由于计算成本高昂,我们通常不这样做。
嗨,Jason,
如果数据集包含气候变量以使用LSTM预测降水,并且假设它不是平稳的,我可以应用差分吗?如果数据有10年,我应该按年进行吗?
提前感谢!
安德烈
尝试一步差分以消除趋势,尝试季节性差分以消除季节性。
嗨,Jason,
我正在尝试将此代码改编为多变量方法 (LSTM),但不太理解 walk_forward_validation 函数。
您已使用训练 (数据) 来拟合模型。为什么在 'yhat = model_predict(model, history, cfg)' 行中,您使用相同的训练数据来预测模型?为什么不使用测试数据?并与测试 (真实) 进行比较。
我在这里给出了多变量预测与LSTM的例子
https://machinelearning.org.cn/start-here/#deep_learning_time_series
这可能是一个很好的起点
https://machinelearning.org.cn/how-to-develop-lstm-models-for-time-series-forecasting/
你好!感谢这篇精彩的教程,但我有上面同样的问题,我在你提供的链接中找不到答案。
我为什么要用训练集(history)来喂我的predict_model函数?
通常情况下,我应该只给我的predict_model函数x_test部分,并要求它计算y_hat!
你能给我们更多解释吗?再次感谢
不客气。
我想你是在问我们如何评估模型的性能。在这种情况下,我们使用的是向前推移验证,这是一种评估序列预测模型的标准技术。你可以在这里了解更多信息
https://machinelearning.org.cn/backtest-machine-learning-models-time-series-forecasting/
嗨,我想预测大约800种产品,是否可以使用RNN一次预测这些产品?还是需要逐个预测?
很好的问题。
有很多方法,我在这里解释了一些方法(将“站点”替换为“产品”)
https://machinelearning.org.cn/faq/single-faq/how-to-develop-forecast-models-for-multiple-sites
嗨,Jason,因为这个回复大约一年前了,我想知道你是否仍然坚持这个观点:普通LSTM在时间序列预测方面表现很差……ConvLSTM非常好?
如果是,你有没有写过关于如何开发ConvLSTM模型的文章(或书籍)?
我之所以问这个问题,是因为我想寻找除贝叶斯方法之外的候选模型(我认为贝叶斯方法在分析上很繁重)。谢谢。
是的,请看这项研究
https://machinelearning.org.cn/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting/
是的,这里涵盖了CNN-LSTMs和ConvLSTMs
https://machinelearning.org.cn/how-to-develop-lstm-models-for-time-series-forecasting/
你好 Jason!非常感谢您给我们提供神经网络预测的见解。我正在处理一个预测问题,需要根据831天的前期生产数据预测23天(大约一个月)的生产量。我已经测试了我的数据,发现它是平稳的,所以LSTM RNN第一次没有奏效。因此,我想到也许一个基本的MLP可以奏效,并遵循了您的教程。一切都很顺利,但我现在需要获取预测数据的列表,以便将它们与测试数据进行绘图,我在编写代码时遇到了麻烦。您能帮帮我吗?(我对所有这些“深度学习”的东西有点陌生)
干得不错。
是的,使用模型进行预测然后绘制它。例如:
yhat = model.predict(newX)
如果模型直接预测23个时间步,那么这就是你所需要的。
如果你将模型设计为预测1个时间步,那么根据需要用不同的输入调用模型23次。
然后用matplotlib绘图
pyplot.plot(yhat)
pyplot.show()
希望这能有所帮助。
嗨,Jason,
收到“model not defined”错误。我应该把它放在定义循环中吗?
谢谢!
“model”是你的模型对象引用的名称,无论它是什么。
嗨,Jason,我在这里尝试了这些模型,我想将它们应用于多站点数据并同时获得它们的预测。例如,对于您这里使用的数据集,如果我想预测不同品牌汽车的月度销量,并且我想获得这些品牌的销量预测,我应该如何修改代码?我看到您有一个EMC数据科学全球黑客马拉松数据集,它是多站点的,但我没有找到它的模型。谢谢,祝您周末愉快!
对于我上面提出的问题,我指的是用于多站点数据的深度学习模型。我看到您为空气污染数据集开发了时间序列模型和机器学习模型。
好问题,我这里有一些建议可能会有帮助
https://machinelearning.org.cn/faq/single-faq/how-to-develop-forecast-models-for-multiple-sites
布朗利博士您好,
感谢所有免费资源。
我正在做一个基于物联网的大学项目,我需要用数据集做一些预测。
我创建的数据集
月度小时小数数据。(dim 500x1, config = [24, 500, 100, 100, n_test = 0.75)
1) 我已经尝试了冬季保持,得到了6.66的RSME。我知道您的文章指出,分析方法通常优于机器学习方法。所以我尝试了MLP示例,并立即得到了更好的结果。但是我不确定您的RSME计算是否具有可比性。每当运行向前推移验证方法时,您都会附加测试集的实际“未来”值。因此,算法只预测未来的一个值。当我将history.append(test[i])更改为history.append(yhat)时,我得到了糟糕的结果。我是不是完全错了,或者这两个RSME不可比?
2) 一旦算法训练好,我如何预测未来,因为模型类型只能在方法内部访问?
否则我只会用一个for循环包裹这个
history = [x for x in data]
x_input = np.array(history[-24:]).reshape(1, 24)
yhat = model.predict(x_input, verbose=0)
print(yhat)
提前感谢并祝您愉快😉
为了公平比较,两种方法必须在相同的测试工具下进行评估。
如果您的模型旨在用于多步预测,那么您应该以这种方式进行评估,而不是单步预测。
希望这能有所帮助。
您好,非常感谢您的这篇文章。
我在CNN-LSTM上有一点不明白。
我有一个与停车相关的数据库。
数据每小时存储一次,我每天有12个数据。
我保存了这些数据10个月。
我想使用CNN-LSTM,但不知道如何设置n_seq和n_step的值。
也许可以从这里给出的简单示例开始,并根据您的数据集进行调整
https://machinelearning.org.cn/how-to-develop-lstm-models-for-time-series-forecasting/
谢谢。
CNN-LSTM 中数据划分样本是否有特定规则?
例如,在您的示例中,有 108 个数据。CNN-LSTM 模型中的样本数量为 36。
n_seq = 3,n_step = 12。
为什么选择这些数字?
是否可以 n_seq=5 且 n_step=20?
选择这些数字的原因是什么?
我有 1312 个数据,样本数量是否应该为 1312 / 3?
1312 / 3 = 437
n_seq = 3
n_step = 437 / n_seq
这样对吗?
我如何选择 n_seq 和 n_steps 的数字?
没有规则。
设计一个测试工具,以你期望在未来使用模型的方式,在你的数据上最好地评估你的模型。
如果你只有大约100个观测值,也许坚持使用像ETS或SARIMA这样的线性方法?
谢谢您,Jason Brownlee先生。
在您编写的示例(CNN-LSTM)中,您将每36个数据视为输入,并将第37个数据作为目标。
这就是为什么n_seq和n_steps的乘积等于36
3 * 12 = 36
现在假设如果我们要将数据分成训练集和测试集,以便有37个数据作为输入,第38个数据作为目标。
因此,n_seq和n_steps的值不能是整数值。
因为12.33 * 3 = 37
我们知道,为了通过CNN解释任何样本,我们必须将样本分成几个子序列和步长。
在这些情况下应该怎么做?
谢谢
您可能需要尝试数据集的不同划分方式,看看哪种方式效果好。多做实验!
感谢Jason提供如此棒的示例。如果您能使用R软件分析相同的问题,那将非常有帮助。
感谢您的建议。
感谢您的精彩文章。
我有一个日期列表,我想用这个日期模式列表来预测下一个日期。
那么我该怎么做呢?我一直在搜索,但失败了。您能给我一个建议吗?
谢谢
也许可以将日期转换为整数,并尝试将其建模为预测问题。
谢谢你的文章!
我有一个问题。有了可以预测特定测试的工具,我应该如何输出预测结果?
你说的“预测特定测试”是什么意思?
意思是,在使用RMSE知道哪个模型可以使用后,我如何知道该模型预测的结果。
您是否正在寻找 model.predict() 函数?
嗨,Jason教授,
希望你一切安好。请告诉我如何将ConvLSTM从单变量预测转变为多变量预测。
谢谢
需要做一些工作。我认为这里的一个关键点是,例如,使用Conv2D层而不是Conv1D,因为您现在在每个时间步都有一个特征向量。
亲爱的博士,我在LSTM代码编写中遇到了问题
# 输入序列 (t-n, ... t-1)
for i in range(n_in, 0, -1)
cols.append(df.shift(i))
TypeError Traceback (most recent call last)
Cell In[45], line 2
1 # 输入序列 (t-n, ... t-1)
----> 2 for i in range(n_in, 0, -1)
3 cols.append(df.shift(i))
TypeError: 'type' 对象不能被解释为整数
如何解决这个问题?
嗨,Mulatu……下面的资源可能对你有帮助
https://stackoverflow.com/questions/28036309/typeerror-list-object-cannot-be-interpreted-as-an-integer
嗨,Jason,
我有10年的火灾时间序列数据(120个月)——训练模型预测未来几年的火灾。我有几个问题。
1. 为什么我的预测值是负数?我如何处理负值?我的训练数据集只有正值。
2. 不同的时间序列模式需要不同的参数吗?或者一个模型可以适用于所有时间序列模式吗?
祝好,
Aditya
嗨,Aditya……谢谢你的提问!
1. 你是说你的MSE是负的吗?如果是这样的话
在 scikit-learn 中计算某些模型评估指标(例如均方误差 (MSE))时,结果为负值。
这令人困惑,因为像 MSE 这样的误差分数实际上不能为负,最小值为零或无误差。
scikit-learn 库有一个统一的模型评分系统,它假定所有模型分数都是最大化的。为了使该系统适用于最小化的分数(如 MSE 和其他误差度量),最小化的分数会通过使其变为负数而被反转。
这也可以在指标的规范中看到,例如,在指标名称“neg_mean_squared_error”中使用了“neg”。
在解释负误差分数时,你可以忽略符号并直接使用它们。
你可以在这里了解更多
模型评估:量化预测质量
2. 我会说是的!时间序列模型将从数据集中学习,权重将相应修改。
您可能希望考虑“迁移学习”,其中您利用经过预训练的模型,以便您可以根据自己的目的进行修改
https://thesai.org/Downloads/Volume13No8/Paper_32-Forest_Fires_Detection_using_Deep_Transfer_Learning.pdf
嗨,Jason,
谢谢你的回复。
1. 我指的是我的预测值由于趋势而为负数——然而火灾数量不能为负数——所以我的问题是——我如何去除趋势成分,只希望处理季节性成分。
祝好,
Aditya
我正在进行一个名为“糖尿病感知:一种基于非侵入性多传感器物联网的呼吸糖尿病预诊断系统”的项目。
给我的任务是减少我的数据文件中的均方误差值。我也会分享一个非常小的我的数据文件样本。问题是我是深度学习的新手,我的导师推荐了你的书,对我来说太长了,我无法读完。但我学习很快,所以我想让你建议我应该从这本书中学习什么来减少我的数据的均方误差,以及我可以使用什么类型的算法来使其更高效。你也可以谷歌项目名称来了解更多项目信息。
我复制粘贴的数据可能看起来不太好理解,请尝试将其复制粘贴以获得其正确的表格形式。
请帮我,我的任务提交日期非常近。
我的数据文件示例 =
Sample_No 温度 湿度 TGS2620 TGS2610 TGS2602 TGS2603 TGS822 TGS2600 TGS826 MQ138 年龄 性别 最高血压 最低血压 心跳 SPO2 血糖
1 30.88289474 38.46973684 1.66 0.77 2.63 2.72 1.15 1.36 0.13 3.46 26 0 120 80 93 98 92
1.65 0.76 2.62 2.71 1.14 1.35 0.12 3.17
1.64 0.75 2.61 2.7 1.13 1.34 0.11 3.09
1.63 0.74 2.6 2.69 1.12 1.33 3.03
1.62 0.73 2.58 2.68 1.11 1.32 2.76
2 30.42844828 37.83362069 1.64 0.77 2.75 2.73 1.15 1.34 0.13 2.59 64 0 140 80 101 98 301
1.63 0.76 2.74 2.72 1.14 1.33 0.12 2.55
1.62 0.75 2.73 2.71 1.13 1.32 0.1 2.51
1.61 0.74 2.72 2.7 1.12 1.31 2.47
1.6 0.73 2.71 2.69 1.11 1.3 2.45
3 30.61521739 41.55869565 1.84 0.66 2.73 3 1.34 1.52 0.12 2.59 79 0 150 70 71 99 156
1.83 0.65 2.72 2.99 1.33 1.51 0.1 2.57
1.82 0.64 2.71 2.98 1.32 1.5 2.55
1.81 0.63 2.7 2.97 1.31 1.49 2.51
1.8 0.62 2.68 2.96 1.3 1.48 2.47
4 30.62681159 41.75507246 1.92 0.68 3.06 3.02 1.48 1.59 0.12 2.43 65 0 130 80 70 97 94
1.89 0.67 3.05 3.01 1.47 1.56 0.1 2.42
1.88 0.66 3.04 3 1.46 1.54 2.41
1.87 0.65 3.03 2.99 1.44 1.53 2.4
1.86 0.64 3.02 2.98 1.42 1.52 2.39
5 30.71958763 39.16082474 1.71 0.69 2.83 2.9 1.28 1.35 0.11 2.31 45 0 110 70 56 99 90
1.7 0.68 2.82 2.89 1.27 1.34 0.1 2.29
1.69 0.67 2.81 2.88 1.26 1.33 2.28
1.68 0.66 2.79 2.87 1.25 1.32 2.27
1.67 0.65 2.78 2.86 1.24 1.31 2.26
6 31.45652174 39.04057971 1.94 0.79 2.67 3.18 1.28 1.52 0.1 2.52 45 0 110 70 56 99 90
1.87 0.78 2.66 2.89 1.27 1.48 2.49
1.85 0.77 2.65 2.76 1.26 1.43 2.46
1.77 0.76 2.64 2.74 1.24 1.41 2.39
1.69 0.74 2.63 2.73 1.22 1.34 2.32
7 30.4516129 38.56129032 1.43 0.7 1.83 2.09 1.05 1.15 0.1 2.44 64 0 140 80 101 98 301
1.42 0.69 1.82 2.08 1.04 1.14 2.43
1.41 0.68 1.81 2.07 1.03 1.13 2.38
1.4 0.67 1.8 2.06 1.02 1.12 2.33
1.37 0.66 1.79 2.04 1.01 1.11 2.29
8 33.44878049 44.1445122 1.78 0.71 2.29 2.76 1.44 1.49 0.1 2.54 65 1 196 100 82 99 117
1.77 0.7 2.28 2.75 1.43 1.48 0.09 2.53
1.76 0.69 2.27 2.74 1.42 1.47 2.52
1.75 0.68 2.26 2.73 1.41 1.46 2.51
1.74 0.67 2.24 2.72 1.4 1.45 2.5
9 33.02 45.04307692 1.74 0.69 2.31 2.75 1.4 1.45 0.1 2.46 85 1 109 57 99 99 160
1.73 0.68 2.3 2.74 1.39 1.44 2.45
1.72 0.67 2.29 2.73 1.38 1.43 2.44
1.71 0.66 2.28 2.72 1.37 1.42 2.43
1.7 0.65 2.27 2.71 1.36 1.41 2.42
10 33.36666667 43.97681159 1.53 0.71 1.61 2.14 1.13 1.28 0.1 2.36 80 1 110 70 70 95 274
1.52 0.7 1.6 2.13 1.12 1.27 2.35
1.51 0.69 1.58 2.12 1.11 1.26 2.34
1.5 0.68 1.57 2.11 1.1 1.25 2.33
1.49 0.67 1.56 2.1 1.09 1.24 2.32
嗨,Vishal……谢谢你的提问。
我很想帮忙,但我实在没有能力为您调试代码。
我很乐意提出一些建议
考虑将代码积极削减到最低要求。这将帮助您隔离问题并专注于它。
考虑将问题简化为一个或几个简单的例子。
考虑寻找其他可行的类似代码示例,并慢慢修改它们以满足您的需求。这可能会暴露您的失误。
考虑在 StackOverflow 上发布您的问题和代码。
感谢您的建议,我非常感谢,我会将您的话付诸实践。
作为深度学习新手,我希望多提问题不会打扰您😅。
另外,有没有其他平台可以直接向您提问,比如与您聊天。
谢谢。