網(wǎng)絡(luò)負(fù)荷預(yù)測實(shí)戰(zhàn):從數(shù)據(jù)構(gòu)造到調(diào)參避坑)
簡介這份資源面向電力系統(tǒng)調(diào)度、能源管理及電氣工程方向的學(xué)習(xí)者與研究人員聚焦基于BP神經(jīng)網(wǎng)絡(luò)的電力負(fù)荷預(yù)測方法幫助解決歷史負(fù)荷數(shù)據(jù)建模、未來用電需求預(yù)估等實(shí)際問題適合具備一定MATLAB基礎(chǔ)、希望掌握神經(jīng)網(wǎng)絡(luò)預(yù)測流程的中級讀者。壓縮包共8個文件約410KB包含4個doc文檔、2個m腳本和2個xls數(shù)據(jù)表文檔側(cè)重BP網(wǎng)絡(luò)原理與負(fù)荷預(yù)報(bào)實(shí)現(xiàn)思路m文件提供可直接運(yùn)行的MATLAB源碼xls表格則存放訓(xùn)練與測試用的負(fù)荷樣本數(shù)據(jù)三者配合可完成從數(shù)據(jù)到預(yù)測的完整實(shí)驗(yàn)。目前已有724人學(xué)習(xí)下載。讀者可借助源碼與數(shù)據(jù)快速復(fù)現(xiàn)負(fù)荷預(yù)測流程理解數(shù)據(jù)預(yù)處理、網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計(jì)、權(quán)重訓(xùn)練與結(jié)果驗(yàn)證等關(guān)鍵環(huán)節(jié)并參考文檔中的改進(jìn)思路如自適應(yīng)學(xué)習(xí)率、動量項(xiàng)等優(yōu)化自身模型的預(yù)測精度與泛化能力。1. BP負(fù)荷預(yù)測從一張日負(fù)荷曲線說起電力負(fù)荷預(yù)測這件事真正做過的人都知道難點(diǎn)不在模型有多深而在“明天早上八點(diǎn)那個尖峰到底抬多高”。一條日負(fù)荷曲線里藏著氣溫、工作日/周末、節(jié)假日、大用戶排產(chǎn)、甚至天氣突變帶來的空調(diào)負(fù)荷這些因素疊加在一起讓負(fù)荷序列既有強(qiáng)周期性又有明顯的非平穩(wěn)擾動。BP神經(jīng)網(wǎng)絡(luò)之所以在這個場景里被反復(fù)提起是因?yàn)樗鳛榍梆伾窠?jīng)網(wǎng)絡(luò)的代表能用一層隱層去逼近任意連續(xù)函數(shù)把“歷史負(fù)荷氣象日期類型”這類多輸入映射到未來時(shí)刻的負(fù)荷值上結(jié)構(gòu)簡單、訓(xùn)練快、可解釋性尚可特別適合做短期負(fù)荷預(yù)測的基線模型。這篇文章面向的是手里有歷史負(fù)荷數(shù)據(jù)、想用BP神經(jīng)網(wǎng)絡(luò)跑出一版能落地預(yù)測結(jié)果的工程師也適合剛接觸負(fù)荷預(yù)測、想找一個完整可復(fù)現(xiàn)路徑的新手。我會從數(shù)據(jù)構(gòu)造講到網(wǎng)絡(luò)搭建、參數(shù)設(shè)置、訓(xùn)練排錯再到預(yù)測結(jié)果怎么驗(yàn)證盡量把每一步的參數(shù)含義和踩坑點(diǎn)說清楚。MATLAB的newff和Python的sklearn/PyTorch兩條路都會給到你可以按自己手頭的工具鏈選一條走通。2. 負(fù)荷預(yù)測的數(shù)據(jù)構(gòu)造與BP網(wǎng)絡(luò)選型為什么不是隨便丟進(jìn)去就能跑2.1 負(fù)荷序列的輸入輸出該怎么切BP網(wǎng)絡(luò)做負(fù)荷預(yù)測本質(zhì)是監(jiān)督學(xué)習(xí)用過去一段時(shí)間的負(fù)荷和相關(guān)特征預(yù)測未來一個或多個時(shí)刻的負(fù)荷。最常見的切法是滑動窗口。假設(shè)采樣間隔是15分鐘一天96個點(diǎn)你想用過去3天288個點(diǎn)預(yù)測未來1天96個點(diǎn)那輸入就是288維輸出是96維。但直接這么切有兩個問題一是輸入維度太高訓(xùn)練慢且容易過擬合二是負(fù)荷的日周期性和周周期性沒有被顯式表達(dá)。我一般會做特征工程把輸入拆成幾類歷史負(fù)荷的滯后項(xiàng)比如前1天同一時(shí)刻、前2天同一時(shí)刻、前1小時(shí)同一時(shí)刻、氣象特征溫度、濕度、降雨、日期特征星期幾、是否節(jié)假日。這樣輸入維度可以壓到20~50維訓(xùn)練效率高很多。輸出可以只預(yù)測下一個時(shí)刻也可以一次預(yù)測未來96個點(diǎn)前者叫單步預(yù)測后者叫多步預(yù)測。單步預(yù)測精度高但需要滾動執(zhí)行多步預(yù)測一次出結(jié)果但誤差會累積。短期負(fù)荷預(yù)測里如果只做日前預(yù)測多步輸出更實(shí)用。下面是一個用Python構(gòu)造滑動窗口數(shù)據(jù)集的例子假設(shè)你已經(jīng)把負(fù)荷和氣象數(shù)據(jù)對齊成了按時(shí)間索引的DataFrameimport numpy as np import pandas as pd def make_dataset(df, target_colload, feat_colsNone, lookback96, horizon96): df: 按時(shí)間排序的DataFrame索引為時(shí)間戳 target_col: 負(fù)荷列名 feat_cols: 額外特征列名列表 lookback: 輸入窗口長度過去多少個點(diǎn) horizon: 輸出窗口長度預(yù)測未來多少個點(diǎn) if feat_cols is None: feat_cols [] data df[[target_col] feat_cols].values n len(data) X, y [], [] for i in range(n - lookback - horizon 1): # 輸入過去lookback個點(diǎn)的所有特征 X.append(data[i:ilookback, :].flatten()) # 輸出未來horizon個點(diǎn)的負(fù)荷 y.append(data[ilookback:ilookbackhorizon, 0]) return np.array(X), np.array(y) # 假設(shè)df已經(jīng)按15分鐘間隔排好包含load和temp兩列 # X, y make_dataset(df, target_colload, feat_cols[temp], # lookback96, horizon96)這段代碼的關(guān)鍵參數(shù)是lookback和horizon。lookback太小模型看不到完整的日周期太大輸入維度爆炸。經(jīng)驗(yàn)上如果采樣間隔15分鐘lookback至少取96一天取192或288兩到三天更穩(wěn)。horizon根據(jù)業(yè)務(wù)需求定日前預(yù)測取96超短期預(yù)測取4或8。注意flatten()會把所有特征按時(shí)間順序拉平所以輸入向量的排列是“時(shí)間1的所有特征、時(shí)間2的所有特征……”這個順序在后續(xù)做歸一化和反歸一化時(shí)要保持一致。2.2 BP網(wǎng)絡(luò)結(jié)構(gòu)選型隱層數(shù)、節(jié)點(diǎn)數(shù)和激活函數(shù)BP神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)選擇網(wǎng)上流傳很多“公式”比如隱層節(jié)點(diǎn)數(shù)輸入輸出節(jié)點(diǎn)數(shù)之和的一半再加常數(shù)或者不超過輸入節(jié)點(diǎn)數(shù)。這些經(jīng)驗(yàn)公式只能當(dāng)起點(diǎn)真正定結(jié)構(gòu)還是要看數(shù)據(jù)量和驗(yàn)證集表現(xiàn)。負(fù)荷預(yù)測里我一般先用單隱層試隱層節(jié)點(diǎn)數(shù)從16、32、64、128逐檔試看驗(yàn)證集MSE什么時(shí)候不再明顯下降。如果數(shù)據(jù)里非線性很強(qiáng)比如氣溫驟變導(dǎo)致負(fù)荷尖峰可以加到兩個隱層但第二個隱層節(jié)點(diǎn)數(shù)要少于第一個形成“漏斗”結(jié)構(gòu)避免參數(shù)過多。激活函數(shù)方面隱層用tanh或relu都行。tanh輸出在-1到1之間配合歸一化后的負(fù)荷數(shù)據(jù)比較自然relu訓(xùn)練更快但要注意學(xué)習(xí)率別太大否則容易死神經(jīng)元。輸出層如果是回歸任務(wù)直接用線性激活purelin或恒等映射不要加sigmoid否則輸出被限制在0到1之間反歸一化后會失真。MATLAB里用newff建網(wǎng)很直接% 假設(shè)X是輸入矩陣每列一個樣本y是輸出矩陣 % 隱層32個節(jié)點(diǎn)輸出層線性 net newff(X, y, [32], {tansh}, trainlm); net.trainParam.epochs 1000; net.trainParam.goal 1e-5; net.trainParam.lr 0.01; net.trainParam.showWindow false; net train(net, X, y);newff的第二個參數(shù)是隱層節(jié)點(diǎn)數(shù)向量[32]表示一個隱層32個節(jié)點(diǎn)[64, 32]表示兩個隱層。{tansig}是隱層激活函數(shù)輸出層默認(rèn)purelin。trainlm是Levenberg-Marquardt算法收斂快但內(nèi)存占用大數(shù)據(jù)量大時(shí)換trainscg。net.trainParam.goal是最小均方誤差目標(biāo)設(shè)太小容易過擬合設(shè)太大學(xué)不到位一般從1e-5試到1e-3。Python里用sklearn的MLPRegressor更省事from sklearn.neural_network import MLPRegressor from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X) y_scaled scaler.fit_transform(y.reshape(-1, 1)).ravel() model MLPRegressor( hidden_layer_sizes(64, 32), activationtanh, solveradam, learning_rate_init0.001, max_iter2000, early_stoppingTrue, validation_fraction0.1, random_state42 ) model.fit(X_scaled, y_scaled)hidden_layer_sizes(64, 32)對應(yīng)兩個隱層節(jié)點(diǎn)數(shù)遞減。early_stoppingTrue會從訓(xùn)練集里切10%做驗(yàn)證驗(yàn)證分?jǐn)?shù)不再下降就停這是防過擬合最省事的辦法。learning_rate_init用adam時(shí)0.001是安全起點(diǎn)太大震蕩太小收斂慢。注意歸一化必須用訓(xùn)練集的均值和方差驗(yàn)證集和測試集只能用訓(xùn)練集的參數(shù)做變換否則信息泄露驗(yàn)證分?jǐn)?shù)會虛高。3. 訓(xùn)練、調(diào)參與預(yù)測把模型跑出可用結(jié)果3.1 訓(xùn)練集/驗(yàn)證集/測試集怎么分才不騙自己負(fù)荷預(yù)測的數(shù)據(jù)劃分不能隨機(jī)打亂因?yàn)闀r(shí)間序列有前后依賴。常見做法是按時(shí)間順序切前70%訓(xùn)練中間15%驗(yàn)證最后15%測試。驗(yàn)證集用來調(diào)超參數(shù)和早停測試集只在最后評估一次。如果數(shù)據(jù)里有明顯的季節(jié)差異比如只有一年數(shù)據(jù)那測試集可能落在冬季模型沒見過冬季模式誤差會偏大。這時(shí)候要么用滾動預(yù)測的方式做交叉驗(yàn)證要么至少保證訓(xùn)練集覆蓋一個完整的年周期。滾動預(yù)測的做法是用第1到第N天訓(xùn)練預(yù)測第N1天然后窗口往后滑一天用第2到第N1天訓(xùn)練預(yù)測第N2天。這樣每個測試點(diǎn)都是“未來”評估更真實(shí)。缺點(diǎn)是訓(xùn)練次數(shù)多計(jì)算量大。如果數(shù)據(jù)量不大我建議至少做一次滾動驗(yàn)證看看模型在不同時(shí)間段的穩(wěn)定性。3.2 學(xué)習(xí)率、迭代次數(shù)和過擬合的平衡BP網(wǎng)絡(luò)的訓(xùn)練過程就是不斷調(diào)權(quán)重使損失下降。學(xué)習(xí)率決定每次更新的步長太大容易跳過最優(yōu)解損失震蕩太小收斂慢可能卡在局部極小。用trainlm時(shí)學(xué)習(xí)率影響不大因?yàn)長M算法自適應(yīng)調(diào)整用trainscg或adam時(shí)學(xué)習(xí)率要手動調(diào)。我一般先設(shè)0.01跑100輪看損失曲線如果下降太慢就加到0.05如果震蕩就降到0.001。迭代次數(shù)不是越多越好。訓(xùn)練集損失一直降但驗(yàn)證集損失開始上升就是過擬合的信號。早停就是在這個時(shí)候停下來保留驗(yàn)證集損失最低的模型。MATLAB里newff默認(rèn)會做驗(yàn)證檢查net.trainParam.max_fail控制連續(xù)多少次驗(yàn)證失敗就停默認(rèn)6次。Python的MLPRegressor用early_stoppingTrue和n_iter_no_change控制。還有一個容易被忽略的點(diǎn)負(fù)荷數(shù)據(jù)的周期性很強(qiáng)如果訓(xùn)練集里某個時(shí)段的樣本特別多比如工作日遠(yuǎn)多于周末模型會偏向工作日模式周末預(yù)測誤差大。解決辦法是分層采樣或者在損失函數(shù)里給周末樣本更高權(quán)重。sklearn的MLPRegressor不支持樣本權(quán)重可以用PyTorch自己寫損失函數(shù)。3.3 預(yù)測結(jié)果的反歸一化與誤差評估模型輸出的是歸一化后的值必須用訓(xùn)練集的均值和方差反變換回原始量綱。反歸一化公式是y_orig y_scaled * std mean其中std和mean來自訓(xùn)練集。如果輸出是多步的每個輸出維度對應(yīng)不同的均值和方差要分別反變換。誤差評估常用三個指標(biāo)MAE平均絕對誤差、MAPE平均絕對百分比誤差、RMSE均方根誤差。負(fù)荷預(yù)測里MAPE最直觀但負(fù)荷接近零時(shí)MAPE會爆炸所以通常只統(tǒng)計(jì)負(fù)荷大于某個閾值的點(diǎn)。RMSE對大誤差敏感適合看尖峰時(shí)段的預(yù)測能力。我一般三個都算再畫一張預(yù)測值和真實(shí)值的對比曲線肉眼看看尖峰和低谷有沒有跟上。from sklearn.metrics import mean_absolute_error, mean_squared_error y_pred_orig scaler.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel() y_true_orig scaler.inverse_transform(y_true_scaled.reshape(-1, 1)).ravel() mae mean_absolute_error(y_true_orig, y_pred_orig) rmse np.sqrt(mean_squared_error(y_true_orig, y_pred_orig)) # 只算負(fù)荷大于10%最大值的點(diǎn)的MAPE mask y_true_orig 0.1 * y_true_orig.max() mape np.mean(np.abs((y_true_orig[mask] - y_pred_orig[mask]) / y_true_orig[mask])) * 100 print(fMAE: {mae:.2f}, RMSE: {rmse:.2f}, MAPE: {mape:.2f}%)如果MAPE在3%以內(nèi)說明模型已經(jīng)能用于實(shí)際調(diào)度參考5%左右需要檢查是不是特征不夠或者結(jié)構(gòu)不合理超過10%基本不可用要回頭查數(shù)據(jù)對齊和歸一化有沒有出錯。4. 避坑與排查BP負(fù)荷預(yù)測里最容易翻車的五件事4.1 現(xiàn)象訓(xùn)練損失降到很低但預(yù)測曲線是一條直線原因輸出層用了sigmoid或tanh而負(fù)荷數(shù)據(jù)沒有歸一化到對應(yīng)區(qū)間導(dǎo)致輸出飽和。或者學(xué)習(xí)率太大權(quán)重直接飛到飽和區(qū)。解決輸出層改線性激活檢查歸一化范圍是否和激活函數(shù)匹配。用tanh時(shí)數(shù)據(jù)歸一化到[-1,1]用sigmoid時(shí)歸一化到[0,1]。如果已經(jīng)用了線性輸出檢查學(xué)習(xí)率是不是太大降到0.001再試。4.2 現(xiàn)象驗(yàn)證集損失比訓(xùn)練集高很多且一直不降原因過擬合??赡苁请[層節(jié)點(diǎn)太多、訓(xùn)練輪數(shù)太多、或者訓(xùn)練集和驗(yàn)證集分布不一致比如驗(yàn)證集落在節(jié)假日。解決減少隱層節(jié)點(diǎn)數(shù)加早停加L2正則化。MLPRegressor里alpha參數(shù)控制L2懲罰從0.0001試到0.01。如果驗(yàn)證集是節(jié)假日考慮在訓(xùn)練集里加入類似日期的樣本或者用滾動驗(yàn)證代替固定驗(yàn)證集。4.3 現(xiàn)象預(yù)測結(jié)果整體偏高或偏低但形狀對得上原因反歸一化時(shí)用的均值和方差不是訓(xùn)練集的或者多步預(yù)測時(shí)每個輸出維度用了同一個scaler。解決檢查scaler是不是只在訓(xùn)練集上fit然后對驗(yàn)證集和測試集只做transform。多步輸出時(shí)如果每個時(shí)間步的負(fù)荷分布不同應(yīng)該對每個輸出維度單獨(dú)歸一化而不是把所有輸出拉平一起歸一化。4.4 現(xiàn)象MATLAB里newff報(bào)錯“輸入矩陣維度不一致”原因newff要求輸入矩陣每列是一個樣本每行是一個特征。如果數(shù)據(jù)是DataFrame直接轉(zhuǎn)置錯了就會維度不匹配。解決確認(rèn)X的shape是(n_features, n_samples)y的shape是(n_outputs, n_samples)。Python里sklearn要求(n_samples, n_features)正好相反轉(zhuǎn)工具時(shí)別搞混。4.5 現(xiàn)象訓(xùn)練速度極慢內(nèi)存爆掉原因trainlm算法要存Jacobian矩陣樣本數(shù)和參數(shù)量大時(shí)內(nèi)存占用是O(N*P)?;蛘咻斎刖S度太高比如直接把288個歷史點(diǎn)全丟進(jìn)去。解決換trainscg或adam降低輸入維度用特征工程代替原始滯后項(xiàng)。如果必須用高維輸入先做PCA降維保留95%方差。5. 進(jìn)階技巧用殘差建模和集成策略把MAPE再壓一個點(diǎn)單靠一個BP網(wǎng)絡(luò)MAPE壓到3%左右就差不多到瓶頸了。想再往下走我試過兩個有效的方向殘差建模和集成。殘差建模的思路是先用一個簡單模型比如線性回歸或昨天的負(fù)荷得到一個基線預(yù)測然后用BP網(wǎng)絡(luò)去預(yù)測基線預(yù)測的殘差。這樣BP網(wǎng)絡(luò)只需要學(xué)“基線沒捕捉到的部分”任務(wù)更簡單收斂更快。具體做法是把基線預(yù)測值作為額外特征加進(jìn)輸入輸出仍然是真實(shí)負(fù)荷但損失函數(shù)里對殘差大的樣本加權(quán)。集成策略更直接訓(xùn)練多個BP網(wǎng)絡(luò)每個用不同的隨機(jī)初始化、不同的隱層結(jié)構(gòu)、不同的訓(xùn)練集子集然后對預(yù)測結(jié)果取平均或加權(quán)平均。權(quán)重可以用驗(yàn)證集上的誤差倒數(shù)來定。我做過一組實(shí)驗(yàn)單網(wǎng)絡(luò)MAPE 3.2%5個網(wǎng)絡(luò)集成后降到2.7%代價(jià)是訓(xùn)練時(shí)間翻5倍。如果業(yè)務(wù)對精度要求高且算力允許集成是性價(jià)比很高的選擇。還有一個細(xì)節(jié)負(fù)荷預(yù)測的誤差在尖峰時(shí)段往往最大因?yàn)榧夥迨茈S機(jī)因素影響大??梢栽趽p失函數(shù)里對高負(fù)荷樣本加權(quán)讓模型更關(guān)注尖峰。PyTorch里自定義損失函數(shù)很方便import torch import torch.nn as nn class WeightedMSELoss(nn.Module): def __init__(self, weight_factor2.0): super().__init__() self.weight_factor weight_factor def forward(self, pred, target): # 對target大于均值的樣本給更高權(quán)重 weights torch.ones_like(target) threshold target.mean() weights[target threshold] self.weight_factor loss weights * (pred - target) ** 2 return loss.mean()weight_factor控制尖峰樣本的權(quán)重倍數(shù)從1.5開始試太大模型會犧牲低谷精度。這個損失函數(shù)配合早停和驗(yàn)證集通常能把尖峰時(shí)段的MAE降低10%到15%。最后說一個我自己的習(xí)慣每次跑完模型不管指標(biāo)多好我都會把預(yù)測曲線和真實(shí)曲線疊在一起挑誤差最大的三個時(shí)間段回去看那幾天的天氣、日期和特殊事件。十次里有八次能發(fā)現(xiàn)數(shù)據(jù)里有個沒處理好的異常點(diǎn)或者某個特征沒加進(jìn)去。模型調(diào)參的收益是線性的數(shù)據(jù)質(zhì)量的收益是指數(shù)的。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取