如何建立Multi-Step(多步預測)的LSTM時間序列模型(以對家庭用電預測爲例)

    譯自How to Develop LSTM Models for Multi-Step Time Series Forecasting of Household Power Consumption~


    隨着智能電錶的興起和太陽能電池板等發電技術的廣泛應用,有大量可用的用電數據。這些數據代表了一系列與電力相關的多元時間序列,進而可以用來建模甚至預測未來的用電量。

    與其他機器學習算法不同,長短時記憶循環神經網絡能夠從序列數據中自動學習特徵,支持多變量數據,並能輸出可用於多步預測的變長序列。

    在本教程中,您將瞭解如何開發長期短期記憶循環神經網絡,用於多步驟時間序列預測家庭用電量。

完成本教程後,您將知道:

  • 如何開發和評估用於多步時間序列預測的單變量和多變量編解碼器LSTMs。
  • 如何建立和評價一個用於多步時間序列預測的CNN-LSTM編解碼器模型。
  • 如何建立和評價用於多步時間序列預測的ConvLSTM編解碼器模型。

讓我們開始吧。

0 教程概述

    本教程分爲九個部分;它們是:

  1. 問題描述
  2. 加載和準備數據集
  3. 模型評價
  4. 用於多步預測的LSTMs
  5. 具有單變量輸入和向量輸出的LSTM模型
  6. 單變量輸入的編解碼器LSTM模型
  7. 多變量輸入的LSTM模型
  8. 具有單變量輸入的CNN-LSTM編解碼器模型
  9. 單變量輸入的ConvLSTM編解碼器模型

1 問題描述

    “家庭用電量”數據集是一個多元時間序列數據集,描述一個家庭四年以上的用電量。這些數據是在2006年12月至2010年11月期間收集的,每分鐘收集一次對家庭用電情況的觀察。

    它是由7個變量(除日期和時間外)組成的多元系列;它們是:

  • global_active_power:家庭消耗的總有功功率(千瓦)。
  • global_reactive_power:家庭消耗的總無功功率(千瓦)。
  • voltage:平均電壓(伏特)。
  • global_intensity:平均電流強度(amps)。
  • sub_metering_1:廚房用的有功能(有功能的瓦特小時)。
  • sub_metering_2:洗衣用的有功能(有功能的瓦特小時)。
  • sub_metering_3:用於氣候控制系統的有功能(有功能瓦特小時)。

    有功和無功是指交流電流的技術細節。

    通過從總有功功率中減去定義的三個子計量變量之和,可以得到第四個子計量變量:

sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3)

2 加載和準備數據集

2.1 數據加載

    數據集可以從UCI機器學習存儲庫下載爲一個20 mb .zip文件:

    下載數據集並將其解壓縮到當前工作目錄中。現在您將擁有文件“household_power_consumption”。它的大小約爲127mb,包含所有的觀測數據。

    我們可以使用read_csv()函數加載數據,並將前兩列合併爲一個日期-時間列,作爲索引使用。

# load all data
dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime'])

2.2 數據清預處理

    接下來,我們可以用'標記所有缺失的值?'字符,帶有NaN值,這是一個浮點數。

    這將允許我們將數據作爲一個浮點值數組來處理,而不是混合類型(效率較低)。

# load and clean-up data
from numpy import nan
from numpy import isnan
from pandas import read_csv
from pandas import to_numeric

# mark all missing values
dataset.replace('?', nan, inplace=True)
# make dataset numeric
dataset = dataset.astype('float32')

    既然已經標記了缺失的值,我們還需要填充它們。

    一個非常簡單的方法是複製前一天同一時間的觀察結果。我們可以在一個名爲fill_missing()的函數中實現這一點,該函數將獲取數據的NumPy數組並複製24小時前的值。

# fill missing values with a value at the same time one day ago
def fill_missing(values):
	one_day = 60 * 24
	for row in range(values.shape[0]):
		for col in range(values.shape[1]):
			if isnan(values[row, col]):
				values[row, col] = values[row - one_day, col]

    我們可以將此函數直接應用於DataFrame中的數據。

# fill missing
fill_missing(dataset.values)

    現在,我們可以使用上一節的計算創建一個包含子計量其餘部分的新列。

# add a column for for the remainder of sub metering
values = dataset.values
dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6])

    現在,我們可以將數據集的清理版本保存到一個新文件中;在本例中,我們將文件擴展名更改爲.csv,並將數據集保存爲' household_power_consumption.csv '。

# save updated dataset
dataset.to_csv('household_power_consumption.csv')

    將所有這些結合在一起,下面列出了加載、清理和保存數據集的完整示例。

# load and clean-up data
from numpy import nan
from numpy import isnan
from pandas import read_csv
from pandas import to_numeric

# fill missing values with a value at the same time one day ago
def fill_missing(values):
	one_day = 60 * 24
	for row in range(values.shape[0]):
		for col in range(values.shape[1]):
			if isnan(values[row, col]):
				values[row, col] = values[row - one_day, col]

# load all data
dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime'])
# mark all missing values
dataset.replace('?', nan, inplace=True)
# make dataset numeric
dataset = dataset.astype('float32')
# fill missing
fill_missing(dataset.values)
# add a column for for the remainder of sub metering
values = dataset.values
dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6])
# save updated dataset
dataset.to_csv('household_power_consumption.csv')

3 模型評價

    在本節中,我們將考慮如何爲家用電力數據集開發和評估預測模型。

    本節共分爲四個部分;它們是:

  1. 問題的框架
  2. 評價指標
  3. 訓練及測試設備
  4. Walk-Forward驗證

3.1 問題的框架

    有許多方法可以利用和探索家庭能耗數據集。

    在本教程中,我們將使用這些數據來探索一個非常具體的問題;那就是:

考慮到最近的用電量,未來一週的預期用電量是多少?

    這需要一個預測模型預測未來七天每天的總有功功率。

    從技術上講,這個問題的框架被稱爲一個多步驟時間序列預測問題,給出了多個預測步驟。利用多個輸入變量的模型可稱爲多元多步時間序列預測模型。

    這種模式可以幫助家庭內部規劃支出。它還可以在供應方面幫助規劃特定家庭的電力需求。

    數據集的這一框架還表明,將每分鐘的耗電量觀測數據降至每日總耗電量是有用的。這不是必需的,但是有意義,因爲我們對每天的總功率感興趣。

    我們可以很容易地在pandas DataFrame上使用resample()函數來實現這一點。使用參數“D”調用此函數允許按日期-時間索引的加載數據按天分組(請參閱所有偏移別名)。然後,我們可以計算每天所有觀察值的總和,併爲這八個變量中的每一個創建一個新的日能耗數據集。

    完整的示例如下所示。

