☰
LSTM光伏发电预测实战:从数据集到PyTorch模型调参全流程
2026/10/7 21:39:45 网站建设 项目流程

简介:这是一套面向计算机相关专业毕业设计与深度学习实践者的光伏发电预测项目源码,以LSTM神经网络为核心,解决短期光伏发电量时间序列预测问题,难度中等,适合作为本科毕设参考或时间序列预测入门案例。压缩包共38个文件,约3.86MB,包含4个Python脚本与1个Jupyter Notebook作为主程序,1个CSV数据集用于模型训练,22张PNG图片记录训练过程与预测结果,另有txt依赖说明、md说明文档及若干备份文件,结构清晰、便于按模块查阅。项目采用模块化架构,涵盖LSTM、TCN等模型实现、数据预处理与损失记录,关键算法附有详细注释,数据集已完成清洗,可直接运行调试。目前已有78人学习下载。读者可获得一套完整可执行的预测方案、经过验证的代码与数据,以及模型对比与调参思路,适合用于学术研究或二次开发。

1. 光伏功率曲线为什么总在午后“跳水”:LSTM 预测要解决的真问题

做过光伏电站运维的人都有一个共同体会:早上功率爬升还算规律,一到中午前后云层飘过,出力曲线能在十分钟里从满载掉到三成,再过二十分钟又弹回来。这种分钟级的剧烈波动,靠传统统计方法或者简单的前馈神经网络基本抓不住,因为它们的输入窗口是固定的,记不住“二十分钟前那朵云”对当前功率的滞后影响。LSTM 神经网络做光伏发电预测,核心价值就在于它的门控结构能把这种时间上的因果链条保留下来,让模型在预测下一时刻功率时,还能“记得”前几个时刻的辐照度、温度和功率变化趋势。

这套方案适合谁?一是手里有光伏电站历史运行数据、想做短期功率预测的运维或算法工程师;二是正在学 LSTM 时间序列预测、需要一个完整可复现项目练手的 Python 开发者。标题里说的“附带数据集”,意味着你不需要自己去电站采数据,可以直接拿现成的历史功率、气象记录跑通全流程。接下来我会按数据长什么样、LSTM 模型怎么搭、参数怎么调、坑在哪,一步步拆开讲,让你看完能自己复现一套能用的光伏发电预测系统。

2. 光伏数据集长什么样:字段、粒度与清洗的硬标准

2.1 一份能用的光伏数据集至少要有哪几列

光伏发电预测的数据集,不管来自哪个电站,核心字段跑不出这几类:时间戳、发电功率、太阳辐照度、环境温度、组件温度、风速、湿度。其中发电功率是预测目标,其余是特征。时间戳的粒度决定了你能做多短期的预测,常见的是 15 分钟一条记录,也有 5 分钟或 1 小时的。粒度越细,LSTM 能学到的波动细节越多,但对数据完整性的要求也越高。

我一般会先检查数据集的时间连续性。如果中间缺了几个小时甚至几天,直接丢进 LSTM 训练,模型会把缺失段当成“功率为零”来学,预测结果在对应时段会系统性偏低。处理办法有两种:缺得少就用前后时刻线性插值补上,缺得多就把缺失段整体切掉,不要硬补。下面这段代码是读取 CSV 并做基础检查的常用写法。

import pandas as pd import numpy as np # 读取光伏数据集,假设时间列名为 timestamp,功率列名为 power df = pd.read_csv('solar_power.csv', parse_dates=['timestamp']) df = df.sort_values('timestamp').reset_index(drop=True) # 检查时间间隔是否均匀 time_diff = df['timestamp'].diff().dropna() print('时间间隔分布:') print(time_diff.value_counts().head()) # 检查缺失值比例 missing_ratio = df.isnull().mean() print('\n各列缺失比例:') print(missing_ratio[missing_ratio > 0]) # 对功率和辐照度做线性插值,限制最多补 4 个连续缺失点 df['power'] = df['power'].interpolate(method='linear', limit=4) df['irradiance'] = df['irradiance'].interpolate(method='linear', limit=4) # 删除仍然缺失的行 df = df.dropna().reset_index(drop=True) print(f'\n清洗后剩余记录数:{len(df)}')

