深度学习实战案例:时间序列预测代码模板(单变量、多元、多步、多元多步)

长短期记忆网络,简称LSTMs,可以应用于时间序列预测。有许多类型的 LSTM 模型可用于每种特定类型的时间序列预测问题。

在本文中,我将分享一系列标准时间序列预测问题开发一套 LSTM 模型。

本文的目的是针对每种类型的时间序列问题提供独立示例作为模板,你可以复制该模板并针对你的特定时间序列预测问题进行调整。

技术提升

技术要学会分享、交流,不建议闭门造车。一个人走的很快、一堆人可以走的更远。

完整代码、数据、技术交流提升, 均可加入知识星球交流群获取,群友已超过2000人,添加时切记的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、添加微信号:pythoner666,备注:来自 CSDN +时序模板
方式②、微信搜索公众号:Python学习与数据挖掘,后台回复:资料

单变量 LSTM 模型

LSTM 可用于对单变量时间序列预测问题建模。这些问题由单个观察系列组成,需要一个模型从过去的一系列观察中学习,以预测序列中的下一个值。

我们将演示用于单变量时间序列预测的 LSTM 模型的多种变体。

本节分为六个部分;他们是:

  1. 数据准备
  2. Vanilla LSTM
  3. 堆叠式 LSTM
  4. 双向 LSTM
  5. CNN LSTM
  6. 卷积 LSTM

这些模型中的每一个都针对一步单变量时间序列预测进行了演示,但可以很容易地进行调整并用作其他类型时间序列预测问题模型的输入部分。

数据准备

在对单变量序列建模之前,必须对其进行准备。
LSTM 模型将学习一个函数,该函数将一系列过去的观察结果映射为输出观察结果的输入。因此,必须将观察序列转换为 LSTM 可以从中学习的多个示例。
考虑一个给定的单变量序列:

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

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

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

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

现在我们知道如何准备用于建模的单变量序列,让我们看看开发可以学习输入到输出映射的 LSTM 模型,从 Vanilla LSTM 开始。

Vanilla LSTM

Vanilla LSTM 是一种 LSTM 模型,它具有一个 LSTM 单元隐藏层和一个用于进行预测的输出层。
我们可以为单变量时间序列预测定义一个 Vanilla LSTM,如下所示。

# define model
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

定义的关键是输入的形状;这就是模型在时间步数和特征数方面对每个样本的期望输入。

我们正在处理一个单变量系列,因此对于一个变量,特征的数量是一个。

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

每个样本的输入形状在第一个隐藏层定义的_input_shape参数中指定。_

我们几乎总是有多个样本,因此,模型将期望训练数据的输入部分具有以下维度或形状:

[samples, timesteps, features]

上一节中的_split_sequence()_函数输出形状为 [ samples, timesteps ] 的 X,因此我们可以轻松地对其进行整形,使其具有一个特征的附加维度。

...
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X = X.reshape((X.shape[0], X.shape[1], n_features))

在这种情况下,我们定义了一个在隐藏层中具有 50 个 LSTM 单元的模型和一个预测单个数值的输出层。
该模型使用随机梯度下降的高效 Adam 版本进行拟合,并使用均方误差或“ mse ”损失函数进行优化。
一旦定义了模型,我们就可以将其拟合到训练数据集上。

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

模型拟合好之后,我们就可以用它来做预测了。
我们可以通过提供输入来预测序列中的下一个值:

[70, 80, 90]

并期望模型预测如下内容:

[100]

该模型期望输入形状是具有 [样本、时间步长、特征]的三维形状,因此,我们必须在进行预测之前对单个输入样本进行整形。

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

运行示例准备数据、拟合模型并进行预测。
我们可以看到模型预测了序列中的下一个值。

[[102.09213]]

Stacked LSTM

在所谓的 Stacked LSTM 模型中,多个隐藏的 LSTM 层可以一个接一个地堆叠。

LSTM 层需要三维输入,默认情况下,LSTM 会产生二维输出作为序列末尾的解释。

我们可以通过在层上设置return_sequences=True_参数让 LSTM 为输入数据中的每个时间步输出一个值来解决这个问题。这使我们能够将隐藏 LSTM 层的 3D 输出作为下一层的输入。

因此,我们可以如下定义 Stacked LSTM。