# resample minute data to total for each day
from pandas import read_csv
# load the new file
dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
# resample data to daily
daily_groups = dataset.resample('D')
daily_data = daily_groups.sum()
# summarize
print(daily_data.shape)
print(daily_data.head())
# save
daily_data.to_csv('household_power_consumption_days.csv')

    運行該示例將創建一個新的每日總能耗數據集,並將結果保存到一個名爲“household_power_consumption ption_days.csv”的單獨文件中。

    我們可以使用這個數據集來擬合和評估問題所選框架的預測模型。

3.2 評價指標

    預測將由7個值組成,每個值代表未來一週的每一天。

    在多步預測問題中,通常對每個預測的時間步分別進行評估。這樣做有幾個好處:

  • 在特定的準備時間(例如+1天vs +3天)對技能進行評價。
  • 根據不同的交付時間(例如,擅長+1天的模型與擅長+5天的模型),對模型進行比較。

    總功率的單位是千瓦,有一個同樣單位的誤差度量是很有用的。均方根誤差(RMSE)和平均絕對誤差(MAE)都符合這一要求,但是RMSE更常用,並將在本教程中採用。與MAE不同,RMSE對預測錯誤的懲罰更大。

    這個問題的性能指標將是從第1天到第7天的每個交付時間的RMSE。

    作爲一種捷徑,使用單個分數來總結模型的性能,以輔助模型選擇,這可能是有用的。

    可以使用的一個可能的評分是所有預測日的RMSE。

    下面的evaluate_forecasts()函數將實現此行爲,並返回基於多個七天預測的模型的性能。

# evaluate one or more weekly forecasts against expected values
def evaluate_forecasts(actual, predicted):
	scores = list()
	# calculate an RMSE score for each day
	for i in range(actual.shape[1]):
		# calculate mse
		mse = mean_squared_error(actual[:, i], predicted[:, i])
		# calculate rmse
		rmse = sqrt(mse)
		# store
		scores.append(rmse)
	# calculate overall RMSE
	s = 0
	for row in range(actual.shape[0]):
		for col in range(actual.shape[1]):
			s += (actual[row, col] - predicted[row, col])**2
	score = sqrt(s / (actual.shape[0] * actual.shape[1]))
	return score, scores

    運行該函數將首先返回總RMSE,而不考慮日期,然後返回每天的RMSE得分數組。

3.3 訓練及測試集

    我們將使用前三年的數據來訓練預測模型,並使用最後一年的數據來評估模型。

    給定數據集中的數據將被劃分爲標準周。這些星期從星期天開始,到星期六結束。

    這是使用所選擇的模型框架的一種現實而有用的方法,可以預測未來一週的電力消耗。它還有助於建模,在建模中,模型可以用來預測特定的一天(例如週三)或整個序列。

    我們將把數據分成標準周,從測試數據集中向後工作。

    數據的最後一年是2010年,2010年的第一個週日是1月3日。該數據將於2010年11月中旬結束,其中最近的一個週六是11月20日。這給出了46周的測試數據。

    下面提供測試數據集的每日數據的第一行和最後一行,以供確認。

2010-01-03,2083.4539999999984,191.61000000000055,350992.12000000034,8703.600000000033,3842.0,4920.0,10074.0,15888.233355799992
...
2010-11-20,2197.006000000004,153.76800000000028,346475.9999999998,9320.20000000002,4367.0,2947.0,11433.0,17869.76663959999

    下面的函數split_dataset()將日常數據分解爲訓練和測試集,並將每個數據組織爲標準周。

    特定的行偏移量用於使用數據集的知識分割數據。然後,使用NumPy split()函數將分割的數據集組織爲每週的數據。

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

     我們可以通過加載日常數據集並打印來自火車和測試集的第一行和最後一行數據來測試這個函數,以確認它們符合上面的期望。完整的代碼示例如下所示。

# split into standard weeks
from numpy import split
from numpy import array
from pandas import read_csv

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

# load the new file
dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
train, test = split_dataset(dataset.values)
# validate train data
print(train.shape)
print(train[0, 0, 0], train[-1, -1, 0])
# validate test
print(test.shape)
print(test[0, 0, 0], test[-1, -1, 0])

    運行該示例表明,訓練數據集確實有159周的數據,而測試數據集有46周。

    我們可以看到,訓練和測試數據集的第一行和最後一行的總有功功率與我們爲每個集合定義的標準周的界限指定的特定日期的數據相匹配。

(159, 7, 8)
3390.46 1309.2679999999998
(46, 7, 8)
2083.4539999999984 2197.006000000004

3.4 Walk-Forward驗證

    模型將使用一種稱爲Walk-Forward驗證的方案進行評估。

    這是需要模型進行一週預測的地方,然後將該周的實際數據提供給模型,以便可以將其用作對下一週進行預測的基礎。這對於在實踐中如何使用模型是現實的,並且對於允許模型使用最佳可用數據是有益的。

    我們可以通過分離輸入數據和輸出/預測數據來說明這一點。

Input, 						Predict
[Week1]						Week2
[Week1 + Week2]				Week3
[Week1 + Week2 + Week3]		Week4
...

    下面提供了評估此數據集上的預測模型的前向驗證方法evaluate_model()。

    標準周格式的訓練和測試數據集作爲參數提供給函數。提供了一個額外的參數n_input,用於定義模型將用作輸入的先前觀察值的數量,以便進行預測。

    調用了兩個新函數:一個函數從名爲build_model()的培訓數據構建模型,另一個函數使用該模型爲每個新標準周進行預測,稱爲forecast()。這些將在後面的小節中討論。

    我們正在研究神經網絡,因此,它們通常訓練速度較慢,但評估速度較快。這意味着模型的首選用法是在歷史數據上構建它們一次,並使用它們來預測前進驗證的每個步驟。模型在評估期間是靜態的(即沒有更新)。

    這與其他訓練更快的模型不同,在這些模型中,當新數據可用時,模型可以重新適應或更新前向驗證的每個步驟。有了足夠的資源,就可以用這種方式使用神經網絡,但在本教程中我們不會這樣做。

    完整的evaluate_model()函數如下所示。

