十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多特征电力负荷预测的深度学习实践:从特征工程到LSTM/CNN模型

多特征电力负荷预测的深度学习实践:从特征工程到LSTM/CNN模型 简介面向电力系统智能化转型需求深度学习驱动的多特征电力负荷预测已成为精准调度与节能降耗的关键技术。这份资源包聚焦于融合气象、历史负荷等多维特征系统展示了从数据清洗、特征工程到LSTM/GRU等时序模型构建与评估的完整流程适合电力数据分析初学者及深度学习应用开发者。包体结构紧凑共9个文件包含Python模型脚本、原始CSV/Excel数据、Markdown说明文档及许可证整体占用仅830KB便于快速下载与本地复现。目前已有148人加入学习内容覆盖数据可视化、预处理、预测实现与结果评估并配套README引导文档能够帮助读者理清建模思路。通过动手实践可获得一套可直接修改运行的电力负荷预测模板提升真实场景下的时序建模与分析能力。1. 电力负荷预测为什么绕不开“多特征”这三个字一条 15 分钟的负荷曲线背后是气温、湿度、节假日、前一天的用电惯性甚至还有工厂排产周期的影子。单靠历史负荷序列做预测模型在晴天、雨天、长假前后几乎同时失效——原因很简单负荷不是纯时间序列它是多因素耦合的结果。深度学习在这类问题上的优势不在于“网络更深”而在于它能同时吸收时序上下文和外部特征在特征交叉中把“为什么今天用电高”这件事学出来。这篇文章从数据构造、模型选型到误差归因把基于深度学习的多特征电力负荷预测的完整链路讲透包含可以直接抄走的代码和参数。适合正在做能源调度、微电网管理或刚接触时序预测项目、想跨过“跑通 demo 但上不了线”这一关的工程师。2. 多特征负荷预测的数据清洗与特征工程先解决“喂什么”2.1 多特征到底包含哪些从气象、日历到滞后负荷多特征电力负荷预测的“多”不是指把 CSV 里所有列都塞进模型。常见做法是把特征分成三组第一组是气象特征包括干球温度、露点温度、相对湿度、风速、降雨量第二组是日历特征包括小时、星期、是否节假日、是否工作日、季节第三组是历史负荷特征也叫滞后特征通常取预测点之前 24、48、168 小时的负荷值。第三组数据实际上已经包含了“最近几天同时段用电水平”的信息对短期预测的贡献往往比气象特征更大。这三组特征的性质不同处理方式也不同。气象特征是连续数值但和负荷之间往往不是线性关系——气温对负荷的影响呈 U 形曲线夏季越热负荷越高冬季越冷负荷越高春秋季存在一个负荷低谷区。日历特征是周期性离散变量小时和星期必须编码成模型能理解的形态。滞后负荷特征是强时序相关变量它的存在让模型有机会“记住”近期模式但也引入了多重共线性风险。理解这一点就明白为什么不能简单地把所有列 concat 进神经网络。2.2 数据对齐与缺失值处理气象数据和负荷数据的时间戳陷阱拿到一份“气象负荷”数据第一件事永远是对齐时间戳。气象站的数据通常是整点记录负荷数据可能是 15 分钟一个点两者直接 merge 会生成大量 NaN。常见做法是以负荷数据的时间戳为基准对气象数据做前向填充用上一时刻的观测值填补当前时刻的空缺——对气象来说这是合理的因为气温在 15 分钟内不会突变。如果缺失窗口超过 2 小时前向填充就不合适了需要用插值或直接丢弃该时段。import pandas as pd import numpy as np load_df pd.read_csv(load.csv, parse_dates[timestamp]) weather_df pd.read_csv(weather.csv, parse_dates[timestamp]) # 以负荷数据为基准气象数据按时间对齐并前向填充 merged pd.merge_asof( load_df.sort_values(timestamp), weather_df.sort_values(timestamp), ontimestamp, directionbackward ) # 连续缺失超过 8 个点2小时的气象列用前后均值插值 for col in [temperature, humidity]: missing_streak merged[col].isna().astype(int).groupby( (merged[col].notna() ! merged[col].notna().shift()).cumsum() ).transform(sum) valid_idx merged[col].notna() | (missing_streak 8) merged.loc[~valid_idx, col] np.nan merged[col] merged[col].interpolate(methodlinear, limit_directionboth)merge_asof的作用是按时间顺序做“最近邻”匹配directionbackward表示取当前时刻之前最近的一条气象记录避免未来数据泄露。缺失值处理时先标记连续缺失长度再决定是插值还是保留 NaN原因是interpolate在长窗口连续缺失时会拉出一条不真实的直线污染训练分布。2.3 离散特征的编码方式小时和星期为什么不能用独热硬怼小时和星期这类周期性特征直觉上会用 One-Hot 编码但这里有一个时序模型特有的问题独热编码会完全切断 23 点和 0 点之间的连续性。在负荷数据里23 点和 0 点的用电模式非常接近独热编码会让模型把它们当成两个互不相干的状态白白增加学习难度。更合理的做法是用正弦余弦编码把小时映射到单位圆上def cyclical_encode(df, col, period): df[col _sin] np.sin(2 * np.pi * df[col] / period) df[col _cos] np.cos(2 * np.pi * df[col] / period) return df merged cyclical_encode(merged, hour, 24) merged cyclical_encode(merged, weekday, 7)编码后的 sin/cos 两个维度构成一个圆形空间0 点和 23 点的距离天然很近模型能更容易学到“深夜负荷低”这一跨日规律。对于节假日这种没有周期的离散特征用 0/1 标志位即可不需要额外处理。随后把这组特征作为模型输入的一部分在构造数据集时与负荷序列拼接。2.4 特征构造的滑窗逻辑与训练样本的组织方式多特征时序预测的数据集不能像图像分类那样随机拆 train/test必须按时间顺序滑窗。设定一个 168 小时7天的 lookback 窗口用过去一周的气象、日历、负荷数据预测未来 24 小时的负荷这是电力负荷预测中最常见的配置。每一步滑动是 1 小时这样 1 年的小时数据能产生约 8600 个训练样本对深度学习模型来说是够用的量级。def create_sequences(features, target, lookback168, horizon24): X, y [], [] for i in range(len(features) - lookback - horizon 1): X.append(features[i:i lookback]) y.append(target[i lookback:i lookback horizon]) return np.array(X), np.array(y) X, y create_sequences(feature_matrix, load_values, lookback168, horizon24) print(X.shape, y.shape) # 输出示例: (8593, 168, feature_dim) (8593, 24)这个函数生成的三维张量第一维是样本数第二维是时间步长 168第三维是特征维度。模型在每个时间步都收到一组完整的“气象日历负荷”特征而不是把所有变量都摊平成一个大向量——这是多特征时序模型和普通 MLP 在数据组织方式上的根本差别也直接决定了模型能否利用时间维度的局部关联。3. 基于深度学习的多特征预测模型从 LSTM 到 CNN-LSTM 的选型与结构3.1 模型选型为什么 LSTM 仍然是电力负荷预测的默认起点电力负荷预测这个场景有几个鲜明特点序列本身存在明显的日内周期和七日周期预测时效要求高通常在秒级到分钟级完成推理数据量相对有限很难支撑超大模型的训练。这些特点指向一个结论——LSTM 依然是性价比最高的起点。GRU 也可以参数更少、训练更快但 LSTM 的门控结构在捕捉负荷序列的长周期依赖上略微稳定行业内的成熟代码和调参经验也更丰富。需要特别注意的一点是单层 LSTM 往往比堆叠多层更实用。负荷预测的特征维度通常不高数据量在万级样本上下多层 LSTM 的参数量很容易超过数据的表达能力上限表现为训练集损失下降正常、验证集波动剧烈。我一般会从单层隐藏单元 64 开始验证集效果不够再加一层或调大单元数而不是一上来就搭三层网络。这不算炫技但在真实项目里能省出大量排错时间。3.2 多特征怎么进网络拼接、分支输入与注意力机制的选择多特征进 LSTM 的方式有三种常见方案。第一种是把全部特征拼成一个向量在每个时间步同时输入第二种是让负荷序列和外部特征走不同的子网络在 LSTM 输出层做融合第三种是加注意力机制让模型自己决定当前时刻更关注哪些特征。实践中第一种方案在小规模数据上效果不错但存在一个隐患气象特征的变化频率比负荷慢得多当外部特征维度较高时LSTM 的遗忘门可能把真正重要的负荷滞后信息给弱化。第二种方案更稳——让负荷序列单独过一个 LSTM气象和日历特征过一个全连接层然后把两者的输出拼接后进预测层。这样做的好处是让 LSTM 专心建模负荷自身的时间模式不被低频外特征干扰。推荐优先尝试分支输入import torch import torch.nn as nn class MultiFeatureLSTM(nn.Module): def __init__(self, load_dim, ext_dim, hidden_size64, horizon24): super().__init__() self.load_lstm nn.LSTM(load_dim, hidden_size, batch_firstTrue) self.ext_fc nn.Sequential( nn.Linear(ext_dim, 32), nn.ReLU(), nn.Dropout(0.2) ) self.fc_out nn.Sequential( nn.Linear(hidden_size 32, 64), nn.ReLU(), nn.Linear(64, horizon) ) def forward(self, load_seq, ext_seq): # load_seq: (batch, lookback, load_dim) ext_seq: (batch, lookback, ext_dim) lstm_out, (h_n, c_n) self.load_lstm(load_seq) last_hidden h_n[-1] ext_feat ext_seq[:, -1, :] # 取预测起点时刻的外部特征 ext_rep self.ext_fc(ext_feat) combined torch.cat([last_hidden, ext_rep], dim-1) return self.fc_out(combined)ext_seq[:, -1, :]取最后一个时间步的外部特征对应的是预测起始时刻的天气和日历信息。这个设计的理由在于未来 24 小时的气象预报本身就有不确定性预测时直接用起点时刻的已知值比用一整段未来预报更稳也规避了测试阶段拿不到未来气象数据的问题。3.3 一种更稳的混合结构CNN 提取局部模式LSTM 捕捉长程依赖LSTM 对逐点变化很敏感但负荷曲线中的局部形态比如持续两小时的尖峰、早晚高峰的过渡段用 CNN 卷积核来提取更高效。CNN-LSTM 混合结构就是先让一维卷积在时间维度上滑动把局部模式抽象成更高层的特征序列再送入 LSTM 做时序建模。这种组合对负荷数据特别友好因为用电模式里存在大量“短时形状”信息比如下班前半小时负荷开始爬升、晚上 10 点后阶梯式下降——卷积核可以学到这些判别性形状。class CNNLSTMPredictor(nn.Module): def __init__(self, n_features, hidden_size64, horizon24): super().__init__() self.conv1 nn.Conv1d(n_features, 32, kernel_size5, padding2) self.conv2 nn.Conv1d(32, 32, kernel_size3, padding1) self.relu nn.ReLU() self.lstm nn.LSTM(32, hidden_size, batch_firstTrue) self.fc nn.Linear(hidden_size, horizon) def forward(self, x): # x: (batch, lookback, n_features) x x.permute(0, 2, 1) # 转为 (batch, n_features, lookback) x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) x x.permute(0, 2, 1) # 转回 (batch, lookback, 32) out, _ self.lstm(x) return self.fc(out[:, -1, :])kernel_size5的卷积核在 168 步的输入上感受野为 5 小时刚好覆盖“傍晚负荷爬坡”这类短时形态padding 保持时间维度不变后续 LSTM 才能按原时序读取。注意卷积输入需要把特征维放到通道维这是Conv1d的格式要求顺手说一句这也是新手最常报错的点。3.4 多步预测策略递归、直接与多输出它们的差距在哪预测未来 24 小时的负荷有三种常见策略。递归预测是把模型上一步的输出当作下一步的输入循环 24 次实现简单但误差会随时间步累加尤其在早晚高峰切换的时段误差会被放大。直接预测是训练 24 个模型每个负责预测一个未来小时精度尚可但训练开销大。多输出预测是让模型一次输出 24 个值即前面代码中输出层维度设为horizon24的做法训练效率高也是本文代码默认采用的方案。多输出策略的关键在于损失函数。如果只用 MSE模型倾向于学习“全天平均”的水平早晚高峰的极值会被平滑掉。可以改用分位数损失或对多个输出步分别加权比如对未来 1-6 小时内的步赋予更高权重因为这些步的预测误差对调度决策影响最大。用 PyTorch 实现加权 MSE 并不复杂核心是构造一个和输出等长的权重向量在损失计算时逐元素相乘。4. 训练与验证时间序列划分、归一化、早停与评价指标4.1 数据划分不能乱 shuffle按时间顺序切 train/val/test图像分类的数据集随机打乱没有问题时序预测这样做就是灾难——模型会在验证阶段“看到”训练阶段未来的数据验证指标虚高上线后立刻露馅。正确的做法是按时间顺序切分假设数据覆盖 2023 年全年前 8 个月训练、中间 2 个月验证、最后 2 个月测试。这个比例不是固定的但有一个原则必须遵守——验证集和测试集的时间范围都要晚于训练集。train_end int(len(X) * 0.7) val_end int(len(X) * 0.85) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:]这种切分方式让验证集和测试集都处在训练集的“未来”真实反映模型在未见时段的泛化能力。另一个细节是滑窗生成的相邻样本之间高度重叠训练集内部存在严重的自相关性这会低估训练损失但不影响验证集评估的可靠性。如果数据量足够大可以用时间序列交叉验证——按年份分折逐年前推训练集但很多人会忽略它带来的训练成本成倍增长数据量不足 3 年时意义不大。4.2 归一化的正确姿势与反归一化的两个坑负荷数据以 MW 为单位数值在几百到几千之间浮动气温在零下到三十几度之间如果不归一化LSTM 的梯度更新会被大数值特征主导。常见做法是对所有连续特征做 Z-score 归一化公式为(x - mean) / std均值和标准差只从训练集统计验证集和测试集复用训练集的统计量——这个顺序不能颠倒否则测试集信息泄露到训练过程中。from sklearn.preprocessing import StandardScaler scaler_X StandardScaler() scaler_y StandardScaler() n_samples, lookback, n_feats X.shape X_flat X.reshape(-1, n_feats) X_flat_scaled scaler_X.fit_transform(X_flat) X_scaled X_flat_scaled.reshape(n_samples, lookback, n_feats) y_flat y.reshape(-1, 1) y_scaled scaler_y.fit_transform(y_flat).reshape(n_samples, -1)反归一化也有两个常见的坑。第一预测出的结果必须用scaler_y.inverse_transform还原成真实负荷值再算误差拿归一化后的数值直接算 MAPE结果没有物理意义。第二如果使用了多个特征的滑窗测试阶段也必须走同样的归一化流程不能单独对某个特征做归一化否则特征间的尺度关系被破坏模型输出的分布就偏移了。很多人在这里省事最后 MAPE 虚高其实是度量单位不一致造成的乌龙。4.3 训练过程的监控指标与早停策略训练时除了看 loss还要盯验证集上的 MAPE。电力负荷预测领域MAPE平均绝对百分比误差是通用的效果度量计算公式为MAPE (1/n) * sum(|y_true - y_pred| / |y_true|) * 100%由于负荷值基本为正MAPE 能直观反映预测偏差的百分比调度人员不需要理解 RMSE 的量纲就能读懂。早停策略的触发条件是验证集 MAPE 连续 10 个 epoch 不下降保存最佳模型而不是最后一个 epoch 的权重——这一步价值很大深度学习模型的验证集指标在训练后期往往出现剧烈波动直接保存最后一轮权重可能丢掉最优解。best_val_mape float(inf) patience 10 bad_epochs 0 for epoch in range(max_epochs): train_loss train_one_epoch(model, train_loader, optimizer, criterion) val_pred, val_true predict(model, val_loader) val_mape calculate_mape(val_true, val_pred) if val_mape best_val_mape: best_val_mape val_mape torch.save(model.state_dict(), best_model.pth) bad_epochs 0 else: bad_epochs 1 if bad_epochs patience: print(fearly stop at epoch {epoch}) break4.4 常用训练超参数的经验范围参数推荐范围备注隐藏单元数32~128从 64 起步数据量大再加大学习率1e-4 ~ 1e-3Adam 优化器下 1e-3 过高时降一半Batch Size64~256数据量大用 256小数据集用 64 防过拟合Dropout0.1~0.3只在 LSTM 输出层加输入层不加滑窗长度72~3361687天是兼顾周期与计算量的默认值输出步长24 或 48多输出策略下越大误差越大学习率的设置往往比网络结构更影响最终效果。Adam 优化器的默认学习率 1e-3 在负荷数据上通常偏大训练初期 loss 下降很快但到后期验证集指标会出现锯齿状波动。遇到这种情况直接把学习率降到 3e-4 重训一轮往往比调整网络结构更有效。另外PyTorch 的ReduceLROnPlateau调度器值得启用它在验证指标停滞时自动降低学习率省去手动调整。scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5, min_lr1e-6 ) # 在每个 epoch 结束时调用 scheduler.step(val_loss)factor0.5表示每次触发学习率减半patience5表示验证 loss 连续 5 轮不降才触发。这里用验证 loss 而不是训练 loss是因为训练 loss 总会持续下降用它做监控没有意义。5. 多步预测误差的归因与残差诊断让负荷预测模型真正可上线5.1 预测误差随预测步长变化的量化方法多输出模型的一个典型特征是越靠后的预测步误差越大。这是因为模型的最后一个全连接层需要同时输出 24 个值距离当前时刻越远的负荷不确定性越高。上线前要做的一件事是把测试集上的误差按预测步分解开来画出每个小时的 MAPE 曲线这能让模型的问题一目了然——如果误差在某个时段出现尖峰可能是该时段负荷波动过大也可能是特征没有覆盖到关键信息。def per_step_mape(y_true, y_pred): # y_true, y_pred: (n_samples, horizon) return np.mean(np.abs(y_true - y_pred) / np.abs(y_true), axis0) * 100 step_mape per_step_mape(y_test_true, y_test_pred) for i, mape in enumerate(step_mape, start1): print(fH{i:02d} MAPE: {mape:.2f}%)如果 H1 的 MAPE 是 2%而 H24 飙到 8%说明模型的长期预测能力不足。常见应对方案有两个一是把输出步长从 24 缩短到 12滚动预测两次拼出 24 小时的结果误差分布会更均匀二是在训练时对远期步的 loss 做降权让模型优先保证近期精度。实际业务中调度决策对 H1 到 H6 的精度要求最高远期结果主要用于趋势判断分步评估的意义在于跟业务方对齐“模型哪里靠谱、哪里只能参考”。5.2 残差分析判断模型是否存在系统性偏差而不是随机噪声只盯着 MAPE 数值是不够的。把测试集预测值和真实值的残差按小时和星期分组统计往往会发现系统性偏差比如中午时段的残差始终为负说明模型系统性地低估了午间负荷节假日前一天的残差波动剧烈说明模型没能从特征中充分捕捉节前用电行为。这种情况下不是简单调参能解决的需要回到特征工程环节——把“是否节前一天”这个特征单独拆出来喂给模型让它有足够的信息去学习这种特殊日期的模式。residuals y_test_true - y_test_pred for hour in range(24): hour_mask test_timestamps.hour hour hour_res residuals[hour_mask] print(fHour {hour:02d}: mean residual {hour_res.mean():.2f} MW)另外还需要检查残差的自相关性。如果残差序列在相邻时间步上存在明显相关性说明模型没有完全提取时序信息常见后果是模型倾向于“跟着上一时刻的误差走”误差会出现同向累积。简单判断方法是计算残差的自相关系数滞后 1 阶和 24 阶的自相关系数如果明显不为零就说明模型还遗漏了短时或日内周期的某些模式。此时优先增大 LSTM 隐藏单元数或加深卷积层大概率能吸收掉这部分残留信息而不是急着换模型结构。5.3 上线前的最后一道检查用滚动验证复现线上行为测试集一次性评估通过不代表线上表现稳定。真正的检验是滚动验证——模拟真实的预测过程从某个起点开始预测未来 24 小时然后向前推进 1 小时用新到达的真实数据更新输入窗口继续预测下一轮。这个过程的代码和训练时的滑窗逻辑一致但注意要实时更新滞后特征不能让模型用旧窗口“硬撑”24 小时否则评估结果会与线上行为脱节。def rolling_forecast(model, X_full, start_idx, horizon24): preds [] for i in range(start_idx, len(X_full) - horizon): x X_full[i:i1] # 每次只取当前窗口 with torch.no_grad(): pred model(x).squeeze().numpy() preds.append(pred) return np.array(preds)滚动验证得到的误差通常比一次性测试集评估高一些这个差距本身就是模型上线前的安全边际。如果滚动验证的 MAPE 明显劣化优先检查滞后特征是否在真实预测时能及时获取因为那一项数据如果延迟线上效果和离线评估差异会很大。多特征负荷预测模型的落地瓶颈往往不在模型结构而在数据管道的稳定性和误差分解的合理性这两点做扎实了模型才能在调度室里稳定运行。本文还有配套的精品资源点击获取
返回列表