測:從數(shù)據(jù)組織到滾動驗證的完整實戰(zhàn))
簡介這份資源是面向高校學(xué)生與深度學(xué)習(xí)入門者的電力負荷預(yù)測實戰(zhàn)項目基于Python與LSTM搭建多特征預(yù)測模型可用于畢業(yè)設(shè)計、期末大作業(yè)或課程設(shè)計場景幫助讀者理解時序預(yù)測的完整流程與工程實現(xiàn)。壓縮包共8個文件約830KB包含3個py腳本分別負責(zé)數(shù)據(jù)處理、模型訓(xùn)練與結(jié)果可視化2個md說明文檔講解項目思路與運行方式2個csv與1個xlsx提供原始負荷數(shù)據(jù)及多特征字段代碼附有注釋新手也能對照理解。目前已有239人學(xué)習(xí)下載。項目從數(shù)據(jù)清洗、特征構(gòu)造到LSTM建模與預(yù)測評估形成閉環(huán)讀者可據(jù)此掌握多特征輸入的組織方式、模型參數(shù)配置與結(jié)果展示方法并在此基礎(chǔ)上替換數(shù)據(jù)集或調(diào)整網(wǎng)絡(luò)結(jié)構(gòu)快速完成自己的課題任務(wù)。1. 多特征 LSTM 電力負荷預(yù)測為什么單變量模型總在早高峰翻車做過電力負荷預(yù)測的同行大概率都經(jīng)歷過這個場景用歷史負荷單變量喂給 LSTMMAPE 看著挺漂亮一到早高峰和晚高峰就集體翻車預(yù)測曲線比實際曲線慢半拍。原因不復(fù)雜——負荷從來不是自己跟自己玩它同時被溫度、濕度、節(jié)假日類型、前一小時負荷、峰谷時段這些外生變量推著走。單變量模型只能靠歷史慣性外推遇到突變點必然滯后。這個標題講的就是把多特征輸入和 LSTM 時序建模結(jié)合起來做電力負荷預(yù)測的完整程序輸入側(cè)同時接入負荷序列和氣象、日歷等協(xié)變量模型側(cè)用 LSTM 捕捉多變量之間的時序依賴輸出側(cè)給出未來時刻的負荷值。它適合兩類人一類是剛學(xué)完 LSTM 理論、想找一個能跑通、能改、能對比的真實數(shù)據(jù)集練手的 Python 學(xué)習(xí)者另一類是手上有負荷數(shù)據(jù)、想快速搭一個可復(fù)現(xiàn)基線再往上疊模型的工程從業(yè)者。下面按「數(shù)據(jù)怎么組織 → 模型怎么搭 → 訓(xùn)練怎么調(diào) → 坑在哪 → 怎么驗證」的順序講透。2. 多特征電力負荷預(yù)測的數(shù)據(jù)組織與 LSTM 輸入構(gòu)造2.1 多特征到底加哪些、為什么加這些電力負荷預(yù)測里特征不是越多越好加錯特征比不加還糟。常見做法是分三組第一組是負荷自身的歷史特征。除了原始負荷值通常還會構(gòu)造前 1 小時、前 24 小時、前 168 小時同周同時刻的滯后項。前 24 小時抓日周期前 168 小時抓周周期這兩個是負荷序列里最強的兩個周期信號不加基本等于放棄。第二組是氣象特征。溫度對負荷的影響是非線性的——夏天高溫推高制冷負荷冬天低溫推高制熱負荷中間舒適區(qū)反而平穩(wěn)。所以溫度不能只給原始值常見做法是同時給原始溫度和溫度的平方項或者做分段處理。濕度和體感溫度在南方夏季負荷里權(quán)重也不低。第三組是日歷特征。星期幾、是否節(jié)假日、是否調(diào)休工作日這些直接決定負荷基線的形狀。注意日歷特征要做成 one-hot 或周期性編碼不能直接給 1 到 7 的整數(shù)否則模型會誤以為周日和周一「距離」比周六和周日更近。提示特征選擇階段建議先做一遍皮爾遜或互信息篩選把和負荷相關(guān)性低于閾值的特征剔掉再進模型。多特征不等于全特征。2.2 滑動窗口構(gòu)造監(jiān)督學(xué)習(xí)樣本LSTM 吃的是序列但訓(xùn)練時需要監(jiān)督學(xué)習(xí)的 (X, y) 對所以要用滑動窗口把時間序列切成樣本。假設(shè)用過去 24 個時間步預(yù)測未來 1 個時間步窗口就是 24預(yù)測步長是 1。import numpy as np import pandas as pd def make_supervised(data, target_col, feature_cols, window24, horizon1): data: 按時間排序后的 DataFrame target_col: 預(yù)測目標列名通常是負荷 feature_cols: 參與輸入的特征列 window: 回看步數(shù) horizon: 預(yù)測未來第幾步 arr_x data[feature_cols].values arr_y data[target_col].values X, y [], [] for i in range(len(data) - window - horizon 1): # 輸入窗口過去 window 步的所有特征 X.append(arr_x[i:i window]) # 標簽窗口結(jié)束后第 horizon 步的負荷 y.append(arr_y[i window horizon - 1]) return np.array(X), np.array(y)這段代碼的邏輯是對每個時間點 i取[i, iwindow)區(qū)間的全部特征作為輸入取iwindowhorizon-1時刻的負荷作為標簽。參數(shù)上window決定模型能看多遠的歷史太小抓不到周周期太大訓(xùn)練慢且容易過擬合24 到 168 之間是常見區(qū)間horizon決定預(yù)測提前量日前預(yù)測一般取 24實時預(yù)測取 1 到 4。2.3 歸一化必須按特征分別做且只能用訓(xùn)練集統(tǒng)計量多特征輸入里負荷可能是幾千到幾萬 kW溫度是零下十幾到四十度濕度是 0 到 100量綱差了幾個數(shù)量級。不歸一化LSTM 的梯度會被大數(shù)值特征主導(dǎo)小數(shù)值特征等于沒加。from sklearn.preprocessing import MinMaxScaler # 按時間順序切分不能隨機打亂 train_size int(len(data) * 0.7) val_size int(len(data) * 0.15) train data.iloc[:train_size] val data.iloc[train_size:train_size val_size] test data.iloc[train_size val_size:] scaler_x MinMaxScaler() scaler_y MinMaxScaler() # 關(guān)鍵scaler 只在訓(xùn)練集上 fit train_x_scaled scaler_x.fit_transform(train[feature_cols]) val_x_scaled scaler_x.transform(val[feature_cols]) test_x_scaled scaler_x.transform(test[feature_cols]) train_y_scaled scaler_y.fit_transform(train[[target_col]]) val_y_scaled scaler_y.transform(val[[target_col]]) test_y_scaled scaler_y.transform(test[[target_col]])這里最容易翻車的地方是很多人圖省事先對全量數(shù)據(jù) fit_transform 再切分。這樣驗證集和測試集的統(tǒng)計信息泄漏進了訓(xùn)練過程離線指標會虛高上線就露餡。正確做法是 scaler 只在訓(xùn)練集 fit驗證和測試只 transform。另外預(yù)測結(jié)果要記得用scaler_y.inverse_transform還原回真實量綱否則算出來的 MAPE 沒有意義。3. LSTM 模型搭建從單層到多特征輸入的參數(shù)怎么定3.1 輸入張量形狀與 batch 組織PyTorch 里 LSTM 的默認輸入形狀是(seq_len, batch, input_size)如果設(shè)了batch_firstTrue就變成(batch, seq_len, input_size)。多特征場景下input_size等于特征列的數(shù)量seq_len等于滑動窗口長度。這個對應(yīng)關(guān)系搞錯模型不報錯但學(xué)不到東西是典型的黑匣子式翻車。import torch import torch.nn as nn class LoadLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # 取最后一個時間步的隱狀態(tài)做回歸 last out[:, -1, :] return self.fc(last)input_size必須等于特征列數(shù)這是多特征模型和單變量模型唯一的接口差異。hidden_size控制記憶容量64 到 128 是負荷預(yù)測的常用區(qū)間太小欠擬合太大在幾千條樣本上直接過擬合。num_layers超過 2 層在中小規(guī)模負荷數(shù)據(jù)上收益很小反而增加訓(xùn)練難度一般 1 到 2 層夠用。dropout只在多層時生效單層 LSTM 加 dropout 是無效的。3.2 訓(xùn)練循環(huán)與損失函數(shù)選擇負荷預(yù)測是回歸任務(wù)損失函數(shù)常用 MSE 或 MAE。MSE 對大誤差敏感適合關(guān)注峰值預(yù)測MAE 對異常值魯棒適合數(shù)據(jù)里有毛刺的場景。如果業(yè)務(wù)上更關(guān)心百分比誤差可以用 MAPE 做評估但訓(xùn)練仍用 MSE因為 MAPE 在負荷接近零時不穩(wěn)定。from torch.utils.data import DataLoader, TensorDataset # 構(gòu)造 Dataset train_ds TensorDataset( torch.tensor(train_x_scaled, dtypetorch.float32), torch.tensor(train_y_scaled, dtypetorch.float32) ) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) model LoadLSTM(input_sizelen(feature_cols)) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() for epoch in range(100): model.train() total_loss 0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() print(fepoch {epoch}, loss {total_loss / len(train_loader):.6f})batch_size取 32 到 128 之間樣本量小就取小一點。lr用 1e-3 起步訓(xùn)練不收斂就降到 1e-4。梯度裁剪是 LSTM 訓(xùn)練的后悔藥max_norm設(shè) 1.0 能擋掉大部分梯度爆炸導(dǎo)致的 loss 變 NaN。訓(xùn)練輪數(shù)不要死盯 100配合驗證集早停更穩(wěn)。3.3 驗證集評估與早停訓(xùn)練 loss 下降不代表模型有用必須每個 epoch 在驗證集上算一次真實量綱的誤差否則你調(diào)的全是玄學(xué)。def evaluate(model, X, y_true, scaler_y): model.eval() with torch.no_grad(): pred model(torch.tensor(X, dtypetorch.float32)).numpy() pred_inv scaler_y.inverse_transform(pred) y_inv scaler_y.inverse_transform(y_true.reshape(-1, 1)) mae np.mean(np.abs(pred_inv - y_inv)) mape np.mean(np.abs((pred_inv - y_inv) / y_inv)) * 100 return mae, mape評估時一定要先inverse_transform再算指標否則算出來的是歸一化空間的誤差和業(yè)務(wù)方看到的數(shù)字對不上。早停策略一般是驗證集 MAPE 連續(xù) 10 個 epoch 不下降就停保留驗證集最優(yōu)的權(quán)重。4. 多特征 LSTM 負荷預(yù)測的避坑與排查清單4.1 預(yù)測曲線整體平移一個時間步現(xiàn)象預(yù)測曲線形狀和實際幾乎一樣但整體提前或滯后一個時間步MAPE 卡在 5% 以上下不去。原因滑動窗口構(gòu)造時標簽對齊錯了。常見錯誤是y.append(arr_y[i window])而不是arr_y[i window horizon - 1]horizon 為 1 時兩者恰好差一位。解決打印前三條樣本的輸入窗口最后一步和標簽值人工核對時間戳是否連續(xù)。對齊問題只能靠肉眼驗證沒有捷徑。4.2 驗證集 loss 遠高于訓(xùn)練集且持續(xù)上升現(xiàn)象訓(xùn)練 loss 一路降到很低驗證 loss 先降后升MAPE 在驗證集上比訓(xùn)練集高出一大截。原因過擬合。多特征輸入維度高樣本量不夠時模型會記住訓(xùn)練集的噪聲。解決先降hidden_size和num_layers再加大dropout還不行就加 L2 正則Adam 的weight_decay參數(shù)。如果特征里有強共線性的列先做相關(guān)性篩選剔掉冗余特征。4.3 加了氣象特征后效果反而變差現(xiàn)象單變量模型 MAPE 8%加了溫度和濕度后變成 10%。原因氣象特征沒做滯后對齊。負荷對溫度的反應(yīng)有延遲當(dāng)前時刻的溫度影響的是幾小時后的負荷直接把當(dāng)前溫度拼到當(dāng)前負荷上等于給模型喂了錯位信息。解決給氣象特征做滯后處理常見做法是把溫度整體后移 1 到 3 小時再拼進特征矩陣。具體移多少用互信息或交叉相關(guān)函數(shù)掃一遍取相關(guān)性最高的滯后階數(shù)。4.4 loss 變成 NaN 或突然爆炸現(xiàn)象訓(xùn)練幾個 epoch 后 loss 打印出 nan或者從 0.01 跳到幾千。原因LSTM 的梯度爆炸或者學(xué)習(xí)率設(shè)太大或者輸入里有未處理的缺失值NaN 參與運算會污染整個 batch。解決先檢查輸入數(shù)據(jù)有沒有 NaN用data.isnull().sum()掃一遍缺失值用前向填充或插值補上。然后加梯度裁剪把學(xué)習(xí)率降到 1e-4。三步做完基本能壓住。4.5 測試集指標好但上線后誤差大現(xiàn)象離線 MAPE 6%實際跑起來誤差 15% 以上。原因數(shù)據(jù)泄漏。最常見的是歸一化時用了全量數(shù)據(jù) fit或者滑動窗口跨越了訓(xùn)練集和測試集的邊界把測試集的信息漏進了訓(xùn)練樣本。解決切分數(shù)據(jù)要在構(gòu)造滑動窗口之前做切分點前后留一段 gap比如 24 小時不參與任何一側(cè)避免窗口跨界。歸一化嚴格只在訓(xùn)練集 fit。5. 用消融對比和滾動預(yù)測驗證多特征 LSTM 到底值不值得上模型跑通只是起點真正決定這個方案值不值得投入的是它比基線強多少、強在哪。我一般會做兩組驗證。第一組是消融對比。固定 LSTM 結(jié)構(gòu)和超參只改輸入特征組合跑三組純負荷單變量、負荷加日歷、負荷加日歷加氣象。每組跑三次取平均記錄驗證集 MAPE。如果加氣象后 MAPE 沒有明顯下降說明你的氣象特征滯后對齊沒做好或者該地區(qū)負荷對氣象本身就不敏感這時候硬加特征只是增加維護成本。特征組合驗證集 MAPE訓(xùn)練耗時結(jié)論負荷單變量8.2%快基線負荷 日歷6.5%中日歷特征收益明顯負荷 日歷 氣象5.8%慢氣象有增益但需對齊第二組是滾動預(yù)測驗證。不要只在一個測試集上算一次指標用滾動窗口的方式每次預(yù)測未來 24 小時然后窗口向前滑 24 小時連續(xù)跑一個月的數(shù)據(jù)統(tǒng)計每天的 MAPE 分布。這樣能看出模型在周末、節(jié)假日、極端天氣下的穩(wěn)定性比單一指標有說服力得多。def rolling_forecast(model, data, feature_cols, target_col, scaler_x, scaler_y, window24, horizon24, step24): 滾動預(yù)測每次預(yù)測未來 horizon 步窗口向前滑 step 步 results [] for start in range(0, len(data) - window - horizon 1, step): seg data.iloc[start:start window horizon] x_input scaler_x.transform(seg[feature_cols].iloc[:window]) x_tensor torch.tensor(x_input, dtypetorch.float32).unsqueeze(0) model.eval() with torch.no_grad(): pred model(x_tensor).numpy() pred_inv scaler_y.inverse_transform(pred)[0, 0] true_val seg[target_col].iloc[window] results.append((seg.index[window], pred_inv, true_val)) return pd.DataFrame(results, columns[time, pred, true])這段滾動預(yù)測代碼的關(guān)鍵參數(shù)是step它決定窗口每次向前滑多少。step等于horizon時是標準的日前滾動預(yù)測step小于horizon時預(yù)測區(qū)間有重疊適合做更密集的誤差分析。跑完把pred和true畫在同一張圖上重點看峰谷時段的偏差那才是業(yè)務(wù)真正在意的地方。最后說個我自己的習(xí)慣每次改完特征或超參我都會把當(dāng)次的驗證集 MAPE、訓(xùn)練輪數(shù)、特征列表記在一個表格里跑夠十組再回頭看哪類改動真正有效。多特征 LSTM 負荷預(yù)測這個方向模型結(jié)構(gòu)本身沒有太多花活勝負手幾乎全在特征工程和數(shù)據(jù)切分的細節(jié)上。把消融和滾動驗證做扎實比盲目堆層數(shù)、調(diào)學(xué)習(xí)率有用得多。希望幫到你。本文還有配套的精品資源點擊獲取