# evaluate a single model
def evaluate_model(train, test, n_input):
	# fit model
	model = build_model(train, n_input)
	# history is a list of weekly data
	history = [x for x in train]
	# walk-forward validation over each week
	predictions = list()
	for i in range(len(test)):
		# predict the week
		yhat_sequence = forecast(model, history, n_input)
		# store the predictions
		predictions.append(yhat_sequence)
		# get real observation and add to history for predicting the next week
		history.append(test[i, :])
	# evaluate predictions days for each week
	predictions = array(predictions)
	score, scores = evaluate_forecasts(test[:, :, 0], predictions)
	return score, scores

    一旦我們對模型進行了評估,我們就可以總結性能。

    下面名爲summarize_scores()的函數將單個行顯示模型的性能,以便與其他模型進行比較。

# summarize scores
def summarize_scores(name, score, scores):
	s_scores = ', '.join(['%.1f' % s for s in scores])
	print('%s: [%.3f] %s' % (name, score, s_scores))

    現在,我們有了開始評估數據集上的預測模型的所有元素。

4 用於多步預測的LSTMs

    遞歸神經網絡(RNNs)是專門用來工作、學習和預測序列數據的。

    遞歸神經網絡是一種神經網絡,其中一個時間步長的輸出作爲後續時間步的輸入。這允許模型根據當前時間步長的輸入和對前一個時間步的輸出的直接知識來決定預測什麼。

    長短時記憶網絡(LSTM)可能是最成功、應用最廣泛的RNN。它的成功之處在於克服了訓練一個遞歸神經網絡所面臨的挑戰,得到了穩定的模型。除了利用前一個時間步的輸出的循環連接之外,LSTMs還有一個內部內存,其操作類似於一個局部變量,允許它們在輸入序列上累積狀態。

    關於LSTM的使用方法,可以參考老鼠屎的舊博文Keras實戰:基於LSTM的股價預測方法

    當涉及到多步時間序列預測時,LSTMs提供了許多好處;它們是:

  • 對序列的本機支持。LSTMs是一種遞歸網絡,其目的是將序列數據作爲輸入,而不像其他模型那樣必須將滯後觀測作爲輸入特徵。
  • 多變量輸入。LSTMs直接支持多元輸入的多個並行輸入序列,而不像其他模型中多元輸入以平面結構表示。
  • 向量輸出。與其他神經網絡一樣,LSTMs能夠將輸入數據直接映射到一個可能表示多個輸出時間步長的輸出向量。

    此外,還開發了專門用於進行多步驟序列預測的專用體系結構,通常稱爲序列到序列預測(sequence-to-sequence prediction,簡稱seq2seq)。這很有用,因爲多步時間序列預測是seq2seq預測的一種類型。

    一個爲seq2seq問題設計的遞歸神經網絡結構的例子是編碼器-解碼器LSTM。

    一個encoder-decoder LSTM模型包括兩個子模型:一個叫編碼器讀取輸入序列和壓縮到一個固定長度的內部表示,和一個稱爲譯碼器的輸出模型解釋內部表示和使用它來預測輸出序列。

    序列預測的編解碼器方法已被證明比直接輸出向量有效得多,是首選的方法。

    一般來說,LSTMs被發現在自動迴歸類型的問題上不是很有效。在這些問題中,預測下一個時間步驟是最近時間步驟的函數。

    關於Encoder-Decoder機制,可以參考老鼠屎的舊博文Attention如何在Encoder-Decoder循環神經網絡中見效(原理篇)

在本教程中,我們將探索一套用於多步驟時間序列預測的LSTM體系結構。具體來說,我們將研究如何開發以下模型:

  • 基於向量輸出的LSTM模型,用於單變量輸入數據的多步預測。
  • 用於單變量輸入數據多步預測的編解碼器LSTM模型。
  • 利用多變量輸入數據進行多步預測的編解碼器LSTM模型。
  • 用於單變量輸入數據多步預測的CNN-LSTM編解碼器模型。
  • 利用單變量輸入數據進行多步預測的ConvLSTM編解碼器模型。

    該模型將應用於家庭用電預測問題。如果一個模型比一個簡單的模型(在七天的預測中RMSE爲465千瓦)的性能更好,那麼這個模型就被認爲是熟練的。

    我們不會專注於調整這些模型以獲得最佳性能;相反,與naive的預測相比,我們將在熟練的模型上作短暫停留。所選擇的結構和超參數的選取經過了少量的試驗和誤差。分數應該僅僅作爲一個例子,而不是對問題的最優模型或配置的研究。

    考慮到模型的隨機性,最好對給定的模型進行多次評估,並報告測試數據集的平均性能。爲了保持代碼的簡潔性和簡單性,我們將在本教程中展示單次運行的模型。

    對於給定的多步預測問題,我們不知道哪種方法是最有效的。探索一套方法是一個好主意,以便發現在您的特定數據集中什麼工作得最好。

5 具有單變量輸入和向量輸出的LSTM模型

    我們將從開發一個簡單的或普通的LSTM模型開始,該模型讀取一系列的日總功耗,並預測下一個標準周的日功耗的向量輸出。這將爲後面幾節中開發的更精細的模型提供基礎。

    之前用作輸入的天數定義了LSTM將讀取並學習提取特徵的數據的一維(1D)子序列。關於這項投入的規模和性質的一些想法包括:

  • 所有之前的日子,直到數年的數據。
  • 前七天。
  • 前兩週。
  • 前一個月。
  • 前一年。
  • 前一週和一年前預測的一週。

    沒有正確的答案;相反,可以測試每種方法和更多方法,並且可以使用模型的性能來選擇輸入的性質,從而獲得最佳的模型性能。這些選擇定義了一些事情:

  • 如何準備訓練數據以適合模型。
  • 如何準備測試數據來評估模型。
  • 如何使用該模型對未來的最終模型進行預測。

    一個好的開始是使用前七天。

    LSTM模型期望數據具有如下形狀:

[samples, timesteps, features]

    關於LSTM模型的形狀,可以參考老鼠屎的舊博文Keras實戰:基於LSTM的股價預測方法如何理解Keras中的TimeDistributed層並在LSTM中使用

    一個樣本將包含7個時間步驟,其中一個特性用於7天的總日功耗。

    訓練數據集有159周的數據,因此訓練數據集的形狀爲:

[159, 7, 1]

    這是一個良好的開端。這種格式的數據將使用之前的標準周來預測下一個標準周。問題是159個實例對於訓練神經網絡來說並不多。

    創建更多培訓數據的一種方法是在培訓期間更改問題,以預測給定前七天的未來七天,而不考慮標準周。

    這隻影響訓練數據,測試問題仍然是相同的:給定前一個標準周,預測下一個標準周的每日功耗。

    這將需要一些培訓數據的準備工作。

    訓練數據以標準周爲單位,包含8個變量,具體形式爲[159,7,8]。第一步是將數據壓平,這樣我們就有8個時間序列序列。

