简介这份资源面向电力系统、能源管理及时间序列分析方向的学习者与开发者提供一套基于PyTorch的LSTM多输入多输出负荷预测完整实现。模型可同时接收温度、湿度、节假日等多类输入信号并输出多个地区或时段的负荷预测结果适合用于课程设计、科研复现与工程原型验证。压缩包共2个文件包含1个py源码与1个csv数据集整体约8KB其中脚本负责定义LSTM网络结构、训练循环与预测流程数据文件则提供建模所需的变量样本。目前已有5495人学习下载说明该案例在入门与实战场景中具备较高参考价值。读者可据此掌握多变量时间序列的读取、预处理、训练集划分、损失函数与优化器选择等关键环节并理解多输入多输出结构如何捕获变量间复杂交互从而快速搭建可运行的负荷预测基线。1. 从一份能直接跑的 LSTM 负荷预测工程说起电力负荷预测这个场景很多人第一次接触都是在做多变量时间序列任务既要看历史负荷又要看温度、湿度、节假日这些外生变量输出还往往不止一个点——未来 1 小时、未来 24 小时可能都要。这时候单输入单输出的 LSTM demo 就不够用了得改成多输入多输出结构。这份工程就是冲着这个需求来的PyTorch 实现内含数据集下载下来不用自己造数据、不用自己搭骨架python train.py就能跑起来看到 loss 往下掉。它适合两类人一类是刚学完 LSTM 理论、想找个完整工程练手的另一类是做能源、楼宇、园区用电分析需要快速搭一个可迭代的预测基线。下面我按「它是什么 → 怎么跑 → 怎么改」的顺序把这份代码拆开讲一遍。2. 工程结构与数据流多输入多输出是怎么落地的2.1 目录布局与各文件职责拿到这份工程第一件事是看目录。它没有搞成复杂的包结构而是典型的「脚本式工程」对新手友好改起来也直接lstm_load_forecast/ ├── data/ │ ├── load_data.csv # 原始负荷 外生变量 │ └── processed/ # 归一化、滑窗后的中间产物 ├── config.py # 超参、路径、窗口长度集中管理 ├── dataset.py # Dataset 滑窗构造 ├── model.py # LSTM 多输出网络定义 ├── train.py # 训练主循环 ├── predict.py # 加载权重做推理 ├── utils.py # 归一化、指标、绘图 └── requirements.txtconfig.py是整份工程的「控制面板」窗口长度、预测步长、batch size、学习率都放这里改实验不用翻遍所有文件。dataset.py负责把一张宽表切成(batch, seq_len, n_features)的输入和(batch, pred_len, n_targets)的输出。model.py只干一件事定义网络。train.py和predict.py分离训练完的权重存成.pt推理时单独加载这个习惯值得保留。提示如果你的数据列名和工程默认不一致先改config.py里的FEATURE_COLS和TARGET_COLS别急着动dataset.py。2.2 多输入多输出的数据组织方式多输入多输出的核心难点不在模型而在数据对齐。假设原始 CSV 长这样timestamploadtemphumidityis_holiday2023-01-01 00:00320.54.26112023-01-01 01:00298.13.8631「多输入」指的是load, temp, humidity, is_holiday一起进网络「多输出」指的是模型一次吐出未来pred_len个时刻的load。滑窗构造的逻辑是这样的# dataset.py 核心滑窗逻辑 def build_windows(features, targets, seq_len, pred_len): xs, ys [], [] total len(features) # 从 seq_len 开始保证每个样本都有完整历史窗口 for i in range(seq_len, total - pred_len 1): xs.append(features[i - seq_len:i]) # 过去 seq_len 步的全部特征 ys.append(targets[i:i pred_len]) # 未来 pred_len 步的负荷 return np.array(xs), np.array(ys)这里有两个参数必须说清楚seq_len是回看窗口决定模型能「记住」多久的历史pred_len是预测跨度决定一次输出几个点。两者不是越大越好——seq_len太长会稀释近期信息pred_len太长误差会累积。工程默认给的是seq_len24、pred_len1也就是用过去 24 小时预测下一小时这是负荷预测里最稳的起点。想改成预测未来 24 小时把pred_len调成 24 即可dataset.py和model.py都不用动因为输出层维度是从config.py读的。2.3 归一化与训练/验证切分负荷数据动辄几百上千温度只有个位数不归一化的话梯度会被大数值主导。工程用的是按列 min-max 归一化并且只在训练集上 fit再应用到验证集和测试集避免信息泄漏# utils.py class MinMaxScaler: def fit(self, data): self.min data.min(axis0) self.max data.max(axis0) def transform(self, data): return (data - self.min) / (self.max - self.min 1e-8) def inverse_transform(self, data): return data * (self.max - self.min 1e-8) self.min切分用的是时间顺序切分不是随机切分——时间序列随机切分是新手最容易翻车的地方会把未来信息漏进训练集指标好看但线上直接崩。工程按 7:1:2 顺序切train.py里能看到split_index的计算。归一化器在训练完后会连同权重一起保存推理时加载同一个 scaler保证输入输出尺度一致。3. 模型定义与训练LSTM 多输出网络的逐层拆解3.1 网络结构从输入到多步输出model.py里的网络不复杂但每一层都有存在的理由# model.py import torch import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, n_features, hidden_size, num_layers, pred_len, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizen_features, # 多输入特征数 hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, # 输入形状 (batch, seq, feature) dropoutdropout if num_layers 1 else 0.0 ) self.fc nn.Sequential( nn.Linear(hidden_size, hidden_size // 2), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_size // 2, pred_len) # 多输出pred_len 个点 ) def forward(self, x): # x: (batch, seq_len, n_features) out, (h_n, c_n) self.lstm(x) last out[:, -1, :] # 取最后一个时间步的隐状态 return self.fc(last) # (batch, pred_len)逐层看nn.LSTM的input_size等于特征列数这就是「多输入」的入口batch_firstTrue让输入维度顺序符合直觉不然还得手动 transpose容易搞混。取out[:, -1, :]是常见做法——用最后一个时间步的隐状态代表整个窗口的浓缩信息。fc头把hidden_size映射到pred_len一次输出多个点这就是「多输出」。中间加了一层Linear ReLU Dropout比直接Linear(hidden, pred_len)更稳能缓解过拟合。注意num_layers 1时 LSTM 的 dropout 才生效单层设了也没用这是 PyTorch 的行为不是 bug。3.2 训练循环与损失函数选择train.py的主循环是标准写法但有几个细节值得抄# train.py 训练片段 criterion nn.MSELoss() # 负荷预测常用 MSE也可换 HuberLoss optimizer torch.optim.Adam(model.parameters(), lrcfg.LR) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 ) for epoch in range(cfg.EPOCHS): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) pred model(xb) loss criterion(pred, yb) optimizer.zero_grad() loss.backward() # 梯度裁剪LSTM 训练防梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() # 验证集评估按 val_loss 调学习率 val_loss evaluate(model, val_loader, criterion, device) scheduler.step(val_loss)损失函数默认 MSE对负荷预测够用如果数据里有明显异常点换成nn.HuberLoss()更抗噪。clip_grad_norm_这行是 LSTM 训练的血泪经验——不加的话遇到长序列或大学习率loss 会突然变 NaN。ReduceLROnPlateau让学习率在验证集不降时自动减半省得手动调。evaluate函数里记得model.eval()加torch.no_grad()不然验证会拖慢训练还占显存。3.3 评估指标与结果可视化训练完不能只看 loss得换算回原始量纲看真实误差。utils.py里给了 MAE、RMSE、MAPE 三个指标# utils.py def mape(pred, true): # 避免除零负荷一般不会为 0加个极小值兜底 return torch.mean(torch.abs((true - pred) / (true 1e-8))) * 100 def evaluate(model, loader, criterion, device): model.eval() preds, trues [], [] with torch.no_grad(): for xb, yb in loader: xb xb.to(device) preds.append(model(xb).cpu()) trues.append(yb) preds torch.cat(preds) trues torch.cat(trues) # 反归一化后再算指标才是真实误差 preds scaler.inverse_transform(preds.numpy()) trues scaler.inverse_transform(trues.numpy()) return mape(preds, trues)关键点是先反归一化再算指标。归一化后的 MAE 是 0.02 还是 0.05对业务没有意义换算回 kW 之后MAE 是 15kW 还是 40kW才能判断能不能用。MAPE 对负荷这种非零序列合适但如果数据里有接近 0 的时段MAPE 会爆掉这时候看 MAE 更靠谱。工程里predict.py跑完会画一张预测 vs 真实的对比图肉眼扫一眼就能看出是整体偏移还是个别尖峰没跟上。4. 跑起来环境、命令与常见报错排查4.1 环境准备与依赖安装工程依赖很干净requirements.txt里主要是torch、numpy、pandas、scikit-learn、matplotlib。建议用 conda 建独立环境别和系统 Python 混conda create -n load_forecast python3.9 -y conda activate load_forecast # 按自己显卡情况装 torchCPU 版直接 pip 即可 pip install torch --index-url https://download.pytorch.org/whl/cpu pip install -r requirements.txtPython 版本建议 3.83.10太新的版本有些科学计算包轮子还没跟上。有 GPU 的话去 PyTorch 官网选对应 CUDA 版本的安装命令train.py里会自动检测cuda是否可用不用改代码。4.2 训练与推理命令数据已经在data/load_data.csv里直接跑# 训练权重默认存到 checkpoints/best.pt python train.py --epochs 100 --batch_size 64 --seq_len 24 --pred_len 1 # 推理加载最优权重输出预测图和指标 python predict.py --ckpt checkpoints/best.pt --plot True参数都可以在命令行覆盖也可以直接改config.py。训练日志每个 epoch 打印 train_loss 和 val_loss如果 val_loss 连续多个 epoch 不降说明过拟合了可以调小hidden_size或加大dropout。推理脚本会输出 MAE/RMSE/MAPE 三个数并在outputs/下存一张对比图。4.3 避坑与常见问题排查现象一loss 一开始就是 NaN。原因通常是学习率太大或数据没归一化。解决确认config.py里LR在 1e-3 量级确认dataset.py里归一化步骤没被注释掉。如果数据里有缺失值pandas 读进来是 NaN进网络必炸先df.fillna(methodffill)补上。现象二验证集指标好得离谱线上完全不能用。这是时间序列随机切分的典型翻车。检查train.py里切分逻辑是不是按时间顺序切的shuffle在DataLoader里对训练集可以开对验证集和测试集必须关。现象三预测曲线整体滞后一个时段。这是 LSTM 预测的常见现象模型倾向于输出「上一个时刻的值」。解决把seq_len适当加大或者把pred_len从 1 改成多步让模型学趋势而不是抄最近点。也可以在输入里加时间特征小时、星期几的 one-hot工程里is_holiday就是这个思路。现象四GPU 显存不够。多半是batch_size或seq_len太大。先降batch_size到 32 试试还不行就降hidden_size。LSTM 的显存占用和seq_len成正比长窗口训练很吃显存。现象五推理时指标和训练时对不上。检查推理用的 scaler 是不是训练时保存的那个。工程里 scaler 的 min/max 存在checkpoints/scaler.npzpredict.py会加载它。如果自己重新 fit 了一个 scaler尺度不一致结果必然错。5. 二次开发换数据、改结构、上多任务5.1 换成自己的数据集换数据是最高频的需求。步骤就三步第一把你的 CSV 放到data/下保证有一列时间戳、一列目标负荷、若干列外生变量第二改config.py里的DATA_PATH、FEATURE_COLS、TARGET_COLS、TIME_COL第三如果时间间隔不是小时级改dataset.py里构造时间特征的逻辑。这里有个容易忽略的点外生变量的缺失值处理策略要和负荷列一致不然归一化后会出现异常大的值。我一般会在dataset.py开头加一段数据体检打印每列的缺失率和取值范围跑之前先看一眼能省很多调试时间。5.2 改网络结构与输出形式想把单目标改成多目标比如同时预测有功和无功改两个地方config.py里TARGET_COLS加一列model.py里fc最后一层输出维度从pred_len改成pred_len * n_targetsforward 里 reshape 一下# 多目标输出改造 self.fc nn.Linear(hidden_size // 2, pred_len * n_targets) # forward 中 out self.fc(last).view(-1, pred_len, n_targets) # (batch, pred_len, n_targets)想加注意力机制的话在 LSTM 输出out之后、取last之前加一层对时间步的加权求和让模型自己决定哪个历史时刻更重要。这是负荷预测里提升明显的一个改动尤其是遇到节假日这种突变日。5.3 一个我踩过的坑验证集指标和业务指标脱节最后说个真实教训。有次我把 MAPE 调到 3% 以下觉得稳了结果业务方拿过去一看说晚高峰时段误差特别大——因为 MAPE 是全时段平均晚高峰负荷高绝对误差大但相对误差被平均掉了。后来我在utils.py里加了一个分时段评估把一天切成峰、平、谷三段分别算 MAE才发现问题。从那以后我每次做完负荷预测都强制走一遍分时段指标不再只看一个总数。这份工程里predict.py的绘图函数留了扩展位你可以按小时分组算误差几行代码的事。希望这份拆解能帮你少走点弯路把这份 LSTM 多输入多输出工程真正跑成自己的基线。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?