简介:本资源为基于LSTM的短期光伏预测算法Python实现包,面向计算机、人工智能、通信工程、自动化等专业的在校学生、教师及企业技术人员,可用于毕业设计、课程设计、项目立项演示或算法进阶学习。包内共11个文件,以6个ipynb交互式笔记本为核心,配合1个py脚本、1个xlsx数据表、1个txt使用说明及png、jpg图示,压缩包约3.89MB,结构轻量便于快速上手。内容围绕园区光伏与负荷数据展开,涵盖单变量与多变量预测流程、规则集与储能框架探索、模拟程序及数据清洗脚本,可帮助读者理解LSTM在时间序列预测中的建模思路、特征构造与结果评估方法。已有248人学习下载,适合希望掌握短期光伏功率预测完整实现路径、并在此基础上修改扩展功能的读者参考。
1. 从一份 LSTM 光伏预测源码包说起:它到底能解决什么问题
光伏电站最头疼的不是发电,而是「发多少」说不准。云层飘过来三分钟,功率能从满载掉到三成,调度侧要备用容量,交易侧要报次日曲线,偏差一大就是真金白银的罚款。短期光伏预测要干的事,就是拿历史功率、辐照度、温度、风速这些时序数据,把未来 1 到 24 小时的出力曲线提前算出来。这份「基于 LSTM 的短期光伏预测算法 python 源码 + 数据集」压缩包,本质是一套能跑通的最小闭环:数据读取、归一化、滑窗构造、LSTM 建模、训练、评估、画图,一条龙。它适合两类人——刚入门时间序列预测、想找一个能跑通全流程的 python 项目练手的新手;以及手上真有电站数据、想快速验证 LSTM 到底比持久化模型强多少的运维或算法工程师。别指望它直接上线,但拿它当基线、当脚手架,性价比很高。
2. LSTM 做光伏预测的原理与选型:为什么不是 ARIMA 也不是 XGBoost
2.1 光伏功率序列的三个特性决定了模型选型
光伏功率序列有三个绕不开的特性。第一是强日周期性,晴天曲线几乎是标准正弦,早上爬坡、正午封顶、傍晚回落。第二是天气突变带来的非平稳性,一片云就能让功率在分钟级抖出 40% 的波动,这种突变没有明显的前兆规律。第三是多变量耦合,功率不只跟自身历史有关,还跟辐照度、组件温度、环境温度强相关。
传统 ARIMA 类模型假设序列线性、平稳,遇到云遮这种非线性突变基本抓瞎,差分阶数调半天也压不住误差。树模型如 XGBoost 对特征工程依赖极重,你得手工构造滞后项、滑动均值、辐照度交互项,特征没做好效果就上不去,而且它天然不擅长处理长序列的时序依赖。LSTM 的门控结构(遗忘门、输入门、输出门)恰好能选择性地记住「昨天同一时刻的功率水平」这种长周期信息,又能对短时突变做出响应,输入多变量也天然支持。这就是为什么短期光伏预测里 LSTM 及其变体(BiLSTM、CNN-LSTM、Attention-LSTM)是主流基线。
提示:LSTM 不是万能的。如果只有功率单变量、且预测步长只有 15 分钟,持久化模型(拿上一时刻值当预测)往往就很难打败,先跑通持久化基线再上 LSTM,才知道模型到底有没有价值。
2.2 从零把环境搭起来:python 与 pytorch 的版本选择
拿到源码包第一件事不是急着跑,而是把环境对齐。LSTM 实现主流用 PyTorch 或 TensorFlow/Keras,这份源码包大概率是 PyTorch 路线(pytorch lstm 源码是热搜高频词)。python 安装建议 3.9 到 3.11,太新的版本某些科学计算库轮子还没跟上,太老的又缺特性。用 conda 建独立环境,避免污染系统 python。
# 创建独立环境,python 版本锁在 3.10 conda create -n pv_lstm python=3.10 -y conda activate pv_lstm # 安装核心依赖,pytorch 按自己显卡的 CUDA 版本去官网选对应命令 # 这里给的是 CPU 版示例,有 GPU 的换成 cu118 对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install numpy pandas matplotlib scikit-learn openpyxl这段命令的逻辑是:先隔离环境,再装框架和数据处理三件套。参数上,python=3.10是稳妥选择;--index-url指定 pytorch 官方源,避免装到不匹配的版本;openpyxl是为了读 Excel 格式的数据集,很多电站导出就是 xlsx。装完用python -c "import torch; print(torch.__version__)"验证,能打印版本号就说明环境通了。vscode python 环境配置的话,记得在右下角把解释器切到刚建的pv_lstm,否则跑起来还是系统 python,报「找不到 torch」的玄学错误多半出在这。
2.3 数据集长什么样:字段、粒度与清洗要点
光伏数据集通常包含时间戳、有功功率、辐照度、组件温度、环境温度、风速、风向这几列。粒度常见 15 分钟或 1 小时一条,一天 96 点或 24 点。拿到数据先做三件事:时间戳解析、缺失值处理、异常值剔除。
import pandas as pd import numpy as np # 读取数据,parse_dates 直接把时间列解析成 datetime df = pd.read_excel("pv_data.xlsx", parse_dates=["timestamp"]) df = df.sort_values("timestamp").reset_index(drop=True) # 缺失值:功率列用线性插值,气象列用前向填充 df["power"] = df["power"].interpolate(method="linear", limit=4) df["irradiance"] = df["irradiance"].ffill() # 异常值:夜间功率应为 0,出现负值或白天超装机容量的直接置 NaN 再插值 df.loc[df["power"] < 0, "power"] = np.nan df.loc[df["power"] > df["capacity"], "power"] = np.nan df["power"] = df["power"].interpolate(method="linear") # 加时间特征,小时和分钟能帮模型捕捉日周期 df["hour"] = df["timestamp"].dt.hour df["minute"] = df["timestamp"].dt.minute逻辑说明:interpolate的limit=4表示连续缺失超过 4 个点就不插了,避免用假数据填出一大段;异常值先置 NaN 再插值,比直接删行保留更多样本。参数上,capacity是电站装机容量,得从数据说明里拿,没有就问现场。清洗完打印df.isna().sum()确认没有残留空值,否则后面归一化会报错。
3. 把 LSTM 模型跑起来:滑窗构造、网络定义与训练循环
3.1 滑窗构造:把时序问题变成监督学习问题
LSTM 吃的是序列,但训练时需要「输入序列 + 对应标签」的样本对。滑窗就是拿过去 N 个时刻的多变量特征,预测未来 M 个时刻的功率。N 是回看窗口,M 是预测步长。
from sklearn.preprocessing import MinMaxScaler features = ["power", "irradiance", "temperature", "hour"] data = df[features].values # 归一化,LSTM 对量纲敏感,不归一化收敛极慢 scaler = MinMaxScaler() data_scaled = scaler.fit_transform(data) def make_windows(data, lookback=24, horizon=4): X, y = [], [] for i in range(len(data) - lookback - horizon + 1): X.append(data[i:i + lookback]) # 过去 24 个点 y.append(data[i + lookback:i + lookback + horizon, 0]) # 未来 4 个点的功率 return np.array(X), np.array(y) X, y = make_windows(data_scaled, lookback=24, horizon=4) print(X.shape, y.shape) # (样本数, 24, 4) (样本数, 4)逻辑说明:lookback=24对应 15 分钟粒度就是过去 6 小时,horizon=4是预测未来 1 小时。y只取第 0 列(功率),因为预测目标就是功率。参数怎么定:回看窗口太短抓不到日周期,太长引入噪声且训练慢,24 到 96 是常见区间;预测步长按业务需求,日前调度要 96 步,实时调度 4 步就够。注意归一化必须用训练集 fit,再 transform 测试集,否则数据泄漏,评估结果虚高,这是血泪经验。
3.2 LSTM 网络定义:层数、隐藏单元与 dropout 的取舍
网络结构不用堆太深,光伏预测这种任务两层 LSTM 足够,堆到四五层反而过拟合。
import torch import torch.nn as nn class PVLSTM(nn.Module): def __init__(self, input_size=4, hidden_size=64, num_layers=2, output_size=4): super().__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=0.2, # 两层以上才生效 ) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) # out: (batch, seq, hidden) out = out[:, -1, :] # 取最后一个时间步 return self.fc(out) model = PVLSTM() print(sum(p.numel() for p in model.parameters()), "个参数")逻辑说明:batch_first=True让输入维度是 (batch, seq, hidden),符合直觉;out[:, -1, :]取最后一个时间步的隐藏状态,因为它聚合了整段序列的信息;dropout=0.2是防过拟合的常规值,注意单层 LSTM 时 dropout 不生效。参数上,hidden_size=64是起点,数据量大可以加到 128,数据少就降到 32;num_layers=2够用,再深收益递减。参数量打印出来心里有数,几万到几十万级别都正常。
3.3 训练循环与早停:损失曲线怎么看
训练用 MSE 损失、Adam 优化器,加早停防止过拟合。
from torch.utils.data import TensorDataset, DataLoader X_t = torch.tensor(X, dtype=torch.float32) y_t = torch.tensor(y, dtype=torch.float32) loader = DataLoader(TensorDataset(X_t, y_t), batch_size=64, shuffle=True) model = PVLSTM() criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) best_loss, patience, wait = float("inf"), 10, 0 for epoch in range(200): model.train() for xb, yb in loader: optimizer.zero_grad() loss = criterion(model(xb), yb) loss.backward() optimizer.step() # 简单起见用训练损失做早停,正式项目应看验证集 if loss.item() < best_loss: best_loss, wait = loss.item(), 0 torch.save(model.state_dict(), "best_lstm.pt") else: wait += 1 if wait >= patience: print(f"早停于 epoch {epoch}") break if epoch % 20 == 0: print(f"epoch {epoch}, loss {loss.item():.6f}")逻辑说明:batch_size=64是显存和收敛稳定性的折中;lr=1e-3是 Adam 的默认甜点值,loss 震荡就降到 5e-4;patience=10表示连续 10 轮没进步就停。看损失曲线:正常是快速下降后趋平,如果训练 loss 一直不降,检查归一化和学习率;如果训练 loss 降但验证 loss 涨,就是过拟合,加 dropout 或减层数。正式项目一定要切验证集,这里为了代码简洁用训练 loss 演示,别照搬到生产。
4. 评估与调参:MAPE、RMSE 怎么算,超参数怎么搜
4.1 反归一化与评估指标:别在归一化空间里算误差
预测出来的值还在 0 到 1 之间,必须反归一化回真实功率量纲再算误差,否则 MAPE 毫无意义。
from sklearn.metrics import mean_absolute_error, mean_squared_error model.eval() with torch.no_grad(): pred = model(X_t).numpy() # 反归一化:功率是第 0 列,用 scaler 的 min/max 还原 p_min, p_max = scaler.data_min_[0], scaler.data_max_[0] pred_real = pred * (p_max - p_min) + p_min y_real = y * (p_max - p_min) + p_min mae = mean_absolute_error(y_real.flatten(), pred_real.flatten()) rmse = np.sqrt(mean_squared_error(y_real.flatten(), pred_real.flatten())) # MAPE 要避开夜间 0 功率,否则除零爆炸 mask = y_real.flatten() > 0.05 * p_max mape = np.mean(np.abs((y_real.flatten()[mask] - pred_real.flatten()[mask]) / y_real.flatten()[mask])) * 100 print(f"MAE {mae:.2f}, RMSE {rmse:.2f}, MAPE {mape:.2f}%")逻辑说明:反归一化用data_min_和data_max_,这是 MinMaxScaler 记录的训练集极值。MAPE 的坑在于夜间功率接近 0,分母极小导致百分比爆炸,所以用mask只统计功率大于装机 5% 的白天点。指标参考:短期光伏预测 MAPE 做到 10% 以内算不错,晴天能到 5%,阴雨天 15% 到 20% 也正常,别拿晴天指标去要求阴天。
4.2 超参数搜索:回看窗口、隐藏单元、学习率三件套
调参优先动这三个,其他先固定。
| 超参数 | 常用范围 | 影响 | 调整建议 |
|---|---|---|---|
| lookback | 16 / 24 / 48 / 96 | 太短抓不到周期,太长噪声大 | 从 24 起,按粒度换算成 6 到 24 小时 |
| hidden_size | 32 / 64 / 128 | 太小欠拟合,太大过拟合 | 数据少于 1 万条用 32 到 64 |
| learning_rate | 1e-3 / 5e-4 / 1e-4 | 太大震荡,太小收敛慢 | 从 1e-3 起,loss 震荡就减半 |
| num_layers | 1 / 2 / 3 | 深了易过拟合 | 2 层起步,数据少用 1 层 |
| dropout | 0.1 / 0.2 / 0.3 | 防过拟合 | 过拟合明显就加到 0.3 |
搜索策略上,网格搜索太慢,用随机搜索或 Optuna 贝叶斯优化,20 到 30 组就能找到不错的组合。别一上来就搜,先把默认参数跑通看基线,再针对性调。
4.3 对比基线:持久化模型和 LSTM 到底差多少
不上基线就不知道 LSTM 值不值。持久化模型最简单:拿当前时刻功率当未来所有时刻的预测。
# 持久化基线:用回看窗口最后一个点的功率,重复 horizon 次 persist = np.repeat(X[:, -1, 0:1], y.shape[1], axis=1) persist_real = persist * (p_max - p_min) + p_min persist_mape = np.mean(np.abs((y_real[mask] - persist_real.flatten()[mask]) / y_real.flatten()[mask])) * 100 print(f"持久化 MAPE {persist_mape:.2f}% vs LSTM MAPE {mape:.2f}%")逻辑说明:X[:, -1, 0]取每个样本回看窗口最后一刻的功率,np.repeat复制成 horizon 长度。如果 LSTM 的 MAPE 只比持久化低一两个点,说明模型没学到东西,得回去查特征和结构;低五个点以上才算有价值。这个对比是判断项目成败的硬标准,别跳过。
5. 避坑与排查:光伏 LSTM 训练里最常见的五个翻车现场
5.1 损失不下降,一直卡在高位
现象:训练几十轮 loss 几乎不动,或者只在小数点后第三位晃。原因通常是归一化没做或做错,或者学习率太小。解决:确认所有输入特征都归一化到 0 到 1,检查scaler.fit是不是只在训练集上做的;学习率从 1e-3 试到 1e-2,还不行就换优化器。
5.2 预测曲线整体平移,形状对但数值偏
现象:预测曲线跟真实曲线形状很像,但整体高了一截或低了一截。原因是反归一化用错了极值,或者训练集和测试集分布差异大。解决:反归一化必须用训练集的data_min_和data_max_,不能用测试集的;如果测试集是不同季节的数据,考虑重新训练或加季节特征。
5.3 夜间预测出非零功率
现象:明明太阳落山了,模型还预测出几百瓦。原因是夜间样本在训练集里占比大且功率为 0,模型没学好这个边界,或者归一化后 0 值被映射到非零。解决:在特征里显式加「是否白天」的 0/1 标志,或者后处理时把夜间时段强制置 0。
5.4 验证集 loss 上升但训练 loss 下降
现象:典型的过拟合。原因是模型太复杂或数据太少。解决:减层数、减隐藏单元、加 dropout、加 L2 正则,或者做数据增强(对训练样本加小幅噪声)。数据少于 5000 条时,LSTM 很容易过拟合,考虑先用简单模型。
5.5 换了数据集就报维度错误
现象:RuntimeError: input size mismatch。原因是新数据集的列数和顺序跟源码里写死的input_size=4不一致。解决:把特征列名和顺序固定下来,input_size改成len(features),别硬编码。每次换数据先打印X.shape确认维度。
6. 让预测再准一点的三个进阶技巧
第一个技巧是加注意力机制。标准 LSTM 把整段序列压成一个隐藏状态,信息有损。在 LSTM 输出上加一层注意力,让模型自己决定回看窗口里哪些时刻更重要——比如突变前那几个点权重更高。实现上就是在out上过一个线性层算权重再加权求和,代码量不大,MAPE 通常能再降一到两个点。
第二个技巧是误差修正。LSTM 对晴天拟合很好,阴雨天误差大。可以训练一个副模型专门预测残差,或者按天气类型分模型训练。我一般会先按辐照度把样本分成晴、多云、阴三类,分别训 LSTM,推理时按预报天气选模型,阴天场景提升明显。
第三个技巧是滚动预测。别一次性预测未来 24 小时,而是预测 1 步、把预测值喂回输入、再预测下一步。这样能利用最新信息,但误差会累积,适合短期几步。实现时注意每次喂回的值要归一化,别把真实值混进去造成泄漏。
验证方法上,别只看一个测试集。用滚动窗口做交叉验证:前 70% 训练、接下来 15% 验证调参、最后 15% 测试,再整体前移重跑几次,看指标稳定性。指标波动大说明模型不稳,别急着上线。
我自己踩过最深的坑是拿测试集调参,调到最后指标漂亮得不像话,一上真实数据就崩。后来养成习惯:测试集只在最后碰一次,调参全在验证集上做。这个习惯比任何调参技巧都值钱。希望帮到你。
本文还有配套的精品资源,点击获取