这段代码的逻辑是先排序保证时间递增,再用diff()看时间间隔是否均匀。如果出现大量非标准间隔,说明数据采集本身有问题,需要回去核对。插值时用limit=4限制连续插值点数,避免把长时间缺失段“编”出平滑曲线。参数上,limit设多少取决于你的采样粒度:15 分钟粒度下,4 个点代表 1 小时,超过 1 小时的缺失我建议直接切掉而不是插值。

2.2 特征归一化和滑动窗口怎么设才不翻车

LSTM 对输入尺度很敏感。功率值可能在 0 到几千千瓦之间,辐照度在 0 到 1200 W/m² 之间,温度在 -10 到 45 摄氏度之间。如果不做归一化,量纲大的特征会主导梯度更新,模型学到的权重几乎只反映功率本身,气象特征等于白给。常见做法是对每个特征单独做 min-max 归一化,把值压到 0 到 1 之间。注意:归一化参数必须只用训练集计算,再应用到验证集和测试集,否则会引入未来信息,造成“看起来准、实际不能用”的假象。

滑动窗口是 LSTM 预测的另一个关键参数。假设你用过去 8 个时刻的数据预测下一时刻功率,窗口大小就是 8。窗口太小,模型看不到完整的波动周期;窗口太大,训练慢且容易过拟合。对于 15 分钟粒度的光伏数据,我一般从 8 到 16 开始试,对应过去 2 到 4 小时。下面是把时间序列转成 LSTM 输入格式的代码。

from sklearn.preprocessing import MinMaxScaler # 选择特征列和目标列 feature_cols = ['irradiance', 'temperature', 'humidity', 'power'] data = df[feature_cols].values # 只用前 70% 数据计算归一化参数 train_size = int(len(data) * 0.7) scaler = MinMaxScaler() scaler.fit(data[:train_size]) data_scaled = scaler.transform(data) # 构建滑动窗口,window_size 为过去时刻数,horizon 为预测步长 def create_sequences(data, window_size, horizon=1): X, y = [], [] for i in range(len(data) - window_size - horizon + 1): X.append(data[i:i + window_size]) y.append(data[i + window_size + horizon - 1, -1]) # 最后一列是 power return np.array(X), np.array(y) window_size = 12 X, y = create_sequences(data_scaled, window_size) print(f'样本数:{X.shape[0]},输入形状:{X.shape[1:]}')

这里scaler.fit只用了训练段数据,这是避免数据泄漏的关键。create_sequences里horizon=1表示预测下一时刻,如果想预测未来 4 个时刻,把horizon改成 4 并调整y的取值逻辑即可。window_size=12对应 15 分钟粒度下的 3 小时历史窗口,这个值不是固定的,后面调参章节会讲怎么判断合不合适。

3. 用 PyTorch 搭 LSTM 光伏预测模型:层数、隐藏单元与训练循环

3.1 LSTM 层数和隐藏单元到底设多少

PyTorch 里搭 LSTM 预测模型,核心参数就三个:input_size、hidden_size、num_layers。input_size等于特征数量,上面例子是 4。hidden_size是隐藏状态维度,决定了模型记忆容量的上限。光伏功率预测这种任务,我一般从 32 或 64 开始试,超过 128 后验证集损失往往不再下降,反而训练时间翻倍。num_layers是 LSTM 层数,1 层能捕捉大部分日周期波动,2 层可以学更复杂的云层遮挡模式,但超过 2 层在小数据集上极易过拟合。

还有一个容易忽略的参数是batch_first。PyTorch 的 LSTM 默认输入形状是(seq_len, batch, input_size),如果你用 DataLoader 按(batch, seq_len, input_size)组织数据,必须设batch_first=True,否则维度对不上,报错信息还不直观。下面是一个完整的模型定义和训练循环。

import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset # 划分训练集和测试集 X_train, X_test = X[:train_size - window_size], X[train_size - window_size:] y_train, y_test = y[:train_size - window_size], y[train_size - window_size:] train_dataset = TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size=1): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=0.2) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch, seq_len, input_size) out, _ = self.lstm(x) # 取最后一个时间步的输出 out = self.fc(out[:, -1, :]) return out model = LSTMPredictor(input_size=4, hidden_size=64, num_layers=2) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # 训练循环 for epoch in range(50): model.train() total_loss = 0 for batch_x, batch_y in train_loader: optimizer.zero_grad() pred = model(batch_x) loss = criterion(pred, batch_y.unsqueeze(1)) loss.backward() optimizer.step() total_loss += loss.item() if (epoch + 1) % 10 == 0: print(f'Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.6f}')

