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