☰
多特征LSTM电力负荷预测:从数据到部署的完整实战
2026/10/12 16:32:14 网站建设 项目流程

简介:这份资源是面向高校学生与深度学习入门者的多特征电力负荷预测项目源码包,基于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(drop=True) # 选取多特征列:负荷 + 温度 + 湿度 + 星期 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, lookback=24): 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_size=64, num_layers=2, dropout=0.2): super().__init__() self.lstm = nn.LSTM( input_size=input_size, # 特征数,与 X.shape[2] 一致 hidden_size=hidden_size, num_layers=num_layers, batch_first=True, # 输入格式 (batch, seq, feature) dropout=dropout ) self.fc = nn.Linear(hidden_size, 1) # 回归输出单值 def forward(self, x): out, _ = self.lstm(x) # 只取最后一个时间步的隐状态做预测 return self.fc(out[:, -1, :]) model = LSTMModel(input_size=X_train.shape[2]) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

batch_first=True这个参数是血泪经验: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_size=32, shuffle=True) 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

这里shuffle=True只作用于训练集的批次内部,不改变样本的时间顺序,和前面切分时不能 shuffle 是两回事,别混淆。torch.save存最优权重而不是最后一轮权重,是因为最后一轮往往已经过拟合。patience=10是经验值,数据量小可以调到 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(f'MAPE: {mape:.2f}%')

dummy矩阵的列数必须等于feature_cols的长度,否则inverse_transform会报维度错误。data_show.py里通常用 matplotlib 把true_real和pred_real画在同一张图上,横轴是时间。这里有个小技巧:如果横坐标时间点太密导致标签重叠,用plt.xticks(rotation=45)或每隔若干个点显示一个标签,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 → 验证集调参 → 测试集只跑一次」的流程,再没被问倒过。希望这份拆解帮到你,把这份源码跑通、改出自己的东西,比收藏十个教程都管用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询