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

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

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

下面我們對(duì)除風(fēng)向(wnd_dir)以外的7個(gè)變量進(jìn)行可視化,我們要查看一下它們的時(shí)序圖 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ù)測(cè)模型在目前這個(gè)應(yīng)用場(chǎng)景中,我們的特征變量有7個(gè)(除pollution變量以外的所有變量),而我們的目標(biāo)變量只有一個(gè)(pollution),我們想通過(guò)前一小時(shí)的7個(gè)特征境變量來(lái)預(yù)測(cè)后一小時(shí)的污染濃度變量(pollution),這就是一個(gè)典型的多變量時(shí)間序列預(yù)測(cè)的問(wèn)題。下面我們要使用LSTM模型來(lái)解決這個(gè)多變量時(shí)間序列的預(yù)測(cè)問(wèn)題。 為L(zhǎng)STM模型準(zhǔn)備數(shù)據(jù)這里將是本文的一個(gè)重點(diǎn),目前的我們的數(shù)據(jù)集是一個(gè)時(shí)間序列數(shù)據(jù)集,它沒(méi)有標(biāo)簽,接下來(lái)我們要做的就是將時(shí)間序列數(shù)據(jù)轉(zhuǎn)換成監(jiān)督學(xué)習(xí)的數(shù)據(jù),并且生成標(biāo)簽。然后對(duì)輸入的變量進(jìn)行標(biāo)準(zhǔn)化處理。 考慮到污染測(cè)量和前一時(shí)刻(t-1)的天氣條件,我們將監(jiān)督學(xué)習(xí)問(wèn)題設(shè)定為預(yù)測(cè)當(dāng)前小時(shí)(t)的污染濃度。 如何將時(shí)間序列數(shù)據(jù)轉(zhuǎn)換成監(jiān)督學(xué)習(xí)的數(shù)據(jù)?這里我們定義了一個(gè)函數(shù):series_to_supervised(), 它通過(guò)數(shù)據(jù)平移的方式將我們的目標(biāo)變量前移一個(gè)時(shí)間單位,這樣就形成了數(shù)據(jù)的標(biāo)簽,有了標(biāo)簽它就變成了監(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ù)測(cè)序列 (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)
#對(duì)風(fēng)向字段進(jìn)行編碼 values[:,4] = encoder.fit_transform(values[:,4]) values = values.astype('float32') #對(duì)數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化出來(lái) scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(values) # 將時(shí)間序列數(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é)果中我們看見(jiàn)var(t-1)至var8(t-1)為我們的前一小時(shí)的特征變量,var1(t)為當(dāng)前時(shí)刻t的目標(biāo)變量,var(t)是var(t-1)進(jìn)行一次數(shù)據(jù)平移所產(chǎn)生的結(jié)果。這樣我們就可以利用t-1時(shí)刻的特征數(shù)據(jù)來(lái)預(yù)測(cè)t時(shí)刻污染濃度。 到目前為止我們已經(jīng)有了特征集和標(biāo)簽,下面我們就可以為L(zhǎng)STM模型準(zhǔn)備訓(xùn)練集和測(cè)試集了。由于這是時(shí)間序列的數(shù)據(jù),t時(shí)刻的污染濃度和t-1時(shí)刻的天氣條件是有密切關(guān)系的,所以在準(zhǔn)訓(xùn)練集和測(cè)試集時(shí),我們不能對(duì)原始數(shù)據(jù)進(jìn)行亂序操作。這里我們要按順序?qū)⒌谝荒甑臄?shù)據(jù)作為訓(xùn)練集,將后面四年的數(shù)據(jù)作為測(cè)試集。然后我們將訓(xùn)練集和測(cè)試集轉(zhuǎn)化成3維數(shù)組的格式。 # 將數(shù)據(jù)分割為訓(xùn)練集和測(cè)試集 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í)間步 ,特征數(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模型我們將定義一個(gè)LSTM模型,在它的第一個(gè)隱藏層中有50個(gè)神經(jīng)元,在輸出層中有1個(gè)神經(jīng)元用于預(yù)測(cè)污染濃度。輸入數(shù)據(jù)的維度將是1個(gè)時(shí)間步(即1小時(shí))和8個(gè)特征。 我們將使用平均絕對(duì)誤差(MAE)損失函數(shù)和隨機(jī)梯度下降的有效Adam版本。該模型的批量大小(batch_size)為72的50個(gè)訓(xùn)練周期。請(qǐng)記住,Keras中LSTM的內(nèi)部狀態(tài)在每個(gè)批次結(jié)束時(shí)重置,因此內(nèi)部狀態(tài)可能是天數(shù)的函數(shù),這可能會(huì)對(duì)你有幫助。 最后,我們通過(guò)在fit()方法中設(shè)置validation_data參數(shù)來(lái)跟蹤訓(xùn)練期間的訓(xùn)練和測(cè)試損失。在訓(xùn)練結(jié)束時(shí),對(duì)訓(xùn)練和測(cè)試損失進(jì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')

