简介基于LSTM的短期光伏预测算法Python源码及配套数据集主要面向新能源、人工智能、数据科学等相关专业的学生与从业者适合用于课程设计、毕业设计或初期项目立项演示。资源以Jupyter Notebook为核心完整覆盖从数据读取、预处理、模型训练到预测评估的流程同时包含基于规则集的模拟程序、单变量光伏预测、园区负荷预测LSTM等多个实验版本便于对比不同建模思路。数据集以Excel表格形式提供另附Python脚本、README说明及规则集参考图可支撑复现与二次开发。资源打包为zip格式共11个文件体积约3.89MB包含6个ipynb、1个xlsx、1个py、1个jpg、1个png和1个md结构紧凑适合快速上手。目前已有1023人学习/下载代码经过运行测试功能正常具备较好的参考价值。通过该资源读者不仅能掌握LSTM在光伏功率预测中的实际应用还能了解园区负荷预测、规则集方法等扩展内容为后续算法优化和工程实践提供基础。1. 基于LSTM的短期光伏预测算法到底解决什么问题先搞懂输入、输出和边界做光伏电站运维或电力交易的人迟早会撞上这个问题明天上午十点我的电站能发多少千瓦调度要数据、交易要曲线、运维要判断是否清洗组件而天气说变就变。基于LSTM的短期光伏预测算法就是拿过去几天的辐照度、温度、历史功率喂给一个循环神经网络让它输出未来数小时或未来一天的光伏功率曲线。它不是一个黑匣子奇迹而是一条已经被验证过的技术路线Python源码负责把数据变成模型能吃的样本数据集负责让模型学会辐照和功率之间的映射关系。适合谁——有光伏电站历史数据但还没上功率预测系统的工程师、做新能源方向毕业设计的在校生、以及需要在电力交易市场里报曲线的交易员。这套方案能让你在普通单机 GPU 上完成从数据清洗到预测结果可视化的全流程精度足以应对短期调度场景但前提是你得先接受一个事实LSTM 预测的是趋势不是天气预报。2. 选型逻辑短期光伏功率预测为什么是 LSTM 的主场以及它与 XGBoost、CNN 的区别2.1 光伏功率序列的三个特征决定 LSTM 比传统回归更合适光伏功率序列有三个绕不开的特征强时序依赖、强非线性、强周期耦合。早上八点的功率不仅取决于此刻的辐照度还取决于从日出开始积累的大气状态变化一块云飘过来功率可能在三分钟内从 80% 跌到 20%这种突变在普通前馈网络里很难被表达因为前馈网络默认每个样本是独立的。LSTM 之所以在这类问题上表现稳定是因为它的门控结构输入门、遗忘门、输出门天然维护了一个隐状态向量这个向量相当于模型的记忆体能把前几个时刻的辐照变化趋势编码进当前的预测决策里。我一般不建议一上来就堆 LSTM 层数。做短期光伏预测单层 LSTM隐藏单元 32128 一层全连接输出往往已经够用。更深的网络在数据量不足比如只有几千条小时级记录时很容易把辐照波动当成规律学进去。2.2 单变量与多变量的选择特征工程决定了模型上限打开这份源码和数据集你最先要判断的是数据集里包含哪些列。常见的光伏数据集至少包含三样时间戳、历史功率、历史辐照度。讲究一点的会有环境温度、组件温度、风速、湿度甚至降水概率。LSTM 是特征敏感型模型——你把什么喂给它它就尝试学习什么到功率的映射。单变量只用历史功率预测未来功率的好处是数据要求低只要功率曲线连续就行坏处是它本质上是在做曲线外推遇到天气突变时表现很差因为你没有给模型任何为什么突变的信息。多变量功率 辐照 温度则给了模型一个解释突变的机会辐照度骤降 - 功率骤降这个因果关系一旦被学习到阴雨天预测表现会明显改善。实际写代码的时候多变量意味着你要分别对每一列做归一化、分别做滑动窗口切片。这块最容易踩的坑在第 5 章细说这里先记住一个原则提示输入特征宁缺毋滥。风速和湿度这类噪声较大的特征若与功率的相关系数低于 0.3建议直接丢弃否则 LSTM 会花费大量隐状态容量去记忆噪声。2.3 对比视角LSTM、XGBoost、CNN 在光伏预测上的差异很多读者一定会问为什么不用 XGBoostXGBoost 在表格型数据上确实太强了但它在光伏预测上有两个先天短板第一它把每个时间点当作独立样本除非你手工构造大量滞后特征lag 特征、滚动均值、差分否则它看不见序列的连续性第二它的训练目标是最小化残差对尖峰和突变的拟合能力不如序列模型平滑。你可以把 XGBoost 当作一个精度不错的基线但它的上限受限于特征工程师的水平。CNN 则落在两者之间。一维卷积可以提取局部时序模式比如过去 30 分钟辐照的上升趋势但它对长期依赖的建模能力弱于 LSTM。不过 CNN 的并行训练速度优势明显工业界有一种常见做法是把 CNN 和 LSTM 串起来CNN 先提取特征LSTM 再对这些特征做序列建模。这份源码如果结构简单通常不会用这种组合但如果你想在后期提升精度这会是一个自然的演进方向。3. 数据准备把光伏数据集切成模型能吃的时序样本四步走3.1 源数据长什么样辐照度、温度、功率的清洗与对齐打开这个压缩包里的数据集一般是 CSV 或 Excel 格式先别急着跑模型。光伏数据随时间戳逐行排列典型字段包括时间精确到分钟或小时、直流功率 / 交流功率、GHI 水平辐照度、环境温度、组件温度、风速。第一步是检查时间戳是否连续。光伏电站的数据采集系统经常因通信中断丢数据最常见的时间间隔是 15 分钟一天应有 96 个点。清洗原则能补则补实在补不了就删。对短于 2 小时的缺失段用前后线性插值对长缺失段直接删除对应日期不要强行插值因为插值出来的伪数据会让模型误以为那段时间功率平稳遇到真实波动时会翻车。下面是清洗片段import pandas as pd df pd.read_csv(pv_data.csv, parse_dates[timestamp], index_coltimestamp) # 重采样到15分钟频率检查缺失 df df.resample(15min).mean() # 线性插值补齐短缺失段少于等于8个点即2小时以内 df[power] df[power].interpolate(limit8, limit_directionboth) # 记录并删掉长缺失段的日期 long_gap_days df[power].isna().resample(D).sum() bad_days long_gap_days[long_gap_days 8].index df df[~df.index.normalize().isin(bad_days)] # 剔除明显异常值功率为负或超过装机容量1.2倍 df df[(df[power] 0) (df[power] 1.2 * installed_capacity)]这段代码的要点是resample(15min)把不规则时间戳统一到固定频率interpolate(limit8)限制插值长度防止用虚假数据填补大段空缺最后一个过滤条件保留 0 到装机容量 120% 之间的功率值因为逆变器在极端工况下可能短暂超过额定值但超过 20% 基本就是采集错误。参数limit_directionboth是必需的否则序列开头和结尾的缺失值不会被填补。3.2 时序窗口切片look_back 窗口怎么选才不玄学LSTM 的输入形状是(batch_size, look_back, n_features)其中look_back决定模型回看多少步。这个参数的设置有点玄学但有一个底线逻辑窗口至少要覆盖光伏功率的一个完整变化单元。15 分钟分辨率的数据一个常见的选择是look_back 24即回看 6 小时如果你要预测未来 4 小时这 6 小时的记忆足够让模型感知上午辐照上升或下午衰减的趋势。窗口太短比如 4 或 8模型看到的信息量不足对多云天气的突变毫无抵抗力窗口太长比如 96即一整天训练样本数骤降而且 LSTM 的长期记忆实际是衰减的——它不会真的把 24 小时前的信息完整记住。我一般先按look_back 4 * 预测步长起调然后扫描 12、24、48 做对比。切片代码如下import numpy as np def create_sequences(data, look_back24, horizon4): X, y [], [] for i in range(len(data) - look_back - horizon 1): X.append(data[i : i look_back, :]) # 过去 look_back 步的所有特征 y.append(data[i look_back : i look_back horizon, 0]) # 未来 horizon 步的功率列 return np.array(X), np.array(y)注意y取的是第 0 列前提是你把功率列放在了data的第一列。horizon是预测步长如果你想预测未来 1 小时4 个 15 分钟点就把horizon设为 4。这个函数生成的是多步预测样本比单步预测更贴近实际业务代价是模型输出维度从 1 变成horizon训练难度略有上升。3.3 归一化为什么 MinMaxScaler 是默认选择以及它的两个陷阱LSTM 使用 tanh 和 sigmoid 作为激活函数输入值必须落在较小范围内才能保证梯度不饱和。MinMaxScaler 把数据压到 01 之间是最安全的选择。但这里埋着两个坑。第一个坑必须在划分训练集和测试集之后在训练集上fit归一化器再分别transform训练集和测试集——顺序错了就是数据泄漏。第二个坑光伏功率天然有上下界0 到装机容量但辐照度在夜间为 0、正午可能超过 1000 W/m²不同特征之间的量纲差异很大所以归一化必须逐列独立进行不能把整个矩阵拉平后一把梭。from sklearn.preprocessing import MinMaxScaler # 先切分再归一化 train_size int(len(df) * 0.7) val_size int(len(df) * 0.15) train_df df.iloc[:train_size] val_df df.iloc[train_size : train_size val_size] test_df df.iloc[train_size val_size :] scaler_X MinMaxScaler() scaler_y MinMaxScaler() train_X_scaled scaler_X.fit_transform(train_df[[power, ghi, temp]]) val_X_scaled scaler_X.transform(val_df[[power, ghi, temp]]) test_X_scaled scaler_X.transform(test_df[[power, ghi, temp]]) # 单独归一化功率列用于预测目标 train_y_scaled scaler_y.fit_transform(train_df[[power]])这段代码的scaler_X和scaler_y是两个独立对象目的就是避免输出反归一化时引入额外维度问题。到预测阶段你要用scaler_y.inverse_transform把预测结果还原成实际功率值这个操作在 4.4 节会用到。3.4 训练/验证/测试划分时间序列绝对不能随机 shuffle很多初学者在这里犯一个致命错误用train_test_split(shuffleTrue)划分光伏数据。光伏功率是强季节性序列随机打乱后训练集和测试集会互相混入相邻日期的数据模型相当于提前看过答案测试指标虚高到离谱。正确做法是按时间顺序切分前 70% 训练中间 15% 验证最后 15% 测试。还有一个细节验证集必须落在测试集之前不能从中间任意截取。模型训练时依据验证 loss 做早停如果验证集在时间上覆盖的是与训练集重叠的区间早停决策就失真了。业界还有个约定俗成的习惯——把最后 710 天单独留作测试而不是简单按百分比切因为近期的天气模式更接近你实际要部署的场景。4. LSTM 模型实现从零搭一个短期光伏预测的 Python 源码4.1 搭建数据管道把清洗后的 DataFrame 转成 PyTorch/TensorFlow 能吃的 Tensor在动手写模型之前先把数据管道想清楚。Python 生态里做 LSTM 不外乎 TensorFlow/Keras 和 PyTorch 两条路。这份源码如果是面向快速落地通常选 Keras因为tf.keras.layers.LSTM封装得足够好CPU 上也能跑通小数据集如果将来要做部署和特征工程深度定制PyTorch 更灵活。我用 Keras 举例因为它的代码量最少适合照着抄。import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader class PVSequenceDataset(Dataset): def __init__(self, X, y): self.X torch.tensor(X, dtypetorch.float32) self.y torch.tensor(y, dtypetorch.float32) def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx] # 用前面生成的序列数据构造 DataLoader train_dataset PVSequenceDataset(train_X_seq, train_y_seq) train_loader DataLoader(train_dataset, batch_size64, shuffleFalse)注意到这里shuffleFalse——时间序列的批次内样本是有顺序的虽然 LSTM 的每个样本内部已经包含时序上下文不同样本之间 shuffle 对训练影响不大但保持顺序可以让你后续做滚动预测验证时更方便追溯。batch_size64是默认起调值如果你的数据集总样本量不足几千条建议降到 32否则每个 epoch 的梯度更新次数太少模型收敛慢且抖动大。4.2 模型结构单层 LSTM 全连接输出隐藏单元怎么定LSTM 的隐藏单元数量是一个直接决定模型容量的参数。做光伏预测我常用的起手式是hidden_size64然后根据验证 loss 调高到 128 或调低到 32。隐藏单元越多模型记住的时序模式模板越多但训练时间线性增长而且在小数据集上更容易过拟合。class LSTMPVModel(nn.Module): def __init__(self, n_features, hidden_size64, num_layers1, horizon4): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, horizon) def forward(self, x): out, _ self.lstm(x) # out: (batch, look_back, hidden_size) out out[:, -1, :] # 只取最后一个时间步的隐状态 out self.fc(out) # (batch, horizon) return out关键在out[:, -1, :]这一步。self.lstm返回了所有时间步的输出序列但我们预测的是看完整个输入窗口之后的未来功率所以只取最后一个时间步的隐状态。这是 LSTM 做预测的标准姿势——整个look_back窗口被压缩成一个固定长度的向量然后映射到horizon个未来值。batch_firstTrue让输入形状变成(batch, sequence, features)更符合直觉。4.3 训练循环学习率、损失函数、早停与模型保存训练 LSTM 和训练普通神经网络没有本质区别但光伏预测有个特殊性夜间功率长时间为 0白天的尖峰功率很高如果损失函数用纯 MSE模型会为了降低大多数夜间样本的误差而偏向低估白天峰值导致正午预测明显偏低。针对这个问题有两个常用补救手段一是在损失函数里给白天样本更高的权重二是在训练后将夜间预测值强制置零。def train_model(model, train_loader, val_loader, epochs100, lr0.001): optimizer torch.optim.Adam(model.parameters(), lrlr) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, factor0.5, patience5) loss_fn nn.MSELoss() best_val_loss float(inf) for epoch in range(epochs): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss loss_fn(pred, y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() * X_batch.size(0) model.eval() val_loss 0.0 with torch.no_grad(): for X_batch, y_batch in val_loader: pred model(X_batch) loss loss_fn(pred, y_batch) val_loss loss.item() * X_batch.size(0) val_loss / len(val_loader.dataset) scheduler.step(val_loss) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_pv_model.pth) if (epoch 1) % 10 0: print(fEpoch {epoch1}: train_loss{train_loss:.6f}, val_loss{val_loss:.6f})这段代码里clip_grad_norm_是一个容易被忽略但很重要的细节。LSTM 的反向传播存在梯度爆炸风险尤其当look_back较长时梯度在时间维度上连乘后可能变得极大。设置max_norm1.0等于给梯度设了上限保证训练稳定。ReduceLROnPlateau在验证 loss 连续 5 个 epoch 不下降时把学习率减半这比手动调学习率靠谱得多光伏数据噪声大学习率固定不变容易在后期震荡。torch.save只在验证 loss 创新低时保存模型这就是你的后悔药——训练 100 个 epoch 后即使过拟合你也能拿回最优权重。4.4 预测与反归一化把 01 的数值还原成功率曲线模型训练完成后在测试集上做预测并反归一化才能得到有物理意义的功率值。这里我不建议直接pred model(test_X)一把梭输出因为模型在测试集上按批处理时你还需要把预测顺序恢复成时间轴才能和真实值对齐。import matplotlib.pyplot as plt model.eval() predictions, actuals [], [] with torch.no_grad(): for X_batch, y_batch in test_loader: pred model(X_batch).numpy() predictions.append(pred) actuals.append(y_batch.numpy()) pred_array np.concatenate(predictions, axis0) actual_array np.concatenate(actuals, axis0) # 反归一化功率列单独还原 pred_power scaler_y.inverse_transform(pred_array.reshape(-1, 1)).reshape(pred_array.shape) actual_power scaler_y.inverse_transform(actual_array.reshape(-1, 1)).reshape(actual_array.shape) # 绘制某一天96个点的真实与预测曲线 day_idx slice(24*15, 24*15 96) # 第16天 plt.figure(figsize(12, 5)) plt.plot(actual_power[day_idx], labelActual) plt.plot(pred_power[day_idx], labelLSTM Forecast) plt.legend() plt.ylabel(Power (kW)) plt.show()inverse_transform的输入形状是(n, 1)所以把预测结果先reshape(-1, 1)再还原最后恢复成(n, horizon)。绘制曲线时只选某几天做可视化对比不要 30 天全画否则曲线挤在一起看不出差异。可视化的意义在于快速排查系统性偏置——如果预测曲线整体低于真实曲线通常说明归一化时夜间 0 值太多把白天峰值压得太低训练时模型学成了平均功率而非峰值功率。5. 短期光伏预测的五个高频坑从数据泄漏到天气突变逐一拆解5.1 数据泄漏归一化在切分前做了测试集被你污染了现象训练时验证 loss 漂亮得很测试集 RMSE 也很低但一上真实新数据就垮掉。原因你在整个数据集上fit了 MinMaxScaler测试集的取值范围已经泄露到训练集的归一化参数里。解决一切可学习的参数——包括归一化的 min/max、特征选择时计算的相关系数——都必须在训练集上计算再应用到测试集。这是光伏预测里最常见的隐形翻车点比模型结构选错更致命。5.2 阴雨天整段失效模型学会了晴天模式但没学会多云现象晴天的预测误差 MAPE 小于 10%阴雨天直接飙到 40% 以上。原因光伏功率在晴天时与辐照度呈高度线性关系LSTM 很容易学会这个规律但多云天气下辐照度剧烈波动功率曲线毛刺极多如果训练数据里阴雨天占比低模型就只拟合了晴天主导的模式。解决第一训练时按天气类型做样本加权给阴雨样本更高的损失权重第二如果数据集里没有天气类型标记就用晴空指数实际辐照/理论晴空辐照作为额外特征输入。后者在原始数据缺失时可以简单用当天实际辐照度与过去 30 天同日同时段辐照度均值的比值近似。5.3 训练损失低于验证损失不一定是过拟合也可能只是噪声现象训练 loss 0.01验证 loss 0.008你觉得模型还没过拟合继续训练结果验证 loss 开始回升。原因光伏功率本身有物理噪声训练集和验证集的噪声分布不一致时训练 loss 略低于验证 loss 是正常状态。但如果你观察到训练 loss 持续下降而验证 loss 连续 10 个 epoch 不降反升这才是过拟合信号。解决不要单看绝对大小要看趋势记录每个 epoch 的验证 loss连续 7 个 epoch 未下降就早停。ReduceLROnPlateau可以在此时把学习率降一半观察是否继续下降这是比暴力早停更平滑的止损手段。5.4 多步预测误差累积滚动预测越滚越偏现象预测未来 1 小时精度尚可未来 4 小时的预测曲线在第二小时后就明显偏离真实值且偏离方向总是滞后于真实变化。原因多步预测时有的实现会把上一步的预测值当作下一步的输入这叫递归式多步预测。预测值本身有误差这个误差会作为伪输入传入下一步导致误差累积。解决检查源码里的实现方式。如果是递归式改成直接多步输出即 4.2 节里的horizon输出模式让模型一次性预测整个 4 步序列每步的输出来自同一个隐状态的不同线性映射避免误差传递。直接多步的缺点是各步预测共享同一个隐状态在长期预测时可能趋于平滑但光伏短期预测4 小时内完全够用。5.5 评估指标选错MAPE 在夜间会除以 0R² 会被零值带偏现象用 MAPE平均绝对百分比误差评估模型算出来一个几十万的离谱数值。原因夜间功率为 0|实际值 - 预测值| / 实际值里分母为 0导致数值爆炸。解决评估光伏预测模型时白天和夜间分开算。白天时段用 RMSE单位 kW直观对应功率误差、MAE 和 MAPE全天候评估用 RMSE 和 R²但 R² 在大量零值存在时会被零值预测对了这一优势撑高掩盖白天峰值预测偏低的真实缺陷。还有一个更贴合业务的自定义指标——日均电量偏差率(预测日发电量 - 实际日发电量) / 实际日发电量这个指标直接对应你的交易结算误差比 RMSE 更容易向非技术同事解释。6. 进阶验证用滚动窗口检验预测稳定性Persistence 基线是及格线模型在测试集上跑通了不代表可以上线。我建议你做三件额外的事第一搭一个 persistence 基线——用今天此刻的功率作为明天同时刻的预测值第二用滚动窗口模拟真实部署场景第三按天气类型分层评估搞清楚模型到底在哪些场景下赚钱、哪些场景下亏钱。Persistence 模型比你想象的强得多。在无云或卫星图像不可用的场景下光伏功率的短期自相关性很高今天下午两点的功率和明天下午两点的功率在天气稳定时差别很小。如果你的 LSTM 在测试集上的 RMSE 没有比 persistence 模型低 10% 以上说明你的模型只是在记忆昨天的功率曲线没有学到辐照-功率映射。这个基线逻辑新能源并网标准里也常引为参考。最后一个实用技巧是记录每个预测时刻的晴空指数把测试样本按晴空指数 0.7晴天、0.30.7多云、 0.3阴天分成三组分别计算 RMSE。你会发现模型性能的离散度远大于平均值暗示的水平。我自己的习惯是在训练日志里固定打印这三组分段指标比盯着总 loss 调参有效得多。这套基于 LSTM 的短期光伏预测方案做到 80 分不难难的是把最后 20 分从能跑打磨到能部署。我踩过的最大一次翻车是花了两周调模型最后发现测试集指标虚高是因为数据切分时没考虑天气连续性——测试集恰好连着三天晴天模型在晴天上本来就强。从那以后我做任何时间序列评估的第一步永远是画出测试集的功率曲线用肉眼确认它包含了晴天、多云、阴雨至少各两天。希望帮到你。本文还有配套的精品资源点击获取