# flatten data
data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))

    然後,我們需要遍歷時間步驟並將數據劃分爲重疊的窗口;每次迭代都沿着一個時間步驟進行,並預測接下來的七天。例如:

Input, Output
[d01, d02, d03, d04, d05, d06, d07], [d08, d09, d10, d11, d12, d13, d14]
[d02, d03, d04, d05, d06, d07, d08], [d09, d10, d11, d12, d13, d14, d15]
...

    我們可以通過跟蹤輸入和輸出的開始和結束索引來實現這一點,因爲我們按照時間步長遍歷扁平數據的長度。

    我們還可以通過參數化輸入和輸出的數量(例如n_input、n_out)來實現這一點,這樣您就可以試驗不同的值,或者根據您自己的問題調整它。

    下面是一個名爲to_supervised()的函數,它接受一個星期列表(歷史記錄)和作爲輸入和輸出使用的時間步數,並以重疊的移動窗口格式返回數據。

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			x_input = data[in_start:in_end, 0]
			x_input = x_input.reshape((len(x_input), 1))
			X.append(x_input)
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

    當我們在整個訓練數據集上運行這個函數時,我們將159個樣本轉換爲1,099個;具體來說,轉換後的數據集具有X=[1099, 7, 1]和y=[1099, 7]的形狀。

    接下來,我們可以在訓練數據上定義和擬合LSTM模型。

    這個多步時間序列預測問題是一個自迴歸問題。這意味着它很可能是最好的模型,在接下來的7天是在之前的時間步長的觀測函數。這和相對較少的數據意味着需要一個較小的模型。

    我們將開發一個有200個LSTM神經元作爲隱含層的模型。隱藏層中的單元數與輸入序列中的時間步長無關。LSTM層之後是一個具有100個節點的全連接層,它將解釋LSTM層學到的特性。最後,輸出層將直接預測一個包含七個元素的向量,在輸出序列中每天一個元素。

    我們將使用均方誤差損失函數,因爲它很好地匹配我們選擇的RMSE誤差度量。我們將使用隨機梯度下降的有效Adam實現,並將該模型擬合到70個批次大小爲16的時代。

    該算法的小批量和隨機性意味着,相同的模型在每次訓練時將學習到輸入到輸出的稍微不同的映射。這意味着在評估模型時,結果可能會有所不同。您可以嘗試多次運行模型並計算模型性能的平均值。

    下面的build_model()準備訓練數據,定義模型,並在訓練數據上對模型進行擬合,返回準備進行預測的擬合模型。

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 70, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# define model
	model = Sequential()
	model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features)))
	model.add(Dense(100, activation='relu'))
	model.add(Dense(n_outputs))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

    既然我們知道了如何擬合模型,我們就可以看看如何使用模型進行預測。

    一般情況下,該模型期望數據在進行預測時具有相同的三維形狀。

    在這種情況下,一個輸入模式的期望形狀是一個樣本,一個特徵七天的日耗電量:

[1, 7, 1]

    當爲測試集進行預測時,以及當最終模型用於預測未來時,數據必須具有這種形狀。如果將輸入天數更改爲14天,那麼在進行預測時,訓練數據的形狀和新樣本的形狀必須相應地更改爲14個時間步長。這是您在使用模型時必須進行的建模選擇。

    我們使用前一節描述的前向驗證來評估模型。

    這意味着我們有前一週的觀測數據,以便預測下一週。這些數據被收集到一個名爲history的標準週數組中。

    爲了預測下一個標準周,我們需要檢索最後幾天的觀測結果。與訓練數據一樣,我們必須首先將歷史數據進行平化,以刪除每週的結構,從而得到8個並行的時間序列。

# flatten data
data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))

    接下來,我們需要檢索過去7天每天消耗的總電量(feature index 0)。

    我們將像對訓練數據所做的那樣對其進行參數化,以便將來可以修改作爲模型輸入的前幾天的天數。

# retrieve last observations for input data
input_x = data[-n_input:, 0]

    接下來,我們將輸入重塑爲預期的三維結構。

# reshape into [1, n_input, 1]
input_x = input_x.reshape((1, len(input_x), 1))

    然後利用擬合模型和輸入數據進行預測,得到七天輸出的矢量。

# forecast the next week
yhat = model.predict(input_x, verbose=0)
# we only want the vector forecast
yhat = yhat[0]

    下面的forecast()函數實現了這一點,並將模型適合於訓練數據集的參數、到目前爲止觀察到的數據歷史以及模型預期的輸入時間步長作爲參數。

# make a forecast
def forecast(model, history, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, 0]
	# reshape into [1, n_input, 1]
	input_x = input_x.reshape((1, len(input_x), 1))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

    現在,我們已經擁有了在單變量數據集上使用LSTM模型進行多步時間序列預測所需的一切。我們可以把所有這些聯繫起來。完整的示例如下所示。

# univariate multi-step lstm
from math import sqrt
from numpy import split
from numpy import array
from pandas import read_csv
from sklearn.metrics import mean_squared_error
from matplotlib import pyplot
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import LSTM

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

# evaluate one or more weekly forecasts against expected values
def evaluate_forecasts(actual, predicted):
	scores = list()
	# calculate an RMSE score for each day
	for i in range(actual.shape[1]):
		# calculate mse
		mse = mean_squared_error(actual[:, i], predicted[:, i])
		# calculate rmse
		rmse = sqrt(mse)
		# store
		scores.append(rmse)
	# calculate overall RMSE
	s = 0
	for row in range(actual.shape[0]):
		for col in range(actual.shape[1]):
			s += (actual[row, col] - predicted[row, col])**2
	score = sqrt(s / (actual.shape[0] * actual.shape[1]))
	return score, scores

# summarize scores
def summarize_scores(name, score, scores):
	s_scores = ', '.join(['%.1f' % s for s in scores])
	print('%s: [%.3f] %s' % (name, score, s_scores))

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			x_input = data[in_start:in_end, 0]
			x_input = x_input.reshape((len(x_input), 1))
			X.append(x_input)
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 70, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# define model
	model = Sequential()
	model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features)))
	model.add(Dense(100, activation='relu'))
	model.add(Dense(n_outputs))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

# make a forecast
def forecast(model, history, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, 0]
	# reshape into [1, n_input, 1]
	input_x = input_x.reshape((1, len(input_x), 1))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

