使用LSTMs時間序列預測北京霧霾

翻譯自Jason Brownlee博客

一、前言

神經(jīng)網(wǎng)絡諸如長短期記憶(LSTM)遞歸神經(jīng)網(wǎng)絡,可以很輕松地對多變量輸入問題進行建模。

這在時間預測問題中非常有用,而經(jīng)典線性方法難以應對多變量預測問題。


本文講解了如何在Keras深度學習庫中,為多變量時間序列預測開發(fā)LSTM模型。

包含三塊內(nèi)容:

  • 如何將原始數(shù)據(jù)集轉(zhuǎn)換為可用于時間序列預測的數(shù)據(jù)集;
  • 如何準備數(shù)據(jù),并使LSTM模型適用于多變量時間序列預測問題;
  • 如何做預測,并將預測的結(jié)果重新調(diào)整為原始數(shù)據(jù)單位。

二、Python環(huán)境

你可以使用Python 2 或Python 3進行代碼編寫。

且需安裝scikit-learn、Numpy、Pandas、Matplotlib、 Scipy、Keras(2.0或更高版本)、TensorFlow或Theano backend等依賴包。

本試驗在Anaconda Jupyter Notebook中進行。

上述大部分依賴包均已內(nèi)置,但仍需要安裝單獨安裝TensorFlow、Theano backend。


三、數(shù)據(jù)集

這里使用空氣質(zhì)量數(shù)據(jù)集進行時間序列預測。

該數(shù)據(jù)集字段包括日期時間、PM2.5濃度、露點、溫度、風向、風速、雨雪累計小時數(shù)等,完整特征列表如下:

  • No:行號
  • year:該行記錄的年
  • month:該行記錄的月
  • day:該行記錄的日
  • hour:該行記錄的小時
  • pm2.5: PM2.5濃度(細顆粒物指環(huán)境空氣中空氣動力學當量直徑小于等于 2.5微米的顆粒物。它能較長時間懸浮于空氣中,其在空氣中含量濃度越高,就代表空氣污染越嚴重)
  • DEWP:露點(又稱露點溫度(Dew point temperature),在氣象學中是指在固定氣壓之下,空氣中所含的氣態(tài)水達到飽和而凝結(jié)成液態(tài)水所需要降至的溫度)
  • TEMP:溫度
  • PRES:大氣壓力
  • cbwd:組合風向
  • lws:累計風速
  • ls:累計小時下雪量
  • lr:累計小時下雨量

該數(shù)據(jù)集記錄了北京某段時間每小時的氣象情況和污染程度。

我們將根據(jù)前幾個小時的記錄預測下個小時的污染程度。

四、數(shù)據(jù)準備

先看看數(shù)據(jù)長什么樣:

    year    month   day hour    pm2.5   DEWP    TEMP    PRES    cbwd    Iws Is  Ir
No                                              
1   2010    1   1   0   NaN -21 -11.0   1021.0  NW  1.79    0   0
2   2010    1   1   1   NaN -21 -12.0   1020.0  NW  4.92    0   0
3   2010    1   1   2   NaN -21 -11.0   1019.0  NW  6.71    0   0
4   2010    1   1   3   NaN -21 -14.0   1019.0  NW  9.84    0   0
5   2010    1   1   4   NaN -20 -12.0   1018.0  NW  12.97   0   0

可以看到日期和時間是分開的,第一步把日期時間合并為一個datetime,以便將其作為Pandas里的索引。

看數(shù)據(jù)表可知,第一個24小時里,PM2.5這一列有很多空值。

因此,我們把第一個24小時里的數(shù)據(jù)行刪掉。

剩余的數(shù)據(jù)里面也有少部分空值,為了保持數(shù)據(jù)完整性和連續(xù)性,只要將空值填補為0即可。

下面的腳本處理順序:

  • 加載原始數(shù)據(jù)集;
  • 將日期時間合并解析為Pandas DataFrame索引;
  • 刪除No(序號)列,給剩下的列重新命名字段;
  • 替換空值為0,刪除第一個24小時數(shù)據(jù)行。
from pandas import read_csv
from datetime import datetime
# 加載數(shù)據(jù)
def parse(x):
    return datetime.strptime(x, '%Y %m %d %H')
dataset = read_csv('https://raw.githubusercontent.com/jbrownlee/Datasets/master/pollution.csv',  parse_dates = [['year', 'month', 'day', 'hour']], index_col=0, date_parser=parse)
#刪除No列
dataset.drop('No', axis=1, inplace=True)
# 修改剩余列名稱
dataset.columns = ['pollution', 'dew', 'temp', 'press', 'wnd_dir', 'wnd_spd', 'snow', 'rain']
dataset.index.name = 'date'
# 將所有空值替換為0
dataset['pollution'].fillna(0, inplace=True)
# 刪除前24小時行
dataset = dataset[24:]
# 打印前5行
print(dataset.head(5))
# 保存數(shù)據(jù)到pollution.csv
dataset.to_csv('pollution.csv')

