
简介面向电力负荷预测与时间序列分析场景这份PyTorch资源实现了基于LSTM的多输入多输出预测模型可同时考虑温度、湿度等多种影响因素并输出多个预测目标适合电力系统、能源管理方向的开发者及深度学习初学者参考学习。压缩包体积仅8KB包含一个Python源码文件和一个CSV数据文件代码可直接运行数据集内已提供负荷相关变量无需另行准备数据。目前已有5481人学习下载实用性和易用性得到验证。脚本完整覆盖数据读取、缺失值处理、归一化、训练集与测试集划分、LSTM网络搭建、损失函数与优化器配置等关键流程并支持多变量输入和多步输出能够帮助读者快速掌握PyTorch时序预测模型的标准实现路径。通过运行项目可直观理解多输入多输出LSTM的构造方式并易于迁移至其他负荷或能耗预测任务。1. 项目概述与需求拆解1.1 这个项目解决的是什么问题先聊一个很多做时序预测的朋友都绕不过去的坎想做电力负荷预测手头能拿到的数据是多变量的——温度、湿度、历史负荷、节假日标记、风力等级可能都有但网上能找到的教程基本都是拿单变量做预测一个序列进、一个序列出跟你实际业务场景根本不匹配。你想把多个特征塞进去模型结构稍微改一下就开始报维度错误调了一下午还是RuntimeError满天飞最后项目搁浅。我这次分享的项目就是专门解决这个痛点的一套基于PyTorch的LSTM多输入多输出负荷预测代码数据集内置拿到就能跑通。多输入指的是模型输入包含多个特征维度的历史观测比如过去一周每天的负荷、温度、湿度等多输出是指模型一次预测未来多个时间点的负荷值比一步一步滚动预测效率高也避免了误差逐点累积的问题。这套代码适合三类人第一类是电力系统、能源管理方向的学生和研究初入门者用这个框架来验证算法思路第二类是工程师需要在短期负荷预测上快速跑通一个基线模型后续替换数据源和调参第三类是刚接触PyTorch的开发者想通过一个完整、可运行的深度学习项目理解LSTM在时间序列上的应用链路。项目本身的代码结构不复杂但胜在闭环清晰——从数据处理到模型训练再到误差评估一条龙跑通之后换自己的数据也只需要改一小块代码。1.2 为什么选择LSTM承担这个任务先说清楚LSTM凭什么在负荷预测里这么受欢迎。电力负荷数据本质上是一个带有周期性和趋势性的时间序列白天用电多、夜间用电少工作日和周末呈现不同形态夏天受空调负荷影响大冬天又受取暖负荷驱动。这类数据有几个显著特点长依赖、非线性、多周期叠加。LSTM长短期记忆网络改进了传统RNN的梯度消失问题引入了输入门、遗忘门和输出门三个门控结构可以自主选择记住什么、遗忘什么。遗忘门决定过去的哪些信息应该丢弃输入门决定当前时刻哪些信息值得更新进状态输出门决定最终输出哪些信息。这套机制让LSTM在处理数百步的历史序列时仍然能把关键的长期依赖关系保留下来。用生活里的例子类比RNN像一个只有短时记忆的人你跟他聊天说到第20句他就把第1句的内容忘光了而LSTM像一个会做笔记的人每听完一句话他会判断哪些信息重要记进本子哪些无关紧要划掉这个本子里的内容就是他持续携带的长期记忆。负荷预测恰恰就需要这种能力——今天下午3点的负荷数据对于明天下午3点的预测非常有参考价值因为每天这个时间点的负荷形态高度相似。关于GRU和LSTM怎么选我在实际项目里做了对比测试。GRU参数量更少、训练更快在数据量充足的情况下效果经常和LSTM接近甚至持平。但LSTM的双状态结构——隐藏状态和细胞状态——在刻画周期性较强的数据时稳定性更好。负荷预测这种场景我更倾向于LSTM踩坑概率小公开研究也多后续对比实验好做。2. 数据构造多输入多输出的核心难点2.1 原始数据集与预处理策略项目自带的数据集是基于某区域实际用电负荷叠加气象因素生成的模拟数据包含的字段有负荷值MW、温度、湿度、风速以及一个是否为节假日的标记。采样频率为每小时一个点连续记录1400多个小时大约覆盖两个月。这个颗粒度和字段设计基本对标了工业界做短期负荷预测的常规输入。拿到这种混合类型的数据第一件要做的事情是分布检查。负荷值通常在几百到上千之间波动温度在零下到三十几度之间湿度是百分比数值风速可能在个位数徘徊。这几个特征的量纲差异极大如果直接塞进LSTM模型的学习过程会出现严重偏差——梯度更新时数值范围大的特征会主导损失函数而数值范围小的特征几乎贡献不了什么信息模型训练结果会非常差。我的处理方案是标准化Standardization也就是每个特征各自减去均值、除以标准差让所有特征都分布在零附近、方差为1。代码上直接用sklearn.preprocessing.StandardScaler就很方便。需要特别记住的是标准化参数只能从训练集上计算然后应用到验证集和测试集上。入这个坑的人太多了如果拿全量数据算均值方差会引入未来信息导致验证结果虚高。关于节假日特征我没有把它变成一个独立的数值特征而是按下述逻辑处理在构造模型输入时节假日的标记会和当天的历史负荷一起被拼接进特征向量里。也就是说模型的每个时间步输入不只有负荷还有对应的温度、湿度、风速和节假日标记是一个完整的五维特征向量。2.2 滑动窗口切分与大坑提醒多输入多输出的关键在于数据变换。假设我们设置时间步长sequence_len为72即用过去72小时的数据预测未来pred_len个时刻的负荷这里pred_len设为12意味着预测未来12小时。代码的思路用伪代码描述是把整个数据集按索引顺序切成长度为72 12的小窗口前72个时间步包含5个特征后12个时间步只取负荷这一个目标字段。窗口每次向后滑动一个时间步也可以设step3或step6来降低样本相关性就得到了一条训练样本。这里有一个非常隐蔽的坑也是这个项目让我最头疼的地方。如果只做一次切分最后得到的训练样本中相邻样本的输入重叠度非常高——第1个样本用的是第0到第71小时的数据第2个样本用的是第1到第72小时的数据两者有71个小时是重合的。如果这些高度相似的样本同时出现在训练集和测试集里模型相当于见了原题考试测试误差会非常好看但毫无参考价值。正确的做法是先把原始序列按时间顺序拆成训练段和测试段再在各自区间内部去做窗口切分。比如前70%时间的数据专门用来生成训练样本后30%时间的数据独立生成测试样本。这样测试集中的任何样本其输入区间都在训练集覆盖范围之外评估结果才是真实可靠的。3. 模型定义与训练要点3.1 模型结构设计思路模型搭得比较简单但这恰恰是它的优势——便于在这个基础上叠加各种新想法。整体结构是输入层输入形状为(batch_size, sequence_len, input_size)的三维张量经过nn.LSTM层处理原始序列得到每个时间步的隐藏状态序列然后将最后一个时间步的隐藏状态或者将最后一个时间步的细胞状态也考虑进来经过一个全连接输出层映射到12个输出值对应未来12小时的负荷。这里考虑过多输出方式。第一种是直接用一个全连接层把隐藏状态映射到12维输出简单粗暴在预测任务上通常够用。第二种是Seq2Seq结构Encoder提取特征Decoder逐步输出未来12步的值灵活但代码复杂度高适合处理预测步长特别长的情况。本项目取第一种方案在代码注释里留了第二种方案的扩展入口。PyTorch里定义模型核心代码就几十行class LSTMRegressor(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size, dropout0.1): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0 ) self.regressor nn.Sequential( nn.Linear(hidden_size, 64), nn.ReLU(), nn.Dropout(dropout), nn.Linear(64, output_size) ) def forward(self, x): lstm_out, (h_n, c_n) self.lstm(x) last_hidden h_n[-1] # 取最后一层的隐藏状态 out self.regressor(last_hidden) return out注意几个细节。batch_firstTrue这个参数非常重要它决定了输入张量的维度顺序是(batch, seq_len, feature)而不是PyTorch默认的(seq_len, batch, feature)少了这个参数会导致大量初学者在维度对齐时晕头转向。h_n的形状是(num_layers, batch, hidden_size)所以要取最后一层的隐藏状态就要用h_n[-1]这里的-1是layer方向的索引不是batch方向的索引很容易写错。hidden_size和num_layers的设置我给了比较适合这个数据规模的默认值——hidden_size取64num_layers取2。面对1400多个小时的序列、几百条样本的数据量过大的隐层只会让模型更容易过拟合。这个规模的模型参数量大概在三万左右训练起来非常快CPU上几分钟就能跑完一个epoch。3.2 训练流程与超参数选择训练部分我是用标准的PyTorch训练循环写的。损失函数选择均方误差MSE因为负荷预测的优化目标是让预测值与真实值尽可能接近MSE对大偏差有更强的惩罚这正符合我们的需求——宁可整体误差均匀一点也不能允许个别时间点出现严重偏离。优化器选了Adam初始学习率设0.001。这里贴一下我多年实践下来的经验Adam配合0.001通常能快速收敛到不错的位置但如果发现训练后期损失不再下降可以试试把学习率降到0.0003重新跑几个epoch。我在这个项目里就遇到了类似情况——第30个epoch后验证集损失开始震荡不再下降加了学习率调度器后效果立竿见影。训练代码核心是这样criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 ) for epoch in range(num_epochs): model.train() train_loss_sum 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() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss_sum loss.item() # 验证阶段 model.eval() with torch.no_grad(): val_loss evaluate(model, val_loader) scheduler.step(val_loss)clip_grad_norm_这行值得单独说说。LSTM在长序列训练中梯度范数可能变得非常大导致参数更新剧烈震荡训练不收敛。这就像开车时油门踩得过猛车会一顿一顿地往前蹿反而走不稳。梯度裁剪就是给油门加一个限位器把梯度范数限制在1.0以内训练稳定性会明显提升。这个细节看似不起眼但在某些数据集上不加这行就是loss不降加了三五个epoch就收敛了。批大小batch_size设为64epoch设80个。还有一个在实际训练中容易忽略的点对每个batch内的样本最好做一次随机打乱避免模型学到样本顺序带来的伪模式。DataLoader里的shuffleTrue就足够了。3.3 效果评估不能只盯着MSE训练完成后如何衡量模型好坏我的评估维度有三个均方根误差RMSE、平均绝对误差MAE、以及决定系数R²。RMSE和MAE反映的是误差的绝对量级。RMSE对大误差更敏感MAE反映平均偏差水平。R²则是一个相对指标取值在0到1之间表示模型捕捉到了数据总方差的多少百分比。如果R²只有0.6说明模型只解释了60%的数据波动剩下40%的波动靠模型是解释不了的业务上还有很大改进空间。测试集的评估代码很简单model.eval() with torch.no_grad(): preds model(X_test_tensor) preds preds.numpy() test_rmse np.sqrt(mean_squared_error(y_test, preds)) test_mae mean_absolute_error(y_test, preds) test_r2 r2_score(y_test, preds)我跑了这个项目自带的数据集标准化还原成原始负荷量纲后测试集RMSE大约在多少范围呢由于模拟数据集的负荷均值在800MW左右标准差约150MWRMSE大约在25到35MW之间换算成MAPE大约在3%到4%。这个精度在短期负荷预测里算是一个比较合理的基线水平——你没有用任何外部高级技巧纯LSTM就能拿到这个成绩后续换更复杂模型或加注意力机制就有了对比的起点。还需要关注一条曲线训练loss和验证loss的走势。我在训练的过程中打印每个epoch的loss如果发现训练loss持续降低但验证loss在第20个epoch之后反而开始上升那就是典型的过拟合信号。应对手段包括增大dropout从0.1到0.3、增加训练数据量、减小模型容量降低hidden_size或num_layers、提前停止训练。4. 常见问题排查与优化方向4.1 运行报错速查表这个项目因为面向“直接可跑”我把代码封装得很规整但环境不同还是会遇到各种报错。整理了这份速查表基本都是我或者朋友们实际踩过的坑报错信息原因分析解决方法RuntimeError: size mismatch输入特征维度与模型input_size不一致检查X.shape[-1]是否等于模型的input_sizeIndexError: index out of rangeLSTM隐藏状态索引写错比如用了h_n[0]取到了第一层而不是最后一层改成h_n[-1]确保是最后一个LSTM层ValueError: Expected input batch_size to match target batch_size标签y_batch的构造与实际预测长度不一致检查切分函数里pred_len是否设置一致损失出现NaN学习率过大、数据包含NaN、梯度爆炸先降学习率到0.0001检查数据有无缺失值确认标准化时没有除零某特征标准差为0CPU直接跑到死机模型太大、批大小太大或num_workers设置过高降低batch_size到32或把num_workers改为0GPU显存不足序列太长或批太大减小batch_size或将数据切成更小的时间窗口这份表格里梯度爆炸导致NaN是最隐蔽的。如果你发现前几个epoch loss正常突然某个epoch变成NaN大概率是数据里有某个填了9999这种异常值模型计算梯度时把这个异常值放大到了溢出。遇到这种情况先用np.isnan(x).sum()和np.isinf(x).sum()检查数据再做一遍异常值清洗。4.2 效果不佳时的排查路径如果你的数据和项目自带数据集字段差别很大跑出来的效果不理想别急着改模型结构先按这个顺序排查第一步是看数据切分。确认你的数据确实是按时间顺序拆分的训练集和测试集检查训练段和测试段的时间戳没有交错。这是最容易被忽略的基础问题。第二步是检查标准化还原。很多人预测完成之后直接用标准化后的数据计算误差得出的RMSE是0.0几觉得模型厉害到不行一还原成真实负荷值才发现完全不对。务必把预测值和真实值都还原回原始量纲再算误差。第三步是看模型的预测曲线。把测试集上的预测值和真实值画在一张图里如果预测曲线比真实曲线滞后了几个时间步像是一条“延迟复制”的曲线那说明模型学到的更多是记忆上一个时间步的负荷值而不是建立多变量与未来负荷之间的映射。这个现象通常和序列长度过长或学习率过大有关适当缩短sequence_len会有帮助。第四步是考虑输入特征的有效性。负荷预测中历史负荷本身是最强的特征温度次之湿度和风速的贡献则因地区而异。如果你发现加了很多特征反而效果更差可以考虑用特征重要性分析的方法筛选特征或者只保留与目标相关性最高的前三个特征。4.3 优化方向从基线到进阶一旦这个基线模型跑通了后续升级路径其实很清晰。我列三个方向供参考第一个方向是多步预测的Decoder结构改造。现在是一个全连接层直接映射12个输出可以改成逐步解码的方式将编码器的最后一个隐藏状态作为初始状态然后逐步预测未来每个时间点的值再将预测值作为输入反馈到下一步。这个结构更精细但实现复杂度高需要控制误差传播。第二个方向是引入注意力机制。在LSTM编码之后不再只取最后一个隐藏状态而是对所有时间步的隐藏状态做加权求和让模型自己决定哪些历史时刻对当前预测更关键。对于负荷预测这种周期性很强的数据注意力机制经常带来明显的精度提升。第三个方向是多模型对比实验。你可以在同一套数据切换上跑一下Transformer、TCN时间卷积网络或者XGBoost看看各自的精度和训练时间差异。LSTM在时序任务上很强但未必永远是最优解尤其是在数据量不足的时候XGBoost往往能打出更亮眼的成绩。5. 完整流程回顾与个人实操心得把整个项目从头到尾捋一遍其实核心步骤只有五个加载并预处理数据、滑动窗口构造样本、定义LSTM模型、训练并评估、结果可视化。每个步骤拆开看不复杂但串联在一起就容易出各种细节问题。这几次实操下来我个人印象最深的有三点。第一点是在跑任何深度模型之前先用一个简单模型做基线测试。我在实际项目中会先跑一个线性回归或者XGBoost看它在同样数据上的误差是多少。如果LSTM的精度连XGBoost都比不过那说明问题大概率不在模型架构上而是在数据处理上——特征构造不合理、样本划分有问题、或者标准化方式不对。先定位问题再优化模型比直接换模型有效得多。第二点是数据预处理代码写成一个独立函数并且输出到文件调试。把标准化参数保存下来、把切分后的样本Shape打印出来别看这些操作简单真能帮你省很多排查时间。我见过太多同学模型出问题结果是因为数据形状和自己以为的形状不一致打印出来一眼就能发现问题。第三点是评估完结果之后一定要把预测曲线和真实曲线画出来看一眼。数字指标再漂亮也不如一张图直观。如果只是RMSE好看但局部峰值处预测完全失真这个模型在实际业务中可能并不好用。负荷预测最关心的往往是尖峰负荷——比如炎热夏天下午两点的最高负荷——这个点预测不准平均值再小也没用。最后再分享一个我自己常用的扩展技巧把训练好的模型保存下来后续做实时预测时直接加载权重文件不需要重头训练。保存和加载的代码在PyTorch里非常简单# 保存 torch.save(model.state_dict(), lstm_load_forecast.pth) # 加载 model LSTMRegressor(input_size5, hidden_size64, num_layers2, output_size12) model.load_state_dict(torch.load(lstm_load_forecast.pth)) model.eval()加载的时候有一个大坑模型类的定义和训练时完全一样才有用如果你改了模型结构再加载旧权重会直接报size mismatch的错误。所以我在模型里加了save_config方法把input_size、hidden_size这些超参数一并保存成JSON文件下一次加载时先用JSON重建模型配置再灌权重有效避免了自己过几个月回来调模型时对不上参数的情况。这套代码我在CPU环境上验证过普通的笔记本跑80个epoch大约五到十分钟就能完成不需要GPU也能愉快地玩耍。如果你想快速体验整个流程直接把main.py跑起来就行如果你想换成自己的数据改写数据加载和标准化这两个函数其他部分基本不用动。本文还有配套的精品资源点击获取