# evaluate a single model
def evaluate_model(train, test, n_input):
	# fit model
	model = build_model(train, n_input)
	# history is a list of weekly data
	history = [x for x in train]
	# walk-forward validation over each week
	predictions = list()
	for i in range(len(test)):
		# predict the week
		yhat_sequence = forecast(model, history, n_input)
		# store the predictions
		predictions.append(yhat_sequence)
		# get real observation and add to history for predicting the next week
		history.append(test[i, :])
	# evaluate predictions days for each week
	predictions = array(predictions)
	score, scores = evaluate_forecasts(test[:, :, 0], predictions)
	return score, scores

# load the new file
dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
# split into train and test
train, test = split_dataset(dataset.values)
# evaluate model and get scores
n_input = 7
score, scores = evaluate_model(train, test, n_input)
# summarize scores
summarize_scores('lstm', score, scores)
# plot scores
days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
pyplot.plot(days, scores, marker='o', label='lstm')
pyplot.show()

    運行示例適合並評估模型,在所有7天內打印整個RMSE,併爲每個提前時間打印每天的RMSE。由於算法的隨機性,您的特定結果可能會有所不同。您可能想嘗試運行這個示例幾次。

    我們可以看到,在這種情況下,與單純的預測相比,模型是熟練的,總體RMSE約爲399千瓦,低於單純模型的465千瓦。

lstm: [399.456] 419.4, 422.1, 384.5, 395.1, 403.9, 317.7, 441.5

    還創建了每日RMSE的圖表。圖表顯示,也許週二和週五比其他日子更容易預測,也許週六是標準週末最難預測的日子。

 6 單變量輸入的編解碼器LSTM模型

    在本節中,我們可以更新普通的LSTM以使用編解碼器模型。

    這意味着模型不會直接輸出向量序列。相反,該模型將由兩個子模型組成,用於讀取和編碼輸入序列的編碼器,以及讀取編碼的輸入序列並對輸出序列中的每個元素進行一步預測的解碼器。這種差別很細微,因爲實際上這兩種方法都可以預測序列輸出。

    重要的不同之處在於,解碼器使用了LSTM模型,這使得解碼器既可以知道前一天在序列中預測了什麼,又可以在輸出序列時積累內部狀態。

    讓我們仔細看看這個模型是如何定義的。和前面一樣,我們定義了一個包含200個單元的LSTM隱藏層。這是解碼器模型,它將讀取輸入序列並輸出一個200個元素向量(每個單元一個輸出),該元素向量從輸入序列捕獲特性。我們將使用14天的總功耗作爲輸入。

# define model
model = Sequential()
model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features)))

    我們將使用一個簡單的編碼器-解碼器架構,易於在Keras中實現,這與LSTM自動編碼器的架構有很多相似之處。

    首先,對輸入序列的內部表示進行多次重複,對於輸出序列中的每個時間步長重複一次。這個向量序列將被呈現給LSTM解碼器。使用 "RepeatVector" 將 Encoder 的輸出(最後一個 time step)複製 N 份作爲 Decoder 的 N 次輸入。

model.add(RepeatVector(7))

    然後我們將解碼器定義爲一個包含200個單元的LSTM隱藏層。重要的是,解碼器將輸出整個序列,而不僅僅是序列末尾的輸出,就像我們對編碼器所做的那樣。這意味着200個單元中的每一個單元都將爲7天中的每一天輸出一個值,表示輸出序列中每天預測的內容的基礎。

model.add(LSTM(200, activation='relu', return_sequences=True))

    然後,我們將使用一全連接層來解釋最終輸出層之前輸出序列中的每個時間步長。重要的是,輸出層預測輸出序列中的一個步驟,不是一次七天,

    這意味着我們將對輸出序列中的每個步驟使用相同的層。它的意思是相同的全連接層和輸出層將用於處理解碼器提供的每個時間步長。爲了實現這一點,我們將解釋層和輸出層封裝在一個TimeDistributed包裝器中,該包裝器允許從解碼器每次執行步驟時都使用所封裝的層。

model.add(TimeDistributed(Dense(100, activation='relu')))
model.add(TimeDistributed(Dense(1)))

    這允許LSTM解碼器計算出輸出序列中每個步驟所需的上下文,以及用於單獨解釋每個時間步驟的被包裝的Dense層,同時重用相同的權重來執行解釋。另一種方法是將LSTM解碼器創建的所有結構壓平,並直接輸出矢量。您可以嘗試將其作爲一個擴展,以查看它是如何進行比較的。

    因此,網絡輸出與輸入結構相同的三維向量,具有維數[樣本、時間步長、特徵]

    它只有一個功能,即每天消耗的總電量,而且總是有7個特徵。因此,一個單一的一週預測將有大小:[1,7,1]。

    因此,在對模型進行訓練時,我們必須對輸出數據(y)進行重構,使其具有三維結構,而不是上一節所使用的[sample, features]的二維結構。

# reshape output into [samples, timesteps, features]
train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))

    我們可以將所有這些都綁定到下面列出的更新build_model()函數中。

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 20, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features)))
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

    下面列出了編碼器-解碼器模型的完整示例。

# univariate multi-step encoder-decoder lstm
from math import sqrt
from numpy import split
from numpy import array
from pandas import read_csv
from sklearn.metrics import mean_squared_error
from matplotlib import pyplot
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import LSTM
from keras.layers import RepeatVector
from keras.layers import TimeDistributed

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

# evaluate one or more weekly forecasts against expected values
def evaluate_forecasts(actual, predicted):
	scores = list()
	# calculate an RMSE score for each day
	for i in range(actual.shape[1]):
		# calculate mse
		mse = mean_squared_error(actual[:, i], predicted[:, i])
		# calculate rmse
		rmse = sqrt(mse)
		# store
		scores.append(rmse)
	# calculate overall RMSE
	s = 0
	for row in range(actual.shape[0]):
		for col in range(actual.shape[1]):
			s += (actual[row, col] - predicted[row, col])**2
	score = sqrt(s / (actual.shape[0] * actual.shape[1]))
	return score, scores

# summarize scores
def summarize_scores(name, score, scores):
	s_scores = ', '.join(['%.1f' % s for s in scores])
	print('%s: [%.3f] %s' % (name, score, s_scores))

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			x_input = data[in_start:in_end, 0]
			x_input = x_input.reshape((len(x_input), 1))
			X.append(x_input)
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 20, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features)))
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

# make a forecast
def forecast(model, history, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, 0]
	# reshape into [1, n_input, 1]
	input_x = input_x.reshape((1, len(input_x), 1))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

