资讯动态

基于深度学习的电力负荷时间序列预测:从数据清洗到概率预测的Python实战

发布时间:2026/10/18 22:27:02 来源:尧图企业网站定制
简介本资源为基于深度学习算法实现电力负荷时间序列未来预测的Python源码包面向计算机、人工智能、数据科学等相关专业的在校学生、教师及企业员工适用于毕业设计、课程设计、大作业及初期项目立项演示等场景。包内共20个文件以16个py源码文件为主辅以2个txt说明与2个md文档压缩包约43KB涵盖LSTM、GRU、Transformer、ARIMA、决策树、随机森林、KNN等多种时序预测模型的实现代码且均配有详细注释便于理解算法原理与建模流程。资源已有266人学习下载具有较高的学习借鉴价值既适合零基础入门者进阶研读也可作为毕设或课程设计的参考方案帮助读者快速掌握电力负荷预测的完整实现思路并在此基础上进行二次开发与功能拓展。需注意解压后项目路径避免使用中文建议重命名为英文后再运行。1. 电力负荷预测为什么总在下午三点翻车做过电力负荷预测的人大概都有过这种体验模型在训练集上 MAPE 压到 2% 以内一上测试集下午三点到五点那段曲线就开始飘晚高峰的尖峰要么被削平要么被提前拉起来。这不是玄学是负荷序列本身的特性决定的——它同时受气温、工作日/节假日、电价时段、大工业用户排产四重因素叠加而这几路信号在下午时段恰好互相打架。基于深度学习算法实现电力负荷的时间序列未来预测本质上要解决的是一个多变量、强周期、带外生变量的回归问题。输入是过去若干天的负荷曲线加上温度、湿度、日期类型等协变量输出是未来 24 小时或 96 点的负荷值。它适合两类人一类是电网侧做短期调度和日前申报的工程师另一类是园区、工厂做需量管理和储能充放策略的开发者。这篇文章不讲空泛概念直接按「数据怎么整 → 模型怎么搭 → 参数怎么调 → 坑在哪」的顺序把一套能跑通的 Python 方案拆开讲清楚。2. 数据管道与特征工程把原始负荷表变成模型能吃的张量2.1 负荷数据的三个脏点与清洗策略真实负荷数据几乎不会干净。常见脏点有三类一是采集终端掉线导致的连续缺失表现为一段平直的 NaN 或 0二是换表、检修造成的阶跃跳变三是量测噪声带来的毛刺。处理顺序不能乱先补缺失再平滑否则平滑会把缺失段拉成一条假曲线。我一般用「同日同时刻均值 线性插值」两级补缺。先用前 7 天同一时刻的中位数填剩下的再用时间索引线性插值。中位数比均值抗异常值这一步能挡掉大部分尖刺污染。import pandas as pd import numpy as np def clean_load(df, time_coltimestamp, load_colload): df df.sort_values(time_col).set_index(time_col) # 重采样到固定 15 分钟粒度缺失标记为 NaN df df.resample(15min).mean() s df[load_col] # 第一级同日同时刻前 7 天中位数 same_slot s.groupby([s.index.time]).transform( lambda x: x.fillna(x.rolling(7, min_periods1).median())) # 第二级线性插值兜底 s same_slot.interpolate(methodtime, limit8) # 负值和异常大值截断 s s.clip(lower0, uppers.quantile(0.999)) df[load_col] s return df.dropna()逻辑说明resample(15min)把不规则时间戳对齐到标准网格这是后续构造滑窗的前提。groupby(s.index.time)按「时刻」分组保证填的是同一时刻的历史值而不是相邻点。limit8限制插值最多补 2 小时超过就说明是长时段掉线硬补会引入虚假信息宁可丢样本。quantile(0.999)截断是为了挡掉量测满量程的坏点。2.2 时间特征与周期编码sin/cos 不是可选项负荷的日周期和周周期必须显式喂给模型。直接把「小时」当 0-23 的整数丢进去是新手最容易犯的错模型会认为 23 点和 0 点相距很远实际上它俩相邻。正确做法是做三角函数编码。def add_time_features(df): idx df.index df[hour] idx.hour idx.minute / 60.0 df[dow] idx.dayofweek df[is_weekend] (idx.dayofweek 5).astype(int) # 日周期编码 df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) # 周周期编码 df[dow_sin] np.sin(2 * np.pi * df[dow] / 7) df[dow_cos] np.cos(2 * np.pi * df[dow] / 7) return df参数说明hour用浮点是为了保留 15 分钟粒度信息如果只取整点会丢掉 96 点预测的精度。is_weekend单独留一列是因为周末负荷形态和工作日差异大让模型有个显式的开关信号比纯靠 sin/cos 去拟合更稳。2.3 滑窗构造lookback 和 horizon 怎么定监督学习样本的构造决定了模型能看到多长的历史、预测多远的未来。电力负荷的常用配置是 lookback 96 点24 小时、horizon 96 点未来 24 小时。lookback 太短模型看不到完整的日周期太长则引入无关的远期信息还增加显存。def make_windows(arr, lookback96, horizon96): X, y [], [] for i in range(len(arr) - lookback - horizon 1): X.append(arr[i:i lookback]) y.append(arr[i lookback:i lookback horizon]) return np.array(X), np.array(y)这里arr是已经拼好所有特征列的二维数组形状为(T, F)。切出来的X是(N, lookback, F)y是(N, horizon)。注意归一化必须在切窗之前按训练集统计量做否则会用到未来信息这是时序任务里最隐蔽的数据泄漏。3. 模型选型LSTM、TCN 还是 Transformer别盲目追新3.1 三种骨干网络的适用边界电力负荷预测这个任务序列长度通常在几百到几千点特征维度十几到几十。这个规模下LSTM 类循环网络依然是性价比最高的选择参数量小、训练快、对中小数据集友好。TCN 靠膨胀卷积扩大感受野并行度高适合数据量上万条的场景。Transformer 的自注意力在长序列上有优势但负荷预测的序列并不算长注意力容易过拟合到噪声上除非数据量足够大否则不划算。骨干参数量级训练速度适合数据量主要风险LSTM10万级中千条级长序列梯度衰减TCN30万级快万条级膨胀系数设错丢周期Transformer百万级慢十万条级小数据过拟合我的建议是先用 LSTM 打基线MAPE 压不下去再考虑换 TCN。直接上 Transformer 在小数据集上大概率翻车血泪经验。3.2 编码器-解码器结构的最小实现下面是一个 LSTM 编码器 全连接解码器的结构编码器吃历史窗口解码器一次性输出未来 horizon 个点。这种「直接多步」策略比自回归逐步预测更稳不会误差累积。import torch import torch.nn as nn class LoadLSTM(nn.Module): def __init__(self, n_feat, hidden128, horizon96, layers2, dropout0.2): super().__init__() self.encoder nn.LSTM(n_feat, hidden, layers, batch_firstTrue, dropoutdropout) self.head nn.Sequential( nn.Linear(hidden, hidden), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden, horizon) ) def forward(self, x): # x: (B, lookback, n_feat) out, (h, _) self.encoder(x) last out[:, -1, :] # 取最后时刻隐状态 return self.head(last) # (B, horizon)逻辑说明batch_firstTrue让输入维度是(B, L, F)符合直觉。取out[:, -1, :]而不是h[-1]是因为out已经过 dropout而h没有用out更一致。head里加一层隐藏层是为了给解码器一点非线性容量直接Linear(hidden, horizon)也能跑但拟合尖峰能力弱一些。参数说明hidden128是中小数据集的常用值数据量上万可以加到 256。layers2足够再深容易过拟合。dropout0.2是正则化关键负荷数据噪声大dropout 调到 0.3 也常见。3.3 损失函数别只用 MSEMSE 对尖峰不敏感因为尖峰样本少梯度被大量平缓时段稀释。电力场景里尖峰预测错了代价很大所以损失函数要加权。常用做法是 MSE 加上对高峰时段的额外权重或者直接用 Huber 损失抗异常值。def weighted_mse(pred, true, peak_weight3.0, peak_thresh0.8): # peak_thresh 按归一化后的负荷分位设定 w torch.where(true peak_thresh, torch.full_like(true, peak_weight), torch.ones_like(true)) return (w * (pred - true) ** 2).mean()peak_thresh0.8表示归一化后负荷超过 0.8 的时段算高峰peak_weight3.0把它的损失放大三倍。这个权重不能设太大超过 5 容易让模型在平缓时段欠拟合整体 MAPE 反而上升。4. 训练与调参让验证集 MAPE 真正降下来的六个动作4.1 数据划分必须按时间切不能随机时序预测里随机划分是最致命的错误。随机切会让未来信息泄漏到训练集验证集 MAPE 虚低上线就崩。正确做法是按时间顺序切前 70% 训练中间 15% 验证最后 15% 测试。n len(X) train_end int(n * 0.7) val_end int(n * 0.85) X_tr, y_tr X[:train_end], y[:train_end] X_va, y_va X[train_end:val_end], y[train_end:val_end] X_te, y_te X[val_end:], y[val_end:]归一化统计量只能用X_tr算然后应用到验证和测试集。这一步做错后面所有指标都不可信。4.2 学习率与批大小的搭配LSTM 这类循环网络对学习率敏感。批大小 64、学习率 1e-3 是稳妥起点。如果 loss 震荡先把学习率降到 5e-4如果收敛太慢加到 2e-3 但配合梯度裁剪。optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)weight_decay1e-5是轻量 L2 正则配合 dropout 一起用。ReduceLROnPlateau在验证 loss 连续 5 轮不降时把学习率砍半比固定衰减更自适应。clip_grad_norm_的max_norm1.0是防梯度爆炸的后悔药LSTM 训练中期梯度爆炸很常见。4.3 早停与模型保存早停的 patience 设 10 到 15 轮。保存验证集最优的权重而不是最后一轮的权重因为 LSTM 后期容易过拟合。best_val float(inf) patience, wait 12, 0 for epoch in range(200): train_one_epoch() val_loss evaluate() scheduler.step(val_loss) if val_loss best_val: best_val val_loss torch.save(model.state_dict(), best.pt) wait 0 else: wait 1 if wait patience: break4.4 评价指标MAPE 之外还要看峰值误差MAPE 在负荷接近 0 时会爆炸所以电力场景常用的是归一化后的 MAPE 或者直接看 RMSE。更重要的是单独统计高峰时段的误差因为那才是调度真正关心的。def peak_mape(pred, true, thresh): mask true thresh return np.mean(np.abs((pred[mask] - true[mask]) / true[mask])) * 100把整体 MAPE 和峰值 MAPE 一起看如果整体 3% 但峰值 8%说明模型没抓住尖峰回去调损失权重。5. 避坑与排查五个让预测曲线跑偏的真实原因5.1 验证集 MAPE 很低但测试集崩了现象验证集 MAPE 2.5%测试集 9%。原因归一化统计量用了全量数据或者滑窗切分时训练集和验证集有重叠。解决归一化只用训练集统计量切窗时在训练集和验证集之间留出 horizon 长度的间隔避免窗口跨越边界。5.2 预测曲线整体平移形状对但幅值偏现象预测曲线和真实曲线形状几乎一样但整体高了一截或低了一截。原因归一化的均值和方差在推理时和训练时不一致常见于部署时忘了保存 scaler 参数。解决把 scaler 的 mean 和 std 一起序列化保存推理时加载同一份。5.3 晚高峰尖峰被削平现象平缓时段拟合很好晚高峰预测值明显低于真实值。原因MSE 损失被大量平缓样本主导尖峰梯度贡献小。解决用 3.3 节的加权损失或者对负荷做对数变换压缩动态范围后再训练。5.4 训练 loss 不降或震荡现象loss 在前几轮就卡住或者上下大幅震荡。原因学习率过大、批大小过小、或者输入特征没归一化。解决先检查特征是否都在同一量级温度、负荷、湿度量纲差异大时必须分别标准化再把学习率降到 5e-4 试。5.5 换一批数据就完全不能用现象在 A 数据集上调好的模型换到 B 数据集 MAPE 翻倍。原因不同地区负荷形态差异大模型过拟合了 A 的特定周期模式。解决要么在 B 数据上微调要么在训练时做数据增强比如随机平移时间轴、加小幅噪声提升泛化。6. 从单点预测到概率预测把「未来 24 小时」变成可决策的区间点预测只给一个数调度侧真正需要的是「明天下午三点负荷大概率在 800 到 850 兆瓦之间」。把上面的 LSTM 改成输出分位数就能得到概率预测。做法是把解码器的输出维度从horizon改成horizon × n_quantile损失换成分位数损失。class QuantileLSTM(nn.Module): def __init__(self, n_feat, hidden128, horizon96, n_q3, dropout0.2): super().__init__() self.encoder nn.LSTM(n_feat, hidden, 2, batch_firstTrue, dropoutdropout) self.head nn.Linear(hidden, horizon * n_q) self.horizon, self.n_q horizon, n_q def forward(self, x): out, _ self.encoder(x) return self.head(out[:, -1, :]).view(-1, self.horizon, self.n_q) def quantile_loss(pred, true, quantiles(0.1, 0.5, 0.9)): losses [] for i, q in enumerate(quantiles): e true - pred[:, :, i] losses.append(torch.maximum(q * e, (q - 1) * e).mean()) return sum(losses)n_q3对应 10%、50%、90% 三个分位数50% 那条就是点预测10% 和 90% 构成 80% 置信区间。分位数损失的核心是max(q*e, (q-1)*e)当真实值高于预测时用q*e惩罚低于时用(q-1)*e这样不同分位数会被推向各自的目标位置。验证概率预测不能只看 MAPE要看区间覆盖率真实值落在 10%-90% 区间内的比例应该接近 80%。覆盖率低于 75% 说明区间太窄高于 90% 说明区间太宽没有决策价值。def coverage(pred, true, lo0, hi2): inside ((true pred[:, :, lo]) (true pred[:, :, hi])).float() return inside.mean().item()我自己的习惯是每次训练完先跑一遍覆盖率覆盖率不对就不看 MAPE因为区间错了点预测再准也没法用来做调度决策。这套流程从数据清洗到概率预测最花时间的其实不是模型结构而是特征工程和损失函数那几行代码模型本身反而是最标准化的部分。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