电力负荷预测系统实战:ARIMA、随机森林、LSTM、Transformer多模型选型与避坑指南
简介这份资源面向电力系统调度、能源管理及时间序列预测方向的学习者与工程人员提供一套基于历史负荷数据预测每小时电力负荷变化趋势的完整代码集合可用于电网调度优化与能源分配决策。压缩包共11个文件约54KB以8个Python脚本为核心分别实现ARIMA、决策树、KNN、GRU、LSTM、随机森林与Transformer等模型另附README、说明文件与附赠文档便于理解各模型的建模思路与调用方式。已有63人学习下载。读者可对照脚本快速复现不同算法的预测流程比较统计方法与深度学习模型在自相关性、长期依赖和全局序列建模上的差异并参考说明文档完成环境配置与结果验证为构建多模型融合的负荷预测方案提供可复用的代码基础与排错参考。1. 电力负荷预测系统从每小时数值到调度决策多模型怎么选做电网调度的人都有个共识负荷曲线看着平滑真到要提前一天排机组出力的时候误差每多一个百分点背后就是实打实的旋转备用成本。这套「基于时间序列数据的电力负荷预测系统」要解决的就是这件事——拿历史每小时电力负荷数值预测未来一段时间的负荷变化趋势输出直接喂给调度优化和能源管理模块。标题里列了 ARIMA、决策树、GRU、KNN、LSTM、随机森林、Transformer 一大串模型很多人第一反应是「堆模型」但真正落地时你会发现模型多不是重点重点是搞清楚每个模型吃什么样的数据形态、在哪个预测步长上更稳、以及怎么把它们组织成一条可复现的流水线。这篇笔记面向已经拿到负荷数据、准备动手搭预测系统的工程师也适合想搞清楚「时间序列预测模型到底怎么选」的算法同学。我会按数据准备、特征工程、模型实现、避坑、进阶验证的顺序把这条链路讲透代码能直接抄。2. 数据准备与时间序列特征工程把原始负荷表变成模型能吃的样本2.1 电力负荷数据的三个天然属性电力负荷序列和普通时间序列不一样它有三个绕不开的属性。第一是强周期性一天 24 小时有早高峰和晚高峰一周七天工作日和周末形态完全不同一年还有夏冬两个空调负荷尖峰。第二是受温度、节假日、电价政策这些外生变量影响纯靠历史负荷自回归遇到极端天气就会翻车。第三是采样频率固定且缺失值有规律电表采集通常是 15 分钟或 1 小时一个点缺失往往成段出现不是随机丢点。理解这三点决定了你后面特征怎么造。常见做法是把时间特征拆成小时、星期、是否节假日、是否周末这几类离散编码再把温度、湿度作为外生变量拼进去。如果只有负荷一列数据那就靠滑动窗口自己造滞后特征这是时间序列预测最朴素也最有效的路子。2.2 用 pandas 做重采样、缺失填充和滞后特征下面这段代码把一张原始负荷表整理成监督学习能用的样本矩阵。假设原始数据是timestamp和load两列采样间隔不规整。import pandas as pd import numpy as np # 读取原始负荷数据timestamp 解析为时间索引 df pd.read_csv(load_raw.csv, parse_dates[timestamp]) df df.set_index(timestamp).sort_index() # 重采样到整点缺失用前向填充 线性插值兜底 df df.resample(1h).mean() df[load] df[load].interpolate(methodlinear, limit3) df[load] df[load].ffill(limit2) # 时间特征小时、星期、是否周末 df[hour] df.index.hour df[weekday] df.index.weekday df[is_weekend] (df[weekday] 5).astype(int) # 滞后特征过去 1、2、3、24、168 小时的负荷 for lag in [1, 2, 3, 24, 168]: df[flag_{lag}] df[load].shift(lag) # 滑动统计特征过去 24 小时均值、标准差 df[roll_mean_24] df[load].shift(1).rolling(24).mean() df[roll_std_24] df[load].shift(1).rolling(24).std() df df.dropna() print(df.shape)逻辑说明resample(1h).mean()把不规整采样对齐到整点这是电力负荷预测的标准动作因为调度计划本身就是按小时排的。插值用limit3限制连续填充长度超过 3 小时的空洞说明采集设备可能离线硬填会引入假信号这种段最好标记出来单独处理。滞后特征里lag_24和lag_168分别捕捉日周期和周周期这是负荷预测里性价比最高的两个特征很多场景下光靠这两个加一个线性模型就能拿到不错的基线。参数说明limit控制插值最大连续长度一般设 2 到 4看你的采集稳定性。rolling(24)的窗口对应一天如果你做的是 15 分钟粒度窗口要改成 96。滞后阶数的选择没有固定公式先看自相关函数 ACF拖尾到哪就取到哪但工程上一般不超过两个完整周期否则特征维度爆炸且容易过拟合。2.3 训练集、验证集、测试集怎么切才不泄露未来信息时间序列切分和普通机器学习最大的区别是不能随机打乱。常见做法是按时间顺序 7:1:2 切但更严谨的是用滚动窗口做交叉验证。下面这个函数生成滚动切分索引。def rolling_split(n_samples, n_splits5, test_size0.2): 按时间顺序生成滚动训练/验证索引避免未来信息泄露 test_start int(n_samples * (1 - test_size)) fold_size test_start // (n_splits 1) splits [] for i in range(n_splits): train_end fold_size * (i 1) val_end train_end fold_size splits.append((np.arange(0, train_end), np.arange(train_end, val_end))) return splits, np.arange(test_start, n_samples)逻辑说明每次训练集只用到当前时间点之前的数据验证集是紧接着的一段这样评估出来的误差才接近真实上线后的表现。测试集留最后 20% 完全不参与调参只在最终验收时用一次。很多人图省事直接train_test_split(shuffleFalse)切一刀调参时反复看测试集最后指标虚高上线就露馅这是血泪经验。3. 统计模型与树模型ARIMA、决策树、随机森林在负荷预测里的真实定位3.1 ARIMA 的定阶与残差诊断ARIMA 是时间序列预测的经典基线优点是解释性强、训练快缺点是只能捕捉线性关系遇到节假日突变基本没辙。用statsmodels跑 ARIMA 的标准流程是先做平稳性检验再定阶最后看残差是不是白噪声。from statsmodels.tsa.stattools import adfuller from statsmodels.tsa.arima.model import ARIMA import warnings warnings.filterwarnings(ignore) # 平稳性检验p 值小于 0.05 认为平稳 adf_result adfuller(df[load].dropna()) print(ADF p-value:, adf_result[1]) # 一阶差分后定阶p、d、q 分别试 0-3 best_aic, best_order np.inf, None for p in range(4): for q in range(4): try: model ARIMA(df[load], order(p, 1, q)) result model.fit() if result.aic best_aic: best_aic, best_order result.aic, (p, 1, q) except Exception: continue print(Best order:, best_order, AIC:, best_aic) # 用最优阶数拟合并预测未来 24 小时 final_model ARIMA(df[load], orderbest_order).fit() forecast final_model.forecast(steps24) print(forecast.head())逻辑说明ADF 检验的 p 值大于 0.05 说明序列非平稳需要差分d参数一般取 1 或 2。定阶用 AIC 网格搜索是最省事的做法虽然慢但不容易出错。残差诊断要看 Ljung-Box 检验如果残差还有自相关说明阶数不够或者模型没抓住某些结构。ARIMA 在负荷预测里通常作为基线预测未来 1 到 6 小时还行超过 24 小时误差会明显放大因为它是纯自回归没有外生变量。参数说明order(p, d, q)里 p 是自回归项q 是移动平均项d 是差分次数。负荷数据一般 d1p 和 q 不超过 3再大容易过拟合。如果你有温度数据可以用 SARIMAX 把外生变量加进去效果比纯 ARIMA 好一截。3.2 决策树和随机森林把时间序列当回归问题做决策树和随机森林本身不是为时间序列设计的但把滞后特征造好之后它们就是普通的回归模型而且对非线性关系捕捉得不错。随机森林比单棵决策树稳因为它靠 bagging 降方差在负荷预测这种噪声不小的场景里更实用。from sklearn.ensemble import RandomForestRegressor from sklearn.tree import DecisionTreeRegressor from sklearn.metrics import mean_absolute_percentage_error feature_cols [c for c in df.columns if c ! load] X, y df[feature_cols].values, df[load].values splits, test_idx rolling_split(len(X)) train_idx, val_idx splits[-1] # 决策树基线 dt DecisionTreeRegressor(max_depth8, min_samples_leaf5, random_state42) dt.fit(X[train_idx], y[train_idx]) dt_pred dt.predict(X[val_idx]) print(DT MAPE:, mean_absolute_percentage_error(y[val_idx], dt_pred)) # 随机森林 rf RandomForestRegressor(n_estimators300, max_depth12, min_samples_leaf3, n_jobs-1, random_state42) rf.fit(X[train_idx], y[train_idx]) rf_pred rf.predict(X[val_idx]) print(RF MAPE:, mean_absolute_percentage_error(y[val_idx], rf_pred))逻辑说明max_depth控制树深负荷预测里一般 8 到 15 之间太深会记住训练集的噪声。min_samples_leaf设 3 到 5 能防止叶子节点样本太少导致预测抖动。随机森林的n_estimators设 300 以上指标基本稳定再往上收益递减。树模型最大的好处是能直接输出特征重要性你可以看到lag_24、hour、lag_168这几个特征排前面验证特征工程做对了没有。参数说明n_jobs-1用满所有 CPU 核训练时间能砍一半以上。random_state固定住方便复现。树模型不需要做归一化这是它比神经网络省事的地方但滞后特征里的量纲差异还是建议统一处理避免某些特征被稀释。4. 深度学习模型GRU、LSTM、KNN、Transformer 的输入构造与训练要点4.1 把负荷序列切成三维张量深度学习模型吃的是(样本数, 时间步长, 特征数)的三维张量。构造这个张量是 LSTM 和 GRU 落地时最容易出错的一步维度对不上、时间步错位都是常见翻车点。import torch from torch.utils.data import Dataset, DataLoader class LoadDataset(Dataset): def __init__(self, data, seq_len24, pred_len1): self.data torch.tensor(data, dtypetorch.float32) self.seq_len seq_len self.pred_len pred_len def __len__(self): return len(self.data) - self.seq_len - self.pred_len 1 def __getitem__(self, idx): x self.data[idx: idx self.seq_len] y self.data[idx self.seq_len: idx self.seq_len self.pred_len, 0] return x, y # 假设第一列是负荷其余是特征 dataset LoadDataset(df.values, seq_len24, pred_len1) loader DataLoader(dataset, batch_size64, shuffleFalse)逻辑说明seq_len24表示用过去 24 小时预测下一小时这是负荷预测最常用的窗口。shuffleFalse在时间序列里很重要训练时如果打乱会破坏时间连续性虽然对 LSTM 本身影响不大但配合滚动验证时容易搞混。y取的是data[..., 0]即负荷那一列确保预测目标明确。参数说明seq_len的选择看你的预测步长和周期长度预测未来 1 小时用 24 够预测未来 24 小时建议用 168一周。batch_size设 32 到 128 之间显存够就大一点训练更稳。4.2 LSTM 和 GRU 的网络结构与训练循环LSTM 和 GRU 是负荷预测里用得最多的两个循环网络GRU 参数少、训练快LSTM 表达能力强一点数据量小的时候 GRU 往往更划算。import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, pred_len1): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_dim, pred_len) def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :]) # 取最后一个时间步 class GRUForecaster(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, pred_len1): super().__init__() self.gru nn.GRU(input_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_dim, pred_len) def forward(self, x): out, _ self.gru(x) return self.fc(out[:, -1, :])逻辑说明batch_firstTrue让输入维度是(batch, seq, feature)和 DataLoader 输出对齐。out[:, -1, :]取最后一个时间步的隐藏状态因为负荷预测是自回归任务最后一步包含了整段序列的信息。dropout0.2在循环层里能缓解过拟合但注意 PyTorch 的 LSTM dropout 只在多层之间生效单层设了没用。参数说明hidden_dim一般 32 到 128负荷预测这种单变量或少量外生变量的任务64 够用。num_layers设 2 层比较稳再深容易过拟合且训练慢。学习率用 1e-3 配 Adam训练 50 到 100 个 epoch早停看验证集损失。4.3 KNN 和 Transformer 在负荷预测里的适用边界KNN 做时间序列预测的思路是找历史上形态相似的片段用它们的后续值做加权平均。优点是简单、可解释缺点是预测时要在全量历史里搜数据量大了慢而且对突变负荷不敏感。适合做短期基线或者作为集成模型的一个弱学习器。Transformer 的优势是自注意力能捕捉长距离依赖适合预测步长较长、周期性复杂的场景。但负荷预测数据量通常不大Transformer 容易过拟合需要配合位置编码、层归一化和较强的正则。实际落地里Transformer 在负荷预测上的收益往往不如把特征工程做扎实的 LSTM除非你有多年高频数据。class TransformerForecaster(nn.Module): def __init__(self, input_dim, d_model64, nhead4, num_layers2, pred_len1): super().__init__() self.embed nn.Linear(input_dim, d_model) self.pos nn.Parameter(torch.randn(1, 168, d_model)) encoder_layer nn.TransformerEncoderLayer(d_model, nhead, batch_firstTrue) self.encoder nn.TransformerEncoder(encoder_layer, num_layers) self.fc nn.Linear(d_model, pred_len) def forward(self, x): x self.embed(x) self.pos[:, :x.size(1), :] x self.encoder(x) return self.fc(x[:, -1, :])逻辑说明位置编码用可学习参数长度按最大序列 168 设。batch_firstTrue保持和 LSTM 一致的输入格式。Transformer 训练时学习率要调小一般 1e-4 到 5e-4配合 warmup 更稳。参数说明d_model和nhead要满足整除关系64 配 4 头是常见组合。num_layers设 2 到 3再深在小数据集上没意义。5. 避坑与排查负荷预测系统上线前必须过的五道坎5.1 现象验证集 MAPE 很低上线后误差翻倍原因验证集和测试集切分时用了随机打乱或者调参阶段反复看了测试集导致指标虚高。另一个常见原因是验证集落在平稳时段上线后遇到节假日或极端天气模型没见过。解决严格用滚动窗口切分测试集只在最终验收时用一次。上线前用最近一个月的真实数据做一次盲测包含至少一个周末和一个节假日。如果盲测误差明显高于验证集说明模型泛化不够需要加外生变量或换更稳的模型。5.2 现象LSTM 训练损失下降但验证损失震荡原因学习率太大或者 batch size 太小导致梯度噪声大。负荷数据本身噪声不小LSTM 对学习率敏感。解决学习率降到 5e-4 或 1e-4加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)。batch size 提到 64 以上。如果还震荡检查输入特征有没有做归一化负荷数值动辄几千上万千瓦不归一化 LSTM 很难收敛。5.3 现象ARIMA 预测出来是一条直线原因差分次数过多导致序列过度平稳或者 p、q 都取了 0模型退化成均值预测。解决检查 ADF 检验结果d 一般取 1 就够。定阶时确保 p 和 q 至少有一个大于 0。如果数据本身周期性很强考虑用 SARIMA 加季节项或者直接换树模型和深度学习模型。5.4 现象随机森林特征重要性里滞后特征排最后原因滞后特征没造对比如shift方向反了或者重采样之后索引错位导致特征和标签对不上。解决造完特征后手动检查几行确认lag_1确实是前一小时的负荷。用df[[load, lag_1]].head(10)打印出来肉眼核对。重采样之后一定要sort_index()否则 rolling 和 shift 都会错位。5.5 现象多模型集成后效果还不如单模型原因集成的模型之间相关性太高比如 LSTM 和 GRU 在同一份数据上预测结果几乎一样加权平均没有信息增益。或者集成权重是在验证集上过拟合出来的。解决选模型时看误差的相关性相关性高于 0.9 的模型留一个就行。集成权重用简单的等权平均先试往往比复杂加权稳。如果要做 stacking元学习器的训练数据要用滚动切分生成不能用全量数据。6. 进阶验证与模型选择用滚动回测和误差分解定最终方案模型选到最后不能只看一个 MAPE 数字。我一般会做两件事滚动回测和误差分解。滚动回测是模拟真实上线场景每次用过去 N 天训练预测未来 24 小时滚动前进统计多轮误差的均值和方差。误差分解是把总误差拆成高峰时段、平段、低谷时段分别看因为调度最关心的是高峰时段的误差低谷差一点无所谓。下面这个回测框架可以直接套用。def rolling_backtest(df, model_fn, train_days60, pred_hours24, n_rounds10): 滚动回测每轮用 train_days 训练预测 pred_hours results [] total_hours len(df) for i in range(n_rounds): test_end total_hours - i * pred_hours test_start test_end - pred_hours train_start test_start - train_days * 24 if train_start 0: break train df.iloc[train_start:test_start] test df.iloc[test_start:test_end] model model_fn(train) pred model.predict(test) mape mean_absolute_percentage_error(test[load], pred) results.append({round: i, mape: mape, peak_mape: mape if test[hour].max() in [18, 19, 20] else None}) return pd.DataFrame(results) # 用随机森林跑一轮回测 def rf_factory(train): feature_cols [c for c in train.columns if c ! load] model RandomForestRegressor(n_estimators300, max_depth12, n_jobs-1) model.fit(train[feature_cols], train[load]) return model backtest_result rolling_backtest(df, rf_factory, train_days60, n_rounds10) print(backtest_result[[round, mape]].describe())逻辑说明train_days60表示每次用过去 60 天数据训练这个窗口要覆盖至少两个完整周周期。n_rounds10滚动 10 次看 MAPE 的均值和标准差标准差大说明模型不稳定。高峰时段单独统计是因为调度对高峰误差更敏感如果高峰 MAPE 明显高于整体说明模型在尖峰处欠拟合需要加温度特征或换能捕捉突变的模型。参数说明train_days根据数据量调数据多可以拉到 90 天数据少 30 天也行但要保证覆盖完整周期。pred_hours按实际调度需求设日前调度就是 24。回测轮数至少 10 轮太少统计意义不够。选模型的最终建议先用 ARIMA 和随机森林跑基线如果随机森林的 MAPE 已经满足调度要求一般日前预测要求 3% 以内就不用上深度学习。如果误差卡在瓶颈再上 LSTM 或 GRU重点调seq_len和hidden_dim。Transformer 留到最后数据量不够别碰。多模型集成优先选误差相关性低的组合比如随机森林加 LSTM比 LSTM 加 GRU 有意义。我自己踩过最大的坑是早期太迷信模型复杂度花两周调 Transformer最后发现把lag_168和温度特征加进随机森林误差就降了一半。后来养成习惯每上一个新模型之前先问自己特征工程还有没有空间。希望帮到你。本文还有配套的精品资源点击获取