多特征LSTM电力负荷预测:从数据到部署的完整实战

发布时间:2026/10/12 16:32:19
多特征LSTM电力负荷预测:从数据到部署的完整实战
简介这份资源是面向高校学生与深度学习入门者的多特征电力负荷预测项目源码包基于Python与LSTM搭建可用于毕业设计、期末大作业或课程设计场景帮助读者快速完成从数据处理到模型预测的完整实践。压缩包共8个文件约830KB包含3个py脚本分别负责数据处理、可视化与LSTM预测2个md说明文档、2个csv数据文件及1个xlsx原始数据表代码附有注释新手也能读懂并部署运行。目前已有239人学习下载。项目围绕多特征输入展开涵盖原始数据读取、特征处理、负荷曲线展示与LSTM建模预测等模块读者可据此理解时序预测的完整流程并在此基础上替换数据或调整网络结构完成自己的课题是一份结构清晰、可直接复用的高分项目参考。1. 从一份 98 分课设说起多特征 LSTM 电力负荷预测到底能跑出什么电力负荷预测这个题目每年毕业设计季都会被翻出来做一遍原因很直接数据好找、指标好算、模型有讲头。但真正动手的人会发现单靠历史负荷做单变量预测MAPE 卡在 3% 上下就下不去了答辩时老师一句「温度对负荷没影响吗」就能把你问住。这份资源解决的正是这个痛点——它把原始负荷、气象、日期三类特征拼成多变量输入用 LSTM 做时序建模配套raw_data/data.xlsx、power.csv和完整可运行脚本从数据处理到预测出图一条链路走通。它适合三类人赶课程设计/毕业设计需要一份能跑通、能讲清原理的完整工程的同学刚接触lstm时间序列预测python想找一个真实数据集练手的入门者以及需要一份带注释、结构清晰的源码笔记作为二次开发底座的从业者。下面我按「数据怎么进 → 模型怎么搭 → 坑在哪 → 怎么验证」的顺序把这份包拆开讲透。2. 数据管线拆解从 data.xlsx 到 LSTM 能吃的三维张量多特征预测和单特征预测最大的区别不在模型而在数据组织。LSTM 的输入要求是(样本数, 时间步长, 特征数)的三维张量而原始 Excel 里是一张二维表中间这层转换如果做错模型要么报维度错误要么悄悄学出错误的时序关系。这一章把data_processing.py的核心逻辑拆开。2.1 多特征选取与量纲统一先看数据本身。raw_data/data.xlsx里通常包含时间戳、有功负荷以及温度、湿度、节假日标记等列。多特征建模的第一步是决定哪些列进模型。常见做法是负荷列必选温度列必选空调负荷对温度极敏感日期类特征星期、是否节假日做 one-hot 或周期编码湿度、风速这类相关性弱的可以先算一遍皮尔逊系数再决定去留。选完特征紧接着是量纲问题。负荷数值在几百到几千 kW温度在 -10 到 40如果直接喂进网络负荷的梯度会完全压制温度特征模型等于只学了负荷自回归。所以必须做归一化。我一般用 MinMax 把每列压到 [0,1]而不是全局统一缩放——按列缩放才能保留每列自己的动态范围。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 读取原始数据第一列通常是时间 df pd.read_excel(raw_data/data.xlsx) df[time] pd.to_datetime(df[time]) df df.sort_values(time).reset_index(dropTrue) # 选取多特征列负荷 温度 湿度 星期 feature_cols [load, temperature, humidity, weekday] data df[feature_cols].values # 按列归一化fit 只在训练集上做避免数据泄漏 scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(data) print(归一化后形状:, scaled.shape) # (样本数, 特征数)这段代码的关键点有三个。第一sort_values(time)不能省Excel 里行序未必按时间排时序模型对顺序极度敏感。第二MinMaxScaler的fit只能在训练集上调用如果拿全量数据 fit 再切分测试集的信息就泄漏进训练过程评估指标会虚高这是答辩最容易被追问的点。第三feature_cols的顺序决定了后面反归一化时取哪一列务必和建模时保持一致。2.2 滑动窗口构造与训练集切分归一化之后是构造监督学习样本。LSTM 需要「用过去 N 个时刻预测下一个时刻」这个 N 就是时间步长lookback。电力负荷有很强的日周期如果采样间隔是 1 小时lookback 取 24 刚好覆盖一整天如果间隔 15 分钟取 96 更合理。这份资源里我建议先用 24 起步。def create_sequences(data, lookback24): X, y [], [] for i in range(len(data) - lookback): # 前 lookback 个时刻的所有特征作为输入 X.append(data[i:i lookback, :]) # 第 lookback 个时刻的负荷列第 0 列作为标签 y.append(data[i lookback, 0]) return np.array(X), np.array(y) lookback 24 X, y create_sequences(scaled, lookback) print(X 形状:, X.shape) # (样本数, 24, 特征数) print(y 形状:, y.shape) # (样本数,) # 按时间顺序 8:2 切分不能 shuffle split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]create_sequences里y.append(data[i lookback, 0])的0就是负荷列在feature_cols里的下标改特征顺序时这里必须同步改。切分用时间顺序切而不是随机切是因为时序数据一旦 shuffle未来信息会混进训练集模型在测试集上的表现会好得不真实。这一点在lstm时间序列预测python的教程里经常被一笔带过但它是评估可信度的底线。3. LSTM 模型搭建与训练lstm_predict.py 逐段过数据管线通了接下来是模型。lstm_predict.py是整个项目的核心它承担了建模、训练、预测、反归一化、出图五件事。这一章按执行顺序把关键段落拆开顺带说清每个超参为什么这么设。3.1 网络结构与超参设置LSTM 层数不是越多越好。电力负荷这种单站点、特征数在个位数的任务两层 LSTM 已经足够堆到四五层只会增加过拟合风险和训练时间。隐藏单元数一般取 32 到 128我倾向 64 起步。Dropout 放在 LSTM 层之后用来抑制过拟合0.2 是安全值。import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, # 特征数与 X.shape[2] 一致 hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, # 输入格式 (batch, seq, feature) dropoutdropout ) self.fc nn.Linear(hidden_size, 1) # 回归输出单值 def forward(self, x): out, _ self.lstm(x) # 只取最后一个时间步的隐状态做预测 return self.fc(out[:, -1, :]) model LSTMModel(input_sizeX_train.shape[2]) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3)batch_firstTrue这个参数是血泪经验PyTorch 的 LSTM 默认输入是(seq, batch, feature)而 numpy 切出来的数据是(batch, seq, feature)不设这个参数就会维度对不上报错信息还特别绕。out[:, -1, :]取最后一个时间步是因为 LSTM 的隐状态已经把整段序列的信息压缩进去了取最后一步等价于「读完 24 小时再预测下一小时」。损失函数用 MSE 是因为负荷预测关心的是数值误差如果更在意百分比误差可以换 MAE 或自定义 MAPE 损失。3.2 训练循环与早停训练部分要处理两个问题批次划分和过拟合监控。批次大小取 32 或 64太小梯度震荡太大显存吃紧且泛化变差。早停early stopping是防止过拟合最实用的手段验证损失连续若干轮不降就停。from torch.utils.data import TensorDataset, DataLoader train_ds TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) best_loss, patience, wait float(inf), 10, 0 for epoch in range(200): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred model(xb).squeeze(-1) loss criterion(pred, yb) loss.backward() optimizer.step() # 验证 model.eval() with torch.no_grad(): val_pred model(torch.FloatTensor(X_test)).squeeze(-1) val_loss criterion(val_pred, torch.FloatTensor(y_test)).item() if val_loss best_loss: best_loss, wait val_loss, 0 torch.save(model.state_dict(), best_lstm.pth) # 存最优权重 else: wait 1 if wait patience: print(f早停于 epoch {epoch}) break这里shuffleTrue只作用于训练集的批次内部不改变样本的时间顺序和前面切分时不能 shuffle 是两回事别混淆。torch.save存最优权重而不是最后一轮权重是因为最后一轮往往已经过拟合。patience10是经验值数据量小可以调到 5数据量大可以放宽到 20。3.3 反归一化与预测出图模型输出的是 [0,1] 区间的归一化值必须反变换回真实负荷量纲才能算 MAPE、画对比图。反归一化的坑在于MinMaxScaler是按多列 fit 的反变换时要构造一个和原特征数相同的矩阵把预测值填进负荷列其余列填 0再inverse_transform后取负荷列。model.load_state_dict(torch.load(best_lstm.pth)) model.eval() with torch.no_grad(): pred_scaled model(torch.FloatTensor(X_test)).squeeze(-1).numpy() # 构造占位矩阵做反归一化 dummy np.zeros((len(pred_scaled), len(feature_cols))) dummy[:, 0] pred_scaled pred_real scaler.inverse_transform(dummy)[:, 0] # 真实值同样反归一化 dummy[:, 0] y_test true_real scaler.inverse_transform(dummy)[:, 0] mape np.mean(np.abs((true_real - pred_real) / true_real)) * 100 print(fMAPE: {mape:.2f}%)dummy矩阵的列数必须等于feature_cols的长度否则inverse_transform会报维度错误。data_show.py里通常用 matplotlib 把true_real和pred_real画在同一张图上横轴是时间。这里有个小技巧如果横坐标时间点太密导致标签重叠用plt.xticks(rotation45)或每隔若干个点显示一个标签python画图横坐标太密集是高频问题提前处理掉。4. 避坑与排查多特征 LSTM 预测最常见的五个翻车点这一章是我自己复现这类项目时踩过的坑也是答辩现场被问得最多的点。每条按「现象 → 原因 → 解决」写照着排查能省不少时间。现象一训练 loss 一直不降停在某个值不动。原因多半是学习率过大导致梯度爆炸或者归一化没做导致数值量级差异过大。解决先把学习率降到 1e-4 试再检查是否按列做了 MinMax如果 loss 变成 NaN加torch.nn.utils.clip_grad_norm_(model.parameters(), 5)做梯度裁剪。现象二训练集 MAPE 很低测试集 MAPE 高得离谱。这是典型的数据泄漏或过拟合。先查切分是不是 shuffle 了再查 scaler 是不是在全量数据上 fit 的。如果两者都没问题就是模型过拟合加 Dropout、减隐藏单元、开早停。现象三预测曲线整体滞后真实曲线一个时间步。这是 LSTM 做自回归预测的固有现象模型倾向于输出「上一个时刻的值」。解决在特征里加入更强的外生变量温度、节假日或者改用 seq2seq 结构一次预测多个未来点而不是单步滚动。现象四换一份数据就报维度错误。多半是feature_cols的列名在新数据里对不上或者create_sequences里标签列的下标写死了。解决把特征列名和标签列名抽成配置项读取后先assert列存在再进管线。现象五MAPE 算出来是负数或异常大。检查真实值里是否有 0 或接近 0 的点MAPE 的分母不能为 0。解决过滤掉负荷为 0 的异常点或改用 SMAPE、RMSE 作为补充指标。提示答辩时老师最爱问「你怎么证明多特征比单特征好」。提前跑一组只用负荷列的对照实验把两个 MAPE 并排放在一张表里比任何解释都有说服力。5. 进阶技巧把 MAPE 从 3% 压到 2% 以内的三个实操手段基础版本跑通之后如果想让指标更好看有三个方向可以试都是我在实际项目里验证过有效的。第一个是特征工程加周期编码。原始数据里的「星期」如果直接当数值 1-7 喂进去模型会误以为周日和周一距离很远。正确做法是做 sin/cos 周期编码sin(2π·weekday/7)和cos(2π·weekday/7)这样周日和周一在特征空间里是相邻的。同理一天内的小时也可以这样编码。这一步通常能把 MAPE 降 0.3 到 0.5 个百分点。第二个是残差修正。先用 LSTM 跑一遍拿到预测值再算预测残差用一个轻量模型比如 XGBoost 或简单的线性回归对残差做二次建模最后把两部分相加。原理是 LSTM 擅长捕捉趋势和周期但对突发性波动比如极端天气拟合不足残差模型正好补这块。第三个是滚动预测代替单点预测。基础版是「用真实历史预测下一时刻」实际部署时未来时刻的真实值是拿不到的必须用上一时刻的预测值滚动。这两种模式的误差会累积评估时要用滚动模式才真实。做法是把测试集按时间逐步推进每预测一步就把预测值填回输入窗口。# 滚动预测示意 window scaled[:lookback].copy() preds [] for i in range(len(X_test)): inp torch.FloatTensor(window[-lookback:]).unsqueeze(0) with torch.no_grad(): p model(inp).item() preds.append(p) # 用预测值构造下一时刻的输入行其余特征沿用真实值 next_row scaled[lookback i].copy() next_row[0] p window np.vstack([window, next_row])这段代码里next_row[0] p是把预测的负荷填回输入其余特征温度等在预测时是已知的所以沿用真实值。滚动预测的 MAPE 通常比单点预测高 0.5 到 1 个百分点但它是真实可部署的指标答辩时用这个数更稳。验证方法上我习惯把数据集切成训练/验证/测试三段验证集用来调超参和早停测试集只在最后跑一次。如果测试集跑了很多次反复调参那测试集就变成了验证集指标不可信。从那以后我每次交项目前都强制走一遍「训练集 fit scaler → 验证集调参 → 测试集只跑一次」的流程再没被问倒过。希望这份拆解帮到你把这份源码跑通、改出自己的东西比收藏十个教程都管用。本文还有配套的精品资源点击获取