ARTICLE DETAIL

资讯详情

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

基于LSTM的短期光伏功率预测:Python源码与数据集实战

基于LSTM的短期光伏功率预测:Python源码与数据集实战 简介这是一份面向计算机、人工智能、通信工程、自动化等专业学生与从业者的短期光伏预测实战资料以LSTM神经网络为核心解决光伏发电功率时序预测问题可用于课程设计、毕业设计、项目立项演示或算法进阶练习。压缩包共11个文件约3.89MB包含6个ipynb交互式笔记本、1个py脚本、1个xlsx数据表、1个txt使用说明及png、jpg图示覆盖模型搭建、园区数据测试、单变量与多变量预测、储能规则集模拟等环节代码均经测试可运行。已有248人学习下载。读者可据此掌握LSTM在光伏与负荷预测中的完整流程理解数据预处理、模型训练与结果可视化方法并借助使用说明与示例数据快速复现实验也可在现有代码基础上修改以适配其他时序预测任务。1. 光伏功率预测为什么总在下午翻车从一份 LSTM 源码与数据集说起做过光伏电站运维的人大多遇到过同一个尴尬中午功率曲线还贴着理论值走一到下午两三点云层飘过来功率直接掉三成调度侧的考核电话紧跟着就来了。短期光伏预测要解决的就是这件事——提前几小时到一天把下一时段的发电功率算准让调度、储能和交易都有个靠谱的预期。这份「基于 LSTM 的短期光伏预测算法 python 源码 数据集」正好卡在这个需求上用 LSTM 这类能记住时序依赖的神经网络吃进历史功率、辐照度、温度、湿度这些气象与运行数据输出未来一段时间的功率预测值。它适合三类人想入门 lstm 时间序列预测 python 的算法新手、需要给电站做功率上报的运维工程师、以及拿光伏数据练手的学生和转行者。下面我按自己复现这类项目的顺序把数据、模型、训练、调参和踩坑一条条讲清楚。2. 数据先过关光伏数据集怎么读、怎么切、怎么对齐2.1 先看清数据集里到底有什么拿到一份光伏数据集别急着往模型里灌。光伏预测的数据通常分两类一类是电站实测的运行数据包括有功功率、组件温度、环境温度、辐照度、风速风向另一类是气象预报或再分析数据比如数值天气预报给出的未来辐照度和云量。这两类数据的时间分辨率经常不一致实测可能是 1 分钟或 15 分钟一条气象预报可能是 1 小时甚至 3 小时一条。常见做法是统一重采样到 15 分钟或 1 小时用前向填充处理气象数据用均值聚合处理高频实测数据。判断一份数据集能不能用我一般先看三件事时间戳是否连续、有没有大段缺失、功率值有没有超过装机容量。时间戳断裂意味着后面做滑动窗口时会跨过缺口模型学到的是拼接出来的假序列功率超过额定容量说明量纲或单位有问题可能是 kW 和 W 混用。这些检查用几行 pandas 就能做完比训练完再回头找原因省事得多。2.2 用 pandas 做重采样与缺失值处理import pandas as pd import numpy as np # 读取原始数据假设时间列名为 time功率列名为 power df pd.read_csv(pv_data.csv, parse_dates[time]) df df.sort_values(time).set_index(time) # 统一重采样到 15 分钟功率取均值气象量取前向填充 df_15min df.resample(15min).agg({ power: mean, # 功率用均值避免瞬时尖峰干扰 irradiance: mean, # 辐照度同样取均值 temp_module: mean, # 组件温度 temp_ambient: mean, # 环境温度 humidity: mean, # 湿度 cloud_cover: ffill # 云量变化慢前向填充即可 }) # 标记并处理缺失功率缺失超过 4 个点1 小时的段落直接丢弃 df_15min[power_missing] df_15min[power].isna() df_15min df_15min[~df_15min[power_missing]] df_15min df_15min.drop(columns[power_missing]) # 剩余零星缺失用线性插值补上 df_15min df_15min.interpolate(methodlinear, limit4) df_15min df_15min.dropna() print(df_15min.shape) print(df_15min.head())这段代码的逻辑是先把时间列转成索引再按 15 分钟重采样。功率和辐照度用均值是因为它们波动大取均值能平滑掉传感器噪声云量用前向填充是因为它本身是缓变量插值反而会造出不存在的中间状态。缺失处理分两层连续缺失超过 1 小时的整段丢掉因为插值补这么长会引入虚假趋势零星缺失用线性插值limit4限制最多补 4 个点防止把长缺口也补上。参数上重采样频率要和你的预测步长匹配如果你要预测未来 4 小时且每小时一个点15 分钟粒度是合适的如果只预测未来 1 小时可以保留更高频率。2.3 构造滑动窗口把时间序列变成监督学习样本LSTM 吃的是序列但训练时需要「输入序列 目标值」的配对。光伏预测里最常见的构造方式是用过去 N 个时间步的多维特征预测未来 M 个时间步的功率。N 通常取 24 到 96对应 6 到 24 小时的历史窗口M 取 4 到 24对应 1 到 6 小时的预测范围。窗口太短模型看不到日周期太长则训练慢且容易过拟合。def make_windows(data, feature_cols, target_col, input_len48, output_len4): data: 处理好的 DataFrame feature_cols: 输入特征列名列表 target_col: 预测目标列名 input_len: 历史窗口长度时间步数 output_len: 预测窗口长度时间步数 X, y [], [] values data[feature_cols].values targets data[target_col].values for i in range(len(data) - input_len - output_len 1): X.append(values[i:i input_len]) y.append(targets[i input_len:i input_len output_len]) return np.array(X), np.array(y) feature_cols [power, irradiance, temp_module, temp_ambient, humidity, cloud_cover] X, y make_windows(df_15min, feature_cols, power, input_len48, output_len4) print(X.shape, y.shape) # 例如 (样本数, 48, 6) 和 (样本数, 4)这里input_len48对应 12 小时历史15 分钟粒度output_len4对应未来 1 小时。注意目标列power同时出现在输入特征里这是允许的因为预测时刻的功率在预测时是未知的但历史功率是已知的。如果你要预测未来 4 小时把output_len改成 16 即可。构造完窗口后一定要按时间顺序切分训练集、验证集和测试集不能随机打乱否则同一段连续时间的数据会同时出现在训练和测试里评估结果会虚高。3. LSTM 模型怎么搭从单层到多层、从 PyTorch 到训练循环3.1 为什么光伏预测偏爱 LSTM 而不是普通 RNN普通 RNN 在反向传播时梯度会指数衰减序列一长就记不住前面的信息光伏功率的日周期和云层移动的滞后效应恰恰需要长记忆。LSTM 通过输入门、遗忘门和输出门控制信息流动遗忘门决定丢掉哪些旧状态输入门决定写入哪些新信息输出门决定当前输出多少。这套机制让它在几百个时间步的序列上依然能学到有效依赖。相比之下Transformer 虽然并行能力强但在中小规模光伏数据集上往往需要更多数据和调参才能超过 LSTM而 LSTM 结构简单、训练稳定适合作为第一版基线。选型上还有几个现实考虑光伏电站的数据量通常不大一个电站几年数据也就几万到几十万条LSTM 的参数量适中不容易过拟合训练资源要求低一块普通显卡甚至 CPU 都能跑调参经验成熟学习率、隐藏层维度、dropout 这些参数有大量可参考的默认值。所以如果你刚开始做短期光伏预测LSTM 是性价比最高的起点。3.2 用 PyTorch 写一个可训练的多步预测 LSTMimport torch import torch.nn as nn class PVLSTM(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, output_dim4, dropout0.2): super().__init__() self.hidden_dim hidden_dim self.num_layers num_layers # batch_firstTrue 让输入形状为 (batch, seq, feature) self.lstm nn.LSTM( input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, dropoutdropout if num_layers 1 else 0, batch_firstTrue ) # 全连接层把最后一个时间步的隐藏状态映射到多步输出 self.fc nn.Sequential( nn.Linear(hidden_dim, hidden_dim // 2), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_dim // 2, output_dim) ) def forward(self, x): # x: (batch, input_len, input_dim) lstm_out, (h_n, c_n) self.lstm(x) # 取最后一个时间步的输出 last_out lstm_out[:, -1, :] return self.fc(last_out) # 实例化6 个输入特征隐藏层 642 层输出未来 4 步 model PVLSTM(input_dim6, hidden_dim64, num_layers2, output_dim4, dropout0.2) print(model)模型结构上input_dim等于特征列数量这里 6 列hidden_dim64是隐藏状态维度太小欠拟合太大过拟合64 到 128 是常见区间num_layers2表示两层 LSTM 堆叠第一层输出序列给第二层能提取更抽象的时序模式但层数超过 3 层在光伏数据上收益很小dropout0.2只在多层时生效用来抑制过拟合。全连接层先把 64 维压到 32 维再输出 4 步中间加 ReLU 和 Dropout是为了让输出层也有非线性表达能力。注意batch_firstTrue这个参数不设的话输入形状要写成(seq, batch, feature)很容易搞混。3.3 训练循环与损失函数的选择from torch.utils.data import TensorDataset, DataLoader from sklearn.preprocessing import StandardScaler import numpy as np # 标准化用训练集的均值和方差避免验证/测试集信息泄漏 scaler_X StandardScaler() scaler_y StandardScaler() # 假设 X_train, y_train, X_val, y_val 已经按时间切好 X_train_scaled scaler_X.fit_transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape) X_val_scaled scaler_X.transform(X_val.reshape(-1, X_val.shape[-1])).reshape(X_val.shape) y_train_scaled scaler_y.fit_transform(y_train) y_val_scaled scaler_y.transform(y_val) train_ds TensorDataset(torch.FloatTensor(X_train_scaled), torch.FloatTensor(y_train_scaled)) val_ds TensorDataset(torch.FloatTensor(X_val_scaled), torch.FloatTensor(y_val_scaled)) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) val_loader DataLoader(val_ds, batch_size64, shuffleFalse) device torch.device(cuda if torch.cuda.is_available() else cpu) model PVLSTM(input_dim6, hidden_dim64, num_layers2, output_dim4).to(device) criterion nn.MSELoss() # 回归任务常用 MSE也可换 HuberLoss 抗异常值 optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) best_val_loss float(inf) patience, counter 10, 0 for epoch in range(100): model.train() train_loss 0.0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() * xb.size(0) train_loss / len(train_ds) model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) pred model(xb) val_loss criterion(pred, yb).item() * xb.size(0) val_loss / len(val_ds) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_pv_lstm.pth) counter 0 else: counter 1 if counter patience: print(fEarly stop at epoch {epoch}) break if epoch % 10 0: print(fEpoch {epoch}: train_loss{train_loss:.4f}, val_loss{val_loss:.4f})训练部分有几个关键点。标准化必须用训练集的统计量去变换验证集和测试集如果对全量数据做fit_transform验证集的信息就泄漏进训练了评估结果会偏乐观。损失函数用 MSE 是因为功率预测是回归任务MSE 对大误差惩罚重能逼模型关注高峰时段的偏差如果数据里有个别异常值可以换成 HuberLoss它在误差大时退化成线性更鲁棒。优化器用 Adam学习率 1e-3 是安全起点weight_decay1e-5做 L2 正则。梯度裁剪clip_grad_norm_是 LSTM 训练的标配光伏序列里偶尔有剧烈波动不裁剪容易梯度爆炸导致 loss 变 NaN。早停 patience 设 10验证损失连续 10 轮不降就停防止过拟合。4. 避坑与排查光伏 LSTM 训练里最容易翻车的五件事4.1 现象验证损失比训练损失低很多原因最常见的是数据泄漏。滑动窗口构造时如果先随机划分再构造窗口相邻窗口会跨越训练集和验证集边界验证集里混入了训练集的信息。另一个可能是验证集太小且恰好落在平稳天气段模型在那段上表现好。解决先按时间切分原始数据再分别构造窗口。切分点要留出至少一个input_len output_len的缓冲带缓冲带的数据丢弃不用确保训练集最后一个窗口和验证集第一个窗口没有重叠。验证集至少占总数据的 15%且要覆盖不同季节和天气类型。4.2 现象模型预测值几乎是一条直线原因要么是输入特征没有标准化功率值几百上千其他特征在 0 到 1 之间LSTM 被大数值主导要么是学习率太低模型还没学出变化就早停了还有一种可能是目标列在输入特征里被错误地做了差分或归一化导致模型看到的功率和要预测的功率量纲不一致。解决检查标准化是否对所有特征统一处理功率和辐照度这类大数值必须标准化。学习率从 1e-3 开始试如果 loss 下降极慢可以提到 3e-3。确认目标列的处理方式如果对功率做了差分预测出来的也是差分值需要反差分才能还原成真实功率。4.3 现象训练 loss 正常下降但测试集上高峰时段误差特别大原因光伏功率在早晚和中午的分布差异极大MSE 损失会被大量低功率样本主导模型倾向于把高峰值预测偏低来降低整体损失。另外如果训练集里阴雨天样本少模型没见过足够多的低功率场景遇到多云天气就懵。解决对损失函数加权给高功率样本更大权重或者改用对峰值更敏感的损失如 MSE 加一阶差分惩罚。数据层面做增强把阴雨天的样本过采样或者在划分数据集时做分层抽样保证各天气类型在训练集和测试集里比例接近。评估指标不要只看 MSE要单独看高峰时段的 MAE 和 MAPE。4.4 现象换一个电站的数据模型效果直接崩了原因不同电站的装机容量、组件朝向、倾角、遮挡情况都不同功率量纲和日周期形状不一样。在一个电站上训练的模型权重里编码了那个电站的特定模式直接迁移到另一个电站自然不适用。解决如果要做跨电站迁移输入特征里要加入电站的静态属性比如装机容量、倾角、经纬度并且对功率做归一化用「功率 / 装机容量」作为预测目标。更稳妥的做法是每个电站单独训练或者用迁移学习在数据多的电站上预训练在目标电站上微调最后几层。微调时学习率要调小比如 1e-4避免把预训练学到的通用时序模式冲掉。4.5 现象推理时预测结果和训练时对不上原因训练时用了model.train()和model.eval()切换推理时忘了调model.eval()dropout 还在随机丢弃神经元输出自然不稳定。另一个常见原因是推理时输入序列的构造方式和训练时不一致比如训练用 48 步历史推理只给了 24 步。解决推理前固定写model.eval()并用torch.no_grad()包住前向传播。把训练时的窗口构造函数单独封装推理时调用同一个函数确保input_len、特征顺序、标准化参数完全一致。标准化参数要保存下来推理时加载同一份 scaler不能重新 fit。5. 把预测误差再压一压残差修正与多模型融合的实操技巧模型跑通之后想把误差从 8% 压到 5% 以内单靠调 LSTM 超参数收益有限。我一般会做两件事残差修正和轻量融合。残差修正的思路是LSTM 的预测误差往往不是白噪声而是有结构的——比如每天下午误差偏正、夜间误差偏负。用一个简单的线性模型或小决策树去拟合「真实功率减去 LSTM 预测功率」这个残差序列把残差预测值加回 LSTM 输出能吃掉一部分系统性偏差。from sklearn.linear_model import Ridge from sklearn.metrics import mean_absolute_error # 假设 lstm_pred_train 是 LSTM 在训练集上的预测y_train 是真实值 residual_train y_train - lstm_pred_train # 形状 (样本数, 4) # 用历史功率统计量作为残差特征过去 1 小时均值、过去 1 小时标准差 residual_features np.hstack([ X_train[:, -4:, 0].mean(axis1, keepdimsTrue), # 过去 1 小时功率均值 X_train[:, -4:, 0].std(axis1, keepdimsTrue), # 过去 1 小时功率标准差 X_train[:, -4:, 1].mean(axis1, keepdimsTrue), # 过去 1 小时辐照度均值 ]) ridge Ridge(alpha1.0) ridge.fit(residual_features, residual_train) # 推理时同样构造残差特征预测残差并修正 residual_pred ridge.predict(residual_features_val) final_pred lstm_pred_val residual_pred print(修正前 MAE:, mean_absolute_error(y_val, lstm_pred_val)) print(修正后 MAE:, mean_absolute_error(y_val, final_pred))这段代码里残差特征只用了过去 1 小时的功率均值和标准差以及辐照度均值没有引入未来信息。Ridge 的alpha1.0是正则强度残差特征少的时候可以调小到 0.1特征多再调大。修正后要重新算 MAE如果没降反而升了说明残差没有可学习的结构直接放弃这一步。融合方面我会再训一个轻量的梯度提升树比如 LightGBM做对比。LSTM 擅长捕捉时序依赖树模型擅长处理气象特征的非线性交互两者预测值做加权平均权重用验证集上的误差倒数来定。常见做法是 LSTM 权重 0.6、树模型 0.4但具体要看验证集表现不要拍脑袋。融合前先确认两个模型的预测误差相关性低于 0.8相关性太高说明它们犯同样的错融合收益有限。最后说个我自己的习惯每次改完模型或特征先把验证集上的预测曲线画出来用肉眼扫一遍。指标只告诉你平均误差曲线能告诉你模型在哪些时段、哪种天气下翻车。我见过太多次 MAE 看着漂亮、但一到阴雨天预测就崩的模型光看数字根本发现不了。把预测值和真实值按时间叠在一起看比任何指标都直观。希望帮到你。本文还有配套的精品资源点击获取
返回列表