简介:这份资源面向电力系统负荷预测方向的学习者与工程实践者,提供一套基于LSTM的短期电力负荷预测完整方案,适合具备Python基础、希望掌握深度学习时序建模的中高级读者。压缩包共15个文件,约5.46MB,包含xls原始数据集、ipynb完整实验笔记、py预测脚本、pt训练模型、md说明文档及9张png结果图,覆盖数据预处理、模型搭建与训练、结果分析全流程。资源依赖pytorch、pandas、numpy、matplotlib等库,并借助chinese_calendar引入节假日信息,xlwt用于导出Excel结果,兼顾工程落地细节。读者可拿到可直接运行的源码与已训练模型,通过预测脚本快速复现某日负荷曲线,并借助准确率散点图、MSE曲线、负荷箱线图等可视化图表理解模型表现与误差分布。目前已有326人学习下载,适合课程设计、科研入门或电力预测项目参考。
1. 从一份能跑通的 LSTM 短期电力负荷预测源码说起
电力负荷预测这个方向,很多人第一次接触都是在课程设计或者工作里的一个临时需求:手上有一列历史负荷数据,领导或者导师要你给出未来一天 96 个点的负荷曲线。真上手才发现,难点根本不在 LSTM 本身,而在数据怎么清洗、日期特征怎么构造、预测结果怎么评估。这份LFforecast-main就是冲着这个场景来的——它把数据预处理、模型搭建训练、结果分析、单日预测整条链路都放在了一个 Python 工程里,配套一份STLF_DATA_IN_1.xls数据集和训练好的model.pt,属于那种下载下来改改路径就能跑的类型。适合刚入门 lstm时间序列预测python 的读者,也适合手上有电力数据、想找个能复现的 lstm模型代码 当基线的人。
2. 工程结构与数据流:先搞清楚每个文件在干什么
2.1 目录拆解与依赖清单
拿到压缩包先别急着跑,把目录结构过一遍能省掉后面一半的报错。这个工程的结构很典型,src放代码,data放数据,img放过程图,根目录放说明文件。
| 路径 | 作用 | 是否要改 |
|---|---|---|
data/STLF_DATA_IN_1.xls | 原始负荷数据集 | 换自己数据时替换 |
src/LF_Forecasting.ipynb | 预处理、建模、训练、分析全流程 | 主要改动点 |
src/model.pt | 训练好的模型权重 | 直接加载或重训覆盖 |
src/predict.py | 预测某一天负荷曲线 | 改日期和路径 |
img/*.png | 训练过程与结果图 | 只读参考 |
README.md/README.txt | 环境与文件说明 | 只读 |
依赖库一共六个,装的时候建议单独建虚拟环境,避免和系统里的老版本 pandas 打架:
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install torch pandas numpy matplotlib xlwt chinesecalendar这里xlwt是用来导出 Excel 的,chinesecalendar用来判断节假日——电力负荷对节假日极其敏感,春节那几天的曲线和普通工作日完全不是一个形状,这个库就是干这个的。注意chinesecalendar的包名和导入名不一样,装的时候是chinesecalendar,代码里import chinese_calendar,第一次用的人经常在这卡一下。
2.2 数据长什么样,特征怎么构造
STLF_DATA_IN_1.xls是典型的短期负荷(STLF,Short-Term Load Forecasting)数据,按时间戳排列的负荷值。LSTM 吃的是序列,所以核心工作是把一维时间序列切成「输入窗口 → 预测目标」的样本对。常见做法是滑动窗口:用过去 N 个时刻的负荷预测下一个时刻,或者用过去若干天的同一时刻预测未来一天。
import pandas as pd import numpy as np # 读取原始负荷数据 df = pd.read_excel('./data/STLF_DATA_IN_1.xls') df.columns = ['timestamp', 'load'] # 统一列名,避免中文列名踩坑 df['timestamp'] = pd.to_datetime(df['timestamp']) df = df.sort_values('timestamp').reset_index(drop=True) # 缺失值处理:负荷数据常见的是整点缺失,用线性插值比均值填充更合理 df['load'] = df['load'].interpolate(method='linear') # 构造时间特征:小时、星期、是否周末、是否节假日 df['hour'] = df['timestamp'].dt.hour df['weekday'] = df['timestamp'].dt.weekday df['is_weekend'] = (df['weekday'] >= 5).astype(int) import chinese_calendar as cc df['is_holiday'] = df['timestamp'].dt.date.apply( lambda d: int(cc.is_holiday(d)) ) print(df.head())这段代码做了三件事:统一列名、按时间排序、补缺失值。参数上,interpolate(method='linear')适合连续缺失不超过几个点的场景,如果某天整段缺失,线性插值会拉出一条假直线,这时候更稳妥的是直接丢掉那天或者用前一周同一天的数据补。is_holiday这个特征别小看,很多基线模型预测春节前后误差爆炸,就是因为没把节假日信息喂进去。
2.3 归一化与滑动窗口切分
LSTM 对输入尺度敏感,负荷值动辄几千上万千瓦,不归一化收敛会很慢甚至不收敛。工程里一般用 Min-Max 归一化,把负荷压到 [0,1]。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) load_scaled = scaler.fit_transform(df[['load']]) def make_windows(series, lookback=96, horizon=1): """lookback: 用过去多少个点; horizon: 预测未来第几个点""" X, y = [], [] for i in range(len(series) - lookback - horizon + 1): X.append(series[i:i + lookback]) y.append(series[i + lookback + horizon - 1]) return np.array(X), np.array(y) X, y = make_windows(load_scaled, lookback=96, horizon=1) print(X.shape, y.shape) # (样本数, 96, 1) (样本数, 1)lookback=96是按 15 分钟一个采样点算的,正好一整天,这是短期负荷预测里很常见的窗口长度。如果你的数据是小时级,那 lookback 设 24 更合适。horizon=1表示预测下一个点,想做「预测未来一天 96 个点」就把 horizon 循环到 96,或者改成多输出结构。切分训练集测试集时一定要按时间顺序切,不能 shuffle,否则就是拿未来数据预测过去,指标好看但毫无意义——这是时间序列里最经典的翻车点。
3. 模型搭建与训练:LSTM 结构怎么定、参数怎么调
3.1 网络结构设计与 PyTorch 实现
这个工程用的是标准 LSTM 回归结构,输入维度 1(单变量负荷),也可以把时间特征拼进去变成多变量。下面是一个能直接替换进 notebook 的实现:
import torch import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2, dropout=0.2): super().__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout ) self.fc = nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, (h_n, c_n) = self.lstm(x) last = out[:, -1, :] # 取最后一个时间步的隐状态 return self.fc(last) model = LSTMForecaster(input_size=1, hidden_size=64, num_layers=2) print(sum(p.numel() for p in model.parameters()), 'params')参数说明:hidden_size=64是单变量负荷预测的常用起点,数据量大可以加到 128;num_layers=2够用,再深容易过拟合且训练慢;dropout=0.2只在多层 LSTM 之间生效,单层时这个参数会被忽略,别以为设了就有用。batch_first=True让输入形状是(batch, seq, feature),符合 pandas 转过来的直觉,忘了设会导致维度对不上。
3.2 训练循环与损失函数选择
训练部分的关键是损失函数和早停。负荷预测常用 MSE 或 MAE,MSE 对大误差惩罚重,MAE 对异常值更稳。
from torch.utils.data import TensorDataset, DataLoader X_t = torch.tensor(X, dtype=torch.float32) y_t = torch.tensor(y, dtype=torch.float32) split = int(len(X_t) * 0.8) # 按时间顺序 8:2 切 train_ds = TensorDataset(X_t[:split], y_t[:split]) test_ds = TensorDataset(X_t[split:], y_t[split:]) train_loader = DataLoader(train_ds, batch_size=64, shuffle=False) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) best_loss, patience, wait = float('inf'), 10, 0 for epoch in range(100): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb) loss = criterion(pred, yb) loss.backward() optimizer.step() # 简单早停:验证损失连续 patience 轮不降就停 model.eval() with torch.no_grad(): val_pred = model(X_t[split:]) val_loss = criterion(val_pred, y_t[split:]).item() if val_loss < best_loss: best_loss, wait = val_loss, 0 torch.save(model.state_dict(), './src/model.pt') else: wait += 1 if wait >= patience: print(f'early stop at epoch {epoch}') break print(f'epoch {epoch} val_loss {val_loss:.6f}')batch_size=64是常规选择,数据量小可以降到 32。shuffle=False在训练集内部其实可以设 True,但测试集绝对不能打乱。早停的patience=10是经验值,太小会欠训练,太大浪费时间。保存模型用state_dict()而不是整个模型对象,加载时更灵活,也不容易因为类定义变动而报错。
3.3 结果评估与可视化
工程img目录里那几张图——real_pred.png、accu_scatter.png、train_test_mse.png——就是评估环节的产物。评估不能只看 MSE,还要看 MAPE 和日负荷曲线的形状贴合度。
import matplotlib.pyplot as plt model.eval() with torch.no_grad(): pred_scaled = model(X_t[split:]).numpy() # 反归一化回原始量纲 pred = scaler.inverse_transform(pred_scaled) true = scaler.inverse_transform(y_t[split:].numpy()) mape = np.mean(np.abs((true - pred) / true)) * 100 print(f'MAPE: {mape:.2f}%') plt.figure(figsize=(12, 4)) plt.plot(true[:96], label='real') plt.plot(pred[:96], label='pred') plt.legend() plt.savefig('./img/real_pred_check.png', dpi=150)MAPE 在负荷预测里比 MSE 更直观,一般要求控制在 3% 以内算合格。注意inverse_transform的输入形状要和 fit 时一致,传一维数组会报维度错误,这是新手高频报错点。画图时取前 96 个点正好是一天,方便肉眼判断曲线形状对不对。
4. 单日预测脚本 predict.py:从模型到可用输出
4.1 加载模型与构造预测输入
predict.py的作用是给定某一天,输出那天的负荷曲线。它和训练脚本最大的区别是:训练时数据是批量的,预测时往往只有一条序列,而且需要把最近的历史负荷喂进去。
import torch import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_model(path='./src/model.pt', input_size=1, hidden_size=64, num_layers=2): model = LSTMForecaster(input_size, hidden_size, num_layers) model.load_state_dict(torch.load(path, map_location='cpu')) model.eval() return model def predict_day(model, history, scaler, lookback=96): """history: 最近 lookback 个点的原始负荷,numpy 一维""" seq = scaler.transform(history.reshape(-1, 1)) x = torch.tensor(seq, dtype=torch.float32).unsqueeze(0) # (1, lookback, 1) with torch.no_grad(): pred_scaled = model(x).item() return scaler.inverse_transform([[pred_scaled]])[0][0]map_location='cpu'是保险做法,避免在有 GPU 的机器上训练、在没 GPU 的机器上加载时报错。unsqueeze(0)是给单条序列补上 batch 维度,LSTM 要求输入至少是三维的,漏了这步会直接抛维度异常。
4.2 滚动预测一整天 96 个点
预测一整天不能只调一次模型,得滚动:预测出下一个点,把它拼回历史,再预测下下个点。
def predict_curve(model, history, scaler, steps=96, lookback=96): hist = list(history[-lookback:]) preds = [] for _ in range(steps): p = predict_day(model, np.array(hist[-lookback:]), scaler, lookback) preds.append(p) hist.append(p) # 把预测值当作真实值继续滚动 return np.array(preds) curve = predict_curve(model, history_raw, scaler, steps=96) print(curve.shape) # (96,)滚动预测有个绕不开的问题:误差会累积。预测到第 96 个点时,前面 95 个预测值已经带进了输入,误差被放大。缓解办法是控制滚动步数,或者用 seq2seq 结构一次性输出 96 个点。这个工程用的是滚动方式,简单直接,适合做基线。如果发现曲线后半段明显跑偏,多半就是误差累积,不是模型坏了。
4.3 导出预测结果到 Excel
工程依赖xlwt就是为了把预测结果落成 Excel 交付。
import xlwt wb = xlwt.Workbook() ws = wb.add_sheet('forecast') ws.write(0, 0, 'point') ws.write(0, 1, 'load') for i, v in enumerate(curve): ws.write(i + 1, 0, i) ws.write(i + 1, 1, float(v)) wb.save('./forecast_result.xls')xlwt只能写.xls老格式,单个 sheet 上限 65536 行,做日曲线完全够用。如果数据量大或者要写.xlsx,换成openpyxl更合适。写的时候记得float(v),numpy 的 float32 直接写进去某些版本会报类型错误。
5. 避坑与排查:这几处不踩一遍不算跑通
5.1 现象:loss 一直不降,甚至变成 nan
原因通常是学习率太大或者数据没归一化。负荷值几千的量级直接喂进 LSTM,梯度会炸。解决:确认MinMaxScaler已经 fit 在训练集上,学习率从1e-3往下调到1e-4试,同时检查数据里有没有 inf 或超大异常值。
5.2 现象:预测曲线整体平移,形状对但数值差一截
这是反归一化用错了 scaler。常见错误是训练用一个 scaler,预测时又新建了一个 fit 在预测数据上。解决:把训练时的 scaler 用joblib.dump存下来,预测时load回来,保证变换一致。
5.3 现象:chinesecalendar导入报错或节假日判断不准
包名是chinesecalendar,导入是chinese_calendar,装错名字会 ModuleNotFoundError。另外这个库只覆盖到特定年份,超出范围会抛异常。解决:装对包名,并在调用处 try/except 兜底,超出范围就按普通日处理。
5.4 现象:训练指标很好,实际预测一塌糊涂
八成是切分时 shuffle 了,或者用了未来信息构造特征。解决:所有切分按时间顺序,特征只能用当前时刻及之前的信息,节假日、星期这类静态特征没问题,但别把「明天的负荷」这种泄漏特征混进去。
5.5 现象:model.pt加载报 key 不匹配
训练和加载时的网络结构参数不一致,比如训练用hidden_size=64,加载时写成 128。解决:把结构参数抽成配置字典,训练和预测共用同一份,别在两处各写一遍。
6. 进阶技巧:把单变量 LSTM 改成多变量输入
跑通基线之后,最值得做的一步是加特征。单变量负荷预测的天花板不高,把温度、星期、节假日拼进输入,MAPE 通常能再降一两个点。做法是把input_size从 1 改成特征数,构造窗口时每个时间步带上多个特征。
# 假设特征列为 load, temp, weekday, is_holiday feat_cols = ['load', 'temp', 'weekday', 'is_holiday'] data = df[feat_cols].values scaler_mv = MinMaxScaler() data_scaled = scaler_mv.fit_transform(data) def make_windows_mv(arr, lookback=96, horizon=1): X, y = [], [] for i in range(len(arr) - lookback - horizon + 1): X.append(arr[i:i + lookback]) # (lookback, n_feat) y.append(arr[i + lookback + horizon - 1, 0]) # 只预测负荷这一列 return np.array(X), np.array(y) Xm, ym = make_windows_mv(data_scaled, lookback=96) model_mv = LSTMForecaster(input_size=len(feat_cols), hidden_size=64, num_layers=2)关键点有两个:一是input_size必须等于特征数,忘了改会直接维度报错;二是预测目标只取负荷那一列,别把温度也当预测目标。多变量归一化要整体 fit,不能每列单独 fit 再拼,否则列间关系会被破坏。
验证多变量是否真的有用,别只看训练 loss,要在测试集上对比单变量和多变量的 MAPE,并且固定随机种子多跑几次取平均,单次结果波动可能掩盖真实差异。我一般会写个小脚本把两组 MAPE 打出来对比,确认稳定下降才保留新特征。
从那以后我每次拿到一份时间序列源码,都会先确认三件事:切分有没有按时间、归一化 scaler 有没有复用、预测时有没有信息泄漏。这三条过了,剩下的调参才有意义。希望帮到你。
本文还有配套的精品资源,点击获取