今天我給大家介紹一個國外深度學習大牛Jason Brownlee寫的一篇關于多變量時間序列預測的博客,我在原文的代碼基礎上做了一點點修改,只是為了便于大家更好的理解。在本文中,您將了解如何在Keras深度學習庫中為多變量時間序列預測開發(fā)LSTM模型。讀完成本文后,您將了解:
如何將原始數據集轉換為可用于時間序列預測的數據。
如何準備數據并使LSTM適合多變量時間序列預測問題。
如何進行預測并將結果重新調整回原始單位。
在本文中,我們將使用空氣質量數據集。
這個數據集來自于在美國駐中國大使館收集的北京五年的每小時的空氣質量數據。
數據包括日期時間,PM2.5濃度,以及包括露點,溫度,氣壓,風向,風速和累積的下雪小時數的天氣信息。原始數據中的完整功能列表如下:
No : 行號
year,month,day,hour : 表示年,月,日,小時
pm2.5 : pm2.5污染濃度
DEWP : 露點(空氣中水氣含量達到飽和的氣溫,低于此溫度時水氣從空氣中析出凝成水珠)
TEMP : 溫度
PRES : 氣壓
cbwd :風向
Iws : 風速
ls : 累積的下雪小時數
lr : 累積的下小時數
我們可以使用這些數據并構建一個預測問題,我們想通過前一個小時的天氣條件和pm2.5濃度,來預測下一個小時的pm2.5濃度。
你們可以在這里下載空氣質量數據。
下面我們查看一下數據
- df = pd.read_csv('./data/pollution.csv')
- print(len(df))
- df.head(10)
- print(f.year.unique())
從上面的數據中我們看到數據集中總共有43824條記錄,時間是從2010至2014總共5年的數據,數據集中前n條數據的pm2.5的值是空值(NaN),下面我們要對數據做一些清洗工作,我們要合并年,月,日,小時并將其設置為索引例,同時刪除原來的年,月,日,小時字段。
- df['date']=df.apply(lambda x:datetime.datetime(x["year"], x["month"], x["day"],x["hour"]), axis=1)
- df = df.set_index(['date'])
- df.drop(['No','year','month','day','hour'], axis=1, inplace=True)
- df.head()
接下來我們重新命名數據集的字段名,并刪除前24條pm2.5的值為空的記錄
- df.columns = ['pollution', 'dew', 'temp', 'press', 'wnd_dir', 'wnd_spd', 'snow', 'rain']
- df['pollution'].fillna(0, inplace=True)
- # 刪除前24小時的數據
- df = df[24:]
- df.head()
下面我們對除風向(wnd_dir)以外的7個變量進行可視化,我們要查看一下它們的時序圖
- values = df.values
- # 指定要可視化的例
- groups = [0, 1, 2, 3, 5, 6, 7]
- i = 1
- # plot each column
- plt.figure(figsize=(8,6))
- for group in groups:
- plt.subplot(len(groups), 1, i)
- plt.plot(values[:, group])
- plt.title(df.columns[group], y=0.5, loc='right')
- i += 1
- plt.show()
在目前這個應用場景中,我們的特征變量有7個(除pollution變量以外的所有變量),而我們的目標變量只有一個(pollution),我們想通過前一小時的7個特征境變量來預測后一小時的污染濃度變量(pollution),這就是一個典型的多變量時間序列預測的問題。下面我們要使用LSTM模型來解決這個多變量時間序列的預測問題。
這里將是本文的一個重點,目前的我們的數據集是一個時間序列數據集,它沒有標簽,接下來我們要做的就是將時間序列數據轉換成監(jiān)督學習的數據,并且生成標簽。然后對輸入的變量進行標準化處理。 考慮到污染測量和前一時刻(t-1)的天氣條件,我們將監(jiān)督學習問題設定為預測當前小時(t)的污染濃度。
如何將時間序列數據轉換成監(jiān)督學習的數據?這里我們定義了一個函數:series_to_supervised(), 它通過數據平移的方式將我們的目標變量前移一個時間單位,這樣就形成了數據的標簽,有了標簽它就變成了監(jiān)督學習的數據,這樣我們就可以訓練lstm模型了。
- # 將序列轉換成監(jiān)督式學習
- def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
- n_vars = 1 if type(data) is list else data.shape[1]
- df = pd.DataFrame(data)
- cols, names = list(), list()
- # 輸入序列(t-n, ... t-1)
- for i in range(n_in, 0, -1):
- cols.append(df.shift(i))
- names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
- # 預測序列 (t, t+1, ... t+n)
- for i in range(0, n_out):
- cols.append(df.shift(-i))
- if i == 0:
- names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
- else:
- names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
- # 將他們整合在一起
- agg = pd.concat(cols, axis=1)
- agg.columns = names
- # 刪除那些包含空值(NaN)的行
- if dropnan:
- agg.dropna(inplace=True)
- return agg
- values = df.values
- #對風向字段進行編碼
- encoder = LabelEncoder()
- values[:,4] = encoder.fit_transform(values[:,4])
- # 確保所有變量都是實數型
- values = values.astype('float32')
- #對數據進行標準化出來
- scaler = MinMaxScaler(feature_range=(0, 1))
- scaled = scaler.fit_transform(values)
- # 將時間序列數據轉換成監(jiān)督學習數據
- reframed = series_to_supervised(scaled, 1, 1)
- # 刪除那些不需要預測的列
- reframed.drop(reframed.columns[[9,10,11,12,13,14,15]], axis=1, inplace=True)
- reframed.head()
從上面的輸出結果中我們看見var(t-1)至var8(t-1)為我們的前一小時的特征變量,var1(t)為當前時刻t的目標變量,var(t)是var(t-1)進行一次數據平移所產生的結果。這樣我們就可以利用t-1時刻的特征數據來預測t時刻污染濃度。
到目前為止我們已經有了特征集和標簽,下面我們就可以為LSTM模型準備訓練集和測試集了。由于這是時間序列的數據,t時刻的污染濃度和t-1時刻的天氣條件是有密切關系的,所以在準訓練集和測試集時,我們不能對原始數據進行亂序操作。這里我們要按順序將第一年的數據作為訓練集,將后面四年的數據作為測試集。然后我們將訓練集和測試集轉化成3維數組的格式。
- # 將數據分割為訓練集和測試集
- values = reframed.values
- n_train_hours = 365 * 24
- train = values[:n_train_hours, :]
- test = values[n_train_hours:, :]
- # 分離出特征集與標簽
- train_X, train_y = train[:, :-1], train[:, -1]
- test_X, test_y = test[:, :-1], test[:, -1]
- # 轉換成3維數組 [樣本數, 時間步 ,特征數]
- train_X = train_X.reshape((train_X.shape[0], 1, train_X.shape[1]))
- test_X = test_X.reshape((test_X.shape[0], 1, test_X.shape[1]))
- print(train_X.shape, train_y.shape, test_X.shape, test_y.shape)
我們將定義一個LSTM模型,在它的第一個隱藏層中有50個神經元,在輸出層中有1個神經元用于預測污染濃度。輸入數據的維度將是1個時間步(即1小時)和8個特征。
我們將使用平均絕對誤差(MAE)損失函數和隨機梯度下降的有效Adam版本。該模型的批量大小(batch_size)為72的50個訓練周期。請記住,Keras中LSTM的內部狀態(tài)在每個批次結束時重置,因此內部狀態(tài)可能是天數的函數,這可能會對你有幫助。
最后,我們通過在fit()方法中設置validation_data參數來跟蹤訓練期間的訓練和測試損失。在訓練結束時,對訓練和測試損失進行可視化。
- # 創(chuàng)建模型
- model = Sequential()
- model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2])))
- model.add(Dense(1))
- model.compile(loss='mae', optimizer='adam')
- print(model.summary())
- # 訓練模型
- history = model.fit(train_X, train_y, epochs=50, batch_size=72, validation_data=(test_X, test_y), verbose=2, shuffle=False)
- # 對損失進行可視化
- plt.plot(history.history['loss'], label='train')
- plt.plot(history.history['val_loss'], label='test')
- plt.legend()
- plt.show()
在模型訓練結束后,我們可以預測整個測試數據集。由于我們在訓練模型之前已經將所有的特征集和標簽進行了標準化處理即將數據值縮放到了0-1之間,因此我們的預測結果也是在0-1之間,為了對預測結果進行有效評估,我們必須將預測結果和標簽值全部轉換成沒有標準處理之前的實際值,這樣我們才能進行有效評估。
通過將預測結果和測試集的標簽值進行逆向縮放,我們就得到了實際值,這樣我們可以計算模型的誤差分數。我們使用均方根誤差(RMSE)作為評估指標。
- # 對測試集進行預測
- yhat = model.predict(test_X)
- test_X = test_X.reshape((test_X.shape[0], test_X.shape[2]))
- # 對預測值進行逆標準處理
- inv_yhat = np.concatenate((yhat, test_X[:, 1:]), axis=1)
- inv_yhat = scaler.inverse_transform(inv_yhat)
- inv_yhat = inv_yhat[:,0]
- # 對測試集標簽進行逆標準化處理
- test_y = test_y.reshape((len(test_y), 1))
- inv_y = np.concatenate((test_y, test_X[:, 1:]), axis=1)
- inv_y = scaler.inverse_transform(inv_y)
- inv_y = inv_y[:,0]
- # 計算 RMSE
- rmse = sqrt(mean_squared_error(inv_y, inv_yhat))
- print('Test RMSE: %.3f' % rmse)
前面這個模型我們是用t-1時刻的天氣數據來預測t時刻的污染濃度,也就是說我們只能預測未來1小時的污染情況,有沒有辦法讓我們的模型可以預測未來幾個小時的空氣污染情況呢?
為了解決預測未來n個小時的空氣污染濃度,我們只要將原先的特征集平移n個時間步,例如我們要預測未來3小時的污染濃度,我們只需要調用series_to_supervised方法,并將參數n_in設置為3,就可以將特征集往前平移3個時間步。
- # 設置預測未來3小時的污染
- n_hours = 3
- n_features = 8
- # 將時間序列轉換成監(jiān)督學習數據集
- reframed = series_to_supervised(scaled, n_hours, 1)
我們的數據集中有3 * 8 + 8列。我們將前24列作為前3小時(t-3,t-2,t-1)的特征。我們將在下一個小時(t)的污染變量作為標簽,如下所示:
下面我們要做的是步驟和之間模型的步驟一樣,首頁要分離出訓練集和測試集,然后要分離出特征集與標簽:
- # 分離訓練集和測試集
- values = reframed.values
- n_train_hours = 365 * 24
- train = values[:n_train_hours, :]
- test = values[n_train_hours:, :]
- # 分離特征集和標簽
- n_obs = n_hours * n_features
- train_X, train_y = train[:, :n_obs], train[:, -n_features]
- test_X, test_y = test[:, :n_obs], test[:, -n_features]
- print(train_X.shape, len(train_X), train_y.shape)
接下來我們要講訓練數據和測試數據轉換成3維數組格式,與之前的3維數組不一樣的是,此時我們三維數組的第二個維度將是3。
- # 轉換成3維數據格式 [樣本數, 時間步, 特征數]
- train_X = train_X.reshape((train_X.shape[0], n_hours, n_features))
- test_X = test_X.reshape((test_X.shape[0], n_hours, n_features))
- # 定義模型
- model = Sequential()
- model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2])))
- model.add(Dense(1))
- model.compile(loss='mae', optimizer='adam')
- # 訓練模型
- history = model.fit(train_X, train_y, epochs=50, batch_size=72, validation_data=(test_X, test_y), verbose=2, shuffle=False)
- # 可視化
- plt.plot(history.history['loss'], label='train')
- plt.plot(history.history['val_loss'], label='test')
- plt.legend()
- plt.show()
- # 對測試集進行預測
- yhat = model.predict(test_X)
- test_X = test_X.reshape((test_X.shape[0], n_hours*n_features))
- # 對預測數據進行逆標準化
- inv_yhat = np.concatenate((yhat, test_X[:, -7:]), axis=1)
- inv_yhat = scaler.inverse_transform(inv_yhat)
- inv_yhat = inv_yhat[:,0]
- # 對測試集標簽進行逆標準化
- test_y = test_y.reshape((len(test_y), 1))
- inv_y = np.concatenate((test_y, test_X[:, -7:]), axis=1)
- inv_y = scaler.inverse_transform(inv_y)
- inv_y = inv_y[:,0]
- # 計算均方根誤差
- rmse = sqrt(mean_squared_error(inv_y, inv_yhat))
- print('Test RMSE: %.3f' % rmse)