|
今天我給大家介紹一個國外深度學(xué)習(xí)大牛Jason Brownlee寫的一篇關(guān)于多變量時間序列預(yù)測的博客,我在原文的代碼基礎(chǔ)上做了一點點修改,只是為了便于大家更好的理解。在本文中,您將了解如何在Keras深度學(xué)習(xí)庫中為多變量時間序列預(yù)測開發(fā)LSTM模型。讀完成本文后,您將了解: 如何將原始數(shù)據(jù)集轉(zhuǎn)換為可用于時間序列預(yù)測的數(shù)據(jù)。 如何準備數(shù)據(jù)并使LSTM適合多變量時間序列預(yù)測問題。 如何進行預(yù)測并將結(jié)果重新調(diào)整回原始單位。
空氣污染的預(yù)測在本文中,我們將使用空氣質(zhì)量數(shù)據(jù)集。 這個數(shù)據(jù)集來自于在美國駐中國大使館收集的北京五年的每小時的空氣質(zhì)量數(shù)據(jù)。 數(shù)據(jù)包括日期時間,PM2.5濃度,以及包括露點,溫度,氣壓,風(fēng)向,風(fēng)速和累積的下雪小時數(shù)的天氣信息。原始數(shù)據(jù)中的完整功能列表如下: No : 行號 year,month,day,hour : 表示年,月,日,小時 pm2.5 : pm2.5污染濃度 DEWP : 露點(空氣中水氣含量達到飽和的氣溫,低于此溫度時水氣從空氣中析出凝成水珠) TEMP : 溫度 PRES : 氣壓 cbwd :風(fēng)向 Iws : 風(fēng)速 ls : 累積的下雪小時數(shù) lr : 累積的下小時數(shù)
我們可以使用這些數(shù)據(jù)并構(gòu)建一個預(yù)測問題,我們想通過前一個小時的天氣條件和pm2.5濃度,來預(yù)測下一個小時的pm2.5濃度。 你們可以在這里下載空氣質(zhì)量數(shù)據(jù)。 下面我們查看一下數(shù)據(jù) df = pd.read_csv('./data/pollution.csv')

從上面的數(shù)據(jù)中我們看到數(shù)據(jù)集中總共有43824條記錄,時間是從2010至2014總共5年的數(shù)據(jù),數(shù)據(jù)集中前n條數(shù)據(jù)的pm2.5的值是空值(NaN),下面我們要對數(shù)據(jù)做一些清洗工作,我們要合并年,月,日,小時并將其設(shè)置為索引例,同時刪除原來的年,月,日,小時字段。 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)

接下來我們重新命名數(shù)據(jù)集的字段名,并刪除前24條pm2.5的值為空的記錄 df.columns = ['pollution', 'dew', 'temp', 'press', 'wnd_dir', 'wnd_spd', 'snow', 'rain'] df['pollution'].fillna(0, inplace=True)

下面我們對除風(fēng)向(wnd_dir)以外的7個變量進行可視化,我們要查看一下它們的時序圖 groups = [0, 1, 2, 3, 5, 6, 7] plt.figure(figsize=(8,6)) plt.subplot(len(groups), 1, i) plt.plot(values[:, group]) plt.title(df.columns[group], y=0.5, loc='right')

多變量LSTM預(yù)測模型在目前這個應(yīng)用場景中,我們的特征變量有7個(除pollution變量以外的所有變量),而我們的目標變量只有一個(pollution),我們想通過前一小時的7個特征境變量來預(yù)測后一小時的污染濃度變量(pollution),這就是一個典型的多變量時間序列預(yù)測的問題。下面我們要使用LSTM模型來解決這個多變量時間序列的預(yù)測問題。 為LSTM模型準備數(shù)據(jù)這里將是本文的一個重點,目前的我們的數(shù)據(jù)集是一個時間序列數(shù)據(jù)集,它沒有標簽,接下來我們要做的就是將時間序列數(shù)據(jù)轉(zhuǎn)換成監(jiān)督學(xué)習(xí)的數(shù)據(jù),并且生成標簽。然后對輸入的變量進行標準化處理。 考慮到污染測量和前一時刻(t-1)的天氣條件,我們將監(jiān)督學(xué)習(xí)問題設(shè)定為預(yù)測當前小時(t)的污染濃度。 如何將時間序列數(shù)據(jù)轉(zhuǎn)換成監(jiān)督學(xué)習(xí)的數(shù)據(jù)?這里我們定義了一個函數(shù):series_to_supervised(), 它通過數(shù)據(jù)平移的方式將我們的目標變量前移一個時間單位,這樣就形成了數(shù)據(jù)的標簽,有了標簽它就變成了監(jiān)督學(xué)習(xí)的數(shù)據(jù),這樣我們就可以訓(xùn)練lstm模型了。 # 將序列轉(zhuǎn)換成監(jiān)督式學(xué)習(xí) 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] cols, names = list(), list() for i in range(n_in, 0, -1): names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)] # 預(yù)測序列 (t, t+1, ... t+n) for i in range(0, n_out): cols.append(df.shift(-i)) names += [('var%d(t)' % (j+1)) for j in range(n_vars)] names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)] agg = pd.concat(cols, axis=1)
values[:,4] = encoder.fit_transform(values[:,4]) values = values.astype('float32') scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(values) # 將時間序列數(shù)據(jù)轉(zhuǎn)換成監(jiān)督學(xué)習(xí)數(shù)據(jù) reframed = series_to_supervised(scaled, 1, 1) reframed.drop(reframed.columns[[9,10,11,12,13,14,15]], axis=1, inplace=True)

