多模型电力负荷预测实战:从ARIMA到Transformer的选型与避坑
简介这份资源面向电力系统调度、能源管理及时间序列预测方向的学习者与工程人员提供一套基于每小时电力负荷数据的预测系统用于研判未来负荷变化趋势辅助电网调度优化与能源分配决策。压缩包共11个文件约54KB以8个Python脚本为核心分别实现ARIMA、决策树、KNN、GRU、LSTM、随机森林与Transformer等模型的建模流程另附README、说明文件与docx资料便于理解各模型的原理、调用方式与适用场景。已有63人学习下载。读者可据此对比传统统计方法与深度学习、集成学习在负荷预测上的表现掌握从数据预处理、特征构建到多模型训练与结果评估的完整思路并可按需组合模型提升预测精度与鲁棒性适合作为课程设计、科研入门或电网调度场景下的实践参考。1. 从一份电力负荷数据说起为什么单一模型总在早高峰翻车很多做电力负荷预测的朋友都有过类似的经历拿一份历史每小时负荷数据随手套一个 LSTM 或者 ARIMAMAPE 看着还行一到早高峰和晚高峰就集体翻车误差直接翻倍。问题往往不在模型本身而在于电力负荷是典型的强周期、多频率叠加时间序列——日周期、周周期、节假日效应、温度敏感性和突发扰动混在一起单一模型很难同时抓住这些模式。这个标题指向的正是这类场景用每小时电力负荷数值预测未来变化趋势服务于电网调度优化和能源管理并且把 ARIMA、决策树、GRU、KNN、LSTM、随机森林、Transformer 等多种机器方法放在同一个系统里做对比和融合。它适合两类人一类是想把时间序列预测真正落到调度业务里的工程师另一类是手里有负荷数据、想搞清楚到底该选哪个模型、参数怎么调、坑在哪的从业者。下面我按自己实际做过的路径把选型、实现、调参和排错一层层拆开。2. 多模型电力负荷预测的选型逻辑谁负责趋势谁负责突变2.1 先看清负荷曲线的三层结构再谈模型电力负荷序列可以粗略拆成三层趋势层季节、经济增长带来的缓慢变化、周期层日周期、周周期、扰动层天气突变、节假日、大用户启停。ARIMA 擅长的是趋势加线性自相关对平稳化后的序列做差分和移动平均本质是线性外推决策树和随机森林擅长的是把多维特征温度、星期、小时、历史滞后映射到负荷值对非线性和特征交互友好但外推能力弱遇到训练集没见过的负荷水平容易给出平台值KNN 在特征空间里找相似日适合捕捉“今天像历史上哪一天”这种模式但对维度灾难和距离度量敏感LSTM 和 GRU 是循环网络GRU 是 LSTM 的简化门控版本参数更少、训练更快在小时级负荷上往往和 LSTM 打平甚至更好Transformer 靠自注意力捕捉长程依赖适合数据量足够大、周期跨度长的场景但小数据集上容易过拟合。选型不是选一个最强的而是先明确你的预测步长和业务容忍度提前 1 小时调度和提前 24 小时排班对模型的要求完全不同。2.2 用一份配置表把模型和场景对齐我一般会先做一张模型-场景对照表避免后面反复返工。下面这张表是我在多个负荷预测项目里沉淀下来的经验值可以直接作为选型起点。模型适合预测步长数据量要求关键优势主要风险ARIMA1-6 小时小可解释、快非线性差、需平稳化决策树1-24 小时中特征交互直观外推差、易过拟合随机森林1-24 小时中抗噪、特征重要度外推平台化KNN1-24 小时中相似日检索维度灾难、慢GRU1-48 小时大训练快、门控简长程依赖弱于 LSTMLSTM1-48 小时大长程记忆训练慢、易过拟合Transformer24-168 小时很大长程注意力小数据过拟合这张表不是绝对规则但能帮你快速排除明显不合适的模型。比如你只有半年小时级数据硬上 Transformer 基本是给自己找麻烦反过来如果要做未来一周的负荷趋势ARIMA 的线性假设会明显不够用。2.3 特征工程比模型选择更影响最终误差在电力负荷预测里特征工程的重要性经常被低估。我通常会把特征分成四组时间特征小时、星期、是否节假日、月份、滞后特征前 1、2、3、24、168 小时负荷、滑动统计过去 24 小时均值、最大值、标准差、外部特征温度、湿度、天气类型。其中滞后 24 和 168 小时这两个特征对日周期和周周期特别关键很多模型加上它们之后误差能降一截。下面是一段用 pandas 构造这些特征的代码可以直接套用。import pandas as pd import numpy as np # 假设 df 有 timestamp 和 load 两列按小时采样 df df.sort_values(timestamp).reset_index(dropTrue) df[hour] df[timestamp].dt.hour df[weekday] df[timestamp].dt.weekday df[is_weekend] (df[weekday] 5).astype(int) df[month] df[timestamp].dt.month # 滞后特征前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[temp_lag_1] df[temp].shift(1) df df.dropna().reset_index(dropTrue)这段代码的逻辑是先构造时间维度特征再用 shift 生成滞后项最后用 rolling 生成滑动统计。注意所有滞后和滑动统计都必须基于 shift(1) 之后的数据否则会引入未来信息导致离线指标虚高、上线直接崩。参数上滞后阶数不是越多越好我一般先用 1、2、3、24、168 这五个再根据特征重要度决定是否加 48、72。滑动窗口 24 对应日周期如果做周级预测可以再加 168。3. 把 ARIMA 到 Transformer 跑通最小可复现流程与参数设置3.1 用 ARIMA 做基线差分阶数和 p、q 怎么定ARIMA 我一般只用来做基线因为它快、可解释能帮你判断数据里还有多少线性自相关没被解释。流程是先做 ADT 平稳性检验不平稳就差分差分阶数 d 通常取 1 或 2然后看 ACF 和 PACF 定 p 和 q 的初值最后用 AIC 或 BIC 网格搜索确认。下面是一段最小示例。import pandas as pd from statsmodels.tsa.arima.model import ARIMA from statsmodels.tsa.stattools import adfuller # 取一列负荷序列 series df.set_index(timestamp)[load].asfreq(H) # 平稳性检验 adf_result adfuller(series.dropna()) print(ADF p-value:, adf_result[1]) # 小于0.05认为平稳 # 如果 p-value 大先差分 series_diff series.diff().dropna() # 网格搜索 p,d,q best_aic np.inf best_order None for p in range(0, 4): for d in range(0, 3): for q in range(0, 4): try: model ARIMA(series, order(p, d, q)) result model.fit() if result.aic best_aic: best_aic result.aic best_order (p, d, q) except Exception: continue print(Best ARIMA order:, best_order, AIC:, best_aic)这段代码先做 ADF 检验判断是否需要差分再用三层循环搜索 p、d、q。参数上p 和 q 一般不超过 3d 不超过 2否则容易过拟合且训练变慢。AIC 越小越好但不要只看 AIC还要看残差是否白噪声。ARIMA 的典型翻车点是差分过度导致序列过度平滑预测值几乎是一条直线或者没做平稳化直接拟合结果完全不收敛。3.2 决策树和随机森林特征重要度比预测值更有用决策树和随机森林在负荷预测里最大的价值不是点预测精度而是特征重要度。你可以先用随机森林跑一遍看看哪些滞后和外部特征真正在起作用再决定给 LSTM 或 Transformer 喂哪些特征。下面是一段随机森林的示例。from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_percentage_error feature_cols [hour, weekday, is_weekend, month, lag_1, lag_2, lag_3, lag_24, lag_168, roll_mean_24, roll_std_24] X df[feature_cols].values y df[load].values # 时间序列交叉验证不能用普通 KFold tscv TimeSeriesSplit(n_splits5) mape_scores [] for train_idx, val_idx in tscv.split(X): X_train, X_val X[train_idx], X[val_idx] y_train, y_val y[train_idx], y[val_idx] rf RandomForestRegressor(n_estimators300, max_depth12, min_samples_leaf5, random_state42) rf.fit(X_train, y_train) pred rf.predict(X_val) mape_scores.append(mean_absolute_percentage_error(y_val, pred)) print(RF MAPE:, np.mean(mape_scores)) # 特征重要度 importances pd.Series(rf.feature_importances_, indexfeature_cols) print(importances.sort_values(ascendingFalse))这里的关键点是必须用 TimeSeriesSplit不能用普通 KFold否则验证集里混入未来数据指标会虚高。参数上n_estimators 300 起步max_depth 控制在 10 到 15 之间防止过拟合min_samples_leaf 设 3 到 5 能明显降低噪声。随机森林的典型问题是外推如果验证集负荷水平超出训练集范围预测会卡在训练集最大值附近这是树模型的固有缺陷不是调参能解决的。3.3 GRU 和 LSTM窗口长度和门控单元的取舍GRU 和 LSTM 是负荷预测里最常用的两个循环网络。GRU 把 LSTM 的输入门、遗忘门、输出门简化为更新门和重置门参数少约三分之一训练更快在小时级负荷上通常和 LSTM 差距很小。我一般先上 GRU如果误差不达标再换 LSTM。下面是一段 PyTorch 的 GRU 示例。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class GRUForecaster(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, output_size1): super().__init__() self.gru nn.GRU(input_size, hidden_size, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): out, _ self.gru(x) return self.fc(out[:, -1, :]) # 取最后一个时间步 # 构造滑动窗口样本 def make_windows(data, window24, horizon1): X, y [], [] for i in range(len(data) - window - horizon 1): X.append(data[i:iwindow]) y.append(data[iwindowhorizon-1]) return np.array(X), np.array(y) # 假设 features 是归一化后的特征矩阵 window 24 X, y make_windows(features, windowwindow, horizon1) X_tensor torch.tensor(X, dtypetorch.float32) y_tensor torch.tensor(y, dtypetorch.float32).unsqueeze(-1) dataset TensorDataset(X_tensor, y_tensor) loader DataLoader(dataset, batch_size64, shuffleTrue) model GRUForecaster(input_sizeX.shape[-1]) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() for epoch in range(50): model.train() for xb, yb in loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step()窗口长度我一般从 24 开始试对应一个日周期如果做周级预测会加到 168。hidden_size 64 到 128 之间num_layers 2 层足够再深容易过拟合。dropout 0.2 是常用起点。GRU 和 LSTM 的典型坑是输入特征没做归一化导致梯度爆炸或训练不收敛窗口太长但数据量不够模型记不住反而变差以及用未来数据做归一化上线后分布偏移。3.4 KNN 和 Transformer一个做相似日一个做长程注意力KNN 在负荷预测里我主要用来做相似日检索比如找历史上和今天温度、星期、季节最接近的若干天用它们的负荷曲线做加权平均。它的优势是可解释调度人员容易接受。Transformer 则适合数据量足够大的场景用自注意力捕捉跨天、跨周的依赖。下面是一段 KNN 相似日检索的示例。from sklearn.neighbors import NearestNeighbors from sklearn.preprocessing import StandardScaler # 用温度、星期、小时等构造日特征 daily_features df.groupby(df[timestamp].dt.date).agg({ temp: mean, load: mean, weekday: first }).reset_index() scaler StandardScaler() X_daily scaler.fit_transform(daily_features[[temp, weekday]]) # 找最近 5 个相似日 knn NearestNeighbors(n_neighbors5, metriceuclidean) knn.fit(X_daily) distances, indices knn.kneighbors(X_daily[-1:]) # 最后一天 print(相似日索引:, indices)KNN 的参数主要是 k 和距离度量k 一般取 3 到 7距离用欧氏或曼哈顿。它的坑是维度灾难特征一多距离就失去区分度所以日特征要精简。Transformer 我一般只在数据超过两年小时级、且要做 24 小时以上预测时才考虑否则 GRU 或 LSTM 性价比更高。4. 避坑与排查电力负荷预测里最容易翻车的五件事4.1 用随机划分做验证指标虚高上线崩现象离线 MAPE 只有 3%上线后误差跳到 15% 以上。原因用 train_test_split 随机划分验证集里混入了未来数据模型提前“看到”了未来负荷水平。解决所有验证必须用时间序列切分比如 TimeSeriesSplit 或按时间点硬切确保训练集时间早于验证集。4.2 归一化用了全量数据上线分布偏移现象训练收敛很好上线后预测值整体偏大或偏小。原因归一化时用了包含验证集和未来数据的均值和方差上线后新数据分布不同输入尺度错位。解决归一化的均值和方差只能用训练集计算然后保存下来推理时复用同一组参数。4.3 滞后特征构造时引入未来信息现象模型在训练集上表现极好但实际预测时滞后特征取不到。原因构造 lag 特征时没有 shift或者 rolling 时用了当前时刻。解决所有滞后和滑动统计必须基于 shift(1) 之后的数据代码里养成先 shift 再 rolling 的习惯。4.4 树模型外推平台化遇到极端负荷失效现象随机森林和决策树在正常负荷区间误差不大但遇到极端高温或节假日负荷时预测值卡在某个平台。原因树模型本质是分段常数无法外推到训练集未见过的负荷水平。解决树模型只做特征筛选和基线最终预测用 GRU、LSTM 或 Transformer或者对树模型残差再做一次线性外推。4.5 多模型融合时权重拍脑袋反而拖累效果现象把 ARIMA、随机森林、GRU 的预测简单平均结果比最好的单模型还差。原因不同模型在不同时段误差结构不同固定权重无法适应。解决用验证集误差做加权或者按时段动态选模型比如早高峰用 GRU、夜间用 ARIMA权重和选择逻辑都要在时间序列验证集上确认。5. 把预测系统落到调度业务滚动预测与误差监控的两个技巧5.1 滚动预测每小时更新一次而不是一次预测 24 小时很多调度场景不需要一次预测未来 24 小时而是每小时滚动更新未来 1 到 6 小时。滚动预测的好处是能不断吸收最新负荷误差不会累积。实现上我一般维护一个滑动窗口每次新数据进来就重新构造特征并推理只保留最近 window 小时作为输入。下面是一个滚动预测的骨架。def rolling_forecast(model, history, window24, steps6): history: 最近 window 小时的特征矩阵 preds [] current history.copy() for _ in range(steps): x torch.tensor(current[-window:], dtypetorch.float32).unsqueeze(0) with torch.no_grad(): pred model(x).item() preds.append(pred) # 把预测值作为下一步的滞后特征拼回去简化示意 next_feat current[-1].copy() next_feat[0] pred # 假设第0维是负荷滞后 current np.vstack([current, next_feat]) return preds这段代码的逻辑是每次预测一步把预测值填回特征矩阵再预测下一步。参数上window 和训练时保持一致steps 根据业务需求设 1 到 6。注意滚动预测的误差会随步数累积所以 steps 不宜太大超过 6 小时建议重新训练或换模型。5.2 误差监控按小时和星期分组看 MAPE上线后不能只看整体 MAPE要按小时和星期分组看。我一般会做一个误差热力图横轴是小时纵轴是星期每个格子是该时段的 MAPE。这样能快速发现哪个时段、哪一天模型系统性偏差。比如早高峰 7 到 9 点误差偏高可能是滞后特征没抓住温度突变周末误差偏高可能是节假日特征没做好。监控指标除了 MAPE还要看最大绝对误差和偏差方向避免模型系统性高估或低估。5.3 我自己的习惯先跑通基线再堆模型做了这么多负荷预测项目我最大的教训是不要一上来就上 Transformer 或堆多模型融合。先用 ARIMA 跑一个基线再用随机森林看特征重要度然后用 GRU 或 LSTM 做主力最后才考虑 Transformer 和融合。每一步都要有时间序列验证和误差分组分析否则你根本不知道误差来自数据、特征还是模型。这个方向值得做但前提是把数据泄漏、归一化和验证方式这三个基础问题解决掉不然再花哨的模型也是白搭。希望帮到你。本文还有配套的精品资源点击获取