打印前5行,并將數(shù)據(jù)保存到pollution.csv。

看一下處理后的數(shù)據(jù)集:


                     pollution  dew  temp   press wnd_dir  wnd_spd  snow  rain
date                                                                          
2010-01-02 00:00:00      129.0  -16  -4.0  1020.0      SE     1.79     0     0
2010-01-02 01:00:00      148.0  -15  -4.0  1020.0      SE     2.68     0     0
2010-01-02 02:00:00      159.0  -11  -5.0  1021.0      SE     3.57     0     0
2010-01-02 03:00:00      181.0   -7  -5.0  1022.0      SE     5.36     1     0
2010-01-02 04:00:00      138.0   -7  -5.0  1022.0      SE     6.25     2     0

原始數(shù)據(jù)集由于網(wǎng)絡問題,不好獲取。

大家如果想跑代碼,直接使用處理好后的pollution數(shù)據(jù),后臺回復pollution即可。

現(xiàn)在我們已經(jīng)獲得了易于使用的數(shù)據(jù)形式,接下來創(chuàng)建每一特征的分布圖表,更好地展示數(shù)據(jù)。

五、數(shù)據(jù)展示

加載pollution.csv文件,分別單獨繪制每一特征分布圖表。

風向這一特征是類別特征,不需要繪圖的。

from pandas import read_csv
from matplotlib import pyplot
#方便在瀏覽器中顯示圖標
%matplotlib inline
# 加載數(shù)據(jù)
dataset = read_csv('pollution.csv', header=0, index_col=0)
values = dataset.values
# 選擇指定列繪圖
groups = [0, 1, 2, 3, 5, 6, 7]
i = 1
# 為每一列繪制圖表
pyplot.figure()
for group in groups:
    pyplot.subplot(len(groups), 1, i)
    pyplot.plot(values[:, group])
    pyplot.title(dataset.columns[group], y=0.5, loc='right')
    i += 1
pyplot.show()

六、多變量LSTM預測模型

現(xiàn)在,我們將LSTM應用到實際問題中。

1、為LSTM模型準備數(shù)據(jù)

將數(shù)據(jù)集構(gòu)建為監(jiān)督學習問題,并且對輸入變量進行標準化。

在給定污染測量標準和前1個小時污染狀況的前提下,我們將構(gòu)建監(jiān)督學習問題以預測現(xiàn)在時段的污染情況。

該構(gòu)想實現(xiàn)起來很簡單,只是為了做個示范。你也可以探索其它設想,比如:

  • 基于天氣狀況和前24小時污染情況,預測下個小時污染情況
  • 如上預測下一個小時污染情況,并給出下一個小時的預期天氣狀況

我們可以使用series_to_supervised()函數(shù),將數(shù)據(jù)集構(gòu)建成適用于監(jiān)督學習的形式。

首先,加載pollution.csv數(shù)據(jù)集。對風速特征進行整數(shù)編碼,即類別標簽編碼,這可以使用獨熱向量編碼技術。

接下來,對所有特征數(shù)據(jù)標準化處理,刪去被預測的這一時段的天氣特征,完整代碼如下:

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import MinMaxScaler
from pandas import DataFrame
from pandas import concat
# 將數(shù)據(jù)轉(zhuǎn)換成監(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 = 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 = concat(cols, axis=1)
    agg.columns = names
    # 刪除空值行
    if dropnan:
        agg.dropna(inplace=True)
    return agg
 
# 加載數(shù)據(jù)
dataset = read_csv('pollution.csv', header=0, index_col=0)
values = dataset.values
# 對風向特征整數(shù)標簽化
encoder = LabelEncoder()
values[:,4] = encoder.fit_transform(values[:,4])
# 確保所有數(shù)據(jù)是浮點數(shù)類型
values = values.astype('float32')
# 對特征標準化
scaler = MinMaxScaler(feature_range=(0, 1))
scaled = scaler.fit_transform(values)
# 構(gòu)建成監(jiān)督學習問題
reframed = series_to_supervised(scaled, 1, 1)
# 刪除我們不想預測的天氣數(shù)據(jù)列,只留下pollution列
reframed.drop(reframed.columns[[9,10,11,12,13,14,15]], axis=1, inplace=True)
print(reframed.head())

運行代碼,打印出前5行已轉(zhuǎn)換的數(shù)據(jù)集。

我們可以看到8個輸入變量 var1(t-1)~var8(t-1) ,這是前一個小時天氣情況和污染情況。