model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features)))
model.add(LSTM(50, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

运行该示例可预测序列中的下一个值,我们预计该值为 100。

双向 LSTM

在一些序列预测问题上,允许 LSTM 模型学习正向和反向输入序列并将两种解释连接起来可能是有益的。
这称为双向 LSTM。
我们可以通过将第一个隐藏层包装在称为双向的包装层中来实现用于单变量时间序列预测的双向 LSTM。
定义双向 LSTM 以向前和向后读取输入的示例如下所示。

...
# define model
model = Sequential()
model.add(Bidirectional(LSTM(50), input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

运行该示例可预测序列中的下一个值,我们预计该值为 100。

[[101.48093]]

CNN LSTM

卷积神经网络,简称 CNN,是一种为处理二维图像数据而开发的神经网络。

CNN可以非常有效地从单变量时间序列数据等一维序列数据中自动提取和学习特征。

CNN 模型可用于具有 LSTM 后端的混合模型,其中 CNN 用于解释输入的子序列,这些子序列一起作为序列提供给 LSTM 模型进行解释。这种混合模型称为 CNN-LSTM。

第一步是将输入序列拆分为可由 CNN 模型处理的子序列。例如,我们可以先将单变量时间序列数据拆分为输入/输出样本,其中四步作为输入,一步作为输出。然后可以将每个样本分成两个子样本,每个子样本有两个时间步长。CNN 可以解释两个时间步长的每个子序列,并将子序列的时间序列解释提供给 LSTM 模型作为输入进行处理。

我们可以对其进行参数化,并将子序列的数量定义为_n_seq_,将每个子序列的时间步数定义为_n_steps_。然后可以对输入数据进行整形以具有所需的结构:

[samples, subsequences, timesteps, features]

例如:

...
# choose a number of time steps
n_steps = 4
# split into samples
X, y = split_sequence(raw_seq, n_steps)
# reshape from [samples, timesteps] into [samples, subsequences, timesteps, features]
n_features = 1
n_seq = 2
n_steps = 2
X = X.reshape((X.shape[0], n_seq, n_steps, n_features))

我们希望在分别读取每个数据子序列时重用相同的 CNN 模型。
这可以通过将整个 CNN 模型包装在一个TimeDistributed 包装器中来实现,该包装器将为每个输入应用一次整个模型,在这种情况下,每个输入子序列一次。
CNN 模型首先有一个卷积层,用于读取需要指定多个过滤器和内核大小的子序列。过滤器的数量是输入序列的读取或解释的数量。内核大小是输入序列的每个“读取”操作所包含的时间步数。
卷积层之后是一个最大池化层,该层将过滤器映射提取到其大小的 1/2,其中包括最显着的特征。然后将这些结构展平为单个一维向量,用作 LSTM 层的单个输入时间步长。

model.add(TimeDistributed(Conv1D(filters=64, kernel_size=1, activation='relu'), input_shape=(None, n_steps, n_features)))
model.add(TimeDistributed(Flatten()))

接下来,我们可以定义模型的 LSTM 部分,它解释 CNN 模型对输入序列的读取并做出预测。

...
model.add(LSTM(50, activation='relu'))
model.add(Dense(1))

运行该示例可预测序列中的下一个值,我们预计该值为 100。

[[101.69263]]

ConvLSTM

与 CNN-LSTM 相关的一种 LSTM 是 ConvLSTM,其中输入的卷积读取直接内置到每个 LSTM 单元中。

ConvLSTM 是为读取二维时空数据而开发的,但也可以适用于单变量时间序列预测。

该层期望输入是二维图像序列,因此输入数据的形状必须是:

[samples, timesteps, rows, columns, features]

为了我们的目的,我们可以将每个样本分成子序列,其中时间步长将成为子序列的数量,或_n_seq_,列将是每个子序列的时间步长数,或_n_steps_。当我们处理一维数据时,行数固定为 1。
我们现在可以将准备好的样本重塑为所需的结构。

# choose a number of time steps
n_steps = 4
# split into samples
X, y = split_sequence(raw_seq, n_steps)
# reshape from [samples, timesteps] into [samples, timesteps, rows, columns, features]
n_features = 1
n_seq = 2
n_steps = 2
X = X.reshape((X.shape[0], n_seq, 1, n_steps, n_features))

我们可以根据过滤器的数量将 ConvLSTM 定义为单层,根据(行,列)定义二维内核大小。当我们处理一维序列时,内核中的行数始终固定为 1。
然后必须将模型的输出展平,然后才能对其进行解释和做出预测。

...
model.add(ConvLSTM2D(filters=64, kernel_size=(1,2), activation='relu', input_shape=(n_seq, 1, n_steps, n_features)))
model.add(Flatten())

运行该示例可预测序列中的下一个值,我们预计该值为 100。
现在我们已经了解了单变量数据的 LSTM 模型,让我们将注意力转向多变量数据。

多元 LSTM 模型

多变量时间序列数据是指每个时间步长有多个观察值的数据。
对于多元时间序列数据,我们可能需要两个主要模型;他们是:

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

与单变量时间序列一样,我们必须将这些数据结构化为具有输入和输出元素的样本。
LSTM 模型需要足够的上下文来学习从输入序列到输出值的映射。LSTM 可以支持并行输入时间序列作为单独的变量或特征。因此,我们需要将数据拆分成样本,以保持两个输入序列之间的观察顺序。
如果我们选择三个输入时间步长,那么第一个样本将如下所示:

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)

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

运行示例首先打印 X 和 y 分量的形状。

我们可以看到 X 分量具有三维结构。

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

这是 LSTM 作为输入所期望的精确三维结构。数据无需进一步整形即可使用。

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

(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

我们现在已准备好在此数据上拟合 LSTM 模型。

可以使用上一节中的任何 LSTM 变体,例如 Vanilla、Stacked、Bidirectional、CNN 或 ConvLSTM 模型。

我们将使用 Vanilla LSTM,其中通过input_shape_参数为输入层指定时间步数和并行序列(特征)。

...
# define model
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

在进行预测时,模型需要两个输入时间序列的三个时间步长。
我们可以预测输出序列中的下一个值,提供以下输入值:

80,	 85
90,	 95
100, 105

具有三个时间步长和两个变量的一个样本的形状必须是 [1, 3, 2]。
我们期望序列中的下一个值是 100 + 105,即 205。

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

运行示例准备数据、拟合模型并进行预测。

[[208.13531]]

多个平行系列

另一个时间序列问题是存在多个并行时间序列并且必须为每个时间序列预测一个值的情况。
例如,给定上一节的数据:

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)

运行示例首先打印准备好的 X 和 y 组件的形状。

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

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

数据已准备好在 LSTM 模型中使用,该模型期望每个样本的 X 和 y 分量的三维输入和二维输出形状。

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

(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]

我们现在已准备好在此数据上拟合 LSTM 模型。

可以使用上一节中的任何 LSTM 变体,例如 Vanilla、Stacked、Bidirectional、CNN 或 ConvLSTM 模型。

_我们将使用 Stacked LSTM,其中通过input_shape_参数为输入层指定时间步数和并行序列(特征)。平行系列的数量也用于指定输出层模型预测的值的数量;再次,这是三个。

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

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

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

进行单个预测的输入形状必须为 1 个样本、3 个时间步长和 3 个特征,或 [1, 3, 3]

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

我们期望向量输出为:

[100, 105, 205]

运行示例准备数据、拟合模型并进行预测。

[[101.76599 108.730484 206.63577 ]]

多步 LSTM 模型

需要预测未来多个时间步长的时间序列预测问题可以称为多步时间序列预测。
具体来说,这些问题的预测范围或间隔超过一个时间步长。
有两种主要类型的 LSTM 模型可用于多步预测;他们是:

  1. 矢量输出模型
  2. 编码器-解码器模型

在我们看这些模型之前,让我们先看一下多步预测的数据准备。

数据准备

与一步预测一样,用于多步时间序列预测的时间序列必须拆分为具有输入和输出分量的样本。
输入和输出组件都将由多个时间步长组成,步数可能相同也可能不同。
例如,给定单变量时间序列:

[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]

现在我们知道了如何为多步预测准备数据,让我们看看一些可以学习这种映射的 LSTM 模型。

矢量输出模型

与其他类型的神经网络模型一样,LSTM 可以直接输出一个可以解释为多步预测的向量。
在上一节中看到了这种方法,每个输出时间序列的一个时间步被预测为一个向量。
与前一节中用于单变量数据的 LSTM 一样,必须首先对准备好的样本进行整形。LSTM 期望数据具有 [样本、时间步长、特征]的三维结构,在这种情况下,我们只有一个特征,因此重塑很简单。

...
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X = X.reshape((X.shape[0], X.shape[1], n_features))

通过在_n_steps_in_和_n_steps_out_变量中指定的输入和输出步数,我们可以定义一个多步时间序列预测模型。
可以使用任何提供的 LSTM 模型类型,例如 Vanilla、Stacked、Bidirectional、CNN-LSTM 或 ConvLSTM。下面定义了一个用于多步预测的 Stacked LSTM。

...
# define model
model = Sequential()
model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps_in, n_features)))
model.add(LSTM(100, activation='relu'))
model.add(Dense(n_steps_out))
model.compile(optimizer='adam', loss='mse')

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

