https://zh.gluon.ai/chapter_recurrent-neural-networks/lang-model.html
翻譯自:
https://stackabuse.com/seaborn-library-for-data-visualization-in-python-part-1/
https://stackabuse.com/time-series-prediction-using-lstm-with-pytorch-in-python/
顧名思義,時間序列數據是隨時間變化的一種數據類型。例如,24小時內的溫度,一個月內各種產品的價格,一年中特定公司的股票價格。諸如長期短期記憶網絡(LSTM)之類的高級深度學習模型能夠捕獲時間序列數據中的模式,因此可用於對數據的未來趨勢進行預測。在本文中,您將看到如何使用LSTM算法使用時間序列數據進行將來的預測。
在我以前的一篇文章中,我解釋了如何使用Keras庫中的LSTM執行時間序列分析,以便預測未來的股價。在本文中,我們將使用PyTorch庫,它是深度學習最常用的Python庫之一。
在繼續之前,假定您已精通Python編程語言,並且已安裝PyTorch庫。此外,基本機器學習概念和深度學習概念的知識也會有所幫助。如果尚未安裝PyTorch,則可以使用以下pip命令進行安裝:
pip install pytorch
備注:安裝pytorch步驟詳見, https://www.cnblogs.com/emanlee/p/14332287.html
數據集和問題定義
我們將使用的數據集是Python Seaborn庫(seaborn
)內置的。 讓我們先導入所需的庫,然后再導入數據集:
安裝 seaborn
pip install seaborn conda install seaborn
導入庫
import torch import torch.nn as nn import seaborn as sns import numpy as np import pandas as pd
from pandas import read_csv
import matplotlib.pyplot as plt
讓我們顯示Seaborn庫內置的所有數據集的列表:
sns.get_dataset_names()
輸出:
['anscombe', 'attention', 'brain_networks', 'car_crashes', 'diamonds', 'dots', 'exercise', 'flights', 'fmri', 'gammas', 'iris', 'mpg', 'planets', 'tips', 'titanic']
我們將使用的數據集是flights(航班)數據集。 讓我們將數據集加載到我們的應用程序中,看看它的外觀(shape):
flight_data = sns.load_dataset("flights") flight_data.head()
備注:如果打開flights數據失敗,可以從這個網站下載數據
https://github.com/emanlee/data/blob/main/flights.csv
flight_data = read_csv("flights.csv")
flight_data.head()
數據集包含三列:年,月和乘客數量。乘客列包含指定月份的旅行乘客總數。 讓我們繪制數據集的形狀:
flight_data.shape
輸出:
(144, 3)
您可以看到數據集中有144行和3列,這意味着數據集包含12年的乘客旅行記錄。
任務是根據前132個月來預測后12個月內旅行的乘客人數。 請記住,我們有144個月的記錄,這意味着前132個月的數據將用於訓練我們的LSTM模型,而模型性能將使用最近12個月(后12個月)的值進行評估。
讓我們繪制每月乘客的出行頻率。 以下腳本增加了默認圖的大小:
fig_size = plt.rcParams["figure.figsize"] fig_size[0] = 15 fig_size[1] = 5 plt.rcParams["figure.figsize"] = fig_size
接下來的腳本繪制了乘客人數的每月頻率:
plt.title('Month vs Passenger') plt.ylabel('Total Passengers') plt.xlabel('Months') plt.grid(True) plt.autoscale(axis='x',tight=True) plt.plot(flight_data['passengers'])
輸出:
輸出顯示,多年來,乘飛機旅行的平均人數有所增加。 一年內旅行的乘客數量波動,這是有道理的,因為在暑假或寒假期間,旅行的乘客數量與一年中的其他部分相比有所增加。
數據預處理
數據集中的列類型為object,如以下代碼所示:
flight_data.columns
輸出:
Index(['year', 'month', 'passengers'], dtype='object')
第一步是將乘客列的類型更改為浮動類型(float類型)。
all_data = flight_data['passengers'].values.astype(float)
現在,如果您打印all_data numpy數組,則應該看到以下浮動類型值:
print(all_data)
輸出:
[112. 118. 132. 129. 121. 135. 148. 148. 136. 119. 104. 118. 115. 126. 141. 135. 125. 149. 170. 170. 158. 133. 114. 140. 145. 150. 178. 163. 172. 178. 199. 199. 184. 162. 146. 166. 171. 180. 193. 181. 183. 218. 230. 242. 209. 191. 172. 194. 196. 196. 236. 235. 229. 243. 264. 272. 237. 211. 180. 201. 204. 188. 235. 227. 234. 264. 302. 293. 259. 229. 203. 229. 242. 233. 267. 269. 270. 315. 364. 347. 312. 274. 237. 278. 284. 277. 317. 313. 318. 374. 413. 405. 355. 306. 271. 306. 315. 301. 356. 348. 355. 422. 465. 467. 404. 347. 305. 336. 340. 318. 362. 348. 363. 435. 491. 505. 404. 359. 310. 337. 360. 342. 406. 396. 420. 472. 548. 559. 463. 407. 362. 405. 417. 391. 419. 461. 472. 535. 622. 606. 508. 461. 390. 432.]
接下來,我們將數據集分為訓練集和測試集。 LSTM算法將在訓練集上進行訓練。然后將使用該模型對測試集進行預測。將預測結果與測試集中的實際值進行比較,以評估訓練后模型的性能。
前132條記錄將用於訓練模型,后12條記錄將用作測試集。以下腳本將數據分為訓練集和測試集。
test_data_size = 12 train_data = all_data[:-test_data_size] test_data = all_data[-test_data_size:]
現在讓我們打印測試和訓練集的長度:
print(len(train_data)) print(len(test_data))
輸出:
132 12
如果現在打印測試數據,您將看到它包含 all_data 數組中的最后12條記錄:
print(test_data)
輸出:
[417. 391. 419. 461. 472. 535. 622. 606. 508. 461. 390. 432.]
我們的數據集目前尚未規范化。與后幾年的乘客總數相比,最初幾年的乘客總數要少得多。標准化數據以進行時間序列預測非常重要。我們將對數據集執行最小/最大縮放,以在一定范圍的最小值和最大值之間將數據標准化。我們將使用sklearn.preprocessing 模塊中的 MinMaxScaler 類來縮放數據。
以下代碼使用最小/最大縮放器分別將最小值和最大值分別為-1和1歸一化。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(-1, 1)) train_data_normalized = scaler.fit_transform(train_data .reshape(-1, 1))
現在,讓我們打印歸一化后的訓練數據的前5條記錄和最后5條記錄。
print(train_data_normalized[:5]) print(train_data_normalized[-5:])
輸出:
[[-0.96483516] [-0.93846154] [-0.87692308] [-0.89010989] [-0.92527473]] [[1. ] [0.57802198] [0.33186813] [0.13406593] [0.32307692]]
您可以看到數據集值現在在-1和1之間。
重要的是,數據標准化僅應用於訓練數據,而不應用於測試數據。如果對測試數據進行歸一化,則某些信息可能會從訓練集中泄漏到測試集中。
下一步是將我們的數據集轉換為張量,因為PyTorch模型是使用張量訓練的。要將數據集轉換為張量,我們只需將數據集傳遞給FloatTensor對象的構造函數,如下所示:
train_data_normalized = torch.FloatTensor(train_data_normalized).view(-1)
最后的預處理步驟是將我們的訓練數據轉換為序列和相應的標簽。
您可以使用任何序列長度,這取決於領域知識。但是,在我們的數據集中,使用12的序列長度很方便,因為我們有每月數據,一年中有12個月。如果我們有每日數據,則更好的序列長度應該是365,即一年中的天數。因此,我們將訓練的輸入序列長度設置為12。
train_window = 12
接下來,我們將定義一個名為create_inout_sequences的函數。該函數將接受原始輸入數據,並將返回一個元組列表。在每個元組中,第一個元素將包含與12個月內旅行的乘客數量相對應的12個項目的列表,第二個元組元素將包含一個項目,即第12 + 1 個月內的乘客數量。【相當於拿一個長度為12的窗在132個元素上從左向右滑動,一次滑動一個元素;每滑動一次,把窗的右側緊鄰的一個元素當做標簽;窗可以滑動132-12=120次,因此獲得120個結果】
def create_inout_sequences(input_data, tw): inout_seq = [] L = len(input_data) for i in range(L-tw): train_seq = input_data[i:i+tw] train_label = input_data[i+tw:i+tw+1] inout_seq.append((train_seq ,train_label)) return inout_seq
執行以下腳本以創建序列和相應的標簽進行訓練:
train_inout_seq = create_inout_sequences(train_data_normalized, train_window)
如果您打印 train_inout_seq 列表的長度,您會看到它包含 120 個項目。 這是因為盡管訓練集包含132個元素,但是序列長度為12,這意味着第一個序列由前12個項目組成,第13個項目是第一個序列的標簽。 同樣,第二個序列從第二個項目開始,到第13個項目結束,而第14個項目是第二個序列的標簽,依此類推。
現在讓我們打印 train_inout_seq 列表的前5個項目:
train_inout_seq[:5]
輸出:
[(tensor([-0.9648, -0.9385, -0.8769, -0.8901, -0.9253, -0.8637, -0.8066, -0.8066, -0.8593, -0.9341, -1.0000, -0.9385]), tensor([-0.9516])), (tensor([-0.9385, -0.8769, -0.8901, -0.9253, -0.8637, -0.8066, -0.8066, -0.8593, -0.9341, -1.0000, -0.9385, -0.9516]), tensor([-0.9033])), (tensor([-0.8769, -0.8901, -0.9253, -0.8637, -0.8066, -0.8066, -0.8593, -0.9341, -1.0000, -0.9385, -0.9516, -0.9033]), tensor([-0.8374])), (tensor([-0.8901, -0.9253, -0.8637, -0.8066, -0.8066, -0.8593, -0.9341, -1.0000, -0.9385, -0.9516, -0.9033, -0.8374]), tensor([-0.8637])), (tensor([-0.9253, -0.8637, -0.8066, -0.8066, -0.8593, -0.9341, -1.0000, -0.9385, -0.9516, -0.9033, -0.8374, -0.8637]), tensor([-0.9077]))]
您可以看到每個項目都是一個元組,其中第一個元素由序列的12個項目組成 tensor([-0.9648, -0.9385, -0.8769, -0.8901, -0.9253, -0.8637, -0.8066, -0.8066, -0.8593, -0.9341, -1.0000, -0.9385]) ,第二個元組元素包含相應的標簽 tensor([-0.9516])。
創建LSTM模型
我們已經對數據進行了預處理,現在是時候訓練我們的模型了。我們將定義一個LSTM類,該類繼承自 PyTorch 庫的 nn.Module 類。
class LSTM(nn.Module): def __init__(self, input_size=1, hidden_layer_size=100, output_size=1): super().__init__() self.hidden_layer_size = hidden_layer_size self.lstm = nn.LSTM(input_size, hidden_layer_size) self.linear = nn.Linear(hidden_layer_size, output_size) self.hidden_cell = (torch.zeros(1,1,self.hidden_layer_size), torch.zeros(1,1,self.hidden_layer_size)) def forward(self, input_seq): lstm_out, self.hidden_cell = self.lstm(input_seq.view(len(input_seq) ,1, -1), self.hidden_cell) predictions = self.linear(lstm_out.view(len(input_seq), -1)) return predictions[-1]
總結一下以上代碼中發生的事情。 LSTM類的構造函數接受三個參數:
input_size:與輸入中的要素數量相對應。盡管我們的序列長度為12,但每個月我們只有1個值,即乘客總數,因此輸入大小為1。
hidden_layer_size:指定隱藏層數以及每層中的神經元數。我們將有一層100個神經元。
output_size:輸出中的項目數,由於我們要預測未來1個月的乘客人數,因此輸出大小為1。
接下來,在構造函數中,我們創建變量hidden_layer_size,lstm,linear和hidden_cell。 LSTM算法接受三個輸入:先前的隱藏狀態,先前的單元狀態和當前輸入。 hidden_cell變量包含先前的hidden和cell狀態。 lstm和線性層變量用於創建LSTM和線性層。
在forward方法內部,將input_seq作為參數傳遞,該參數首先通過lstm層傳遞。 lstm層的輸出是當前時間步的隱藏狀態和單元狀態,以及輸出。 lstm層的輸出將傳遞到線性層。預測的乘客人數存儲在預測列表的最后一項中,該列表返回到調用函數。
下一步是創建LSTM()類的對象,定義損失函數和優化器。由於我們正在解決分類問題,因此將使用交叉熵損失。對於優化器功能,我們將使用adam優化器。
model = LSTM() loss_function = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
讓我們打印模型:
print(model)
輸出:
LSTM( (lstm): LSTM(1, 100) (linear): Linear(in_features=100, out_features=1, bias=True) )
訓練模型
我們將訓練模型150個epoch。 如果需要,可以嘗試更多的epoch。 loss將每25個epoch打印一次。
epochs = 150 for i in range(epochs): for seq, labels in train_inout_seq: optimizer.zero_grad() model.hidden_cell = (torch.zeros(1, 1, model.hidden_layer_size), torch.zeros(1, 1, model.hidden_layer_size)) y_pred = model(seq) single_loss = loss_function(y_pred, labels) single_loss.backward() optimizer.step() if i%25 == 1: print(f'epoch: {i:3} loss: {single_loss.item():10.8f}') print(f'epoch: {i:3} loss: {single_loss.item():10.10f}')
輸出:
epoch: 1 loss: 0.00517058 epoch: 26 loss: 0.00390285 epoch: 51 loss: 0.00473305 epoch: 76 loss: 0.00187001 epoch: 101 loss: 0.00000075 epoch: 126 loss: 0.00608046 epoch: 149 loss: 0.0004329932
由於默認情況下權重是在 PyTorch 神經網絡中隨機初始化的,因此您可能會獲得不同的值。
做出預測
現在我們的模型已經訓練完畢,我們可以開始進行預測了。由於我們的測試集包含最近12個月的乘客數據,並且我們訓練了模型以使用12的序列長度進行預測。我們將首先從訓練集中過濾出最后12個值:
fut_pred = 12 test_inputs = train_data_normalized[-train_window:].tolist() print(test_inputs)
輸出:
[0.12527473270893097, 0.04615384712815285, 0.3274725377559662, 0.2835164964199066, 0.3890109956264496, 0.6175824403762817, 0.9516483545303345, 1.0, 0.5780220031738281, 0.33186814188957214, 0.13406594097614288, 0.32307693362236023]
您可以將上述值與 train_data_normalized 數據列表的最后12個值進行比較。
最初,test_inputs 項目將包含12個項目。在for循環中,這12個項目將用於對測試集中的第一個項目(即項目編號133)進行預測。然后將預測值附加到 test_inputs 列表中。在第二次迭代中,最后的12個項目將再次用作輸入,並將進行新的預測,然后將其再次添加到test_inputs列表中。由於測試集中有12個元素,因此for循環將執行12次。在循環結束時,test_inputs列表將包含24個項目。最后12個項目將是測試集的預測值。
以下腳本用於進行預測:
model.eval() for i in range(fut_pred): seq = torch.FloatTensor(test_inputs[-train_window:]) with torch.no_grad(): model.hidden = (torch.zeros(1, 1, model.hidden_layer_size), torch.zeros(1, 1, model.hidden_layer_size)) test_inputs.append(model(seq).item())
如果打印test_inputs列表的長度,您將看到它包含24個項目。可以按以下方式打印最后12個預測項目:
test_inputs[fut_pred:]
輸出:
[0.4574652910232544, 0.9810629487037659, 1.279405951499939, 1.0621851682662964, 1.5830546617507935, 1.8899496793746948, 1.323508620262146, 1.8764172792434692, 2.1249167919158936, 1.7745600938796997, 1.7952896356582642, 1.977765679359436]
需要再次提及的是,根據用於訓練LSTM的權重,您可能會獲得不同的值。
由於我們對訓練數據集進行了標准化,因此預測值也進行了標准化。我們需要將歸一化的預測值轉換為實際的預測值。我們可以通過將規范化的值傳遞給用於規范化數據集的最小/最大縮放器對象的inverse_transform方法來實現。
actual_predictions = scaler.inverse_transform(np.array(test_inputs[train_window:] ).reshape(-1, 1)) print(actual_predictions)
輸出:
[[435.57335371] [554.69182083] [622.56485397] [573.14712578] [691.64493555] [761.46355206] [632.59821111] [758.38493103] [814.91857016] [735.21242136] [739.92839211] [781.44169205]]
現在讓我們將預測值與實際值作圖。看下面的代碼:
x = np.arange(132, 144, 1) print(x)
輸出:
[132 133 134 135 136 137 138 139 140 141 142 143]
在上面的腳本中,我們創建一個列表,其中包含最近12個月的數值。第一個月的索引值為0,因此最后一個月的索引值為143。
在下面的腳本中,我們將繪制144個月的乘客總數以及最近12個月的預計乘客數量。
plt.title('Month vs Passenger') plt.ylabel('Total Passengers') plt.grid(True) plt.autoscale(axis='x', tight=True) plt.plot(flight_data['passengers']) plt.plot(x,actual_predictions) plt.show()
輸出:
繪制乘客總數
我們的LSTM所做的預測用橙色線表示。您可以看到我們的算法不太准確,但是它仍然能夠捕獲最近12個月內旅行的乘客總數的上升趨勢以及偶爾的波動。您可以嘗試在LSTM層中使用更多的epoch和更多的神經元,以查看是否可以獲得更好的性能。
為了更好地查看輸出,我們可以繪制出最后12個月的實際和預測乘客數量,如下所示:
plt.title('Month vs Passenger') plt.ylabel('Total Passengers') plt.grid(True) plt.autoscale(axis='x', tight=True) plt.plot(flight_data['passengers']) plt.plot(x,actual_predictions) plt.show()
輸出:
再次,預測不是很准確,但是該算法能夠捕獲趨勢,即未來幾個月的乘客數量應高於前幾個月,且偶爾會有波動。
結論
LSTM是解決序列問題最廣泛使用的算法之一。在本文中,我們看到了如何使用LSTM的時間序列數據進行未來的預測。您還看到了如何使用PyTorch庫實現LSTM,然后如何將預測結果與實際值作圖,以了解訓練后的算法的性能如何。