# evaluate a single model
def evaluate_model(train, test, n_input):
	# fit model
	model = build_model(train, n_input)
	# history is a list of weekly data
	history = [x for x in train]
	# walk-forward validation over each week
	predictions = list()
	for i in range(len(test)):
		# predict the week
		yhat_sequence = forecast(model, history, n_input)
		# store the predictions
		predictions.append(yhat_sequence)
		# get real observation and add to history for predicting the next week
		history.append(test[i, :])
	# evaluate predictions days for each week
	predictions = array(predictions)
	score, scores = evaluate_forecasts(test[:, :, 0], predictions)
	return score, scores

# load the new file
dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
# split into train and test
train, test = split_dataset(dataset.values)
# evaluate model and get scores
n_input = 14
score, scores = evaluate_model(train, test, n_input)
# summarize scores
summarize_scores('lstm', score, scores)
# plot scores
days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
pyplot.plot(days, scores, marker='o', label='lstm')
pyplot.show()

    運行該示例符合該模型,並總結了測試數據集上的性能。

    由於算法的隨機性,您的特定結果可能會有所不同。您可能想嘗試運行這個示例幾次。

    我們可以看到,在這種情況下,模型是熟練的,實現了一個總RMSE評分約372千瓦。

lstm: [372.595] 379.5, 399.8, 339.6, 372.2, 370.9, 309.9, 424.8

    還創建了一個每日RMSE的線圖,顯示了與上一節中看到的類似的誤差模式。

7 多變量輸入的LSTM模型

    在本節中,我們將更新上一節中開發的編碼器-解碼器LSTM,使用8個時間序列變量中的每一個來預測下一個標準周的每日總功耗。

    我們將通過將每個一維時間序列作爲單獨的輸入序列提供給模型來實現這一點。

    LSTM將依次創建每個輸入序列的內部表示,這些輸入序列將由解碼器一起解釋。

    使用多元輸入有助於解決這樣的問題,即輸出序列是來自多個不同特徵的先前時間步長的某個函數,而不只是(或包括)預測的特徵。目前還不清楚在電力消耗問題上是否存在這種情況,但我們可以探索它。

    首先,我們必須更新培訓數據的準備工作,使其包含所有八個特性,而不僅僅是每天消耗的總能量。它需要一行更改:

X.append(data[in_start:in_end, :])

 完成此更改的to_supervised()函數如下所示。

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			X.append(data[in_start:in_end, :])
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

    我們還必須更新使用fit模型進行預測的函數,以使用前面時間步驟中的所有8個特性。

    還有一個小變化:

# retrieve last observations for input data
input_x = data[-n_input:, :]
# reshape into [1, n_input, n]
input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1]))

    修改後的complete forecast()函數如下:

# make a forecast
def forecast(model, history, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, :]
	# reshape into [1, n_input, n]
	input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1]))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

    直接使用相同的模型體系結構和配置,儘管我們將把訓練週期的數量從20個增加到50個,因爲輸入數據量增加了8倍。

    完整的示例如下所示。

# multivariate multi-step encoder-decoder lstm
from math import sqrt
from numpy import split
from numpy import array
from pandas import read_csv
from sklearn.metrics import mean_squared_error
from matplotlib import pyplot
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import LSTM
from keras.layers import RepeatVector
from keras.layers import TimeDistributed

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

# evaluate one or more weekly forecasts against expected values
def evaluate_forecasts(actual, predicted):
	scores = list()
	# calculate an RMSE score for each day
	for i in range(actual.shape[1]):
		# calculate mse
		mse = mean_squared_error(actual[:, i], predicted[:, i])
		# calculate rmse
		rmse = sqrt(mse)
		# store
		scores.append(rmse)
	# calculate overall RMSE
	s = 0
	for row in range(actual.shape[0]):
		for col in range(actual.shape[1]):
			s += (actual[row, col] - predicted[row, col])**2
	score = sqrt(s / (actual.shape[0] * actual.shape[1]))
	return score, scores

# summarize scores
def summarize_scores(name, score, scores):
	s_scores = ', '.join(['%.1f' % s for s in scores])
	print('%s: [%.3f] %s' % (name, score, s_scores))

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			X.append(data[in_start:in_end, :])
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 50, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features)))
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

# make a forecast
def forecast(model, history, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, :]
	# reshape into [1, n_input, n]
	input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1]))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

# evaluate a single model
def evaluate_model(train, test, n_input):
	# fit model
	model = build_model(train, n_input)
	# history is a list of weekly data
	history = [x for x in train]
	# walk-forward validation over each week
	predictions = list()
	for i in range(len(test)):
		# predict the week
		yhat_sequence = forecast(model, history, n_input)
		# store the predictions
		predictions.append(yhat_sequence)
		# get real observation and add to history for predicting the next week
		history.append(test[i, :])
	# evaluate predictions days for each week
	predictions = array(predictions)
	score, scores = evaluate_forecasts(test[:, :, 0], predictions)
	return score, scores

# load the new file
dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
# split into train and test
train, test = split_dataset(dataset.values)
# evaluate model and get scores
n_input = 14
score, scores = evaluate_model(train, test, n_input)
# summarize scores
summarize_scores('lstm', score, scores)
# plot scores
days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
pyplot.plot(days, scores, marker='o', label='lstm')
pyplot.show()

8 具有單變量輸入的CNN-LSTM編解碼器模型

    卷積神經網絡(CNN)可以作爲編解碼器結構中的編碼器。

    CNN不直接支持序列輸入;相反,一維CNN能夠讀取序列輸入並自動學習顯著特徵。然後可以按照正常情況由LSTM解碼器解釋這些。我們將使用CNN和LSTM的混合模型稱爲CNN -LSTM模型,在本例中,我們將在一個編解碼器體系結構中同時使用它們。

    CNN希望輸入的數據具有與LSTM模型相同的3D結構,儘管多個特性被讀取爲不同的通道,最終具有相同的效果。

    我們將簡化這個示例,並將重點放在具有單變量輸入的CNN-LSTM上,但是它也可以很容易地更新爲使用多元輸入,這只是一個練習。

    與之前一樣,我們將使用由14天的日總功耗組成的輸入序列。

    我們將爲編碼器定義一個簡單但有效的CNN架構,它由兩個卷積層和一個最大池化層組成,然後將結果扁平化。

    第一個卷積層讀取輸入序列並將結果投影到特徵圖上。第二層對第一層創建的特徵圖執行相同的操作,試圖放大任何顯著的特徵。我們將在每個卷積層使用64個feature map,讀取內核大小爲3個時間步長的輸入序列。

    最大池層通過保留最大信號值的1/4來簡化特徵映射。在池化層之後提取的特徵映射被壓扁成一個長向量,然後可以用作解碼過程的輸入。