[70, 80, 90]

我们期望预测输出为:

[100, 110]

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

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

运行示例预测并打印序列中接下来的两个时间步长。

[[100.98096 113.28924]]

编码器-解码器模型

专门为预测可变长度输出序列而开发的模型称为编码器-解码器 LSTM。
该模型专为同时存在输入和输出序列的预测问题而设计,即所谓的序列到序列或 seq2seq 问题,例如将文本从一种语言翻译成另一种语言。
该模型可用于多步时间序列预测。
顾名思义,该模型由两个子模型组成:编码器和解码器。
编码器是负责读取和解释输入序列的模型。编码器的输出是一个固定长度的向量,表示模型对序列的解释。编码器传统上是 Vanilla LSTM 模型,但也可以使用其他编码器模型,例如 Stacked、Bidirectional 和 CNN 模型。

...
model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features)))

解码器使用编码器的输出作为输入。
首先,重复编码器的固定长度输出,输出序列中每个所需的时间步重复一次。

...
model.add(RepeatVector(n_steps_out))

然后将该序列提供给 LSTM 解码器模型。该模型必须为输出时间步长中的每个值输出一个值,该值可以由单个输出模型解释。

...
model.add(LSTM(100, activation='relu', return_sequences=True))

我们可以使用相同的输出层或层来对输出序列中的每个一步进行预测。这可以通过将模型的输出部分包装在TimeDistributed 包装器中来实现。

