
简介基于LSTM的短期光伏预测算法Python实现项目主要面向计算机相关专业的学生和从业者适合作为毕业设计或期末课程大作业的完整参考方案。资源包共11个文件压缩后大小约3.89MB包含6个ipynb代码文件分别对应光伏预测主流程、负荷预测LSTM对照、单变量预测、基于规则集的方法与模拟试验另有xlsx格式的数据集、py辅助脚本、结果图片和Markdown说明文档目录结构清晰便于按模块学习和二次开发。该项目源自个人高分大作业评审分达到95分以上代码经过严格调试下载后可直接运行利用内置真实园区数据学习者可以完整走通数据预处理、LSTM模型搭建、预测误差评估与结果可视化等核心环节并通过与规则集方案的对比直观理解短期光伏预测中的建模差异与调优思路。资源发布后已有224人浏览学习适合需要快速上手时序深度学习预测项目的读者也可作为课程设计素材或个人自学练习使用。1. 基于LSTM的短期光伏预测为什么这个课题值得做成完整Python工程「基于LSTM的短期光伏预测」这个课题在不少课程训练营和本科毕业设计里都是常客——压缩包里有源码、有数据集目标是一套能跑的Python实现最终交出一份分数好看的大作业。课题本质是拿光伏电站的历史发电功率和气象观测训练一个LSTM神经网络预测未来几小时乃至一天的出力曲线。它适合刚学完RNN/LSTM、想做时间序列预测完整项目的Python开发者也适合新能源方向需要交课程设计的学生。一个容易被低估的结论是LSTM只是最后一个环节真正拉分的是数据清洗、滑窗构造和评估方式——这三个环节做对了最普通的双层LSTM也能拿到高分。2. 为什么选LSTM而不是ARIMA光伏数据的三个特性与网络结构对应关系光伏预测在大作业里出现频率极高但多数人把精力花在调LSTM层数上忽略了选型问题本身。选型不是「因为LSTM火」而是光伏功率序列的三个特性恰好落在LSTM的能力圈内。这一章从数据特性出发讲清楚为什么是LSTM顺带给你一张判断自己数据集适不适合LSTM的对照表。2.1 光伏功率序列的三个特性非平稳、强自相关、天气驱动光伏功率序列第一个明显特征是「强自相关」。晴天时上午十点的出力大概率接近昨天上午十点相邻15分钟或1小时的功率值相关性很高。这意味着预测主要靠历史值就能拿到不少分数也说明任何模型都得先具备记住昨天的能力。第二个特征是「非平稳」。均值、方差、日内形态会随季节和天气在多个时间尺度上漂移。夏季晴天的基线功率远高于冬季阴天云层在数分钟内就能让功率从80%跌到20%统计模型用固定差分和固定截距很难刻画这种局部突变。这类序列做差分平稳化处理时往往差分了依旧不平稳因为方差也在变。第三个特征是「强外生性」。功率由辐照度主导温度、湿度、风速、云量次之单纯的历史滞后项在天气突变时完全失效。大作业数据集里通常都带辐照度和温度列这就决定了输入特征必须包含气象变量而不是只把功率列丢给网络。三个特性合在一起光伏预测的本质是「历史序列记忆加外部天气特征」的组合问题。ARIMA擅长第一个特性但不擅长后两个普通RNN能处理序列但梯度消失问题明显LSTM在结构上同时覆盖这三者的能力。如果你的数据集里没有气象列LSTM仍能靠功率自回归拿及格分但想要高分表现气象特征是必需品。2.2 LSTM的门结构如何匹配这类序列LSTM在1997年被提出核心是给循环结构增加三个门遗忘门决定上一时刻的长期记忆保留多少输入门决定当前时刻的新信息写入多少输出门决定从记忆里读出多少给当下输出。这个机制的工程意义在于两点。第一梯度在跨越多时间步时有了「高速公路」。普通RNN的隐状态在每个时刻都执行一次矩阵乘法和tanh压缩反向传播经过几十步后梯度会呈指数级衰减LSTM通过单元状态逐时刻直传让模型实际上学会了时间尺度可调的滤波器。光伏序列里「昨天上午十点」对「今天上午十点」的影响跨越24个时间步普通RNN早就记不住LSTM可以把这种日周期性主动留在单元状态里。第二门结构天然适配「天气突变」场景。当云层遮挡导致辐照度骤降输入门会把异常气象写入遗忘门同时衰减此前晴天的强记忆输出功率预测随之跳变。这种「按需遗忘」的机制恰好对应光伏预测里最难的一类样本晴天转多云、阵雨过境。实际测试中你会发现这类样本的误差往往比平稳晴天样本大3到5倍但LSTM至少不会像ARIMA那样把预测值死死拉在历史均值附近。工程上不必手写门结构Keras里一行LSTM(64)就完成所有门的嵌入。但「遗忘门对应按需丢弃旧天气模式、输入门对应吸纳新辐照度信号」这个心智模型会直接影响你对dropout比例与层数选择的判断。理解它比多堆一层网络更值钱答辩时也能把「你为什么用LSTM」从一句口号讲成有依据的选型。2.3 算法对比ARIMA、普通RNN、XGBoost与LSTM的适用边界这是答辩最容易被追问的地方提前把对比坐实现场能省很大力气。模型时序依赖外生特征突变响应数据量需求大作业里的适用场景ARIMA强弱弱数千点即可无气象列、纯功率序列的基线模型XGBoost 滞后特征手动构造强中中等特征工程繁琐但上限高普通RNN强可接入中中到高流程演示不建议最终使用LSTM强可接入强中等偏高本课题核心模型效果稳定XGBoost其实在很多光伏预测竞赛里和LSTM打得有来有回它的优势是特征重要性可解释劣势是「昨天上午十点」这种跨日依赖需要手动构造滞后列。LSTM的优势是不用手工设计滞后数量滑窗一给网络自己学跨日规律。如果你的数据集不足3个月的逐时数据我反而建议优先试XGBoostLSTM在小样本上很难训练出稳定的跨日记忆硬跑只会得到一条被拉平的平均曲线。选型做完后不要直接开训。拿数据做两个快速检查画出连续7天的功率曲线看是否呈现「日峰值稳定、夜间归零、突变处有气象列对应」的形态再算一列24小时滞后的自相关系数如果数值在0.6以上LSTM就有足够的结构优势去发挥。这两步花不了十分钟但能帮你判断后续问题是出在数据还是出在模型答辩时也能成为你的工程佐证。3. 把CSV数据集变成训练样本归一化、滑窗与时间切分代码这一章是整个项目里翻车率最高的区域。大部分大作业成绩差不是LSTM没调好而是数据在进网络之前已经被处理坏了缺失值填错、归一化泄露、打乱时序、滑窗长度拍脑袋。这里按做光伏项目时常用的顺序走一遍代码可以直接拿去用。3.1 先看清CSV里有什么字段处理与缺失值填法光伏预测数据集常见的字段是时间戳、有功功率、辐照度、组件温度、环境温度、湿度、风速有的还带云量和气压。拿到CSV第一步不是建模而是用pandas把字段和缺失值清点一遍。import pandas as pd import numpy as np df pd.read_csv(solar_power.csv, parse_dates[time], index_coltime) print(df.columns.tolist()) print(df.isna().sum()) print(df.describe()) # 功率列夜间为0缺失用前向填充只有极端情况才补0 df[power_kw] df[power_kw].ffill().fillna(0) # 气象列用线性插值避免前向填充产生长平台 df[irradiance] df[irradiance].interpolate(methodlinear, limit_directionboth) df[temp_c] df[temp_c].interpolate(methodlinear, limit_directionboth)逻辑说明功率列在夜间本来就是0个别缺失点用ffill补出的平台不会破坏物理含义但气象列如果也用ffill阴晴交界处会出现一段「假持续值」线性插值在两个正常观测之间补出的过渡更符合实际情况。limit_directionboth保证序列首尾的缺失也能被补齐。参数说明interpolate默认限制是连续50个缺失点超过会保留NaN。如果你的数据是5分钟一采大段缺失更容易出现在通信故障段建议先打印缺失索引段长度再决定。fillna(0)只用于功率列且仅在极端情况下触发不要对气象列这么做。功率异常值值得单独处理一次。常见做法是超过装机容量1.2倍的值修剪到1.0倍低于0且偏差超过阈值10的值置0。这类规则写两行即可但能让后续MinMaxScaler不被极端值带偏也能避免训练时被一个传感器坏点把loss拉爆。3.2 时间特征与归一化先fit训练集再transform全量光伏预测必须引入时间位置信息否则模型分不清上午十点和晚上十点的差异。把小时编码成sin/cos两个连续变量比直接喂hour数字要平滑得多也避免午夜与凌晨之间的数值突变。随后做归一化原则只有一条MinMaxScaler只能fit到训练集片段上再transform到验证和测试集这是避免数据泄露的红线。from sklearn.preprocessing import MinMaxScaler df[hour] df.index.hour df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) features [power_kw, irradiance, temp_c, humidity, hour_sin, hour_cos] data df[features].values.astype(np.float32) train_len int(len(data) * 0.7) scaler MinMaxScaler() scaler.fit(data[:train_len]) data_scaled scaler.transform(data)逻辑说明scaler只看到前70%数据的最大值和最小值之后才把验证集与测试集按同一尺度变换。这一步做反先在全量数据上fit是大作业里最隐蔽的扣分点测试集的分布信息被提前透入训练过程测试误差会被系统性低估答辩追问一句「你的scaler是在哪里fit的」就能当场戳穿。参数说明特征列顺序建议把power_kw放在第0列后续反归一化时逐一对应。hour_sin与hour_cos不会增加太多计算量但能显著降低夜间样本的预测抖动。如果数据带明显季节趋势还可以加month的sin/cos光伏项目里通常只做hour就够。数值类型记得统一转成np.float32TensorFlow在float64上训练会莫名慢不少。3.3 滑窗构造与按时间切分lookback是唯一需要认真调的超参数现在把连续序列切成「过去lookback个时刻 → 未来某个时刻」的样本对。这一步决定了模型能看到多长的历史直接影响跨日记忆能力。def make_windows(data, lookback24, pred_step1): X, y [], [] # pred_step1 表示预测下一个采样点 for i in range(lookback, len(data) - pred_step 1): X.append(data[i - lookback:i, :]) y.append(data[i pred_step - 1, 0]) return np.array(X), np.array(y) lookback 24 X, y make_windows(data_scaled, lookbacklookback, pred_step1) print(X shape:, X.shape, y shape:, y.shape) # 按时间顺序切分严禁随机打乱 train_cut int(len(X) * 0.7) val_cut int(len(X) * 0.85) X_train, y_train X[:train_cut], y[:train_cut] X_val, y_val X[train_cut:val_cut], y[train_cut:val_cut] X_test, y_test X[val_cut:], y[val_cut:] print(X_train.shape, X_val.shape, X_test.shape)逻辑说明X的每个样本形状是(24, 6)表示连续24个时刻的6个特征y是该窗口后面一个时刻的功率。pred_step1是单步预测属于整个项目最稳妥的基线。想做成「提前1小时预测」把pred_step改成对应步数即可但误差会随先验距离快速上升建议先跑通单步再扩展。参数说明lookback24的前提是数据是1小时间隔。如果你的数据是15分钟一采24代表6小时跨日预测就得至少96。一个稳定起步值是「包含一个完整日周期再加2到4个点」。验证集尺寸取15%并不能保证覆盖晴天和阴天更稳的做法是按月份切前若干月训练、中间1月验证、最后1月测试这样能直接检验跨时段泛化能力。提示保存一份没归一化前的原始功率序列。后面调试模型、复现曲线、给答辩老师展示真实量纲结果时这份数据能让你少走很多弯路。4. LSTM预测模型搭建与训练源码从Keras模型到权重保存模型部分不需要堆太多创新点。一个双层LSTM加Dropout在多数大作业数据上已经足够拿到不错的验证分数。真正影响分数的是训练配置早停、学习率衰减、checkpoint保存三件套一个都不能少。这一章给完整可跑的源码思路按Keras/TensorFlow 2.x组织。4.1 网络结构双层LSTM加Dropout的源码与参数解释from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout n_features X_train.shape[2] model Sequential([ LSTM(64, return_sequencesTrue, input_shape(lookback, n_features)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()逻辑说明第一层LSTM的return_sequencesTrue保证输出仍是一整个时间步序列第二层才能继续压缩时序信息第二层return_sequencesFalse只输出最后一个时间步的隐状态再接Dense(1)完成单点功率回归。两个Dropout层在大作业数据量下能明显抑制过拟合但比例不宜超过0.3否则晴天曲线会被削平峰值段预测变得保守。参数说明64和32是经验起步值。数据量小于1万样本时建议32→16跑得更快更稳数据量超过5万128→64收益更明显。input_shape不要写死具体数字用lookback变量和n_features推导后面换数据分辨率时不用改模型代码。loss用mse而不是mae因为MSE对峰值预测误差惩罚更大光伏功率峰值段是最难也最重要的区域模型会自然把注意力多放一些在高峰值样本上。4.2 训练配置早停、学习率衰减与checkpoint三件套LSTM训练是典型的「前期快速下降、后期长期震荡」。不配回调函数的话模型会在过拟合后才被人工发现白跑几十个epoch。配齐三件套是省心省力的底线。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience12, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5), ModelCheckpoint(best_lstm.h5, monitorval_loss, save_best_onlyTrue) ] history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size64, callbackscallbacks, verbose1 )逻辑说明EarlyStopping看验证集loss连续12个epoch不下降就停restore_best_weightsTrue自动回退到验证集最优的权重这是最省心的过拟合后悔药。ReduceLROnPlateau在验证loss进入平台期时把学习率减半配合min_lr1e-5防止减到负而进程死掉。ModelCheckpoint在训练过程中持久化最优权重即使notebook崩溃也不会前功尽弃。参数说明patience12在100个epoch预算下不会过早停配合验证集覆盖多种天气是很安全的数值。batch_size64适合中等内存注意batch太大256以上会让LSTM收敛不稳定、验证loss反而升高batch太小8以下会让训练过程震荡到看不见下降趋势。epochs可以设到200有早停兜底不会白跑。训练完成后看一眼history里train_loss和val_loss的间距间距小说明泛化正常间距大且val_loss在训练后期不降反升说明该减小units或增大Dropout。如果train_loss也降不下去优先检查数据预处理是不是弄坏了功率序列而不是继续堆层数。模型结构本身在这个课题里的翻车率远低于数据问题。4.3 预测还原与可视化把归一化结果翻译回真实功率模型输出是只含归一化的单列需要结合scaler把功率列还原成真实kW单位否则画出的曲线没有物理含义打分也只是自娱自乐。import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error y_pred model.predict(X_test).flatten() y_true y_test def inverse_scale(arr, col_idx0, n_featuresn_features): dummy np.zeros((len(arr), n_features)) dummy[:, col_idx] arr return scaler.inverse_transform(dummy)[:, col_idx] y_true_real inverse_scale(y_true) y_pred_real inverse_scale(y_pred) mae mean_absolute_error(y_true_real, y_pred_real) rmse np.sqrt(mean_squared_error(y_true_real, y_pred_real)) print(fMAE: {mae:.2f} kW, RMSE: {rmse:.2f} kW) import matplotlib.pyplot as plt plt.figure(figsize(14, 4)) plt.plot(y_true_real[:300], labelreal, linewidth1) plt.plot(y_pred_real[:300], labelpred, linewidth1, alpha0.8) plt.legend() plt.ylabel(power (kW)) plt.show()逻辑说明inverse_scale用全零矩阵的对应列填充预测值走一次scaler.inverse_transform后把功率列取回。这一写法绕开了「scaler只能整行还原」的限制也能用在气象特征列上。输出指标和曲线要放一起看RMSE低但曲线滞后半拍说明模型学到了均值但没学到变化节奏量化与定性对照才是完整评估。参数说明预测结果默认是单步、单值y_pred的长度与X_test第一条样本的窗口相对位置有关。曲线里前几十个点不要看那是滑窗边界上的不稳定段。如需评估模型对峰值的捕捉可以把预测值和真实值同时按日最大功率做归一化再看峰谷时段的RMSE这比全局RMSE更能反映模型好坏。5. 短期光伏预测避坑清单五个拉低大作业分数的细节这一章的值直接体现在成绩单上。以下五条是光伏预测项目里反复翻车的点每一条都按「现象 → 原因 → 解决」写清楚排查时按顺序过一遍基本能覆盖大多数问题。5.1 测试集指标漂亮到不真实归一化泄露现象训练时val_loss正常但测试集RMSE比验证集还低一截画出的预测曲线和真实曲线几乎重合答辩演示时好看得让人心虚。原因MinMaxScaler在特征工程的开始就对整个数据集做了fit测试集的最大最小值进入了训练特征尺度。模型训练时已经间接见过测试集的分布边界测试误差不能反映真实泛化能力。解决把scaler.fit的范围严格限制在训练集内验证集和测试集只做transform。检查方法很简单训练启动前打印scaler.data_min_和scaler.data_max_再看它们是否只由训练集范围决定。这属于原则性错误改了重训一次损失不大但答辩被当场指出就是大事故。5.2 训练时随手shuffleTrue时序记忆被洗成集合记忆现象训练曲线非常健康验证误差持续下降但把模型放到后一个月数据上预测时误差暴增尤其是阴雨天完全失效。原因LSTM依赖的是时间顺序shuffleTrue把时间戳打乱后每个批次的样本不再是一条连续历史片段网络实际上在学「这批特征组合对应什么功率」的静态映射完全丢掉跨时间步的依赖关系。解决model.fit里不要设置shuffleTrue用默认顺序即可。如果数据量太大想更多随机性可以只在每个epoch内部做小范围扰动但必须保持单条样本内的时间顺序。自查技巧训练完成后随机打印10条测试样本的真实窗口和预测窗口如果时间对不上多半是切分或采样阶段乱了序。5.3 夜间零值把loss淹没模型只学白天、忽略黑夜现象模型白天预测尚可但凌晨和夜间的预测功率一直报出非零值整体误差被夜间段拖到极大。原因光伏功率在夜间是严格0这部分样本占了全天近一半但loss值却为0。如果训练样本中白天突变段占比太小LSTM的梯度几乎全部来自夜间平稳段它学会了「输出0」白天的辐照度变化信息被稀释晴天曲线的上升沿和下降沿都被修圆。解决两种常见做法。一是简单粗暴去掉辐照度低于20 W/m²的样本只保留有光照时段训练二是保留样本但给loss加权比如把白天的样本权重设为2到3倍。第一种更稳妥也更符合物理直觉但预测时依然要覆盖全天否则输出夜间段没有参考意义。注意别把夜间段从测试集里也删掉你需要这部分做完整度展示。5.4 lookback拍脑袋设成48或96样本同质化与过拟合现象lookback设得很大后训练时间显著变长val_loss在下降后反而上升预测曲线的峰值被系统性压低。原因lookback过长让每条样本包含的信息高度冗余。以15分钟采样、lookback96为例每条样本覆盖整整一天在数据量不大时相邻样本交叠区域太多模型反复看到相似模式很快把训练集背下来验证集稍微出现新天气形态就崩。解决先做一次滞后相关性检查看功率序列滞后多久后自相关系数降到0.3以下lookback取这个滞后点的三分之一到二分之一更稳。另一个可操作做法是写个简单循环分别测lookback在6、12、24、48下的验证RMSE选最低的。这个实验跑一轮网络很快换来的收益比调units还大。5.5 整体MAPE数值得分虚高零值分母与评价失真现象答辩展示MAPE8%看起来很厉害但画出的预测曲线在夜间段飘忽不定白天峰值的相对误差反而比夜间大得多。原因MAPE的分母是真实功率夜间真实功率接近0时任何一个微小偏差都会产生极大的百分比误差。模型为了降低MAPE会把夜间输出压到0附近白天的相对误差就相对被加权压得更大。整体MAPE是被夜间样本稀释后的数字游戏。解决用nRMSERMSE除以功率序列的峰值或均值作为主指标分两个时段分别报指标只算辐照度大于阈值时段的MAPE再单独报全天RMSE。这样既反应白天峰值预测能力又不会让夜间样本把误差藏起来。答辩时出示这种分层指标比单一MAPE有说服力得多。6. 结果验证与调参技巧不只输出RMSE还要画出逐时曲线6.1 与持续模型对比判断你的LSTM到底有没有用光伏预测有一个不可绕过的基准——持续预测也就是用当前功率预测下一个时刻。在晴朗连续时段它的精度极高LSTM跑不赢是正常现象真正要对比的是突变天气时段y_persist np.roll(y_true_real, 1) # 模拟持续模型 persist_rmse np.sqrt(mean_squared_error(y_true_real[1:], y_persist[1:])) lstm_rmse rmse print(fLSTM RMSE: {lstm_rmse:.2f} kW, Persistence RMSE: {persist_rmse:.2f} kW) print(fSkill Score: {1 - lstm_rmse / persist_rmse:.2%})如果算出的skill score是负数别慌先确认数据和训练配置没问题然后简化模型到单层LSTM再试。光伏预测里持续模型是最强对手在这个基准上哪怕提升5%都是能在答辩阶段展开讲的工程成果。6.2 低成本升级双向LSTM与会按时段评估在数据量允许的情况下把第一层LSTM换成Bidirectional(LSTM(...))往往能让验证RMSE再降3%到8%代价是训练时间翻倍。如果时间成本敏感我更推荐保留单向结构重点放在按季节和天气类型分统计的验证表上。我的习惯是训练结束后把预测曲线和真实曲线按「晴天/多云/阴雨」三张图横向对比再看逐月RMSE趋势。这个习惯救过我几次有一次全局指标很漂亮分月一看训练数据里包含了台风月模型在台风季的预测完全失控。光靠一个RMSE数字很容易把这种问题藏住。如果你正在赶工这份大作业记住一个优先级数据切分正确 时间滑窗合理 训练回调配齐 指标分层呈现这些做完再用剩余时间去调网络宽度。希望帮到你。本文还有配套的精品资源点击获取