model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features)))
model.add(Conv1D(filters=64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())

    解碼器與前面幾節定義的解碼器相同。唯一的其他變化是將訓練時間設置爲20個。帶有這些更改的build_model()函數如下所示。

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 20, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features)))
	model.add(Conv1D(filters=64, kernel_size=3, activation='relu'))
	model.add(MaxPooling1D(pool_size=2))
	model.add(Flatten())
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

    現在我們準備使用CNN編碼器來嘗試編碼器-解碼器架構。

    下面提供了完整的代碼清單。

# univariate multi-step encoder-decoder cnn-lstm
from math import sqrt
from numpy import split
from numpy import array
from pandas import read_csv
from sklearn.metrics import mean_squared_error
from matplotlib import pyplot
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import LSTM
from keras.layers import RepeatVector
from keras.layers import TimeDistributed
from keras.layers.convolutional import Conv1D
from keras.layers.convolutional import MaxPooling1D

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

# evaluate one or more weekly forecasts against expected values
def evaluate_forecasts(actual, predicted):
	scores = list()
	# calculate an RMSE score for each day
	for i in range(actual.shape[1]):
		# calculate mse
		mse = mean_squared_error(actual[:, i], predicted[:, i])
		# calculate rmse
		rmse = sqrt(mse)
		# store
		scores.append(rmse)
	# calculate overall RMSE
	s = 0
	for row in range(actual.shape[0]):
		for col in range(actual.shape[1]):
			s += (actual[row, col] - predicted[row, col])**2
	score = sqrt(s / (actual.shape[0] * actual.shape[1]))
	return score, scores

# summarize scores
def summarize_scores(name, score, scores):
	s_scores = ', '.join(['%.1f' % s for s in scores])
	print('%s: [%.3f] %s' % (name, score, s_scores))

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			x_input = data[in_start:in_end, 0]
			x_input = x_input.reshape((len(x_input), 1))
			X.append(x_input)
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

# train the model
def build_model(train, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 20, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features)))
	model.add(Conv1D(filters=64, kernel_size=3, activation='relu'))
	model.add(MaxPooling1D(pool_size=2))
	model.add(Flatten())
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

# make a forecast
def forecast(model, history, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, 0]
	# reshape into [1, n_input, 1]
	input_x = input_x.reshape((1, len(input_x), 1))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

# evaluate a single model
def evaluate_model(train, test, n_input):
	# fit model
	model = build_model(train, n_input)
	# history is a list of weekly data
	history = [x for x in train]
	# walk-forward validation over each week
	predictions = list()
	for i in range(len(test)):
		# predict the week
		yhat_sequence = forecast(model, history, n_input)
		# store the predictions
		predictions.append(yhat_sequence)
		# get real observation and add to history for predicting the next week
		history.append(test[i, :])
	# evaluate predictions days for each week
	predictions = array(predictions)
	score, scores = evaluate_forecasts(test[:, :, 0], predictions)
	return score, scores

# load the new file
dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
# split into train and test
train, test = split_dataset(dataset.values)
# evaluate model and get scores
n_input = 14
score, scores = evaluate_model(train, test, n_input)
# summarize scores
summarize_scores('lstm', score, scores)
# plot scores
days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
pyplot.plot(days, scores, marker='o', label='lstm')
pyplot.show()

    運行該示例符合該模型,並總結了測試數據集上的性能。一個小實驗表明,使用兩個卷積層比只使用一個卷積層使模型更加穩定。由於算法的隨機性,您的特定結果可能會有所不同。您可能想嘗試運行這個示例幾次。

    我們可以看到,在這種情況下,模型是熟練的,實現了一個總RMSE評分約372千瓦。

lstm: [372.055] 383.8, 381.6, 339.1, 371.8, 371.8, 319.6, 427.2

    還創建了每日RMSE的線圖。

9 單變量輸入的ConvLSTM編解碼器模型

    CNN-LSTM方法的進一步擴展是對CNN的卷積(例如CNN如何讀取輸入序列數據)執行LSTM的每個時間步驟。這種組合稱爲卷積LSTM,簡稱ConvLSTM,就像CNN -LSTM也用於時空數據一樣。

    與爲了計算內部狀態和狀態轉換而直接讀入數據的LSTM不同,與解釋CNN模型輸出的CNN -LSTM不同,ConvLSTM使用卷積作爲直接讀入LSTM單元本身的輸入的一部分。

    Keras庫提供了ConvLSTM2D類,該類支持針對2D數據的ConvLSTM模型。它可以配置爲一維多變量時間序列預測。

    ConvLSTM2D類默認情況下期望輸入數據具有如下形狀:

[samples, timesteps, rows, cols, channels]

    其中數據的每個時間步長都定義爲(行*列)數據點的圖像。

    我們正在處理一個一維的總功耗序列,如果我們假設使用兩週的數據作爲輸入,我們可以將其解釋爲一行14列。

    對於ConvLSTM,這將是一次讀取:也就是說,LSTM將讀取一個14天的時間步長,並在這些時間步長之間執行卷積。這並不理想。

    相反,我們可以將14天分成兩個子序列,子序列的長度爲7天。ConvLSTM可以跨兩個時間步驟讀取數據,並對每個時間步驟中的七天數據執行CNN處理。因此,對於所選擇的問題框架,ConvLSTM2D的輸入爲:

[n, 2, 1, 7, 1]

    或者:

  • samples:n,表示訓練數據集中樣本的數量。
  • timesteps:2,我們將一個14天的窗口分割爲兩個子序列。
  • rows:1,表示每個子序列的一維形狀。
  • cols:7,表示每個子序列中的七天。
  • channels:1,對於作爲輸入的單個特性。

     您可以研究其他配置,比如將21天的輸入分成三個7天的子序列,或提供所有8個特性或通道作爲輸入。

    我們現在可以爲ConvLSTM2D模型準備數據。首先,我們必須將訓練數據集重塑爲[samples, timesteps, rows, cols, channels]的預期結構。

# reshape into subsequences [samples, time steps, rows, cols, channels]
train_x = train_x.reshape((train_x.shape[0], n_steps, 1, n_length, n_features))

    然後,我們可以將編碼器定義爲一個ConvLSTM隱藏層,然後是一個準備解碼的flatten層。

model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features)))
model.add(Flatten())

    我們還將參數化子序列的數量(n_steps)和每個子序列的長度(n_length),並將它們作爲參數傳遞。

    模型的其餘部分和訓練是相同的。帶有這些更改的build_model()函數如下所示。