模型定义里dropout=0.2只在num_layers > 1时生效,作用是随机丢弃部分隐藏单元输出,降低过拟合。forward里取out[:, -1, :]是因为 LSTM 输出包含每个时间步的隐藏状态,预测下一时刻功率只需要最后一个时间步的信息。损失函数用 MSE 是回归任务的标准选择,如果数据里存在极端异常值,可以换成 HuberLoss 降低异常点影响。学习率1e-3是 Adam 的常用起点,训练损失震荡不降时先降到1e-4试试。

3.2 训练过程中怎么判断模型是不是在“假学习”

训练损失下降不代表模型学到了有用模式。光伏数据有明显的日周期性,如果模型只是学会了“白天功率高、晚上功率低”这个规律,在晴天测试集上表现很好,一到多云天气就崩。判断方法是在验证集上分时段看误差:把测试集按辐照度高低分成三组,分别算 MAE。如果高辐照度组误差明显大于低辐照度组,说明模型对波动剧烈的时段预测能力不足,需要增加训练数据中的多云样本,或者把window_size调大让模型看到更长的波动历史。

另一个常见现象是验证损失先降后升,这是过拟合的典型信号。早停策略是记录验证损失最低的模型参数,后续 epoch 如果验证损失连续 5 次不下降就停止训练并回滚到最佳参数。PyTorch 里可以用torch.save(model.state_dict(), 'best_model.pth')保存最佳权重,不要等到训练结束才保存最后一个 epoch 的模型。

4. 预测结果评估与调参:MAE、RMSE 和那几个必调参数

4.1 光伏预测该看哪些指标,数值多少算能用

回归预测常用 MAE、RMSE、MAPE 三个指标。光伏功率预测里,MAE 反映平均偏差,RMSE 对大误差更敏感,MAPE 在功率接近零的夜间会爆炸,所以一般不用或者只算白天时段。行业里对短期光伏预测的精度要求,常见参考是归一化 RMSE 在 10% 到 20% 之间算可用,低于 10% 算优秀。但要注意,这个数值和电站容量、天气类型强相关,不能跨电站直接比。

计算指标前必须做反归一化,把预测值还原到原始功率量纲。下面代码演示了从模型输出到指标计算的完整流程。

model.eval() with torch.no_grad(): X_test_tensor = torch.FloatTensor(X_test) pred_scaled = model(X_test_tensor).numpy().flatten() # 反归一化:构造与原始特征相同列数的矩阵,只替换 power 列 def inverse_power(scaled_values, scaler, power_col_index=-1): dummy = np.zeros((len(scaled_values), scaler.n_features_in_)) dummy[:, power_col_index] = scaled_values return scaler.inverse_transform(dummy)[:, power_col_index] pred_real = inverse_power(pred_scaled, scaler) y_test_real = inverse_power(y_test, scaler) mae = np.mean(np.abs(pred_real - y_test_real)) rmse = np.sqrt(np.mean((pred_real - y_test_real) ** 2)) print(f'MAE: {mae:.2f} kW, RMSE: {rmse:.2f} kW')

反归一化时构造dummy矩阵是因为scaler.inverse_transform要求输入维度和拟合时一致。只把 power 列填预测值,其余列填零,反变换后只取 power 列,这样不会影响结果。算完指标后,建议把预测曲线和真实曲线画在同一张图上,肉眼检查模型是不是在关键波动点跟上了。很多时候指标看着还行,但曲线在午后跳水段明显滞后,这种模型上线后会被运维骂。

4.2 window_size、hidden_size、学习率怎么配合调

调参顺序我一般是这样:先定window_size,再调hidden_size,最后微调学习率和 dropout。window_size的判断方法是看自相关函数,光伏功率序列的自相关通常在 24 小时(96 个 15 分钟点)附近有峰值,但预测下一时刻不需要那么长,8 到 16 足够覆盖短时波动。如果window_size从 8 加到 16 验证集 RMSE 下降不明显,就不要再加,加了只是浪费算力。

hidden_size和num_layers要一起看。1 层 64 单元和 2 层 32 单元的总参数量接近,但后者非线性更强。光伏预测这种任务,我倾向于先用 1 层 64 单元跑基线,如果欠拟合再升到 2 层。学习率方面,Adam 配1e-3是起点,训练损失在前 10 个 epoch 下降不到 20% 就说明学习率偏小,可以升到3e-3试一轮;如果损失直接变成 NaN,说明太大,降到1e-4。

