Time series prediction — LSTM的各種用法

CW Lin
11 min readJun 16, 2022
Photo by Ismail Hamzah on Unsplash

前陣子剛好有機會碰到time series的題目,但在要使用 LSTM 時突然很猶豫到底該怎麼用比較好,於是便每種都使用看看,這篇以股價預測的資料作為範例紀錄以下這幾種做法:

  1. LSTM for multi-step prediction
  2. LSTM auto encoder model
  3. LSTM Seq2Seq model

LSTM的原理就不贅述了,網路上很多教學,不過當然還是首推李宏毅老師的課程,真的蠻仔細的 👍
另外會用 multi-step prediction 的原因是想說,如果有個模型可以輔助我去決定要不要買某檔股票,我應該不會只想知道隔天的預測,我可能會想知道未來一週(5天)的預測結果來做一個綜合的考量。

data

首先,我們要先準備一下實驗用的資料,就挑選2330 台積電近五年的資料來做實驗吧,可以在奇摩財經這邊直接下載csv

資料會長成像上面那樣,那我們這次的目標就是:

透過前四週(20天) 的 (open, high, low, close, volume) 的數據去預測未來一週(五天) 的 close price

也就是說我們每筆資料的input size是(seq_length, feature_dim) =(20, 5)

那接著就是把資料給整理成一個一個的 time windows 來做 model的 input:

sliding windows 示意圖 (source)

上面的程式碼做了四件事:

  1. normalization
  2. 切 validation set
  3. create sequence windows
  4. create train, val 的 dataloader

設定train ratio = 0.8,看一下收盤價的 trend:

2330 close price

然後看一下切 window 後資料的形狀:

print(X_train.shape) # torch.Size([624, 20, 5])
print(Y_train.shape) # torch.Size([624, 5])

我們一共切了624個長度為20的windows,每個window 的dimension是 5,
ok 那這樣資料就準備好了~接著就是來準備模型吧

LSTM for multi-step prediction

這個情境下的模型相對另外兩種來說比較簡單,就是直接output一個要預測多久未來長度的向量,大架構如下圖:

把他寫成 pytorch 程式碼:

這個模型架構基本上就是兩層 LSTM 然後接一層linear 調整成output的形狀

LSTM(
(lstm): LSTM(5, 128, num_layers=2, batch_first=True, dropout=0.2)
(linear): Linear(in_features=128, out_features=5, bias=True)
)

Note1: LSTM layer 出來的 (batch_size, sequence length)和 input 相同,只有 feature dim 被改變了

Note2: LSTM layer 出來有兩個東西 output, (hn, cn) 其中
output: 保存了最後一層,每個 time step的輸出 h,如果是雙向 LSTM,每個time step的輸出 h = [h正向, h逆向] (同 time step的正向和逆向的 h連起來)。
hn保存了每一層,最後一個 time step 的 h,而 cn 保存的是 c 的值

上面的程式碼,LSTM 出來後取最後一層的最後一個time step 的 state 來丟到linear layer(如下圖的紅框)

那接下來就是把這個模型給訓練起來就好,loss function使用 nn.MSELoss(),optimizer 使用 Ranger,並且使用 SAM 進行訓練。

我每個epoch 除了計算MSE loss 外,也把 input 和 target 換算回原來的scale(normalize的反運算)來比較直觀地看每個epoch 模型的誤差是多少

一共訓練了200 epoch,結果如下:

可以看到,最後val set MAE在未來五天的整體平均誤差來到17.5左右,其實還蠻爛的,我不會相信這個模型去買股票XD

後來又想說,每過一天我們又有新的收盤資料,應該會用新的資料來更新對未來2~5天的預測結果,所以如果我每次預測都只看最近一天的預測結果,來看看預測的準確度會如何:

得到的預測情形就會像這樣:

也就是說,如果我們只看即將到來的那一天的預測值,那麼 MAE 誤差變成14.9,而誤差百分比 MAPE 則是2.5%

LSTM Auto Encoder model

這個模型的想法是由 encoder 來讀前20天的資料,並且將這20天資料的日月精華傳給decoder,再由decoder 轉成想要的output shape。和前面不一樣的是,decoder 重構出來的資料不只是未來五天的close price,我打算output每個 feature 的預測(感覺也不一定要這樣,只是這樣比較有和encoder對稱一點的感覺),所以dimension 就是:

input: [batch_size, seq_length=20, feature_dim=5]
output: [batch_size, seq_length=5, feature_dim=5]

模型架構大概如下:

那來把這個架構給變成程式碼:

LSTMAutoencoder(
(encoder): LSTMEncoder(
(lstm1): LSTM(5, 64, batch_first=True)
(lstm2): LSTM(64, 36, batch_first=True)
)
(decoder): LSTMDecoder(
(lstm1): LSTM(36, 36, batch_first=True)
(lstm2): LSTM(36, 64, batch_first=True)
(output_layer): Linear(in_features=64, out_features=5, bias=True)
)
)

接下來就是訓練這個模型,但這個架構下我對未來五個feature 都有進行預測(open, high, low, close, volume),但其實我們只在意 close 的表現,所以對loss function 做了一下小修改:

loss_fn = weighted_MSE([0., 0., 0., 1., 0.]) #只看close price的維度

Note: 這邊可以試一下也給其他feature loss,若只給 close price權重,也可以把output改成只有一個維度 (BTW 我試了之後結果也差不多)

另外在 rescale_MAE loss 的部分(將MAE反算回原本scale 比較直觀來觀察),由於 output 多了一個維 [batch, seq, feature_dim] 所以這裡直接使用inverse transform 然後取close price 的dimension 的值:

接下來一樣使用ranger+SAM訓練200個epochs,結果如下:

最後val set MAE在未來五天的整體平均誤差來到 22.3 左右,比前一個模型直接做簡單預測的還爛 XD,不過auto-encoder 拿來進行預測,直觀來想也覺得好像怪怪的,AE一般來說是期望重構回和原始一樣的資料。

不管怎樣,也順便看一下只預測未來一天的話會是如何:

恩…看不出來這個模型在預測什麼東東

或許AE架構的模型還是比較適合使用在anomaly detection 的task.

LSTM Seq2Seq model

最後來看LSTM seq2seq model,其實這個架構是我覺得最符合股價預測這個題目的做法(如果預測天數>1天)

seq2seq

跟AE一樣先一個encoder 濃縮input的資訊,但 seq2seq 的 decoder 是一個一個依序產出,而每一個產出會是下一個時間點的input。

來看一下程式:

encoder 和之前一樣,就是一層LSTM而已,比較不一樣的是 decoder,因為decoder每一個 step 的 input 都只有一個時間點的資料,所以input size 變成 [batch_size, feature_size],另外 input 還多了一個 hidden 和 cell 給 LSTM (encoder最後的hidden, cell state 拿來給decoder的 initial hidden , cell state,一般我們直接套LSTM時不會給,pytorch直接 default 設 0)

decoder 經過一層 LSTM 後透過 linear 轉成 output size,而這個LSTM會在後面重複被調用(因為我們一次只預測一個,總共要預測未來五天,所以會調用5次,也就是下面seq2seq程式碼迴圈的部分)

把encoder 和decoder 組起來:

上面的程式碼其實就是在組下面這張圖的網路架構:

在decoder這邊會發現有一個teacher forcing,這是個訓練的小技巧,因為 decoder會把前一個時間點的輸出當成下一個時間點的輸入,如果我們在前個時間點做了錯誤的預測,那往後所有的時間點都會受到這個錯誤影響,也就是一步錯,步步錯的狀況

一步錯 步步錯

所以我們讓 teacher 介入去糾正預測出來的答案(input 正確答案),但也不能每次都介入,到時模型拿來預測真的上戰場時,就沒有老師來告訴你正確答案了,所以我們設一個 random flag,老師偶爾出來糾正一下,避免一直訓練不起來。

好啦,那模型也組好了,再來就把他訓練起來就行,一樣用 Ranger+SAM 訓練 200 個 epoch

最後 val set MAE在未來五天的整體平均誤差來到 14.5左右

接著一樣來看看,如果只看最近一天的預測值,結果會是如何。 由於是對未來的預測,所以不會有正確答案來給我們 input,可以創一個 psedo的答案來input 然後設定 teacher_forcing_ratio=0 來完成預測:

最後就會得到:

平均誤差約 12.75,誤差率為2.1%,是目前表現最好的架構呢^^

Conclusion

這邊比較了LSTM的幾種用法:

multi-step prediction vs. Auto Encoder vs. Seq2Seq

這三種方法用在股價預測未來 5 天的表現,結果如下

以 MAE 的表現來看 seq2seq 的表現是最好的, MAPE 大概是 2.1%。

若一次預測未來一小段time series的命題下,seq2seq 應該是最適合的做法,若是對一段time series的資料做 classification 或 regression 可能一般的LSTM 直接做預測會比較好又簡單,如過是要做time series的anomaly detection 則 AutoEncoder 或許會是比較好的選擇~

最後,如果是資料量比較大的情況下,這三種做法都可以把 LSTM 換成transformer (self-attention) 計算應該會比較快也比較準。

--

--