简介:这份资源是面向计算机、通信、人工智能、自动化等专业学生与从业者的深度学习实战项目源码,聚焦区域电力负荷预测这一典型时序任务,可作为毕业设计、课程大作业或自学进阶的参考方案。压缩包共61个文件,约3.7MB,以39个Python源码为主体,辅以17张JPG图表、2个Markdown说明文档及少量缓存与图片文件,涵盖数据加载、模型定义、训练流程与结果可视化等环节。项目按dataset、model、task、trainer等模块组织,包含CNN、LSTM、Bi-LSTM_MLP、Transformer、ARIMA等多种预测模型实现,并配有测试脚本与实验记录,便于理解不同方法在负荷预测中的差异。目前已有580人学习下载,适合希望掌握时序建模完整流程、快速搭建实验基线或在此基础上修改扩展功能的读者参考使用。
1. 区域电力负荷预测为什么值得用深度学习重做一遍
如果你在省级电网调度、园区能源管理或者售电公司做过负荷预测,大概率经历过这样的场景:周五下午三点,气温骤降 6℃,周一早高峰的负荷曲线直接比上周同期高出 8%,而手头的 ARIMA 或者多元线性回归模型给出的预测偏差超过 5%,调度计划被迫临时调整。区域电力负荷预测的核心难点从来不是"能不能拟合历史曲线",而是气象突变、节假日错位、产业结构调整这些非线性因素叠加时,传统时序模型抓不住耦合关系。深度学习之所以在这个场景里被反复提起,是因为它能把温度、湿度、风速、日期类型、历史负荷这些异构输入塞进同一个网络,自动学出高阶交互特征。这篇笔记面向的是手上有 Python 环境、想跑通一套区域电力负荷预测源码的工程师,从数据构造、模型选型到训练调参和上线验证,把能复现的路径和踩过的坑一次讲清楚。
2. 区域电力负荷预测的数据构造与特征工程怎么做
2.1 负荷数据的三种来源与清洗规则
区域电力负荷预测的第一步不是选模型,而是把数据搞干净。常见的数据来源有三类:调度自动化系统导出的 15 分钟或 1 小时负荷序列、气象站提供的温度/湿度/风速/辐照度、以及人工维护的节假日和检修计划表。这三类数据的时间粒度往往不一致,负荷是 15 分钟一条,气象是 1 小时一条,节假日是日级标签,直接 merge 会产生大量空值。
我一般会先把所有数据统一到 1 小时粒度,负荷用均值降采样,气象用线性插值升采样,节假日标签按日期广播。清洗规则里最容易被忽略的是"零值"和"尖峰":传感器掉线会补 0,这个 0 不是真实负荷,必须用前后 3 个点的中位数替换;尖峰超过历史同期均值 3 倍标准差的,先标记再人工确认,不要直接删,因为有些尖峰是真实的大工业负荷启动。
import pandas as pd import numpy as np def clean_load_series(df, time_col='timestamp', load_col='load'): """清洗区域负荷序列:处理零值、尖峰、缺失""" df = df.sort_values(time_col).reset_index(drop=True) # 零值替换:传感器掉线补0,用前后3点中位数 zero_mask = df[load_col] == 0 for idx in df[zero_mask].index: left = max(0, idx - 3) right = min(len(df), idx + 4) neighbors = df.loc[left:right, load_col] neighbors = neighbors[neighbors > 0] if len(neighbors) > 0: df.loc[idx, load_col] = neighbors.median() # 尖峰标记:超过同期均值3倍标准差 df['is_spike'] = False rolling_mean = df[load_col].rolling(window=24, min_periods=6).mean() rolling_std = df[load_col].rolling(window=24, min_periods=6).std() spike_mask = (df[load_col] - rolling_mean).abs() > 3 * rolling_std df.loc[spike_mask, 'is_spike'] = True # 缺失值线性插值 df[load_col] = df[load_col].interpolate(method='linear', limit=6) return df这段代码的逻辑是:先按时间排序保证时序正确,零值用邻域中位数替换而不是全局均值,避免被整体趋势带偏;尖峰只标记不删除,保留人工复核入口;缺失值限制最多插 6 个点,超过 6 小时的连续缺失说明数据源有问题,插值会引入虚假规律。参数上,window=24对应一天 24 小时,limit=6是经验值,如果你的数据质量差可以放宽到 12,但要在日志里记录插值比例。
2.2 气象与日历特征的编码方式
区域电力负荷对温度的响应不是线性的,夏天 35℃ 和 38℃ 的负荷差异远大于 20℃ 和 23℃ 的差异。常见做法是做分段映射:把温度切成若干区间,每个区间单独编码,或者用温度的三次样条展开。我一般会同时保留原始温度、温度平方项和"是否超过 30℃"的布尔标记,让网络自己去学非线性。
日历特征里,星期几用 one-hot 编码,节假日用"距最近节假日的天数"这种连续变量比单纯 0/1 更有信息量。还有一个容易翻车的点:春节、国庆这种长假,负荷曲线形状和普通周末完全不同,如果训练集里长假样本太少,模型会把这些天预测成普通周末。解决办法是在损失函数里给长假样本加权,权重设为 2 到 3 倍。
def build_features(load_df, weather_df, calendar_df): """构造区域负荷预测的特征矩阵""" df = load_df.merge(weather_df, on='timestamp', how='left') df = df.merge(calendar_df, on='date', how='left') # 温度非线性展开 df['temp_sq'] = df['temperature'] ** 2 df['temp_high'] = (df['temperature'] > 30).astype(int) # 星期one-hot for i in range(7): df[f'weekday_{i}'] = (df['weekday'] == i).astype(int) # 距节假日天数 df['days_to_holiday'] = df['is_holiday'].groupby( (df['is_holiday'] != df['is_holiday'].shift()).cumsum() ).cumcount() # 滞后特征:前1天、前7天同一时刻负荷 df['load_lag_24'] = df['load'].shift(24) df['load_lag_168'] = df['load'].shift(168) return df.dropna()滞后特征是负荷预测里性价比最高的特征,load_lag_24捕捉日周期,load_lag_168捕捉周周期。注意shift之后会产生空值,dropna会丢掉前 168 条记录,如果数据总量少于一年,这个损失要提前评估。参数上,滞后步数取决于你的采样粒度,1 小时粒度就是 24 和 168,15 分钟粒度要改成 96 和 672。
3. 深度学习模型选型:LSTM、TCN 还是 Transformer
3.1 三种主流结构的适用边界
区域电力负荷预测的模型选型没有绝对最优,只有场景匹配。LSTM 适合数据量在 1 到 3 年、特征维度不超过 50 的场景,训练快、调参少,缺点是长序列依赖捕捉能力有限,超过 72 小时的预测窗口容易衰减。TCN(时序卷积网络)用膨胀因果卷积替代循环结构,并行度高,适合数据量大、需要快速迭代的场景,但对超长周期的建模仍然依赖感受野设计。Transformer 在负荷预测里的优势是注意力机制能直接建模任意两个时间点的关系,适合多区域联合预测,缺点是数据量少于 2 年时容易过拟合,训练成本也高。
我的经验是:单区域、1 年数据、预测未来 24 小时,LSTM 足够;多区域、3 年以上数据、预测未来 7 天,上 Transformer;如果只是做基线对比,TCN 是性价比最高的中间选项。下面给一个 LSTM 的最小可跑实现,输入是 168 小时历史窗口,输出未来 24 小时。
import torch import torch.nn as nn class LoadLSTM(nn.Module): def __init__(self, input_dim, hidden_dim=128, num_layers=2, output_dim=24): super().__init__() self.lstm = nn.LSTM( input_size=input_dim, hidden_size=hidden_dim, num_layers=num_layers, batch_first=True, dropout=0.2 ) self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): # x: (batch, seq_len=168, input_dim) out, (h_n, c_n) = self.lstm(x) # 取最后一个时间步的隐状态 last_hidden = out[:, -1, :] return self.fc(last_hidden) # 训练配置 model = LoadLSTM(input_dim=20, hidden_dim=128, num_layers=2, output_dim=24) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode='min', factor=0.5, patience=5 )input_dim=20是特征数量,包括负荷滞后、温度、湿度、星期 one-hot 等,实际跑的时候要按你的特征表调整。hidden_dim=128是隐层维度,数据量小于 1 万条时建议降到 64,否则容易过拟合。dropout=0.2是 LSTM 层间的丢弃率,如果验证集 loss 震荡明显可以提到 0.3。ReduceLROnPlateau是学习率衰减策略,patience=5表示验证集 loss 连续 5 轮不降就砍半学习率,这个参数在负荷预测里很关键,因为负荷数据有明显的季节性,固定学习率后期会震荡。
3.2 损失函数与评价指标的选择
区域电力负荷预测的损失函数不能只用 MSE。MSE 对大误差敏感,但电力场景里更关心的是峰值时段的偏差,因为峰值预测偏低会导致备用容量不足。常见做法是加权 MSE:给高峰时段(比如 8:00-11:00 和 18:00-21:00)的样本更高权重。另一个选择是 Pinball Loss,它直接优化分位数预测,适合需要给出预测区间的场景。
评价指标上,MAPE(平均绝对百分比误差)是最常用的,但负荷接近 0 时 MAPE 会爆炸,所以我会同时看 MAE 和 RMSE。行业里通常要求日前预测 MAPE 低于 3%,如果超过 5% 基本不可用。注意 MAPE 的计算要排除真实负荷小于额定容量 10% 的点,否则夜间低谷时段的百分比误差会拉高整体指标。
def weighted_mse(pred, target, peak_mask, peak_weight=2.0): """加权MSE:高峰时段权重更高""" loss = (pred - target) ** 2 weights = torch.ones_like(loss) weights[peak_mask] = peak_weight return (loss * weights).mean() def mape(y_true, y_pred, capacity, threshold=0.1): """排除低负荷点的MAPE""" mask = y_true > capacity * threshold return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100peak_weight=2.0是经验值,如果峰值预测偏差仍然大可以提到 3.0,但不要超过 4.0,否则模型会牺牲低谷精度。threshold=0.1表示只计算真实负荷大于额定容量 10% 的点,这个阈值在省级电网里通常对应 200 万千瓦左右。
4. 训练流程与调参:从数据划分到早停策略
4.1 时序数据的划分方式与归一化
区域电力负荷预测的数据划分不能用随机切分,必须按时间顺序切。常见做法是前 70% 训练、中间 15% 验证、最后 15% 测试。如果数据跨年,要保证训练集和测试集覆盖相同的季节,否则模型在测试集上会遇到没见过的温度区间。我一般会检查训练集和测试集的温度分布,如果 KS 检验 p 值小于 0.05,说明分布差异大,需要重新划分或者做分布校正。
归一化方面,负荷用 MinMax 缩放到 [0,1],温度和湿度用 Z-score 标准化。注意归一化参数必须只用训练集计算,然后应用到验证集和测试集,否则会引入未来信息。这个坑很隐蔽,很多人用全量数据算均值和方差,测试集指标看起来很好,上线后直接崩。
from sklearn.preprocessing import MinMaxScaler, StandardScaler def split_and_scale(df, train_ratio=0.7, val_ratio=0.15): """按时间顺序划分并归一化""" n = len(df) train_end = int(n * train_ratio) val_end = int(n * (train_ratio + val_ratio)) train_df = df.iloc[:train_end] val_df = df.iloc[train_end:val_end] test_df = df.iloc[val_end:] load_scaler = MinMaxScaler() weather_scaler = StandardScaler() # 只用训练集fit train_df['load_scaled'] = load_scaler.fit_transform(train_df[['load']]) val_df['load_scaled'] = load_scaler.transform(val_df[['load']]) test_df['load_scaled'] = load_scaler.transform(test_df[['load']]) weather_cols = ['temperature', 'humidity', 'wind_speed'] train_df[weather_cols] = weather_scaler.fit_transform(train_df[weather_cols]) val_df[weather_cols] = weather_scaler.transform(val_df[weather_cols]) test_df[weather_cols] = weather_scaler.transform(test_df[weather_cols]) return train_df, val_df, test_df, load_scaler, weather_scalertrain_ratio=0.7和val_ratio=0.15是常规配置,如果数据量少于 2 年,建议改成 0.8/0.1/0.1,保证训练集有足够样本。load_scaler和weather_scaler要保存下来,上线推理时用同一套参数,否则预测值会偏移。
4.2 早停、学习率与批大小的联动调参
训练区域电力负荷预测模型时,早停策略比固定 epoch 更可靠。我一般设patience=10,监控验证集 MAE,如果 10 轮不降就停。学习率初始值设 1e-3,配合 ReduceLROnPlateau 衰减。批大小在 32 到 128 之间试,数据量小于 5000 条用 32,大于 2 万条用 128。批大小太大会导致梯度估计不准,太小则训练慢且震荡。
还有一个玄学问题:LSTM 的初始隐状态。默认是全零,但在负荷预测里,用训练集最后一条记录的隐状态作为验证集的初始状态,能提升 1% 到 2% 的精度。这个技巧在 PyTorch 里需要手动传递h_0和c_0,代码稍复杂但值得做。
def train_epoch(model, loader, optimizer, criterion, peak_weight=2.0): model.train() total_loss = 0 for x, y, peak_mask in loader: optimizer.zero_grad() pred = model(x) loss = weighted_mse(pred, y, peak_mask, peak_weight) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() total_loss += loss.item() return total_loss / len(loader) # 早停逻辑 best_val_mae = float('inf') patience_counter = 0 for epoch in range(200): train_loss = train_epoch(model, train_loader, optimizer, criterion) val_mae = evaluate(model, val_loader) scheduler.step(val_mae) if val_mae < best_val_mae: best_val_mae = val_mae torch.save(model.state_dict(), 'best_model.pt') patience_counter = 0 else: patience_counter += 1 if patience_counter >= 10: print(f'Early stop at epoch {epoch}') breakclip_grad_norm_的max_norm=1.0是防止梯度爆炸的保险,LSTM 在负荷序列上偶尔会出现梯度尖峰。patience=10配合ReduceLROnPlateau的patience=5,形成两级耐心机制:先降学习率,再早停。这个组合在多数负荷数据集上能省 30% 到 50% 的训练时间。
5. 避坑与排查:区域负荷预测里最容易翻车的五件事
5.1 现象:验证集 MAPE 很低,上线后偏差翻倍
原因通常是数据泄漏。最常见的是归一化用了全量数据,或者滞后特征在构造时用了未来信息。比如shift(-1)写成了shift(1),模型在训练时看到了下一时刻的负荷。排查方法是检查特征构造代码里所有shift的符号,以及 scaler 的 fit 范围。解决方式是严格按时间切分,所有统计量只用训练集计算,并在测试集上做一次"模拟上线"验证:用测试集前 168 小时预测后 24 小时,滚动前进,看误差是否稳定。
5.2 现象:春节前后预测曲线完全失真
原因是训练集里长假样本太少,模型把长假当成了普通周末。区域电力负荷在春节期间的曲线形状是"双峰变单峰、整体下移 20% 到 30%",和周末完全不同。解决办法有两个:一是给长假样本加权,权重设为 2 到 3 倍;二是在特征里加入"距春节天数"这个连续变量,让模型能区分"春节前三天"和"普通周五"。如果数据量允许,还可以做迁移学习:用普通日训练基础模型,再用长假样本微调最后两层。
5.3 现象:温度特征加入后模型反而变差
原因是温度与负荷的关系是非线性的,直接扔一个原始温度进去,线性层学不到拐点。区域电力负荷对温度的响应在 18℃ 到 25℃ 之间最平缓,低于 10℃ 和高于 30℃ 时斜率陡增。解决办法是做分段编码或者加平方项、三次项。另一个可能原因是温度数据的时间对齐错了,气象站的数据是整点观测,负荷是 15 分钟平均,直接 merge 会导致温度滞后或超前。排查方法是画温度-负荷散点图,看拐点是否清晰。
5.4 现象:训练 loss 正常下降,但预测曲线整体平移
原因是归一化的反变换出了问题。MinMaxScaler 的inverse_transform要求输入形状是二维数组,如果预测输出是一维的,直接调用会报错或者静默返回错误结果。另一个常见原因是训练时用了load_scaled,预测时忘了反变换,直接把 0 到 1 之间的值当成了兆瓦。排查方法是打印预测值和真实值的范围,如果预测值全在 [0,1] 之间,基本可以确定是反变换漏了。
5.5 现象:多区域联合预测时,某个区域误差拖累整体
原因是不同区域的负荷量级差异大,大区域绝对误差大,在 MSE 里占主导,小区域的梯度被淹没。解决办法是对每个区域的负荷分别归一化,或者在损失函数里按区域容量加权。如果某些区域数据质量差,可以考虑多任务学习:共享底层 LSTM,每个区域单独一个输出头,训练时给低质量区域更低权重。排查方法是分区域统计 MAPE,看是否有个别区域显著高于其他。
6. 进阶技巧:用滚动预测和集成策略把 MAPE 压到 2% 以内
区域电力负荷预测做到 3% 以内不算难,但想稳定压到 2% 以内,单模型基本不够。我一般会用两个策略叠加:滚动预测和集成。
滚动预测是指每次只预测未来 1 小时,然后把预测值作为已知历史,滑动窗口前进,再预测下一小时。这样做的好处是每一步都有最新的真实负荷作为输入,误差不会累积。缺点是推理速度慢,24 小时预测需要调用 24 次模型。优化方法是用 PyTorch 的torch.no_grad()和批处理,把 24 次调用合并成一次前向传播,但要注意每次的输入窗口要更新。
集成策略上,我会训练三个不同结构的模型:LSTM、TCN 和 LightGBM。LSTM 和 TCN 捕捉时序依赖,LightGBM 用滞后特征和气象特征做树模型,三者误差相关性低。融合方式用加权平均,权重通过验证集上的最小二乘优化确定。实测下来,三模型集成比单 LSTM 的 MAPE 低 0.5 到 0.8 个百分点。
def rolling_predict(model, initial_window, steps=24): """滚动预测未来steps小时""" model.eval() window = initial_window.clone() # (1, 168, input_dim) predictions = [] with torch.no_grad(): for _ in range(steps): pred = model(window) # (1, 24) next_val = pred[:, 0:1] # 取第一个预测值 predictions.append(next_val.item()) # 更新窗口:去掉最旧的一小时,加入预测值 new_step = window[:, -1:, :].clone() new_step[:, :, 0] = next_val # 假设第0维是负荷 window = torch.cat([window[:, 1:, :], new_step], dim=1) return predictions # 集成权重优化 from scipy.optimize import minimize def ensemble_weights(preds, y_true): """最小二乘求集成权重""" def loss(w): w = np.abs(w) / np.abs(w).sum() blended = sum(wi * pi for wi, pi in zip(w, preds)) return np.mean((blended - y_true) ** 2) res = minimize(loss, x0=np.ones(len(preds)) / len(preds), method='Nelder-Mead') w = np.abs(res.x) / np.abs(res.x).sum() return w滚动预测里最关键的是窗口更新逻辑:new_step[:, :, 0] = next_val假设负荷是第 0 维特征,实际跑的时候要按你的特征顺序调整。集成权重用 Nelder-Mead 优化,虽然慢但稳定,数据量小的时候比梯度下降更可靠。注意权重归一化用绝对值,避免负权重导致预测值超出合理范围。
最后说一个我自己的习惯:每次训练完模型,我都会把预测曲线和真实曲线叠在一起画出来,重点看三个位置——早高峰爬坡段、晚高峰回落段、周末转工作日过渡段。这三个位置如果拟合得好,MAPE 基本不会差;如果某个位置明显偏离,回去查特征里对应的滞后项和日历编码。这个习惯帮我省了很多次盲目调参的时间。希望帮到你。
本文还有配套的精品资源,点击获取