還有一個輸出變量,是當前小時的污染情況,如下:

var1(t-1)  var2(t-1)  var3(t-1)  var4(t-1)  var5(t-1)  var6(t-1)  \
1   0.129779   0.352941   0.245902   0.527273   0.666667   0.002290   
2   0.148893   0.367647   0.245902   0.527273   0.666667   0.003811   
3   0.159960   0.426471   0.229508   0.545454   0.666667   0.005332   
4   0.182093   0.485294   0.229508   0.563637   0.666667   0.008391   
5   0.138833   0.485294   0.229508   0.563637   0.666667   0.009912   

   var7(t-1)  var8(t-1)   var1(t)  
1   0.000000        0.0  0.148893  
2   0.000000        0.0  0.159960  
3   0.000000        0.0  0.182093  
4   0.037037        0.0  0.138833  
5   0.074074        0.0  0.109658  

數(shù)據(jù)準備過程比較簡單,但有一些知識點可以另外深入研究下。比如:

  • 對風向進行獨熱向量編碼操作;
  • 通過差分和季節(jié)性調(diào)整平穩(wěn)所有series;
  • 把前多個小時的輸入作為變量預測該時段的情況。

考慮到在學習序列預測問題時,LSTM在時間上使用反向傳播,最后一點可能是最重要的。

2、定義和擬合模型

這一部分,我們將會在多變量輸入數(shù)據(jù)上擬合LSTM模型。

首先,分割訓練集和測試集。

為了加快這個演示模型的訓練,我們僅僅在第1年數(shù)據(jù)上擬合模型,然后在剩余4年的數(shù)據(jù)上對其進行評估。

如果你有時間,可以試試倒置一下,在前4年數(shù)據(jù)做訓練,最后1年數(shù)據(jù)做測試。

下面的示例將數(shù)據(jù)集拆分為訓練集和測試集,然后將訓練集和測試集分別拆分為輸入和輸出變量。

最后將輸入變量(X)轉(zhuǎn)變成LSTMs需要的三維格式,即[samples,timesteps,features]。

# 切分訓練集和測試機
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]
# 將輸入轉(zhuǎn)換為三維格式 [samples, timesteps, features]
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)

執(zhí)行上面代碼后,打印出訓練集和測試集輸出、輸出數(shù)據(jù)的規(guī)格。

大約9K小時的數(shù)據(jù)用于訓練,大約35K小時的數(shù)據(jù)用于測試。

(8760, 1, 8) (8760,) (35039, 1, 8) (35039,)

現(xiàn)在開始定義和擬合LSTM模型

第一個隱藏層中有50個神經(jīng)元,輸出層中有1個神經(jīng)元用于預測污染情況,輸入變量為一小時里的8個天氣和污染特征。

我們將使用平均絕對誤差損失函數(shù),以及隨機梯度下降高效Adam版本。

該模型訓練50次,批量大小為72。

請記住,Kearas中LSTM的內(nèi)部狀態(tài)在每個訓練批次結(jié)束后重置,所以作為若干天函數(shù)的內(nèi)部狀態(tài)可能會有作用。

最后,我們通過在fit()函數(shù)中設置validation_data參數(shù)來跟蹤訓練期間的訓練和測試損失。

在運行結(jié)束時,繪制訓練和測試損失趨勢線。

from sklearn.metrics import mean_squared_error
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
# 設計模型
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)
# 繪制損失趨勢線
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()

可以看到,測試損失低于訓練損失,該模型可能過度擬合訓練數(shù)據(jù)。

3、評估模型

擬合模型后,開始預測測試集。

將預測結(jié)果與測試集結(jié)合起來,并反轉(zhuǎn)縮放。

還要將測試集真實的污染結(jié)果數(shù)據(jù)和測試集結(jié)合起來,進行反轉(zhuǎn)縮放。

通過對比原始比例的預測值和實際值,我們可以計算模型的誤差分數(shù),這里計算誤差用均方根誤差。

from numpy import concatenate
from keras.layers import LSTM
from math import sqrt
# 開始預測
yhat = model.predict(test_X)
test_X = test_X.reshape((test_X.shape[0], test_X.shape[2]))
# 預測值反轉(zhuǎn)縮放
inv_yhat = concatenate((yhat, test_X[:, 1:]), axis=1)
inv_yhat = scaler.inverse_transform(inv_yhat)
inv_yhat = inv_yhat[:,0]
# 實際值反轉(zhuǎn)縮放
test_y = test_y.reshape((len(test_y), 1))
inv_y = concatenate((test_y, test_X[:, 1:]), 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)

打印出結(jié)果:

Test RMSE: 26.455
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容