接上文,本文继续讲解如何开发LSTM时间序列预测模型–多步LSTM模型。
【Part1】如何开发LSTM实现时间序列预测详解 01 Univariate LSTM
【Part2】如何开发LSTM实现时间序列预测详解 02 Multivariate LSTM
多步时间序列预测:通过历史数据对未来多个时间步进行预测的时间序列预测问题。两种主要的LSTM模型可用于多步预测:
与一个时间步预测任务一样,用于多步时间序列预测的序列数据也必须分成具有输入和输出分量的样本。输入和输出由多个时间步的采样值组成,并且输入和预测输出可能具有不同的时间步数。例如,给定单变量时间序列:
[10, 20, 30, 40, 50, 60, 70, 80, 90]
假设我们通过以往三个时间步的数据来预测接下来两个时间步的数据,那么就可以把单变量时间序列划分为如下的样本:
[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]
以上样本生成的过程中,滑动窗口的窗口宽度为3,滑动步长为1,特征数为1,生成的样本数为5。
数据准备好之后,接下来重塑样本形状;定义模型,进行训练、测试。
与其他类型的神经网络模型一样,LSTM可以直接输出一个可以解释为多步预测的向量。在上一篇文章中,这种方法是将每个输出时间序列的一个时间步长作为向量进行预测。与单变量LSTM一样,再进行训练之前,必须重塑样本。顺序模型(Sequential)中,LSTM的第一层输入要求数据的形状为:[样本、时间步、特征]([samples, timesteps, features])
,这里的时间步(timesteps)其实就是窗口宽度,在我们的示例中,特征数为1,使用 reshape
方法进行重塑数据:
n_features = 1
X = X.reshape((X.shape[0], X.shape[1], n_features))
训练样本和样本标签的shape分别为:
(5, 3, 1), (5, 2)
本系列的第一篇文章中的任何一种LSTM都可以使用,例如Vanilla、Stacked、Bidirectional、CNN或Conv LSTM模型。此处使用Stacked LSTM进行演示。注意:此处的 n_features = 1
,因为只有一个时间序列并且是根据序列的之前三个值预测后两个值,这与多输入序列的特征数是不同的。一定要搞清楚样本、时间步、特征数的定义和相互关系,要不在开发过程中很容易感到困惑。
训练完成之后,在进行预测时,单个样本的形状也需要重塑为跟训练数据中单个训练样本相同的shape,在本文的示例中,尺寸应该为: [1、3、1]
。其中“1”表示单个样本,“3”表示一个样本包含多少个采样点,最后一个“1” 表示样本中的特征数,因为是单变量预测,所以为1。通过代码实现:
test_seq = array([70, 80, 90])
test_seq = test_seq.reshape((1, sw_width, n_features))
yhat = model.predict(test_seq, verbose=0)
专门为预测可变长度输出序列而开发的模型称为 编码器-解码器LSTM(Encoder-Decoder LSTM)。该模型是为序列到序列(seq2seq)的预测问题(即同时有输入和输出序列)而设计的,seq2seq模型常用在自然语言处理领域,例如语言翻译。该模型也可用于多步时间序列预测。顾名思义,该模型由两个子模型组成:编码器(Encoder)和解码器(Decoder)。
编码器是负责读取和解释输入序列的模型。编码器的输出是固定长度的向量,代表模型对序列的解释。传统上,该编码器是Vanilla LSTM模型,但也可以使用其他编码器模型,例如Stacked,Bidirectional和CNN模型。
model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features)))
解码器使用编码器的输出作为输入。首先,编码器的固定长度输出被重复,对于输出序列中的每个所需时间步重复一次。
model.add(RepeatVector(n_steps_out))
我们可以使用一个或多个相同的输出层在输出序列中的每个时间步进行预测。这可以通过将模型的输出部分包装在 TimeDistributed()
包装器中来实现。
完整代码:
model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features)))
model.add(RepeatVector(n_steps_out))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')
有几点关于以上API的说明:
1.keras.layers.RepeatVector(n)
(num_samples, features)
(num_samples, n, features)
2.LSTM()
的 return_sequences
参数
True
返回完整序列;设置为 False
返回输出序列中的最后一个输出;在这段时间的学习过程中,遇到了很多疑惑的地方,发现很多教程都没有对关键的地方做解释,可能是大神写的吧…咱也不知道,咱也不敢问,反正对我这样的小白不友好。
随着理解的加深,也算是对LSTM处理时间序列预测问题有些许领悟和体会了,于是参考Jason的博客,自己重新梳理总结了一遍,将代码中比较难理解的地方做了注释,小白应该也能看懂,算是保姆级教程了吧… 数据处理、转化过程、尺寸变化等信息也打印出来了,结合文章内容应该不难理解。
import numpy as np
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Dense, LSTM, Flatten, Bidirectional
from tensorflow.keras.layers import TimeDistributed,RepeatVector
class MultiStepModels:
'''
多时间步预测时间序列LSTM模型
'''
def __init__(self, train_seq, test_seq, sw_width, pred_length, features, epochs_num, verbose_set, flag=0):
'''
初始化变量和参数
'''
self.train_seq = train_seq
self.test_seq = test_seq
self.sw_width = sw_width
self.pred_length = pred_length
self.features = features
self.epochs_num = epochs_num
self.verbose_set = verbose_set
self.flag = flag
self.X, self.y = [], []
def split_sequence(self):
'''
该函数实现多输入序列数据的样本划分
'''
for i in range(len(self.train_seq)):
# 找到最后一个元素的索引,因为for循环中i从1开始,切片索引从0开始,切片区间前闭后开,所以不用减去1;
end_index = i + self.sw_width
# 找到需要预测指定时间步长的最后一个元素的索引;
out_end_index = end_index + self.pred_length
# 如果最后一个期望输出最后一个元素的索引大于序列中最后一个元素的索引则丢弃该样本;
# 这里len(self.sequence)没有减去1的原因是:保证最后一个元素的索引恰好等于序列数据索引时,能够截取到样本;
if out_end_index > len(self.train_seq) :
break
# 实现以滑动步长为1(因为是for循环),窗口宽度为self.sw_width的滑动步长取值;
seq_x, seq_y = self.train_seq[i:end_index], self.train_seq[end_index:out_end_index]
self.X.append(seq_x)
self.y.append(seq_y)
self.X, self.y = np.array(self.X), np.array(self.y)
self.X = self.X.reshape((self.X.shape[0], self.X.shape[1], self.features))
self.test_seq = self.test_seq.reshape((1, self.sw_width, self.features))
if self.flag == 1:
self.y = self.y.reshape((self.y.shape[0], self.y.shape[1], self.features))
else:
pass
for i in range(len(self.X)):
print(self.X[i], self.y[i])
print('X:\n{}\ny:\n{}\ntest_seq:\n{}\n'.format(self.X, self.y, self.test_seq))
print('X.shape:{}, y.shape:{}, test_seq.shape:{}\n'.format(self.X.shape, self.y.shape, self.test_seq.shape))
return self.X, self.y, self.test_seq
def stacked_lstm(self):
model = Sequential()
model.add(LSTM(100, activation='relu', return_sequences=True,
input_shape=(self.sw_width, self.features)))
model.add(LSTM(100, activation='relu'))
model.add(Dense(units=self.pred_length))
model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])
print(model.summary())
history = model.fit(self.X, self.y, epochs=self.epochs_num, verbose=self.verbose_set)
print('\ntrain_acc:%s'%np.mean(history.history['accuracy']), '\ntrain_loss:%s'%np.mean(history.history['loss']))
print('yhat:%s'%(model.predict(self.test_seq)),'\n-----------------------------')
def encoder_decoder_lstm(self):
model = Sequential()
model.add(LSTM(100, activation='relu',
input_shape=(self.sw_width, self.features)))
model.add(RepeatVector(self.pred_length))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])
print(model.summary())
history = model.fit(self.X, self.y, epochs=self.epochs_num, verbose=self.verbose_set)
print('\ntrain_acc:%s'%np.mean(history.history['accuracy']), '\ntrain_loss:%s'%np.mean(history.history['loss']))
print('yhat:%s'%(model.predict(self.test_seq)),'\n-----------------------------')
if __name__ == '__main__':
train_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
test_seq = np.array([70, 80, 90])
sliding_window_width = 3
predict_length = 2
n_features = 1
epochs_num = 100
verbose_set = 0
print('-------以下为 【向量输出 LSTM 模型】 相关信息------')
MultiStepLSTM = MultiStepModels(train_seq, test_seq, sliding_window_width, predict_length, n_features,
epochs_num, verbose_set)
MultiStepLSTM.split_sequence()
MultiStepLSTM.stacked_lstm()
print('-------以下为 【编码器-解码器 LSTM 模型】 相关信息------')
MultiStepLSTM = MultiStepModels(train_seq, test_seq, sliding_window_width, predict_length, n_features,
epochs_num, verbose_set, flag=1)
MultiStepLSTM.split_sequence()
MultiStepLSTM.encoder_decoder_lstm()
-------以下为 【向量输出 LSTM 模型】 相关信息------
[[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]
X:
[[[10]
[20]
[30]]
[[20]
[30]
[40]]
[[30]
[40]
[50]]
[[40]
[50]
[60]]
[[50]
[60]
[70]]]
y:
[[40 50]
[50 60]
[60 70]
[70 80]
[80 90]]
test_seq:
[[[70]
[80]
[90]]]
X.shape:(5, 3, 1), y.shape:(5, 2), test_seq.shape:(1, 3, 1)
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
lstm_4 (LSTM) (None, 3, 100) 40800
_________________________________________________________________
lstm_5 (LSTM) (None, 100) 80400
_________________________________________________________________
dense_2 (Dense) (None, 2) 202
=================================================================
Total params: 121,402
Trainable params: 121,402
Non-trainable params: 0
_________________________________________________________________
None
train_acc:1.0
train_loss:752.8949569225312
yhat:[[107.224014 119.64806 ]]
-----------------------------
-------以下为 【编码器-解码器 LSTM 模型】 相关信息------
[[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]]
X:
[[[10]
[20]
[30]]
[[20]
[30]
[40]]
[[30]
[40]
[50]]
[[40]
[50]
[60]]
[[50]
[60]
[70]]]
y:
[[[40]
[50]]
[[50]
[60]]
[[60]
[70]]
[[70]
[80]]
[[80]
[90]]]
test_seq:
[[[70]
[80]
[90]]]
X.shape:(5, 3, 1), y.shape:(5, 2, 1), test_seq.shape:(1, 3, 1)
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
lstm_6 (LSTM) (None, 100) 40800
_________________________________________________________________
repeat_vector_1 (RepeatVecto (None, 2, 100) 0
_________________________________________________________________
lstm_7 (LSTM) (None, 2, 100) 80400
_________________________________________________________________
time_distributed_1 (TimeDist (None, 2, 1) 101
=================================================================
Total params: 121,301
Trainable params: 121,301
Non-trainable params: 0
_________________________________________________________________
None
train_acc:0.0
train_loss:694.8172243946791
yhat:[[[102.96109 ]
[116.349144]]]
-----------------------------
本文是LSTM处理时间序列预测任务的第3篇,最后一篇介绍多变量多时间步预测的LSTM模型,请看下一篇文章。