参数常用范围调大效果调小效果
window_size8–16看到更长历史,可能过拟合训练快,短时波动捕捉弱
hidden_size32–128记忆容量大,易过拟合欠拟合,预测偏平滑
num_layers1–2非线性强,训练慢简单任务够用
dropout0.1–0.3抑制过拟合过拟合风险高
learning_rate1e-4–3e-3收敛快,可能震荡收敛慢,可能陷局部最优

这张表里的范围是我在多个光伏数据集上试出来的经验值,不是理论边界。实际调参时每次只动一个参数,记录验证集 RMSE,避免同时改多个导致无法归因。

5. 光伏 LSTM 预测的避坑与排查:5 个血泪教训

5.1 现象:白天预测很准,夜间功率预测出现负值

原因:模型输出层是线性层,没有加任何约束,夜间真实功率为零,但模型在归一化空间里可能输出略小于零的值,反归一化后变成负功率。解决:在模型输出后加torch.clamp(pred, min=0),或者在损失函数里对负值加惩罚项。更彻底的做法是在数据预处理阶段把夜间功率统一置零,并增加一个“是否白天”的二值特征,让模型自己学会区分。

5.2 现象:训练损失正常下降,但验证集 RMSE 始终在 30% 以上

原因:最常见的是数据泄漏方向搞反了。归一化时用了全量数据计算 min/max,导致验证集信息提前进入训练。另一个可能是滑动窗口跨越了训练集和验证集的边界,把验证段的数据混进了训练输入。解决:严格按时间顺序切分,归一化参数只用训练段拟合,滑动窗口构建时在切分点断开,不要跨段取窗口。

5.3 现象:模型在晴天测试集上 MAE 很低,多云天误差翻三倍

原因:训练数据里晴天样本占绝大多数,模型学到了“功率跟随辐照度平滑变化”的捷径,没有学会处理云层遮挡导致的骤降。解决:对多云样本过采样,或者在损失函数里给高波动时段的样本更高权重。也可以增加一个“辐照度变化率”特征,让模型显式看到波动强度。

5.4 现象:换一个电站的数据,模型完全不能用

原因:不同电站的装机容量、组件朝向、采样粒度都不同,归一化后的数值分布差异很大。在一个电站上训练的模型,权重反映的是该电站的特定模式。解决:如果要做跨电站预测,需要做迁移学习,冻结 LSTM 层只微调全连接层,或者把功率替换成“功率/装机容量”的归一化功率再训练。

5.5 现象:训练时 GPU 显存够,但 DataLoader 加载越来越慢

原因:create_sequences用 Python 循环逐条构建样本,数据量大时内存占用高且速度慢。解决:改用 NumPy 的滑动窗口视图或者torch.utils.data.Dataset的懒加载方式,不要一次性把所有窗口都存成数组。对于超过 10 万条记录的数据集,懒加载能把内存占用降到十分之一。

6. 让 LSTM 光伏预测真正可用的两个进阶技巧

第一个技巧是预测区间而不是单点。运维人员不只需要“下一时刻功率是多少”,还需要知道“这个预测有多不确定”。实现方法是在 LSTM 输出层同时输出均值和方差,用高斯负对数似然作为损失函数。这样模型在波动剧烈时会给出更宽的预测区间,运维可以据此决定是否提前调整储能策略。代码上只需把self.fc改成输出两个值,损失函数换成0.5 * (log_var + (y - mu)**2 / exp(log_var)),训练流程不变。

第二个技巧是用多步预测替代单步滚动。单步预测每次只输出下一时刻,做 4 小时预测需要滚动 16 次,误差会累积。直接让 LSTM 输出未来 16 个时刻的功率序列,虽然训练难度大一些,但长 horizon 的误差累积明显更小。实现时把output_size改成 16,y从单值变成序列,损失函数用 MSE 对所有步求平均。我试过在同一个数据集上,多步直接输出的 4 小时 RMSE 比滚动单步低 15% 左右。

验证方法上,我习惯留出最近一个月的连续数据做最终测试,而不是随机划分。光伏数据的季节性很强,随机划分会让夏季和冬季样本混在一起,指标虚高。按时间留出最后一个月,模型在真实部署场景下的表现才可信。这个习惯是我踩过好几次“测试集漂亮、上线就崩”的坑之后养成的,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询