
简介一套基于深度学习LSTM的多特征电力负荷预测Python项目源码集成包内含项目说明文档与实验数据集。适合计算机、电气工程及自动化等专业学生用于毕业设计、期末大作业或课程设计解决电力负荷时间序列预测建模与代码落地的问题。压缩包仅830KB共8个文件涵盖3个Python脚本、2个Markdown说明文档、2个CSV数据集及1个Excel数据表。Python脚本分别对应数据预处理、LSTM模型训练预测与结果可视化展示Markdown文档提供项目说明与使用指引数据文件则包含多特征负荷数据便于直接替换为自身场景数据。代码附有详细注释整体结构清晰下载后简单部署即可运行无需复杂配置。无论是需要快速搭建预测模型还是想学习LSTM在电力负荷场景下的完整实现流程都可以参照本资源进行二次开发或直接提交。已有239人下载学习作为导师认可的高分项目具备较强的参考价值。1. 数据决定了LSTM的上限多特征电力负荷预测从源码到落地的完整路径拿到一份Python基于深度学习LSTM的多特征电力负荷预测程序源码(附有项目说明数据集)时多数人第一反应是直接跑通看效果但这样大概率会翻车。电力负荷预测是典型的时间序列预测场景LSTM的多特征输入能捕捉负荷与气温、日期类型、湿度等多因素的耦合关系让预测误差从10%以上压到3%以内。这篇笔记围绕这套程序源码展开适合电力行业数据分析师、能源系统方向的研究生以及刚接触LSTM时间序列预测的Python工程师。我会把数据的滑窗构造、模型训练的完整代码、评估指标和部署技巧都拆开讲你跟着做完就能把这份源码吃透换到自己的数据上跑通。2. 多特征电力负荷数据怎么处理清洗、滑窗与归一化的三个关键步骤2.1 原始数据字段构成负荷之外还有哪些特征能用电力负荷预测里多特征指的不只是历史负荷这一条序列而是把影响负荷的多种因素一起喂给模型。常见的数据集字段包括时间戳、有功负荷MW、干球温度、相对湿度、节假日标记、星期几。部分公开数据集还会带上电价、风速、光照强度但用于负荷预测温度、湿度、日期类型是性价比最高的三个外生特征。拿到源码里的数据集后第一步是检查数据的时间粒度和缺失情况。多数项目说明里标注的是15分钟或1小时间隔你需要在代码中确认。数据清洗时我会先做三件事检查时间戳是否连续、处理缺失值、剔除异常突变点。缺失值用前后线性插值异常突变点用滚动窗口的中位数替换——电力负荷的瞬跳通常是数据采集故障直接删掉会让时间序列断裂。import pandas as pd import numpy as np # 读取CSV数据time列作为索引 df pd.read_csv(load_data.csv, parse_dates[time], index_coltime) # 检查缺失值电力负荷数据常见的坑是节假日前后整段缺失 print(缺失值统计\n, df.isnull().sum()) # 用线性插值填充缺失值 df df.interpolate(methodlinear, limit_directionboth) # 用滚动窗口中位数识别并处理异常突变点 window 12 # 1小时窗口15分钟粒度 df[load_median] df[load].rolling(window, centerTrue).median() # 超过3倍标准差的点视为异常用中位数替换 df[load] np.where((df[load] - df[load_median]).abs() 3 * df[load].std(), df[load_median], df[load]) df df.drop(columns[load_median])这里有个关键参数滚动窗口window的大小。15分钟粒度的数据取12个点3小时比较合适因为电力负荷的短时波动周期一般在小时级。窗口太小会把正常的负荷毛刺误判为异常窗口太大则对真实的负荷突变失去响应。你在这个程序源码上调试时从这份数据集自带的说明里看一看到底是15分钟还是1小时粒度再调整这个参数。2.2 滑窗构造用过去N小时预测未来M小时的完整实现LSTM不能直接吃一整条时间序列它需要你手工构造样本对。每一组样本包括一个seq_len长度的历史窗口和一个pred_len长度的目标窗口。对电力负荷预测来说最常见的是用过去72小时的数据预测未来24小时。滑窗构造是这套源码里最核心的数据预处理逻辑也是新手最容易写错的地方——滑窗的步长、样本之间的重叠度、是否打乱顺序都会直接影响模型效果。def create_sequences(data, feature_cols, target_col, seq_len72, pred_len24, stride1): 构造LSTM训练样本对。 data: 完整DataFrame已填充缺失值 feature_cols: 参与预测的特征列名列表 target_col: 要预测的负荷列名 seq_len: 历史窗口长度小时数 pred_len: 预测窗口长度小时数 stride: 滑窗步长 X, y [], [] feature_data data[feature_cols].values target_data data[target_col].values for i in range(0, len(data) - seq_len - pred_len 1, stride): X.append(feature_data[i:i seq_len]) # 历史 seq_len 步的所有特征 y.append(target_data[i seq_len:i seq_len pred_len]) # 未来 pred_len 步的负荷 return np.array(X), np.array(y) # 示例调用 feature_cols [load, temperature, humidity, is_holiday] X, y create_sequences(df, feature_cols, target_colload, seq_len72, pred_len24, stride3) print(样本形状:, X.shape, y.shape) # (样本数, 72, 4) / (样本数, 24)stride参数容易被忽略。工程上我一般设置stride3每3小时采样一个样本这样既能覆盖足够多的训练样本又避免了相邻样本过于相似导致的过拟合倾向。如果数据集足够大stride1也可以但训练时间会大幅增加。滑窗构造后的X三维结构(样本数, seq_len, 特征数)就是 LSTM 需要的输入格式这一步做完模型部分的代码才能跑通。2.3 归一化与数据集划分时间序列不能随机打乱LSTM 对输入数据的尺度非常敏感。多特征里负载的量级在几十到几千MW气温在零下到40度如果不归一化模型会天然偏向量级大的特征学到错误的关系。常规做法是MinMaxScaler把所有特征压缩到 [0,1] 区间。这里有一个不能省的操作scaler必须只用训练集的数据来fit再对验证集和测试集做transform。from sklearn.preprocessing import MinMaxScaler # 按时间顺序划分前80%训练中间10%验证最后10%测试 train_size int(len(df) * 0.8) val_size int(len(df) * 0.1) train_df df.iloc[:train_size] val_df df.iloc[train_size:train_size val_size] test_df df.iloc[train_size val_size:] feature_cols [load, temperature, humidity, is_holiday] scaler MinMaxScaler() # 用训练集 fit再分别 transform scaler.fit(train_df[feature_cols]) train_arr scaler.transform(train_df[feature_cols]) val_arr scaler.transform(val_df[feature_cols]) test_arr scaler.transform(test_df[feature_cols])这里为什么强调不能对整个数据集做fit因为scaler在fit时看到了未来的最大值和最小值等于把测试集的信息泄露到了训练阶段会让验证指标虚高。这是这套源码里最容易翻车的地方。另外数据集划分绝对不能用train_test_split(random_state42)这种随机切分——电力负荷是强自相关的时序数据随机切分会把连续的时间段打散模型相当于偷看了未来临近时段的数据测试结果毫无参考价值。3. 用PyTorch搭建LSTM多特征预测模型核心代码与参数含义3.1 为什么多特征输入要reshape成三维张量PyTorch 的nn.LSTM要求的输入形状是(seq_len, batch_size, input_size)如果设置了batch_firstTrue则形状为(batch_size, seq_len, input_size)。input_size就是特征数量在上一步构造的X里就是feature_cols的长度4。有时你会看到源码里出现X X.reshape(-1, seq_len, input_size)这样的代码它的作用就是把二维或扁平的输入整理成这个三维结构这一步漏掉会直接报维度错误。常见误区把特征拼接成(batch_size, seq_len * input_size)的二维输入丢给 LSTM这是不对的。LSTM 期望的是时间步这一维度独立存在它才能逐步迭代计算。如果已经是X.shape (样本数, 72, 4)的形状就无需再 reshape直接转成 PyTorch 张量即可。import torch import torch.nn as nn X_tensor torch.tensor(X, dtypetorch.float32) # (样本数, seq_len, 特征数) y_tensor torch.tensor(y, dtypetorch.float32) # (样本数, pred_len)3.2 网络结构定义input_size、hidden_size、num_layers怎么定LSTM 模型的结构并不复杂但参数选择直接决定拟合能力。hidden_size是 LSTM 细胞状态的维度可以理解成记忆容量num_layers是堆叠的 LSTM 层数堆两层通常能捕捉更高层的时序模式但超过三层容易过拟合且训练极慢。下面这个定义是这套源码的标准结构我补充了一些注释和参数建议。class LSTMPredictor(nn.Module): def __init__(self, input_size4, hidden_size64, num_layers2, pred_len24, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout # num_layers1时生效 ) # 从LSTM的hidden_state映射到pred_len个预测值 self.fc nn.Linear(hidden_size, pred_len) def forward(self, x): # x: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # 取最后一层的最后时间步输出: out[:, -1, :] - (batch, hidden_size) last_out out[:, -1, :] preds self.fc(last_out) # (batch, pred_len) return predsout[:, -1, :]是只取最后一个时间步的隐藏状态作为整个序列的浓缩表示。理论依据是LSTM 的最后一个状态已经编码了整个历史窗口的信息再往后接一个全连接层做回归输出即可。有时你会看到有人return self.fc(out)这是把每个时间步都做了预测只有在做序列到序列seq2seq预测时才需要。对单步多步预测取最后状态是更常见的做法。dropout参数只在num_layers1时才会生效如果你只堆了一层却加了dropout0.5代码不会报错但模型实际上没有使用 dropout——这是 PyTorch 里的一个隐藏细节。3.3 训练循环与损失函数MSE之外还要关注MAPE训练循环本身是标准的 PyTorch 流程但有几个针对时间序列预测的调整。损失函数用nn.MSELoss()没问题因为它对大误差的惩罚更重但电力负荷预测的业务方往往更关心百分比误差所以我会在每一个 epoch 结束后额外计算MAPE用来判断模型是否真的学得准而不只是看 loss 数字在降。import torch.optim as optim model LSTMPredictor(input_size4, hidden_size64, num_layers2, pred_len24) optimizer optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() batch_size 64 epochs 50 train_dataset torch.utils.data.TensorDataset(X_tensor_train, y_tensor_train) train_loader torch.utils.data.DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) def mape_loss(y_true, y_pred): return torch.mean(torch.abs((y_true - y_pred) / (y_true 1e-8))) * 100 for epoch in range(epochs): model.train() epoch_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() y_pred model(X_batch) loss criterion(y_pred, y_batch) loss.backward() optimizer.step() epoch_loss loss.item() * X_batch.size(0) # 每个epoch后计算验证集MAPE不需要梯度 model.eval() with torch.no_grad(): val_pred model(X_tensor_val) val_mape mape_loss(y_tensor_val, val_pred).item() print(fEpoch {epoch1}/{epochs} | Train Loss: {epoch_loss/len(train_dataset):.6f} | Val MAPE: {val_mape:.2f}%)学习率的设置是玄学但我给一个相对稳定的起步经验lr0.001Adam是大多数序列预测项目的默认配置。如果训练 loss 震荡剧烈或出现 NaN把学习率降到0.0003如果 loss 下降太慢提升到0.003但需要配合早停。batch_size在负荷预测这种样本量几千到几万的任务里取64或128比较稳过小会让梯度更新太频繁过大则每个 epoch 时间太长且容易陷入局部极值。shuffleTrue在这里是合理的因为样本在滑窗构造时已经天然按时间排列打乱批次顺序只影响梯度更新顺序不影响时间序列的时间依赖关系——注意这与数据集划分时的不能打乱不是一个概念。4. LSTM电力负荷预测最常见的5个坑现象、原因与解决方案4.1 预测结果是一条水平直线现象测试集上的预测曲线几乎是一条水平线数值接近训练集的均值。原因我第一次跑类似的模型时也遇到过。水平线往往意味着模型没有学到时序特征退化为对每个输入都输出平均负荷。常见诱因有两个一是数据没有归一化LSTM的激活函数默认tanh对原始尺度差异大的输入直接饱和二是网络过于简单hidden_size太小或没有隐藏层模型容量不足。解决先确认特征都做了MinMaxScaler再把hidden_size从 16 往上加到 64 或 128 试一轮如果还不行检查是不是seq_len太短低于24小时LSTM看不到足够长的历史模式。4.2 训练损失下降但验证损失升高现象训练集 loss 稳定下降但验证集的 MAPE 在第 10 个 epoch 后不降反升。原因这是典型的过拟合。时间序列模型因为样本之间存在强自相关过拟合比图像分类更容易发生——模型把训练集里某些时段的特殊模式背下来了而不是学到通用的负荷变化规律。解决在nn.LSTM中把dropout从 0.2 提到 0.5把num_layers从 2 降到 1或者直接加早停当验证 MAPE 连续 5 个 epoch 不下降就停止训练并回滚到最优权重。这套源码里的训练脚本如果没有早停逻辑我建议一定自己补上它是防止过拟合的后悔药。4.3 滑窗构造后样本顺序随机化导致预测结果失真现象验证集和测试集的 MAPE 都很好看但部署到线上后效果一塌糊涂。原因你在源码里用了train_test_split(X, y, random_state42)或DataLoader中shuffleTrue作用于整个数据集——这两个操作都会把时间序列打乱。模型在训练时见过未来相邻时间点的信息测试时相当于开卷考试。解决按时间顺序划分数据参考 2.3 的代码并且DataLoader只在训练集上设置shuffleTrue验证集和测试集加载时保持shuffleFalse保证评估时严格按时间推进模拟真实预测。4.4 loss 变成 NaN现象训练到某一步loss 直接变成nan。原因通常有三个学习率过大导致梯度爆炸、输入数据里含有 NaN滑窗构造时把缺失值窗口带进来了、LSTM 的梯度值本身在长序列反向传播时容易出现梯度爆炸。解决先检查X_tensor和y_tensor是否包含 NaNtorch.isnan(X_tensor).any()再把学习率从 0.001 降到 0.0003最后可以考虑对梯度做裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)这个操作在时间序列训练里是常见的保底手段。4.5 预测曲线滞后于真实负荷一天现象预测的负荷曲线形状和真实曲线一致但整体向右平移了一天。原因这是多特征负荷预测里最经典的问题——模型学到的其实是今天的负荷接近昨天同一时刻的负荷而不是真正利用气温、节假日等信息给出前瞻性预测。深层原因是特征里load占了绝对主导LSTM 直接把上一个周期的负荷值复制给了输出而额外特征气温、湿度、节假日的权重被摊薄了。解决尝试在训练时把load这一个特征和其他特征分别归一化或增大模型中其他特征的嵌入权重或者在损失函数里对预测误差中滞后部分加大惩罚——具体做法是把预测序列的一阶差分也加入 loss 计算迫使模型捕捉变化趋势而不是平移复制。也可以考虑把seq_len从 72 小时缩短到 24 小时减少模型对昨天数据的依赖。这个坑不解决你的多特征模型实际效果和单特征模型没有任何区别。5. 评估与调优用三个指标看清模型真实水平5.1 MAPE、RMSE、R2 的PyTorch实现与业务含义写完训练流程接下来最关键的是评估。电力负荷预测在业务侧看的是偏差了多少MAPE 是行业里最通用的指标——它把误差换算成百分比调度员和领导都能看懂。RMSE 则放大了大误差的惩罚适合你判断模型是否在高峰负荷时段出现严重失守。R2 是统计学拟合优度但在负荷预测上我一般作为辅助参考不过度依赖。def evaluate_model(model, X_tensor, y_tensor): model.eval() with torch.no_grad(): y_pred model(X_tensor) # 如果做了归一化需要反归一化才能计算业务指标 y_pred_inv scaler.inverse_transform( np.concatenate([y_pred.numpy(), np.zeros((len(y_pred), 3))], axis1) )[:, 0] y_true_inv scaler.inverse_transform( np.concatenate([y_tensor.numpy(), np.zeros((len(y_tensor), 3))], axis1) )[:, 0] # MAPE: 平均绝对百分比误差 mape np.mean(np.abs((y_true_inv - y_pred_inv) / (y_true_inv 1e-6))) * 100 # RMSE: 均方根误差 rmse np.sqrt(np.mean((y_true_inv - y_pred_inv) ** 2)) # R2: 决定系数 ss_res np.sum((y_true_inv - y_pred_inv) ** 2) ss_tot np.sum((y_true_inv - np.mean(y_true_inv)) ** 2) r2 1 - ss_res / ss_tot print(fMAPE: {mape:.2f}% | RMSE: {rmse:.2f} MW | R2: {r2:.4f}) return mape, rmse, r2注意scaler.inverse_transform这一步因为我在第 2 章把整个特征矩阵一起归一化了反归一化时要把预测的负荷列和零填充的其他列拼回去才能还原到真实量纲。这个细节漏掉的话你打印出来的 MAPE 是 0.002%会让你误以为自己已经封神实际上只是拿归一化数字算了个自嗨指标。MAPE在电力负荷上的合理区间短期未来1-24小时负荷预测MAPE在2%到5%之间算良好超过8%需要认真调参。5.2 超参数的影响范围一张表说清楚调参方向调参是深度学习中看起来最像玄学的环节但 LSTM 负荷预测的超参数影响有迹可循。我整理了这套源码里最值得调整的几个参数按重要性排序参数推荐范围效果与调整方向seq_len24-168小时太短学不到负荷的日周期与周周期太长引入噪声且训练变慢。先设为24或72hidden_size32-128决定LSTM记忆容量。验证MAPE高且训练loss收敛慢欠拟合就加大num_layers1-32层是性价比之选。3层以上训练时间翻倍易过拟合dropout0-0.5验证集MAPE反弹时从0.2往上调但超过0.5会让模型欠拟合lr0.0003-0.003训练loss震荡时调低loss下降极慢时调高配合CosineAnnealing更稳batch_size32-128数据集在万级样本时用64或128更大数据集可上256有一个易被忽略的联动seq_len增大时hidden_size也可以适当增大因为更长的历史窗口需要更大的记忆容量来编码。反过来seq_len不变只加大hidden_size收益很快会饱和。我一般先固定seq_len72、hidden_size64、num_layers2跑一个基线再单独动一个参数看验证集 MAPE 的变化不要同时调多个参数否则你无法判断是谁起了作用。5.3 单特征 vs 多特征的对比实验多出来的两个点MAPE从哪来很多读者拿到源码后关心一个问题所谓多特征到底比只用负荷强多少我建议你在自己的数据集上做一次对照实验只改feature_cols其他训练配置完全不变。用同一个测试集评估两条路径单特征feature_cols [load]input_size1多特征feature_cols [load, temperature, humidity, is_holiday]input_size4我跑过多个电力数据集后得到的典型差距多特征方案在平稳日 MAPE 只比单特征好 0.5 到 1 个百分点但在气温骤变日比如夏季高温预警或冬季寒潮差距会拉到 3 到 5 个百分点。原因是单特征模型本质上只能做历史相似日的外推而多特征模型能感知到气温的跳升预先拉升预测值。如果你的数据所在地区气候稳定、负荷季节性弱多特征带来的提升不明显这时候把精力放在调seq_len和hidden_size上更划算。做这个对比实验的另一个好处是你能直观看到源码里每个模块的作用而不是黑匣子式地跑完就扔。6. 模型存档与向前一步把最优权重固定下来训练完模型后最要紧的是把权重和scaler完整保存下来。只保存模型不保存scaler是新手最常见的失误——部署时你拿到的线上数据是新尺度的没有scaler就无法把输入转换到训练时的分布。我会在同一目录下保存两个文件# 最优模型与scaler的保存 torch.save(model.state_dict(), lstm_load_forecast.pth) import joblib joblib.dump(scaler, scaler.save)加载时要注意必须先恢复scaler再用同样的参数重建模型结构最后load_state_dict。社区里有同学把scaler.save丢了只能从训练集重新估算归一化范围预测结果偏差明显。更稳妥的做法是用torch.save({model_state: model.state_dict(), config: {input_size: 4, hidden_size: 64, ...}}, checkpoint.pth)把配置和权重打成一个包加载时从config里重建模型省去手动维护模型结构代码。最后一个习惯正式训练前固定随机种子。LSTM 的初始化权重和DataLoader的批次顺序都带随机性不固定种子的话同一次调参跑出的 MAPE 会在小数点后两位浮动干扰你判断参数调整的效果。在训练脚本开头加三行torch.manual_seed(42) np.random.seed(42) random.seed(42)这套源码在电力负荷预测这个方向上算是性价比很高的起点它的边界在于模型本质上是统计模型学不到电网检修导致某区域短暂停电这类突发零负荷事件也没有用到 Transformer 这类更强的序列模型如果你手里的数据量超过 10 万样本可以换成 PyTorch 的TransformerEncoder做对照。把滑窗、归一化、反归一化和评估这四个环节摸透换到任何时间序列预测任务光伏功率、交通流量你都能快速迁移。希望这份笔记帮你把坑填平跑出靠谱的预测曲线。本文还有配套的精品资源点击获取