評(píng)估LSTM模型在模型訓(xùn)練結(jié)束后,我們可以預(yù)測(cè)整個(gè)測(cè)試數(shù)據(jù)集。由于我們?cè)谟?xùn)練模型之前已經(jīng)將所有的特征集和標(biāo)簽進(jìn)行了標(biāo)準(zhǔn)化處理即將數(shù)據(jù)值縮放到了0-1之間,因此我們的預(yù)測(cè)結(jié)果也是在0-1之間,為了對(duì)預(yù)測(cè)結(jié)果進(jìn)行有效評(píng)估,我們必須將預(yù)測(cè)結(jié)果和標(biāo)簽值全部轉(zhuǎn)換成沒(méi)有標(biāo)準(zhǔn)處理之前的實(shí)際值,這樣我們才能進(jìn)行有效評(píng)估。 通過(guò)將預(yù)測(cè)結(jié)果和測(cè)試集的標(biāo)簽值進(jìn)行逆向縮放,我們就得到了實(shí)際值,這樣我們可以計(jì)算模型的誤差分?jǐn)?shù)。我們使用均方根誤差(RMSE)作為評(píng)估指標(biāo)。 # 對(duì)測(cè)試集進(jìn)行預(yù)測(cè) yhat = model.predict(test_X) test_X = test_X.reshape((test_X.shape[0], test_X.shape[2])) # 對(duì)預(yù)測(cè)值進(jìn)行逆標(biāo)準(zhǔn)處理 inv_yhat = np.concatenate((yhat, test_X[:, 1:]), axis=1) inv_yhat = scaler.inverse_transform(inv_yhat) # 對(duì)測(cè)試集標(biāo)簽進(jìn)行逆標(biāo)準(zhǔn)化處理 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ù)測(cè)未來(lái)多天的污染濃度前面這個(gè)模型我們是用t-1時(shí)刻的天氣數(shù)據(jù)來(lái)預(yù)測(cè)t時(shí)刻的污染濃度,也就是說(shuō)我們只能預(yù)測(cè)未來(lái)1小時(shí)的污染情況,有沒(méi)有辦法讓我們的模型可以預(yù)測(cè)未來(lái)幾個(gè)小時(shí)的空氣污染情況呢? 為了解決預(yù)測(cè)未來(lái)n個(gè)小時(shí)的空氣污染濃度,我們只要將原先的特征集平移n個(gè)時(shí)間步,例如我們要預(yù)測(cè)未來(lái)3小時(shí)的污染濃度,我們只需要調(diào)用series_to_supervised方法,并將參數(shù)n_in設(shè)置為3,就可以將特征集往前平移3個(gè)時(shí)間步。 # 設(shè)置預(yù)測(cè)未來(lái)3小時(shí)的污染 # 將時(shí)間序列轉(zhuǎn)換成監(jiān)督學(xué)習(xí)數(shù)據(jù)集 reframed = series_to_supervised(scaled, n_hours, 1)
我們的數(shù)據(jù)集中有3 * 8 + 8列。我們將前24列作為前3小時(shí)(t-3,t-2,t-1)的特征。我們將在下一個(gè)小時(shí)(t)的污染變量作為標(biāo)簽,如下所示: 
下面我們要做的是步驟和之間模型的步驟一樣,首頁(yè)要分離出訓(xùn)練集和測(cè)試集,然后要分離出特征集與標(biāo)簽: 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)

接下來(lái)我們要講訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)轉(zhuǎn)換成3維數(shù)組格式,與之前的3維數(shù)組不一樣的是,此時(shí)我們?nèi)S數(shù)組的第二個(gè)維度將是3。 # 轉(zhuǎn)換成3維數(shù)據(jù)格式 [樣本數(shù), 時(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')

評(píng)估模型# 對(duì)測(cè)試集進(jìn)行預(yù)測(cè) yhat = model.predict(test_X) test_X = test_X.reshape((test_X.shape[0], n_hours*n_features)) # 對(duì)預(yù)測(cè)數(shù)據(jù)進(jìn)行逆標(biāo)準(zhǔn)化 inv_yhat = np.concatenate((yhat, test_X[:, -7:]), axis=1) inv_yhat = scaler.inverse_transform(inv_yhat) # 對(duì)測(cè)試集標(biāo)簽進(jìn)行逆標(biāo)準(zhǔn)化 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)

|