接上文,本文繼續講解如何開發LSTM時間序列預測模型–多步LSTM模型。
之前的文章:
【Part1】如何開發LSTM實現時間序列預測詳解 01 Univariate LSTM
【Part2】如何開發LSTM實現時間序列預測詳解 02 Multivariate LSTM
文章目錄
3. 多步 LSTM 模型
多步時間序列預測:通過歷史數據對未來多個時間步進行預測的時間序列預測問題。兩種主要的LSTM模型可用於多步預測:
- Vector Output Model
- Encoder-Decoder Model
3.1 準備數據
與一個時間步預測任務一樣,用於多步時間序列預測的序列數據也必須分成具有輸入和輸出分量的樣本。輸入和輸出由多個時間步的採樣值組成,並且輸入和預測輸出可能具有不同的時間步數。例如,給定單變量時間序列:
[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。
數據準備好之後,接下來重塑樣本形狀;定義模型,進行訓練、測試。
3.2 Vector Output Model
與其他類型的神經網絡模型一樣,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)
3.3 Encoder-Decoder Model
專門爲預測可變長度輸出序列而開發的模型稱爲 編碼器-解碼器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)
- n:整數,重複因子
- 輸入shape:
(num_samples, features)
- 輸出shape:
(num_samples, n, features)
2.LSTM()
的 return_sequences
參數
- 布爾值,設置爲
True
返回完整序列;設置爲False
返回輸出序列中的最後一個輸出;
3.4 完整代碼
在這段時間的學習過程中,遇到了很多疑惑的地方,發現很多教程都沒有對關鍵的地方做解釋,可能是大神寫的吧…咱也不知道,咱也不敢問,反正對我這樣的小白不友好。
隨着理解的加深,也算是對LSTM處理時間序列預測問題有些許領悟和體會了,於是參考Jason的博客,自己重新梳理總結了一遍,將代碼中比較難理解的地方做了註釋,小白應該也能看懂,算是保姆級教程了吧… 數據處理、轉化過程、尺寸變化等信息也打印出來了,結合文章內容應該不難理解。
3.4.1 數據處理與模型定義
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-----------------------------')
3.4.2 實例化代碼
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()
3.4.3 數據處理過程、訓練、測試信息詳情
-------以下爲 【向量輸出 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模型,請看下一篇文章。