简介:这份资源是面向计算机相关专业毕业设计学生与项目实战学习者的LSTM短期光伏预测完整项目,选题贴合新能源与深度学习交叉方向,难度适中,可直接作为毕设参考或课程设计素材。压缩包共28个文件,约3.38MB,以22张png训练曲线与模型结构图、1个csv光伏数据集、1个py脚本、1个ipynb笔记本为主,另含requirements依赖清单、README说明与gitignore配置,覆盖数据读取、模型搭建、训练评估到结果可视化的完整链路。项目经导师指导并通过评审,源码在本地编译运行、严格调试,可复现预测流程。目前已有125人学习,读者可据此掌握LSTM时序建模思路、光伏功率预测的数据处理方式与实验图表组织方法,并借助notebook逐步理解各模块实现,快速搭建自己的预测实验环境。
1. 从一份 LSTM 光伏预测毕设说起:它到底解决什么问题
光伏电站最怕的不是阴天,而是功率的突然抖动。早上云层一过,出力从 20% 跳到 80%,调度侧来不及响应,逆变器侧的限功率指令就下来了。超短期光伏功率预测要干的事,就是在未来 15 分钟到 4 小时这个窗口里,把出力曲线提前算出来。这个时间尺度上,数值天气预报基本帮不上忙,能用的只有历史功率序列和实时气象数据,而 LSTM 恰好擅长从这种带时序依赖的数据里挖规律。
这份「基于 LSTM 的光伏预测项目 python 源码 + 数据集」的毕设,本质上是把「历史功率 + 气象特征 → 未来功率」这条链路用代码跑通。它适合三类人:正在做新能源方向毕设、需要一份能跑通、能改、能写进论文的完整工程的学生;刚入门lstm 时间序列预测 python、想找一个真实场景练手的开发者;以及做电站运维、想验证预测模型到底能带来多少收益的工程师。数据集和源码是骨架,真正决定分数和落地效果的是特征工程、滑窗构造和评价口径这几处细节。
2. 数据与特征:光伏功率序列进 LSTM 之前要过哪几道手
2.1 光伏数据集的典型结构与字段含义
一份能用的光伏数据集,通常按时间戳逐点记录,采样间隔 5 分钟、15 分钟或 1 小时不等。核心字段一般包括:时间戳、实际功率(kW 或 MW)、辐照度(GHI/POA)、组件温度、环境温度、风速、湿度。有些数据集还会带理论功率或清洗后的标杆功率,用来做对比基准。
拿到数据第一件事不是建模,是看时间连续性。光伏数据最常见的三个毛病:夜间功率恒为 0 造成大量冗余样本、传感器故障导致整段缺失、限电时段功率被人为压低。这三类问题不处理,模型学到的就是「夜里不出力」这种废话,白天该预测的时段反而没学到东西。
我一般会先做一张时间覆盖表,统计每天的有效点数、缺失点数、零值点数,把明显异常的日期标出来。这一步用 pandas 几行就能搞定,但能省掉后面大量返工。
import pandas as pd df = pd.read_csv("pv_data.csv", parse_dates=["timestamp"]) df = df.set_index("timestamp").sort_index() # 按天统计有效点、缺失点、零值点 daily = df["power"].resample("D").agg( total="size", missing=lambda s: s.isna().sum(), zero=lambda s: (s == 0).sum(), valid=lambda s: s.notna().sum() - (s == 0).sum() ) print(daily.head(10))这段代码的作用是把「数据质量」量化成每天一行。resample("D")按天聚合,missing统计 NaN,zero统计夜间零值,valid是真正参与白天建模的点数。参数上,采样频率要和原始数据一致,如果原始是 15 分钟,resample就别写成"H",否则统计口径全乱。看结果时重点盯valid明显偏低的日期,那些天要么删,要么单独处理。
2.2 缺失值、异常值与夜间零值的处理策略
缺失值处理没有万能解。短缺口(连续 1~3 个点)用线性插值最稳;长缺口(超过 1 小时)我倾向于直接标记为无效,不参与训练,而不是硬插。异常值用物理边界卡:功率不可能为负,也不可能超过装机容量,超出的一律按缺失处理。
夜间零值是个容易被忽略的坑。很多人直接把全天数据丢进模型,结果模型花大量精力拟合「0 → 0」的平凡模式,白天的高波动反而欠拟合。常见做法是只保留辐照度大于某个阈值(比如 20 W/m²)的时段,或者给夜间样本降权。我一般会加一列is_daytime,训练时按它筛选,预测时也只对白天时段输出。
import numpy as np # 物理边界清洗 df.loc[df["power"] < 0, "power"] = np.nan df.loc[df["power"] > CAPACITY, "power"] = np.nan # 短缺口线性插值,长缺口保留 NaN df["power"] = df["power"].interpolate(method="linear", limit=3) # 白天标记 df["is_daytime"] = df["irradiance"] > 20limit=3是关键参数,表示最多连续插 3 个点,超过就留 NaN。这个值要结合采样间隔定:15 分钟采样时,3 个点就是 45 分钟,再长就不该插了。is_daytime的阈值 20 W/m² 是经验值,辐照度低于这个数时组件基本不发电,留着只会引入噪声。
2.3 归一化与滑窗构造:把序列喂给 LSTM 的正确姿势
LSTM 对输入尺度敏感,功率和辐照度量纲差好几个数量级,必须归一化。功率用 Min-Max 归一到 [0,1],辐照度、温度同理。注意归一化参数只能用训练集算,再应用到验证集和测试集,否则就是数据泄漏,论文里的高分全是假的。
滑窗构造是 LSTM 时间序列预测的核心。假设用过去 96 个点(15 分钟采样即 24 小时)预测未来 4 个点(1 小时),就要把序列切成一个个(X, y)对。X 是[样本数, 96, 特征数],y 是[样本数, 4]。
def make_windows(data, lookback=96, horizon=4): X, y = [], [] for i in range(len(data) - lookback - horizon + 1): X.append(data[i:i + lookback]) y.append(data[i + lookback:i + lookback + horizon, 0]) # 第0列是功率 return np.array(X), np.array(y) X, y = make_windows(scaled_array, lookback=96, horizon=4) print(X.shape, y.shape) # (N, 96, F) (N, 4)lookback决定模型能看多远的历史,horizon决定预测多长。这两个参数直接对应业务需求:调度要 4 小时预测,horizon就得覆盖 16 个点。切窗时注意别跨越日期边界把夜间和白天混在一起,否则样本语义就乱了。这一步做完,数据才算真正准备好进模型。
3. LSTM 模型搭建与训练:从 lstm 模型代码到能收敛的网络
3.1 网络结构选型:单层、堆叠还是加注意力
光伏功率预测里,单层 LSTM 加一个全连接输出层,往往就能拿到不错的基线。堆叠两层 LSTM 能提升对长依赖的建模能力,但参数量翻倍,小数据集上容易过拟合。注意力机制(Attention)这两年被大量用在光伏预测论文里,思路是让模型对不同时间步加权,突出临近预测点的关键信息。
我的建议是:数据量少于一年、采样 15 分钟时,先用单层 LSTM(隐藏单元 64~128)跑通基线;数据够多、追求论文创新点时,再上「LSTM + Attention」或「CNN-LSTM」混合结构。别一上来就堆复杂结构,毕设答辩时说不清每一层为什么加,反而扣分。
import torch import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, n_features, hidden=64, horizon=4): super().__init__() self.lstm = nn.LSTM(n_features, hidden, batch_first=True) self.fc = nn.Linear(hidden, horizon) def forward(self, x): out, _ = self.lstm(x) # out: (B, T, H) last = out[:, -1, :] # 取最后一个时间步 return self.fc(last) # (B, horizon)n_features是输入特征数(功率 + 辐照度 + 温度等),hidden是 LSTM 隐藏单元数,horizon是预测步长。batch_first=True让输入维度是(batch, seq, feature),符合直觉。取out[:, -1, :]表示只用最后一个时间步的隐状态做预测,这是多步预测里最常见的做法。如果想预测更长的序列,可以改成 Seq2Seq 结构,但毕设阶段没必要。
3.2 训练循环、损失函数与早停
损失函数用 MSE 或 MAE 都行。MSE 对大误差惩罚重,适合追求整体精度;MAE 对异常值更鲁棒,适合数据里有限电干扰的场景。优化器用 Adam,学习率 1e-3 起步,配合ReduceLROnPlateau在验证损失不降时衰减。
早停是防止过拟合的后悔药。设一个patience,验证损失连续若干轮不降就停,同时保存验证损失最低的模型权重。
model = LSTMForecaster(n_features=X.shape[2]) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.MSELoss() scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5) best_loss, wait, patience = float("inf"), 0, 15 for epoch in range(200): model.train() for xb, yb in train_loader: optimizer.zero_grad() loss = criterion(model(xb), yb) loss.backward() optimizer.step() model.eval() val_loss = 0 with torch.no_grad(): for xb, yb in val_loader: val_loss += criterion(model(xb), yb).item() scheduler.step(val_loss) if val_loss < best_loss: best_loss, wait = val_loss, 0 torch.save(model.state_dict(), "best.pt") else: wait += 1 if wait >= patience: print(f"early stop at epoch {epoch}") breakpatience=15表示验证损失连续 15 轮不降就停。这个值太小会早停,太大浪费时间。ReduceLROnPlateau的patience=5和学习率衰减配合,能让模型在后期精细收敛。保存best.pt而不是最后一轮权重,是因为最后一轮往往已经过拟合。
3.3 评价指标:别只看 RMSE,还要看这几个
光伏预测的评价指标,论文里最常出现的是 RMSE、MAE、MAPE 和准确率(1 - 归一化 RMSE)。但 MAPE 在功率接近 0 时会爆炸,所以白天时段算 MAPE 才有意义。我一般会同时报 RMSE 和「晴空准确率」——只统计晴天样本的准确率,因为阴天波动大,拉低整体指标是正常的。
| 指标 | 公式含义 | 适用场景 | 注意点 |
|---|---|---|---|
| RMSE | 均方根误差 | 整体精度 | 对大误差敏感 |
| MAE | 平均绝对误差 | 鲁棒评估 | 不放大异常 |
| MAPE | 平均绝对百分比误差 | 白天时段 | 夜间会爆炸 |
| 准确率 | 1 - RMSE/装机容量 | 汇报口径 | 需说明归一化方式 |
指标算完要分时段看:上午、正午、傍晚各算一遍。如果傍晚误差明显偏大,说明模型对爬坡阶段建模不足,可以考虑加特征或换损失函数。
4. 避坑与排查:光伏 LSTM 项目里最容易翻车的五件事
4.1 数据泄漏:归一化和滑窗顺序搞反
现象:验证集和测试集指标好得离谱,RMSE 低到不像真实数据,答辩时被问「测试集是不是参与训练了」答不上来。
原因:先用全量数据算 Min-Max 参数,再切训练测试集。归一化时用到了测试集的极值,等于把未来信息泄漏给了训练。
解决:严格按时间顺序切分,先切 train/val/test,再用训练集的 min/max 去 transform 验证和测试集。滑窗也要在切分之后做,别先滑窗再切分,否则窗口会跨集合。
4.2 夜间零值主导:模型学会了「不出力」但白天全错
现象:整体 RMSE 看着还行,但白天时段预测曲线几乎是平的,跟不上实际波动。
原因:夜间零值样本占比过高(一天里可能一半以上是 0),损失被这些平凡样本主导,模型没动力去拟合白天的高波动。
解决:训练时按is_daytime筛选,只保留白天样本;或者给白天样本更高权重。评估时也分开算,别让夜间零值把指标「拉好看」。
4.3 过拟合:训练损失一路降,验证损失早早反弹
现象:训练集 RMSE 降到 0.02,验证集停在 0.08 不动甚至上升。
原因:模型参数太多、数据太少,或者训练轮数过多。光伏数据如果只有几个月,堆两层 LSTM 很容易过拟合。
解决:加 Dropout(0.2~0.3)、减小隐藏单元、加 L2 正则,最直接的是早停。数据量实在少,可以用数据增强,比如对历史序列加轻微噪声生成新样本。
4.4 预测曲线滞后:模型只会「抄」上一个点
现象:预测曲线形状对,但整体比实际曲线晚一两个时间步,爬坡时尤其明显。
原因:LSTM 在强自相关序列上容易退化成「预测值 ≈ 上一时刻值」,这是时序预测的经典问题。
解决:在损失函数里加大爬坡时段的权重,或者引入差分特征(当前功率减上一时刻功率)让模型关注变化量。也可以换用能直接建模变化的结构,比如在输入里显式加入一阶差分。
4.5 环境与依赖:python 安装、numpy 版本这些小事最耗时间
现象:代码在别人机器上跑不通,报numpy版本冲突、torch和 CUDA 不匹配。
原因:深度学习环境对版本敏感,numpy、pandas、torch之间经常有兼容问题。
解决:用 conda 建独立环境,锁定版本。python 安装 numpy 库这类操作别用系统 pip 直接装,容易污染全局。建议写一份requirements.txt,把torch、numpy、pandas、scikit-learn的版本都钉死,换机器时一键复现。
5. 把毕设做成能打的项目:多步预测、误差分析与可复现习惯
5.1 从单步到多步:直接多步 vs 滚动预测
毕设里如果只做单步预测(预测下一个点),工作量偏薄。多步预测有两条路:直接多步(一次输出未来 4 个点)和滚动预测(预测 1 个点,把它拼回输入再预测下一个)。直接多步实现简单、误差不累积,但各步之间独立;滚动预测更贴近真实调度场景,但误差会逐步放大。
我一般两个都跑,对比着写进论文。直接多步用前面的LSTMForecaster就行,horizon=4一次输出。滚动预测要写个循环,每次取预测值拼到输入尾部,滑窗前进一格。
def rolling_forecast(model, init_window, steps=4): window = init_window.copy() # (1, lookback, F) preds = [] for _ in range(steps): with torch.no_grad(): p = model(window).item() # 单步输出 preds.append(p) # 把预测值填到窗口最后一行的功率列,滑窗前进 new_row = window[:, -1, :].clone() new_row[:, 0] = p window = torch.cat([window[:, 1:, :], new_row.unsqueeze(1)], dim=1) return preds这段代码的关键在new_row[:, 0] = p,把预测功率填回特征矩阵的功率列,其他气象特征沿用上一时刻值(真实场景里未来气象也未知,这是合理近似)。滚动预测的误差会累积,4 步之后偏差可能明显变大,所以论文里要如实报告每一步的误差,而不是只报最后一步。
5.2 误差分析:按天气类型和时段拆开看
整体 RMSE 是个笼统数字,真正有价值的是拆开看。按天气分:晴天、多云、阴天、雨天各算一遍;按时段分:上午爬坡、正午平稳、傍晚下降各算一遍。拆完你会发现,模型在晴天正午误差很小,在多云天的爬坡阶段误差最大——这才是论文里该重点讨论的地方。
我习惯画两张图:一张是预测 vs 实际的时序对比,挑一个典型多云天;一张是误差随预测步长变化的曲线。前者看形状,后者看衰减。如果误差随步长快速上升,说明模型对长依赖建模不够,可以考虑加 Attention 或换更长的lookback。
5.3 可复现习惯:随机种子、配置文件和实验记录
毕设最尴尬的场景是:答辩前想复现最好那组结果,发现忘了当时的学习率是多少。避免这个坑的办法很土但有效——所有超参数写进一个config.yaml,每次实验存一份日志,固定随机种子。
import random, numpy as np, torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)固定种子后,同样的代码和数据能跑出同样的结果,论文里的数字才站得住。配置文件里把lookback、horizon、hidden、lr、batch_size、patience全列出来,换实验只改配置不改代码。这套习惯看起来麻烦,但等你写到第三章要对比五组实验时,会庆幸当初这么干了。
最后说个我自己的教训:做光伏预测这几年,翻车最多的从来不是模型结构,而是数据清洗和评价口径。模型再花哨,数据里混着限电时段的假低值,指标再漂亮也是自欺欺人。先把数据摸透,再谈网络深浅,这个顺序别反。希望帮到你。
本文还有配套的精品资源,点击获取