深度學習 - 時間序列分析例項(二)
上一篇介紹了基於LSTM模型預測24小時氣溫的基本過程,和實現程式碼。本篇就以下內容繼續探討:
如何緩解over-fitting(過擬合)?
如何評估模型預測能力?
其他常用模型
GRU模型
簡單RNN模型
雙向(Bidirectional)LSTM模型
多層LSTM模型
各個模型的預測能力比較
如何緩解over-fitting(過擬合)?
在上一篇的模型中,有73,345個需要訓練的模型引數,用於訓練的資料X_train僅有1000天的小時粒度的資料 [1000, 24, 14]。直觀上,如此小規模的資料要擬合如此多的自由引數,是很容易過擬合的。過擬合的模型泛化能力很差,也就是說,對於它沒有見過的資料輸入,預測結果將會很差。
針對神經網路模型,緩解過擬合的典型方法有:
資料正則化(規範化)
棄權(Dropout)
增加訓練樣本。比如:把1000天的資料增加10倍,到10000天
前兩種方法在上一篇的模型中都已經採用了。為了增強感性認識,現特意將引數 dropout = 0。2 改為 0,其他均不變,看看模型訓練的效果。程式碼如下:
model
<-
keras_model_sequential
()
%>%
layer_lstm
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
,
recurrent_dropout
=
0
,
#
enable
CUDNN
recurrent_activation
=
‘
sigmoid
’
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
執行fit訓練之後,訓練過程如下圖左側所示,也就是dropout = 0時的情況。作為對比,下圖右側是上一篇(dropout = 0。2)的訓練過程。
肉眼可見地,右側的綠色validation loss(驗證損失)曲線相對更加平穩,大的趨勢是在減小;
相反,左側的綠色validation loss隨著訓練loss下降的過程中,上下波動,未有明顯的下降趨勢,說明模型的泛化能力並未隨著loss下降而增強,甚至在減弱。
具體dropout設為多大合適呢?手工調調,然後看結果吧!筆者通常設為0。2,或0。3。
如何評估模型預測能力?
本例項是基於連續變數(氣溫)的預測,屬於迴歸(Regression)型別。
迴歸模型的評估方法,通常有:
MSE(均方誤差)
RMSE(均方根誤差)
殘差均值、殘差均方差
R-Square
MAE(平均絕對誤差)
視覺化方法:殘差圖、結果對照圖等。
由於MSE, RMSE的計算結果隨著樣本個數和樣本值的大小而變化,所以不好比較,在此省略。
上一篇結束於“結果對照圖”,下面計算殘差(= 預測值 – 實際值),如下:
rs
<-
matrix
(
rep
(
0
。
0
,
nrow
(
pred_test
)
*
ncol
(
pred_test
)),
nrow
=
nrow
(
pred_test
))
for
(
i
in
1
:
nrow
(
rs
)){
for
(
j
in
1
:
ncol
(
rs
)){
rs
[
i
,
j
]
<-
(
pred_test
[
i
,
j
]
*
col_stddevs
+
col_means
)
-
(
y_test_hour
[
i
,
j
,
1
]
*
col_stddevs
+
col_means
)
}
}
殘差值是二維矩陣rs[360, 24],其中24列對應的是預測24個小時中每一個小時的殘差。以第一列的前100個殘差值,畫點圖觀察一下分佈如下:
plot
(
rs
[
1
:
100
,
1
],
xlab
=
“時間點”
,
ylab
=
“殘差值”
)
abline
(
h
=
0
,
col
=
“grey”
)
殘差在0附件波動,看起來是隨機分佈的。如果殘差不是隨機分佈的,並且,看起來存在某種規律性,那麼需要仔細分析資料並檢視模型。因為,模型並沒有將資料中的規律榨乾,以致於仍然存在某種規律遺留在殘差中。
另外,我們有理由猜測:預測的24個小時的結果中,每一個小時的殘差會有一些不同。因此,從統計的意義上,計算這24個小時的殘差均值,和殘差均方差,並繪圖顯示。
par
(
mfrow
=
c
(
1
,
2
))
plot
(
sapply
(
data
。
frame
(
rs
),
mean
),
xlab
=
“預測時間點”
,
ylab
=
“殘差均值”
)
plot
(
sapply
(
data
。
frame
(
rs
),
sd
),
xlab
=
“預測時間點”
,
ylab
=
“殘差均方差”
)
從圖中可以看到,相鄰時間點的預測殘差存在相關性,筆者認為是合理的,因為,氣溫在相鄰時間點上本身就是相關的,比如:早上氣溫逐漸上升,黃昏時分氣溫逐漸下降。如果8時的預測結果比較差(殘差較大),那麼9時的預測也好不到哪裡去(殘差也會較大)。
為了方便模型比較,後續將計算MAE, R-Square值。為此,筆者寫了兩個函式用於計算MAE, R-Square值。
calc_mae
<-
function
(
residuals
){
return
(
round
(
mean
(
abs
(
residuals
)),
3
))
}
calc_r2
<-
function
(
residuals
,
actual
){
ssr
<-
sum
(
residuals
^
2
)
sst
<-
sum
(((
actual
-
mean
(
actual
))
*
col_stddevs
)
^
2
)
r2
<-
1
-
ssr
/
sst
return
(
round
(
r2
,
3
))
}
在上例中,計算MAE, R-Square如下:
calc_mae
(
rs
);
calc_r2
(
rs
,
y_test_hour
[,,
1
])
[1] 2。776
[1] 0。84
請注意,同一個模型在相同的訓練資料下,每次訓練的結果(模型引數)都會有差異的,因此,對同一批資料的預測、殘差,及MAE, R-Square的值都會不同。
我們將上述LSTM模型,重複執行100次,從模型定義、編譯、訓練、到結果預測,相應地得到每一次預測的殘差、MAE, R-Square的計算結果。改造後的程式碼如下:
model_lstm
<-
function
(){
model
<-
keras_model_sequential
()
%>%
layer_lstm
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
recurrent_activation
=
‘
sigmoid
’
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
model
%>%
compile
(
loss
=
“logcosh”
,
optimizer
=
optimizer_rmsprop
(),
metrics
=
list
(
“mean_squared_error”
)
)
history
<-
model
%>%
fit
(
x
=
X_train
,
y
=
y_train_hour
,
validation_split
=
0
。
2
,
#
validation_data
=
list
(
X_valid
,
y_valid
),
batch_size
=
batch_size
,
epochs
=
30
,
callbacks
=
callbacks
)
return
(
model
)
}
num
<-
100
rs_lstm
<-
matrix
(
rep
(
0
。
0
,
num
*
4
),
nrow
=
num
)
for
(
i
in
1
:
num
){
pred_test
<-
model_lstm
()%>%
predict
(
X_test
,
batch_size
=
batch_size
)
%>%
。[,
,
1
]
rs_lstm
[
i
,
]
<-
calc_model_lstm_residuals
(
pred_test
)
}
rs_lstm包含100行記錄,下面的表格顯示前面10次的預測評估結果:
訓練/預測序號
殘差均值
殘差均方差
MAE
R-Square
1
0。176
3。51
2。733
0。843
2
-0。1
3。40
2。653
0。852
3
-0。49
3。44
2。724
0。846
4
0。137
3。49
2。717
0。845
5
-0。372
3。53
2。788
0。839
6
-0。119
3。50
2。743
0。843
7
-0。129
3。51
2。744
0。843
8
-0。245
3。43
2。690
0。849
9
-0。124
3。45
2。688
0。848
10
-0。103
3。48
2。709
0。845
基於這100次模型預測的殘差均值、殘差均方差、MAE、R-Square,繪製分佈圖如下。同時,疊加藍色正態分佈曲線,中間藍色線代表中值,兩側綠色豎線代表p = 0。05下的雙側置信區間。
直方圖的密度不夠精細,而且看起來有點左偏,或右偏(綠色置信區間相對藍色中值線不對稱)。但是相對正態分佈曲線的擬合基本是一致的,“左偏”或“右偏”的一個重要因素是由於取樣資料量不夠(僅有100個測試結果)造成的。
注:連續執行100次LSTM模型的訓練和預測,在筆者的配置RTX4000的Ubuntu上運行了大約40分鐘。
基於上述結果,補充說明幾點如下:
殘差均值的中值是
-0.091
,基本上是在0附近。一個有效的模型,預測結果的殘差應該是均值為0的正態分佈,因此,此模型基本滿足。
殘差均方差、MAE、R-Square的均方差都很小,在很窄的區間(置信區間也比較窄)波動。這說明模型的每次執行結果是比較穩定的。
下面針對其他針對時間序列的常用深度學習模型,每一個都執行100次訓練和預測,並比較各個模型的預測評估指標。可以看出:只要模型是收斂的,每次訓練的模型,對資料的預測結果雖然有差異,但是差異一般都在可控範圍內。
其他用於時間序列預測的深度學習模型
LSTM作為典型的RNN (Recurrent Neural Network),被廣泛應用,此外,為了學習的目的,下面繼續探索其他幾個常用的、用於時間序列分析的深度學習模型,包括LSTM的擴充套件。
此外,常用的多層感知器模型MLP(Multilayer Perceptron)也可以用於時間序列分析,只是相對於上述模型,沒有任何優勢。這裡就不浪費版面了。
GRU模型
GRU是LSTM的變體。據說,可以有效緩解LSTM的梯度消失問題(vanish gradient problem)。由於GRU比LSTM少一個門(Gate),訓練的收斂時間會更快。
在保持各項引數與LSTM模型一樣的前提下,僅僅模型定義有一點不同(注意紅色GRU layer名稱),程式碼如下:
model_gru
<-
keras_model_sequential
()
%>%
layer_gru
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
簡單(Simple)RNN模型
RNN的結構有很多,此處Simple RNN代表最簡單的RNN模型。類似於前面的模型定義,僅僅紅色Simple RNN layer名稱不同。
model_rnn
<-
keras_model_sequential
()
%>%
layer_simple_rnn
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
雙向(Bidirectional)LSTM模型
Bidirectional LSTM相對於前面那個簡單的LSTM,通常能夠提供更優的表現,常用於自然語言處理(NLP)任務,有人稱它為自然語言處理的瑞士軍刀(曾經的)。
LSTM等RNN模型處理輸入資料的時候,是按照時間順序來處理的,與MLP, CNN等模型相比,充分利用時間順序的特性,因此是處理時序資料的更加合適的選擇。
Bidirectional LSTM在處理正向時序的同時,還處理了反向時序,所以,相當於包含了兩個LSTM網路模組,理論上能夠獲取單向LSTM可能忽略的特性。大多數實踐證明了Bidirectional LSTM的表現比單向LSTM更優。
Bidirectional LSTM的實現比較簡單,在第一個LSTM模型的基礎上簡單改造一下,在layer_lstm外面呼叫bidirectional即可。
model_lstm_bi
<-
keras_model_sequential
()
%>%
bidirectional
(
layer_lstm
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
recurrent_activation
=
‘
sigmoid
’
,
#
enable
CUDNN
return_sequences
=
TRUE
)
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
在模型定義完了之後,如果立即呼叫summary(model_lstm_bi)函式會報錯,因為bidirectional需要完成構造之後才能給出模型摘要資訊,這是這個模型的不同之處。
其次,由於是雙向的LSTM,模型引數幾乎翻了一倍。
第三,模型預測效果相對前面的幾個模型,好了不少,尤其是在極大值、極小值處。將這個模型輸出的結果和第一個LSTM模型輸出的結果進行比較:
下圖中,黑線代表真實值,紅線/紅點代表LSTM模型預測的結果,藍線/藍點代表Bidirectional LSTM模型預測的結果。很明顯,藍線/藍點更加接近真實值。
左邊這張圖選擇從100-150(因為中間包含了極小值)預測序列中的第一個小時(每個輸入預測後續20小時);
右邊這張圖顯示第一個預測序列的全部24小時的結果。
多層LSTM模型
前面所有的模型都是最小化模型的層級。下面試驗LSTM層之上再疊加一層網路,分兩種情況,看看預測效果如何。
第一種,兩層LSTM層。第二層LSTM輸出單元數也是128,方便和Bidirectional LSTM比較。
model_lstm2
<-
keras_model_sequential
()
%>%
layer_lstm
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
recurrent_activation
=
‘
sigmoid
’
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
layer_lstm
(
units
=
128
,
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
recurrent_activation
=
‘
sigmoid
’
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
第二種,在LSTM層之上疊加一層Dense(全連線層),每個時間節點輸出單元數為128,與LSTM + LSTM及Bidirectional LSTM引數設定一致,以便於比較效果。
model_lstm_dense
<-
keras_model_sequential
()
%>%
layer_lstm
(
units
=
128
,
input_shape
=
list
(
time_steps
,
14
),
dropout
=
0
。
2
,
recurrent_dropout
=
0
,
#
enable
CUDNN
recurrent_activation
=
‘
sigmoid
’
,
#
enable
CUDNN
return_sequences
=
TRUE
)
%>%
layer_dense
(
units
=
128
)
%>%
time_distributed
(
layer_dense
(
units
=
1
))
模型預測能力比較
上述所有6種模型都是基於不同神經網路元件搭建的淺層(除了最後一層作為輸出層time_distributed(layer_dense(units = 1)),僅有一到兩層元件)深度學習模型。為了方便比較,第一層都是採用128個輸出神經元(每個時間點)。
因為每一次執行,同一個模型會給出略微不同的訓練結果(模型引數)和預測結果。為了更好地比較模型的預測能力,如同前面針對LSTM模型所作的,筆者針對每一個模型,都執行100次,計算每一個模型下各個引數的中值(殘差均值、殘差均方差、MAE、R-Square)。
如下表所示,除了模型的自由引數數量,各個引數顯示的是基於100次訓練和預測結果,計算的中值。
Bidirectional LSTM相對其他模型而言算是鶴立雞群,MAE(越小越好)明顯小了很多,R-Square(越接近1越好)明顯大了很多。
為了方便比較,做了兩個箱型圖(Box plot),更好地比較MAE, R-Square在各個模型下的分佈情況:
Bidirectional LSTM無論是MAE,還是R-Square都是表現最好,一枝獨秀。
RNN相對所有其他模型而言效果最差,但是從肉眼可見的角度,除了Bidirectional LSTM, LSTM之外,和其他3個模型都存在箱形重疊,因此從統計的角度看,這種差別是不顯著的,所以不能說它和那3個模型在預測能力方面存在統計上的差別。
從箱型的寬度和尾部點數分佈來看,RNN, LSTM+Dense模型的穩定性稍差。
大概氣溫預測問題的複雜度相對較小,兩層LSTM模型相對於單層LSTM模型並未表現出優勢來。
下一步
本篇探討的都是簡單的、網路層數最小化的結構模型,模型的深度非常淺,除了最後輸出層(time_distributed(layer_dense(units = 1))),最多隻有一層,或兩層。當應用了NVIDIA GPU之後,每一論(epoch)的訓練時間都是秒殺(1秒以內),以致於都感受不到,不同模型在訓練時間上的差異。
下一篇繼續以LSTM作為基本元件,探討具有相對複雜結構的深度學習模型,並瞭解他們之間的差異和特性。
金戈老馬:深度學習 - 時間序列分析例項(一)
金戈老馬:深度學習 - 時間序列分析例項(三)
金戈老馬:時間序列分析 - 大資料競賽例項
我的專欄:
深度學習入門及實戰
汽車、出行大資料分析