從上面的輸出結(jié)果中我們看見var(t-1)至var8(t-1)為我們的前一小時的特征變量,var1(t)為當前時刻t的目標變量,var(t)是var(t-1)進行一次數(shù)據(jù)平移所產(chǎn)生的結(jié)果。這樣我們就可以利用t-1時刻的特征數(shù)據(jù)來預(yù)測t時刻污染濃度。 到目前為止我們已經(jīng)有了特征集和標簽,下面我們就可以為LSTM模型準備訓(xùn)練集和測試集了。由于這是時間序列的數(shù)據(jù),t時刻的污染濃度和t-1時刻的天氣條件是有密切關(guān)系的,所以在準訓(xùn)練集和測試集時,我們不能對原始數(shù)據(jù)進行亂序操作。這里我們要按順序?qū)⒌谝荒甑臄?shù)據(jù)作為訓(xùn)練集,將后面四年的數(shù)據(jù)作為測試集。然后我們將訓(xùn)練集和測試集轉(zhuǎn)化成3維數(shù)組的格式。 # 將數(shù)據(jù)分割為訓(xùn)練集和測試集 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] # 轉(zhuǎn)換成3維數(shù)組 [樣本數(shù), 時間步 ,特征數(shù)] 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模型我們將定義一個LSTM模型,在它的第一個隱藏層中有50個神經(jīng)元,在輸出層中有1個神經(jīng)元用于預(yù)測污染濃度。輸入數(shù)據(jù)的維度將是1個時間步(即1小時)和8個特征。 我們將使用平均絕對誤差(MAE)損失函數(shù)和隨機梯度下降的有效Adam版本。該模型的批量大小(batch_size)為72的50個訓(xùn)練周期。請記住,Keras中LSTM的內(nèi)部狀態(tài)在每個批次結(jié)束時重置,因此內(nèi)部狀態(tài)可能是天數(shù)的函數(shù),這可能會對你有幫助。 最后,我們通過在fit()方法中設(shè)置validation_data參數(shù)來跟蹤訓(xùn)練期間的訓(xùn)練和測試損失。在訓(xùn)練結(jié)束時,對訓(xùn)練和測試損失進行可視化。 model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2]))) model.compile(loss='mae', optimizer='adam')

訓(xùn)練LSTM模型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')

評估LSTM模型在模型訓(xùn)練結(jié)束后,我們可以預(yù)測整個測試數(shù)據(jù)集。由于我們在訓(xùn)練模型之前已經(jīng)將所有的特征集和標簽進行了標準化處理即將數(shù)據(jù)值縮放到了0-1之間,因此我們的預(yù)測結(jié)果也是在0-1之間,為了對預(yù)測結(jié)果進行有效評估,我們必須將預(yù)測結(jié)果和標簽值全部轉(zhuǎn)換成沒有標準處理之前的實際值,這樣我們才能進行有效評估。 通過將預(yù)測結(jié)果和測試集的標簽值進行逆向縮放,我們就得到了實際值,這樣我們可以計算模型的誤差分數(shù)。我們使用均方根誤差(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) 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) rmse = sqrt(mean_squared_error(inv_y, inv_yhat)) print('Test RMSE: %.3f' % rmse)

完善LSTM模型:預(yù)測未來多天的污染濃度前面這個模型我們是用t-1時刻的天氣數(shù)據(jù)來預(yù)測t時刻的污染濃度,也就是說我們只能預(yù)測未來1小時的污染情況,有沒有辦法讓我們的模型可以預(yù)測未來幾個小時的空氣污染情況呢? 為了解決預(yù)測未來n個小時的空氣污染濃度,我們只要將原先的特征集平移n個時間步,例如我們要預(yù)測未來3小時的污染濃度,我們只需要調(diào)用series_to_supervised方法,并將參數(shù)n_in設(shè)置為3,就可以將特征集往前平移3個時間步。 # 將時間序列轉(zhuǎn)換成監(jiān)督學(xué)習(xí)數(shù)據(jù)集 reframed = series_to_supervised(scaled, n_hours, 1)
我們的數(shù)據(jù)集中有3 * 8 + 8列。我們將前24列作為前3小時(t-3,t-2,t-1)的特征。我們將在下一個小時(t)的污染變量作為標簽,如下所示: 
下面我們要做的是步驟和之間模型的步驟一樣,首頁要分離出訓(xùn)練集和測試集,然后要分離出特征集與標簽: 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)

接下來我們要講訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)轉(zhuǎn)換成3維數(shù)組格式,與之前的3維數(shù)組不一樣的是,此時我們?nèi)S數(shù)組的第二個維度將是3。 # 轉(zhuǎn)換成3維數(shù)據(jù)格式 [樣本數(shù), 時間步, 特征數(shù)] 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))
訓(xùn)練模型model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2]))) 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')

評估模型yhat = model.predict(test_X) test_X = test_X.reshape((test_X.shape[0], n_hours*n_features)) # 對預(yù)測數(shù)據(jù)進行逆標準化 inv_yhat = np.concatenate((yhat, test_X[:, -7:]), axis=1) inv_yhat = scaler.inverse_transform(inv_yhat) 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) rmse = sqrt(mean_squared_error(inv_y, inv_yhat)) print('Test RMSE: %.3f' % rmse)

|