
简介本资源是一套基于深度学习算法实现电力负荷时间序列未来预测的Python源码包面向计算机、人工智能、数据科学等相关专业的在校学生、教师及企业员工可用于毕业设计、课程设计、大作业或初期项目立项演示也适合希望入门并进阶时间序列预测的小白学习者。压缩包共20个文件包含16个py源码文件、2个txt说明与2个md文档整体约43KB源码覆盖LSTM、GRU、Transformer、ARIMA、决策树、随机森林、KNN等多种预测模型实现并配有详细注释便于理解模型结构与训练流程。项目代码完整、功能验证稳定可直接运行并作为学习借鉴或二次开发基础读者能从中掌握电力负荷数据预处理、模型搭建、训练评估与未来值预测的完整思路也可在此基础上DIY其他功能。目前已有266人学习下载具备一定参考热度。需注意解压后项目路径不要含中文建议重命名为英文后再运行。1. 电力负荷预测为什么总在下午三点翻车做过电力负荷预测的人大概都有过这种体验模型在训练集上MAPE低到1.8%一上测试集下午三点前后直接飙到8%以上。更玄学的是同样的代码换一个台区的数据误差曲线形状完全不一样。这不是模型不行而是电力负荷这个时间序列本身就不是平稳的——它同时叠加了日周期、周周期、节假日效应、温度敏感性和突发扰动任何一层没处理好预测就会在特定时段集体翻车。基于深度学习算法实现电力负荷的时间序列未来预测核心要解决的就是这件事用历史负荷序列通常还加上温度、湿度、日期类型等外生变量预测未来一段时间1小时、24小时甚至7天的负荷值。它适合两类人一类是电网侧做调度辅助决策的工程师需要提前知道明天峰值大概在哪另一类是新能源场站或园区微网运营方需要根据预测结果安排储能充放和购电计划。Python源码方案之所以流行是因为从数据清洗到模型训练再到滚动预测整条链路都能用一套脚本串起来改起来快验证起来也快。但能跑通和能落地之间隔着好几道坎。下面按我实际做过的路径从数据、模型、训练、避坑到进阶一层层拆开讲。2. 数据准备与特征工程把负荷序列拆成模型能吃的形状2.1 负荷数据的三个典型脏法电力负荷数据最常见的脏法不是缺失而是看起来正常但语义错误。第一种是量测冻结某个时刻电表通信中断系统自动用上一时刻值填充导致连续多个点完全相等。这种在差分特征里会变成一堆零模型会误以为负荷真的没变化。第二种是时区错位历史数据存的是UTC时间但日期特征按本地时间生成导致早高峰标签打在了凌晨。第三种是倍率跳变台区扩容或CT变比调整后负荷数值整体缩放如果不做归一化或分段处理模型会把跳变当成真实趋势。处理方式我一般分三步走先做冻结检测连续相同值超过3个点就标记为可疑再做时区对齐统一转成带时区信息的本地时间最后做分段归一化按台区容量或历史最大值做缩放而不是全局MinMax。import pandas as pd import numpy as np def clean_load_series(df, time_coltimestamp, load_colload): 电力负荷序列清洗冻结检测 时区对齐 异常值标记 df: 至少包含时间列和负荷列 df df.copy() # 1. 时区对齐统一转为本地时间假设原始为UTC df[time_col] pd.to_datetime(df[time_col], utcTrue).dt.tz_convert(Asia/Shanghai) df df.sort_values(time_col).reset_index(dropTrue) # 2. 冻结检测连续相同值超过3个点标记为可疑 df[is_frozen] False same_as_prev df[load_col].diff() 0 # 用累计和分组连续True的组长度3则标记 groups (~same_as_prev).cumsum() frozen_counts same_as_prev.groupby(groups).transform(sum) df.loc[frozen_counts 3, is_frozen] True # 3. 异常值标记超出3倍标准差且不是冻结点 mean, std df[load_col].mean(), df[load_col].std() df[is_outlier] (np.abs(df[load_col] - mean) 3 * std) (~df[is_frozen]) # 4. 对可疑点做线性插值仅用于训练预测时不能插值未来 df.loc[df[is_frozen] | df[is_outlier], load_col] np.nan df[load_col] df[load_col].interpolate(methodlinear, limit6) return df这段代码的关键参数是limit6意思是最多连续插值6个点对应30分钟采样下的3小时。超过这个长度就不插了宁可留NaN让后续模型跳过也不要用一条直线糊弄过去。is_frozen和is_outlier两列要保留训练时可以给这些位置更低的样本权重。2.2 时间特征怎么构造才不冗余很多人一上来就pd.get_dummies把小时、星期、月份全展开结果特征维度爆炸树模型还好LSTM直接过拟合。我的做法是周期编码 少量离散标记小时用sin/cos编码周期24星期sin/cos编码周期7月份sin/cos编码周期12节假日单独一个0/1列不要展开成12个月温度原始值 滑动平均3小时窗口def add_time_features(df, time_coltimestamp): 周期编码 温度滑动平均 df df.copy() t df[time_col] df[hour_sin] np.sin(2 * np.pi * t.dt.hour / 24) df[hour_cos] np.cos(2 * np.pi * t.dt.hour / 24) df[week_sin] np.sin(2 * np.pi * t.dt.dayofweek / 7) df[week_cos] np.cos(2 * np.pi * t.dt.dayofweek / 7) df[month_sin] np.sin(2 * np.pi * t.dt.month / 12) df[month_cos] np.cos(2 * np.pi * t.dt.month / 12) df[is_holiday] t.dt.dayofweek.isin([5, 6]).astype(int) # 简化版实际用节假日表 if temperature in df.columns: df[temp_ma3] df[temperature].rolling(3, min_periods1).mean() return df周期编码的好处是让模型知道23点和0点是近的而独热编码会把它们当成完全不同的类别。温度滑动平均是为了平滑掉传感器抖动3小时窗口对应负荷对温度响应的滞后性。2.3 滑动窗口构造lookback和horizon怎么定这是电力负荷预测里最影响效果的一个参数。lookback是回看多少历史点horizon是预测未来多少点。常见组合场景lookbackhorizon采样间隔说明日前调度168241小时回看一周预测一天实时校正24415分钟回看6小时预测1小时周计划3361681小时回看两周预测一周lookback不是越长越好。我试过用720个点30天回看LSTM的验证误差反而比168个点高因为太长的历史里噪声占比大模型记不住重点。一般建议lookback至少覆盖2个完整周期比如日周期周周期就是168小时horizon不超过lookback的1/4。def make_windows(data, lookback, horizon, target_colload): 构造滑动窗口样本 返回: X shape (n, lookback, n_features), y shape (n, horizon) X, y [], [] values data.drop(columns[timestamp]).values target_idx data.columns.get_loc(target_col) - 1 # 减去timestamp列 for i in range(len(data) - lookback - horizon 1): X.append(values[i:ilookback]) y.append(values[ilookback:ilookbackhorizon, target_idx]) return np.array(X), np.array(y)注意target_idx的计算因为data里已经去掉了timestamp列所以列索引要减1。这个坑我踩过导致y取到了错误列训练loss正常但预测完全不对。3. 模型选型LSTM、TCN还是Transformer3.1 三种主流结构的适用边界电力负荷预测不是非深度学习不可但深度学习在多变量、长序列、非线性场景下确实有优势。目前Python源码里最常见的是三类LSTM/GRU适合lookback在168以内、特征维度不超过20的场景。优点是训练快、显存占用小、调参经验成熟。缺点是并行度低序列太长时梯度传播会衰减。我一般把它作为baseline如果LSTM的MAPE已经低于3%就不折腾更复杂的了。TCN时序卷积网络适合lookback较长336以上、需要捕捉多尺度周期的场景。膨胀卷积可以在不增加参数量的情况下扩大感受野。缺点是感受野固定对突发扰动的响应不如注意力机制灵活。Transformer/Informer适合多变量、有外生变量、需要可解释注意力的场景。但原始Transformer在长序列上计算量是O(n²)电力负荷动辄几百个点直接上会爆显存。常见做法是用Informer的ProbSparse注意力或者把序列先做卷积下采样再进Transformer。我的选型习惯先用LSTM跑通全流程确认数据管道没问题如果误差不达标再换TCN只有在外生变量特别多温度、湿度、风速、辐照度、电价且需要分析变量重要性时才上Transformer。3.2 一个可复现的LSTM基线配置import torch import torch.nn as nn class LoadLSTM(nn.Module): def __init__(self, n_features, hidden_size64, num_layers2, horizon24, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) self.fc nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(dropout), nn.Linear(32, horizon) ) def forward(self, x): # x: (batch, lookback, n_features) out, _ self.lstm(x) # 取最后一个时间步的隐状态 last out[:, -1, :] return self.fc(last)关键参数说明hidden_size64是起点如果欠拟合加到128过拟合降到32num_layers2足够超过3层在电力负荷上几乎不会带来提升dropout0.2是安全值数据量小于1万条时加到0.3。fc层先降维到32再输出horizon比直接Linear(hidden_size, horizon)更稳因为中间加了非线性。3.3 损失函数MSE不是唯一选择电力负荷预测里MSE对大误差惩罚重但实际业务更关心峰值是否被低估。我常用Pinball Loss分位数损失或者MSE 峰值加权def weighted_mse(pred, true, peak_weight3.0): 对高负荷时段加权避免峰值低估 weights torch.ones_like(true) threshold true.mean() true.std() weights[true threshold] peak_weight return (weights * (pred - true) ** 2).mean()peak_weight3.0是经验值意思是峰值时段的误差权重是普通时段的3倍。如果业务对峰值特别敏感可以加到5。但不要超过5否则模型会为了拟合峰值牺牲整体平滑性导致谷时段误差飙升。4. 训练与滚动预测从单步验证到多步输出4.1 训练集/验证集/测试集怎么切时间序列不能随机切分这是铁律。但按时间切也有讲究如果直接前70%训练、中间15%验证、后15%测试会遇到分布漂移问题——后15%可能正好是冬季供暖负荷和训练集的夏季负荷完全不同。我的做法是滚动切分用前6个月训练第7个月验证第8个月测试然后窗口后移1个月重复3次取平均指标。这样能看出模型在不同季节的稳定性。def rolling_split(data, train_months6, val_months1, test_months1, step_months1): 滚动切分返回多个(train, val, test)元组 splits [] data data.set_index(timestamp) start data.index.min() while True: train_end start pd.DateOffset(monthstrain_months) val_end train_end pd.DateOffset(monthsval_months) test_end val_end pd.DateOffset(monthstest_months) if test_end data.index.max(): break train data[start:train_end] val data[train_end:val_end] test data[val_end:test_end] splits.append((train, val, test)) start start pd.DateOffset(monthsstep_months) return splits4.2 滚动预测的两种模式单步滚动每次只预测下一个点然后把预测值拼回输入序列再预测下一个点。优点是简单缺点是误差会累积预测24个点后可能偏得离谱。多步直接输出模型一次输出未来24个点。优点是误差不累积缺点是需要更多训练数据且模型要学到一个从历史到未来的映射难度更大。我一般先用多步直接输出如果峰值捕捉不好再改用单步滚动 误差校正。误差校正的做法是训练一个辅助模型预测残差然后把主模型输出和残差预测相加。def rolling_predict(model, initial_seq, horizon, devicecpu): 单步滚动预测 initial_seq: (1, lookback, n_features) model.eval() seq initial_seq.clone().to(device) preds [] with torch.no_grad(): for _ in range(horizon): pred model(seq) # (1, horizon) 但只取第一个 next_val pred[:, 0:1] preds.append(next_val.item()) # 把预测值拼到序列末尾去掉最早的点 # 注意这里假设target是最后一列实际要根据特征顺序调整 new_step seq[:, -1, :].clone() new_step[:, -1] next_val.squeeze() seq torch.cat([seq[:, 1:, :], new_step.unsqueeze(1)], dim1) return preds这段代码里有个容易翻车的地方new_step[:, -1] next_val假设负荷值是特征矩阵的最后一列。如果特征顺序变了这里要同步改。我建议在数据管道里就把target列固定放在最后避免后续混乱。4.3 学习率调度与早停电力负荷训练最容易出现的是验证loss先降后升而且升得很突然。我一般用ReduceLROnPlateau耐心值设5因子0.5早停耐心值设10。如果10个epoch验证loss不降就停。from torch.optim.lr_scheduler import ReduceLROnPlateau optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler ReduceLROnPlateau(optimizer, modemin, factor0.5, patience5) early_stop_patience 10 best_val float(inf) wait 0 for epoch in range(200): train_loss train_one_epoch(model, train_loader, optimizer) val_loss evaluate(model, val_loader) 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 early_stop_patience: print(fEarly stop at epoch {epoch}) break初始学习率1e-3是Adam的常用值如果loss震荡厉害降到5e-4。factor0.5意味着每次降一半不要设0.1降太狠会导致模型卡在局部最优。5. 避坑与排查那些让MAPE翻倍的细节5.1 现象训练loss正常验证loss爆炸原因最常见的是数据泄漏。比如在构造滑动窗口之前就做了全局归一化导致验证集的均值和方差信息被模型看到了。另一个可能是验证集的时间戳和训练集有重叠滚动切分时边界没对齐。解决归一化只能在训练集上fit然后transform验证集和测试集。滚动切分时用pd.DateOffset而不是整数索引避免边界重叠。5.2 现象预测曲线整体平移原因时区或时间对齐错误。比如训练时用的是UTC预测时输入的是本地时间模型学到的周期相位完全错位。解决在数据管道入口就统一时区并在特征里显式加入hour_sin/cos让模型自己学相位。如果已经训练完了才发现可以把预测结果整体平移但这是后悔药不如一开始就对齐。5.3 现象峰值永远预测偏低原因MSE损失对峰值不敏感且峰值样本在训练集中占比小。模型倾向于预测均值来降低整体loss。解决用加权MSE或Pinball Loss或者对峰值样本过采样。我一般先用加权MSEpeak_weight3如果还不够再加一个分位数损失分支。5.4 现象换台区后模型完全失效原因不同台区的负荷基线和波动模式差异大模型过拟合到了训练台区的特定模式。解决做迁移学习——在源台区预训练在目标台区微调最后几层。或者用归一化 台区嵌入把台区ID作为一个类别特征输入。如果目标台区数据太少可以用源台区的模型做零样本预测但误差会大需要人工校正。5.5 现象GPU利用率低训练慢原因DataLoader的num_workers设成了0或者batch_size太小。电力负荷数据量不大但序列长如果batch_size16GPU根本跑不满。解决num_workers4batch_size64起步pin_memoryTrue。如果显存不够用梯度累积模拟大batch。train_loader DataLoader( train_dataset, batch_size64, shuffleTrue, # 时间序列可以shuffle因为样本之间已经通过窗口解耦 num_workers4, pin_memoryTrue )注意时间序列的shuffleTrue是安全的因为每个样本是独立的滑动窗口不是按时间顺序喂入。但如果你用的是RNN的stateful模式跨batch传递隐状态就不能shuffle。6. 进阶技巧用残差校正和集成把MAPE压到2%以内当单模型MAPE卡在3%左右上不去时我一般从两个方向突破残差校正和模型集成。残差校正的思路是主模型预测完计算残差序列然后训练一个轻量级模型比如XGBoost或小LSTM来预测残差。残差往往比原始负荷更平稳更容易学。具体做法# 假设主模型已经预测了验证集和测试集 residual_train y_train - main_model.predict(X_train) residual_val y_val - main_model.predict(X_val) # 用残差训练一个辅助模型 from xgboost import XGBRegressor res_model XGBRegressor(n_estimators100, max_depth3, learning_rate0.1) res_model.fit(X_train_flat, residual_train) # X_flat是展平后的特征 # 最终预测 主模型 残差模型 final_pred main_model.predict(X_test) res_model.predict(X_test_flat)残差模型不要用太复杂的结构max_depth3足够否则会把噪声也学进去。learning_rate0.1配合100棵树是XGBoost的稳健配置。模型集成方面我常用LSTM TCN 线性回归的加权平均。权重不是拍脑袋定的而是用验证集上的误差倒数来分配def ensemble_weights(val_preds, y_val): val_preds: list of arrays, 每个模型的验证集预测 errors [np.mean(np.abs(p - y_val)) for p in val_preds] inv_errors [1 / e for e in errors] total sum(inv_errors) return [ie / total for ie in inv_errors]这个权重分配法的好处是自动给误差小的模型更高权重而且不需要额外训练。但要注意如果某个模型在验证集上过拟合了它的误差会偏低权重会虚高。所以验证集一定要用滚动切分不能只用一段。最后一个技巧是分时段建模。电力负荷在白天和夜间的模式差异很大与其用一个模型硬拟合不如训练两个模型一个专门预测6:00-18:00一个预测18:00-次日6:00。分界点可以根据负荷曲线的谷值动态调整。我试过在某个园区数据上分时段建模比单模型MAPE降低了0.8个百分点代价是维护两套模型推理时要先判断时段再路由。这些技巧不是每个都值得上。我的习惯是先确保数据管道干净、baseline跑通再按误差来源逐个加技巧。如果残差已经接近白噪声加集成只会增加复杂度不涨精度。希望帮到你。本文还有配套的精品资源点击获取