....
model.add(TimeDistributed(Dense(1)))

下面列出了用于多步时间序列预测的编码器-解码器模型的完整定义。

# define model
model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features)))
model.add(RepeatVector(n_steps_out))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')

与其他 LSTM 模型一样,输入数据必须重新整形为预期的 [样本、时间步长、特征]的三维形状。

...
X = X.reshape((X.shape[0], X.shape[1], n_features))

在编码器-解码器模型的情况下,训练数据集的输出或 y 部分也必须具有此形状。这是因为该模型将为每个输入样本预测给定数量的时间步长和给定数量的特征。

...
y = y.reshape((y.shape[0], y.shape[1], n_features))

运行示例预测并打印序列中接下来的两个时间步长。

[[[101.9736  
  [116.213615]]]

多元多步 LSTM 模型

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

可以混合和匹配目前针对不同问题提出的不同类型的 LSTM 模型。这也适用于涉及多变量和多步预测的时间序列预测问题,但它可能更具挑战性。

在本节中,我们将提供多元多步时间序列预测的数据准备和建模的简短示例,作为缓解这一挑战的模板,具体而言:

  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
# covert 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]

我们现在可以开发用于多步预测的 LSTM 模型。

可以使用矢量输出或编码器-解码器模型。在这种情况下,我们将演示带有 Stacked LSTM 的向量输出。

运行该示例可以拟合模型并预测超出数据集的输出序列的接下来两个时间步长。

我们预计接下来的两个步骤是:[185, 205]

这是一个具有极少数据的具有挑战性的问题框架,并且模型的任意配置版本变得接近。

[[188.70619 210.16513]]

多路并行输入多步输出

并行时间序列的问题可能需要预测每个时间序列的多个时间步长。
例如,考虑上一节中的多元时间序列:

[[ 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
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import RepeatVector
from keras.layers import TimeDistributed

# 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
# covert 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]]

运行该示例可以拟合模型,并预测数据集末尾之后接下来两个时间步长的三个时间步长中每个时间步长的值。
我们期望这些序列和时间步长的值如下:

90, 95, 185
100, 105, 205

我们可以看到模型预测与预期值相当接近。

[[[ 91.86044   97.77231  189.66768 ]
  [103.299355 109.18123  212.6863  ]]]
  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值