# train the model
def build_model(train, n_steps, n_length, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 20, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape into subsequences [samples, time steps, rows, cols, channels]
	train_x = train_x.reshape((train_x.shape[0], n_steps, 1, n_length, n_features))
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features)))
	model.add(Flatten())
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

    該模型期望五維數據作爲輸入。因此,在進行預測時,我們還必須更新forecast()函數中單個樣本的準備。

# reshape into [samples, time steps, rows, cols, channels]
input_x = input_x.reshape((1, n_steps, 1, n_length, 1))

     下面提供了帶有此更改和參數化子序列的forecast()函數。

# make a forecast
def forecast(model, history, n_steps, n_length, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, 0]
	# reshape into [samples, time steps, rows, cols, channels]
	input_x = input_x.reshape((1, n_steps, 1, n_length, 1))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

    我們現在有了所有的元素來評估一個多步時間序列預測的編碼器-解碼器體系結構,其中使用ConvLSTM作爲編碼器。

    完整的代碼示例如下所示。

# univariate multi-step encoder-decoder convlstm
from math import sqrt
from numpy import split
from numpy import array
from pandas import read_csv
from sklearn.metrics import mean_squared_error
from matplotlib import pyplot
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import LSTM
from keras.layers import RepeatVector
from keras.layers import TimeDistributed
from keras.layers import ConvLSTM2D

# split a univariate dataset into train/test sets
def split_dataset(data):
	# split into standard weeks
	train, test = data[1:-328], data[-328:-6]
	# restructure into windows of weekly data
	train = array(split(train, len(train)/7))
	test = array(split(test, len(test)/7))
	return train, test

# evaluate one or more weekly forecasts against expected values
def evaluate_forecasts(actual, predicted):
	scores = list()
	# calculate an RMSE score for each day
	for i in range(actual.shape[1]):
		# calculate mse
		mse = mean_squared_error(actual[:, i], predicted[:, i])
		# calculate rmse
		rmse = sqrt(mse)
		# store
		scores.append(rmse)
	# calculate overall RMSE
	s = 0
	for row in range(actual.shape[0]):
		for col in range(actual.shape[1]):
			s += (actual[row, col] - predicted[row, col])**2
	score = sqrt(s / (actual.shape[0] * actual.shape[1]))
	return score, scores

# summarize scores
def summarize_scores(name, score, scores):
	s_scores = ', '.join(['%.1f' % s for s in scores])
	print('%s: [%.3f] %s' % (name, score, s_scores))

# convert history into inputs and outputs
def to_supervised(train, n_input, n_out=7):
	# flatten data
	data = train.reshape((train.shape[0]*train.shape[1], train.shape[2]))
	X, y = list(), list()
	in_start = 0
	# step over the entire history one time step at a time
	for _ in range(len(data)):
		# define the end of the input sequence
		in_end = in_start + n_input
		out_end = in_end + n_out
		# ensure we have enough data for this instance
		if out_end < len(data):
			x_input = data[in_start:in_end, 0]
			x_input = x_input.reshape((len(x_input), 1))
			X.append(x_input)
			y.append(data[in_end:out_end, 0])
		# move along one time step
		in_start += 1
	return array(X), array(y)

# train the model
def build_model(train, n_steps, n_length, n_input):
	# prepare data
	train_x, train_y = to_supervised(train, n_input)
	# define parameters
	verbose, epochs, batch_size = 0, 20, 16
	n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
	# reshape into subsequences [samples, time steps, rows, cols, channels]
	train_x = train_x.reshape((train_x.shape[0], n_steps, 1, n_length, n_features))
	# reshape output into [samples, timesteps, features]
	train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1))
	# define model
	model = Sequential()
	model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features)))
	model.add(Flatten())
	model.add(RepeatVector(n_outputs))
	model.add(LSTM(200, activation='relu', return_sequences=True))
	model.add(TimeDistributed(Dense(100, activation='relu')))
	model.add(TimeDistributed(Dense(1)))
	model.compile(loss='mse', optimizer='adam')
	# fit network
	model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
	return model

# make a forecast
def forecast(model, history, n_steps, n_length, n_input):
	# flatten data
	data = array(history)
	data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
	# retrieve last observations for input data
	input_x = data[-n_input:, 0]
	# reshape into [samples, time steps, rows, cols, channels]
	input_x = input_x.reshape((1, n_steps, 1, n_length, 1))
	# forecast the next week
	yhat = model.predict(input_x, verbose=0)
	# we only want the vector forecast
	yhat = yhat[0]
	return yhat

# evaluate a single model
def evaluate_model(train, test, n_steps, n_length, n_input):
	# fit model
	model = build_model(train, n_steps, n_length, n_input)
	# history is a list of weekly data
	history = [x for x in train]
	# walk-forward validation over each week
	predictions = list()
	for i in range(len(test)):
		# predict the week
		yhat_sequence = forecast(model, history, n_steps, n_length, n_input)
		# store the predictions
		predictions.append(yhat_sequence)
		# get real observation and add to history for predicting the next week
		history.append(test[i, :])
	# evaluate predictions days for each week
	predictions = array(predictions)
	score, scores = evaluate_forecasts(test[:, :, 0], predictions)
	return score, scores

# load the new file
dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime'])
# split into train and test
train, test = split_dataset(dataset.values)
# define the number of subsequences and the length of subsequences
n_steps, n_length = 2, 7
# define the total days to use as input
n_input = n_length * n_steps
score, scores = evaluate_model(train, test, n_steps, n_length, n_input)
# summarize scores
summarize_scores('lstm', score, scores)
# plot scores
days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
pyplot.plot(days, scores, marker='o', label='lstm')
pyplot.show()

    運行該示例符合該模型,並總結了測試數據集上的性能。一個小實驗表明,使用兩個卷積層比只使用一個卷積層使模型更加穩定。

    我們可以看到,在這種情況下,模型是熟練的,實現了一個總RMSE評分約367千瓦。

lstm: [367.929] 416.3, 379.7, 334.7, 362.3, 374.7, 284.8, 406.7

    還創建了每日RMSE的線圖。

擴展

    本節列出了一些擴展教程的想法,您可能希望探索這些想法。

  • 輸入的大小。探索用作模型輸入的天數,例如3天、21天、30天或更多。
  • 模型調優。調整模型的結構和超參數,進一步提高模型的平均性能。
  • 數據擴展。研究數據擴展(如標準化和規範化)是否可以用於提高任何LSTM模型的性能。
  • 學習診斷。使用諸如訓練學習曲線、驗證損失和均方誤差等診斷來幫助調整LSTM模型的結構和超參數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章