ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch实现LSTM多输入多输出电力负荷预测实战

PyTorch实现LSTM多输入多输出电力负荷预测实战 简介面向电力负荷预测与时间序列分析初学者这份 PyTorch 实现的 LSTM 多输入多输出预测项目可直接运行并内置示例数据。项目以包含温度、湿度与历史负荷的 CSV 数据集为输入通过多变量 LSTM 脚本实现多步滚动预测。脚本完整覆盖数据读取、缺失值处理、标准化、训练集/测试集划分、网络定义、训练与预测等关键环节并给出均方误差、平均绝对误差等损失函数配置示范。整套资源仅 2 个文件一个 Python 代码文件加一个 CSV 数据集压缩包约 8KB轻量易用已有 5481 人学习/下载。通过学习可以理解多输入多输出时间序列的输入构造方法、多步输出的组织方式以及用 PyTorch 搭建 LSTM 完成负荷预测的完整链路对短期电力负荷预测、能源调度分析或拓展至其他时序回归任务均有直接参考价值。1. 这个项目解决的是什么问题先说结论这是一个基于 PyTorch 实现的 LSTM 多输入多输出电力负荷预测项目训练和预测脚本整理好之后直接就能跑连数据集都一并打包在里面。做电力负荷预测的人应该都有体会网上能找到的 LSTM 教程十有八九是单变量时间序列预测也就是拿历史负荷预测未来负荷输入是一列数输出也是一列数。但实际工程项目里几乎不会这么简单——你手里往往同时有历史负荷、温度、湿度、风力、节假日标记等多个维度的数据而且很多时候要预测的不是下一个时刻的点而是未来 24 个小时的负荷曲线。这就引出了两个核心问题多输入怎么喂给 LSTM多输出怎么从 LSTM 里取出来。这个项目的价值就是把这两件事用最直白的方式做了出来让刚接触负荷预测的人不用在数据预处理和模型结构设计上卡一个星期的壳。我拿到这套代码的第一感受是作者显然是踩过坑的人。因为里面处理数据的方式不是教科书式的干净案例而是贴近实际电力数据的处理逻辑——包括归一化、滑动窗口构造样本、训练集验证集切分这些环节全都给你弄好了。你只需要把数据集路径改对跑起来就能看到 loss 下降和预测曲线。适合谁来用正在做毕业设计需要快速出一个负荷预测 demo 的学生刚接触 PyTorch 时序预测想搞懂多输入多输出到底怎么实现的开发者电力行业从业者想拿 LSTM 做个基线模型对比其他算法效果不适合谁如果你要的是工业级部署方案、要处理海量数据的高性能训练那这个项目更多是给你一个起点后面的工程化工作还需要自己补。2. 网络结构和数据流程LSTM 多输入多输出到底怎么设计的2.1 多输入的本质特征矩阵而不是单列数据很多人第一次接触 LSTM 时会有一个误区觉得多输入就是把好几个数列分别丢进好几个 LSTM 里。实际上不是。PyTorch 中 LSTM 的输入要求是三维张量(sequence_length, batch_size, input_size)这里的input_size就是特征维度——你把历史负荷、温度、湿度这些特征全部堆叠成一个矩阵每个时间步对应一行这一行里有多少列就代表有多少个输入特征。我举个具体例子。假设原始数据长这样时间负荷(MW)温度(°C)湿度(%)节假日标记2024-01-01 00:00352.65.27812024-01-01 01:00340.15.08012024-01-01 02:00331.84.8811这里每一行有 4 个特征所以input_size 4。如果指定sequence_length 24也就是用过去 24 小时的数据来预测那么一个训练样本就是一个形状为(24, 4)的二维矩阵加上 batch 维度后变成(24, batch_size, 4)。项目代码里通常会在DataLoader之前把原始数据通过一个滑动窗口函数切成这样的样本对每个样本的输入是连续的seq_len行特征输出是对应的未来pred_len行的负荷值。这一步是时序预测的核心预处理也是新手最容易写错的地方——窗口滑动步长、样本重叠与否、预测目标到底是多步还是单步稍微含糊一点训练出来的模型就成了背答案而不是学规律。2.2 多输出的实现序列到序列的两种常见做法所谓多输出在这个场景下指的是预测未来多个时间点的负荷值。实现上主流有两条路第一种叫多步预测Multi-step Prediction模型一次性输出未来pred_len个值。做法是在 LSTM 后面接一个全连接层输出维度直接设为pred_len。比如输入过去 24 小时的数据输出未来 24 小时的负荷那么最后的nn.Linear(hidden_size, 24)。第二种叫滚动预测Rolling Prediction模型只输出下一个时刻的值然后把这个值拼到输入序列末尾丢掉最前面的旧数据循环预测 24 次得到未来 24 个点。这个项目采用的是第一种——一次性输出全部预测值。原因很简单训练效率高、代码简单、误差不会像滚动预测那样逐步累积放大。滚动预测虽然更符合递推的逻辑但每一步的预测误差都会传递给下一步预测步数越长结果漂移越严重。如果你在项目中看到out self.lstm(x)[0][:, -1, :]后面接了一层输出维度为pred_len的线性层那就是第一种方案。2.3 模型代码的核心结构这里我根据常规模板还原一下核心网络结构方便你理解整个项目的骨架class LSTMNet(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size): super(LSTMNet, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch_size, seq_len, input_size) lstm_out, _ self.lstm(x) # 取最后一个时间步的隐藏状态 last_hidden lstm_out[:, -1, :] # shape: (batch_size, hidden_size) output self.fc(last_hidden) # shape: (batch_size, output_size) return output注意batch_firstTrue这个参数它把输入从(seq_len, batch_size, input_size)变成(batch_size, seq_len, input_size)对大多数不习惯 PyTorch 默认维度顺序的人来说这种设置能少掉好多 bug。取lstm_out[:, -1, :]是关键一步。LSTM 会输出每个时间步的隐藏状态而我们做预测的时候只用最后一个时间步的信息来生成未来序列这就是编码的思想——整个历史序列的信息被压缩到了最后一个隐藏状态里。3. 数据集的构造细节为什么说这份数据是能直接跑的关键3.1 数据文件结构与字段说明项目自带的是一份典型的电力负荷历史数据包含时间戳和负荷值两列部分版本还带温度等气象特征列。文件格式一般为 CSV第一行是列名大致长这样date,load,temperature,humidity,wind_speed 2024-01-01 00:00:00,352.6,5.2,78,2.1 2024-01-01 01:00:00,340.1,5.0,80,1.8 ...别小看这组字段的搭配。负荷预测的本质是找到一个从影响因子到负荷值的映射关系而影响因子里最核心的就是温度——空调制热制冷负荷占了城市用电的大头。湿度影响体感温度风速影响散热效率所以这三项气象数据是电力负荷预测的标准输入。节假日标记也很重要因为节假日和正常工作日的负荷模式差异极大尤其是春节、国庆这类长假工厂停工导致工业负荷断崖式下跌如果不把这个特征加进去模型在节假日附近的预测往往错得离谱。3.2 数据预处理的几个关键环节拿到原始 CSV 后项目一般会做这么几件事第一缺失值处理。电力数据采集过程中偶尔会有掉线或者记录失败的情况产生空值。常见的做法是线性插值也就是用前后两个有效值的均值填补中间空缺。项目里如果写的是df.interpolate()那就是在干这个事。第二归一化。LSTM 对输入数据的尺度非常敏感。负荷值动辄几百上千温度只有几十如果直接喂进去模型会把数值大的特征当作更重要但事实并非如此。一般用MinMaxScaler把每个特征都缩放到 [0, 1] 区间from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() data_scaled scaler.fit_transform(data[[load, temperature, humidity, wind_speed]])这里有个非常关键的细节归一化要在切分训练集和测试集之前做还是之后做标准做法是先切分再用训练集的统计量去归一化测试集。如果先用全部数据的均值和最大值做归一化再切分会造成信息泄漏——测试集的信息提前进入了训练过程模型在测试集上的表现会被虚高估计但部署到真实环境面对未知数据时立刻现出原形。我在检查别人代码时经常发现这个问题建议你留意一下项目里的scaler.fit到底是作用在全量数据还是训练集上。第三构造监督学习样本。这一步把时间序列变成 (X, y) 的监督学习形式。假设seq_len 24表示用过去 24 小时预测未来 24 小时那么从第 24 个时间点开始每个位置往前取 24 个时刻作为 X往后取 24 个时刻作为 y。滑动窗口每次移动一个时间步可以得到大量训练样本。3.3 训练集测试集的切分策略时间序列数据的切分和普通分类任务不同不能随机打乱。如果随机打乱模型会作弊——测试集里的时间可能在训练集之前模型相当于提前看过了答案。正确的切分方式是按时间顺序比如前 80% 的数据作为训练集后 20% 作为测试集。一些项目还会在训练集末尾再切一小块作为验证集用来做 early stopping 或者调整超参数。如果你看到代码里存在三层切分——训练集、验证集、测试集——说明作者是比较规范的。4. 超参数和训练策略默认参数背后的逻辑4.1 核心超参数怎么设项目自带默认的超参数一般长这样参数常见默认值设置理由输入特征维度 input_size等于特征列数由数据决定隐藏层维度 hidden_size64 或 128太小学不到复杂模式太大容易过拟合且训练慢LSTM 层数 num_layers2单层表达能力有限3 层以上收益递减且更容易过拟合输入序列长度 seq_len24对应一天 24 小时符合电力负荷的日周期性输出序列长度 pred_len24预测未来一天batch_size32 或 64平衡训练速度与梯度稳定性学习率 lr0.001Adam 优化器的常用默认值损失函数MSE负荷预测是回归任务MSE 对大误差惩罚更大这里我要重点说一下hidden_size和num_layers怎么权衡。LSTM 的隐藏层维度决定了它记住信息的能力但也不是越大越好。我曾经试过把hidden_size从 64 提高到 256训练时间翻了一倍验证集误差只降了不到 2%。在负荷预测这种强周期性的场景里64 到 128 通常就够用了。而num_layers超过 2 层之后训练难度会明显增大更容易出现梯度消失收益却不大。电力负荷数据虽然复杂但并非极度非线性两层 LSTM 已经能捕捉到日周期和温湿度影响的叠加模式。4.2 训练过程中的关键观察点训练时建议把每个 epoch 的training loss和validation loss都打印出来观察。正常情况下你会看到这样的趋势前几个 epoch 两个 loss 都在快速下降中期训练 loss 继续下降但验证 loss 降幅趋缓后期如果训练 loss 还在降但验证 loss 开始反弹那就是典型的过拟合信号。如果验证 loss 曲线一路波动很大、完全没有下降趋势问题大概率出在数据预处理上——最常见的是归一化尺度不对、或者样本构造时 X 和 y 对错了位置。别急着调模型结构先回头检查数据。我一般训练这个模型时设置epochs 200左右配合 Adam MSE大约在第 50 到第 100 个 epoch 之间损失趋于平稳。如果你发现 30 个 epoch 就已经收敛得很好说明数据模式比较简单hidden_size可以适当调小防止过拟合如果 150 个 epoch 还在缓慢下降可能需要多跑一些轮次或者增大hidden_size。4.3 训练脚本的核心流程项目的主训练脚本一般按这个思路组织# 1. 读取数据并预处理 df pd.read_csv(data/load_data.csv) features df[[load, temperature, humidity, wind_speed]].values scaled scaler.fit_transform(features) # 2. 构造滑动窗口样本 X, y create_sequences(scaled, seq_len24, pred_len24) # 3. 切分数据集 train_X, test_X, train_y, test_y train_test_split(X, y, shuffleFalse) # 4. 封装成 DataLoader train_dataset TensorDataset(torch.tensor(train_X, dtypetorch.float32), torch.tensor(train_y, dtypetorch.float32)) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) # 5. 初始化模型、优化器、损失函数 model LSTMNet(input_size4, hidden_size64, num_layers2, output_size24) optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() # 6. 训练循环 for epoch in range(200): for batch_X, batch_y in train_loader: optimizer.zero_grad() output model(batch_X) loss criterion(output, batch_y) loss.backward() optimizer.step()有两个细节值得注意。第一DataLoader里的shuffleTrue只对训练集使用测试集不能用——因为测试集需要保持时序完整性来画预测曲线打乱之后无法对齐时间轴。第二batch_y的形状是(batch_size, pred_len)而模型输出的形状也是这个所以MSE可以直接算不需要额外的维度调整。5. 复现过程中的高频踩坑点5.1 数据泄漏最常见的隐性错误前面提到过的归一化泄漏问题在实际操作中几乎每个人都犯过。我自己最早写负荷预测的时候就是先在整个数据集上做MinMaxScaler.fit_transform然后才切分训练集和测试集。当时的模型在测试集上表现得非常好R² 高达 0.97我当时还挺高兴——直到我把同样一套代码换成只看到部分数据的新场景时效果立刻崩塌。后来才明白测试集里的最大值和最小值已经被用来缩放训练数据了等于模型在训练时已经偷看过测试集的分布信息。在真实部署时未来的数据是未知的你只能用过去的数据计算缩放参数。正确的姿势是scaler MinMaxScaler() train_scaled scaler.fit_transform(train_data) test_scaled scaler.transform(test_data) # 注意不是 fit_transform5.2 训练和预测时的维度不一致很多用户在跑通训练之后拿模型做预测时报维度错误通常是因为预测时输入的张量形状和训练时不匹配。训练时输入是(batch_size, seq_len, input_size)但预测时往往只给一个样本维度变成了(seq_len, input_size)少了 batch 维度。解决办法是reshape成(1, seq_len, input_size)也就是手动加一个 batch 维度。还有一点如果训练时模型设了batch_firstTrue预测时的输入也必须保持同样的格式否则 LSTM 会把 seq_len 和 batch_size 解释反。5.3 预测结果需要反归一化模型输出的是归一化后的数值范围0 到 1 之间画图之前必须用之前保存的 scaler 做逆变换pred_load scaler.inverse_transform(pred_reshaped)这一步看似简单但实际经常被忽略——有人看预测曲线和真实曲线的形状几乎一致只是整体数值差一大截数据是原始量纲和归一化量纲混在一起了本质上就是忘了反归一化或者反归一化时用错了特征列。另一个相关坑是逆变换时不能用整个特征矩阵的 scaler 直接对负荷列做变换。如果你当初把负荷和其他气象特征拼在一起做的归一化现在逆变换也要保持同样的特征结构单独对负荷列做会得到错误结果。稳妥的做法是在构造数据时就把负荷值和其他特征分开处理或者保存一个专门针对负荷列的 scaler。5.4 模型收敛但预测结果是一条延迟平移曲线这是一个很有迷惑性的现象预测曲线看起来和真实曲线走势几乎一致但整体向右偏移了一段时间。比如真实负荷在 12:00 达到峰值预测峰值出现在 14:00。很多人以为这是数据时间戳对齐错了但实际上这往往是模型过度依赖上一时刻负荷特征导致的——由于负荷序列有很强的自相关性模型发现最简单的策略就是复制最近的历史值而不是学习温度和湿度的因果影响。解决方法有三个思路一是检查输入中是否包含了太多滞后的负荷值考虑增大seq_len或调整特征组合二是加入差分处理也就是预测负荷的变化量而不是绝对量三是减少对纯滞后项的依赖适当增加气象特征的权重。负荷预测领域把这种现象叫惯性预测是所有自回归模型的通病意识到这一点能帮你少走不少弯路。6. 换数据集和调参的扩展思路6.1 如何迁移到自己的数据拿到这个项目之后替换成自己的数据至少需要改三处CSV 文件的路径、特征列的选取、input_size的值。如果自己的数据没有温度湿度这些气象特征只有负荷和时间戳那就把input_size改成 1做单变量多步预测。如果数据是 15 分钟一个采样点而不是一小时那么seq_len应该从 24 调整成 96——因为一天有 96 个 15 分钟点。这里有一个通用原则seq_len和pred_len的选择要基于采样频率和预测需求而不是照搬默认值。小时级数据做日预测用 24 进 24 出是合理的但 15 分钟级数据做未来 4 小时预测用 16 进 16 出就比较合适。6.2 模型结构可以做哪些升级如果觉得纯 LSTM 的效果不够理想有两条性价比很高的升级路径。第一条是LSTM 注意力机制。注意力机制的本质就是让模型在解码时不再只依赖最后一个时间步的隐藏状态而是有权重地回顾输入序列中每个时间步的信息。对于负荷预测来说这意味着模型可以自己学会重点关注前天同一时刻的负荷值或重点参考最近几个小时的趋势变化而不是把所有历史信息硬生生压缩到一个向量里。改进方式通常是在 LSTM 输出后面接一个注意力层对每个时间步的隐藏状态计算注意力权重加权求和后再通过全连接层输出预测值。第二条是CNN LSTM 的混合结构。用一维卷积先对输入序列做特征提取捕捉局部模式再把卷积的输出序列送入 LSTM 学习时间依赖。这样能减少 LSTM 需要处理的时间步数训练更快同时 CNN 能帮助模型识别短期的突变特征——比如几点温度骤降对应的负荷跳升。从实测效果看这两种升级在常见电力负荷数据集上一般能比纯 LSTM 提升 5% 到 15% 的精度具体取决于数据本身的复杂度。如果预测目标对峰值时刻的准确性要求很高注意力的优势会更明显。6.3 超参数调优的优先级调参不要眉毛胡子一把抓按对结果的影响程度排序依次是seq_len输入序列长度直接影响模型能看到多长的历史信息影响最大hidden_size模型容量影响表达能力num_layers深度一般 1 到 3 层之间尝试learning_rate收敛速度和最终精度batch_size影响训练稳定性和泛化能力建议先固定其他参数一个个调每次只改一个变量记录验证集 loss 的变化这样才知道每个参数到底起了什么作用。网格搜索在这个数据规模下完全可行因为 LSTM 负荷预测的单次训练通常在几分钟以内搜索几十组参数组合也就一两个小时的事。7. 跑通之后再往前一步模型效果评估7.1 该看哪些评价指标负荷预测领域最常用的三个回归指标MAE平均绝对误差、RMSE均方根误差、MAPE平均绝对百分比误差。from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100这三个指标各有侧重。MAE 反映平均误差大小单位是 MWRMSE 对大误差更敏感如果预测曲线上有个别离谱的尖峰RMSE 会比 MAE 大得多所以它更适合用来发现偶尔大错的问题MAPE 是百分比方便跨数据集对比但当真实负荷接近 0 时计算会失真一般会剔除负荷极小的时刻后再算。我建议把这三个指标都打印出来不要只看 loss。loss 是归一化空间里的数值不直观而 MAE 和 MAPE 直接对应真实的预测误差。国网系统里对短期负荷预测的考核标准通常在 2% 到 3% 的 MAPE 以内你可以拿这个作为参考——如果你的模型 MAPE 在 3% 以下说明已经达到工程可用的水平。7.2 画图应该看什么项目一般会输出预测值和真实值的对比曲线。画图时我建议画两类一类是测试集整个时间段的全局对比看整体拟合程度另一类是挑一个连续几天的窗口放大看看峰谷细节。重点关注三个地方峰值时刻是否准确。负荷预测最关键的就是早晚高峰的位置和幅值。如果峰值总是偏低可能是训练样本中峰值样本占比不足可以考虑在损失函数里对峰值附近的样本加权。节假日是否出错。模型如果在普通日模式上拟合得很好但一遇到节假日就偏差很大说明节假日信息的特征表达不足可以强化节假日标记的特征编码。突变点的响应速度。比如温度骤降导致负荷快速攀升模型是立刻跟上还是延迟几个点才反应。延迟越短说明模型捕捉因果特征的能力越强。从整体调优策略来说先把预测曲线的形态做对再去追求评价指标小数位上的精度提升。形态对意味着模型学到了负荷的周期规律这比指标数值好看更有意义。这套代码跑通之后你一定会有种原来多输入多输出并没有那么神秘的感觉——本质上就是把特征矩阵喂进去把未来序列取出来中间的 LSTM 只是帮你完成了从历史到未来的模式映射。电力负荷预测这条路走通一个完整案例之后后面换数据集、加注意力机制、扩展成多步滚动预测都会顺很多。本文还有配套的精品资源点击获取
返回列表