LSTM短期光伏功率预测实战:从数据清洗到模型部署 简介一份基于Python与LSTM的短期光伏预测算法实战资源适合计算机、人工智能、电气及自动化方向的学生用于毕设、课设或项目初探。项目源自个人毕业设计代码经完整测试可结合园区数据完成光伏与负荷预测建模覆盖单变量和多变量LSTM、规则集对比、数据获取与预处理脚本、可视化图表及README说明帮助快速理解从数据清洗、特征构造到模型训练与效果评估的完整流程。压缩包共11个文件以6个Jupyter Notebook为核心演示代码辅以Python脚本、Excel示例数据、图片和Markdown文档整体仅3.89MB轻量易部署。已有199人学习适合需要快速复现LSTM时序预测流程、或在此基础上修改扩展的读者。资源还包含模拟程序与储能框架参考可支撑答辩演示和后续功能拓展是短期光伏预测项目起步的实用参考。1. 短期光伏功率预测用LSTM不只是因为它能记住昨天并网光伏电站要提前一天申报发电计划电网侧则要按小时级甚至分钟级去调整备用容量。晴天还好真正让调度头疼的是云团过境前一分钟出力还在额定值的八成后一分钟掉到两成等云过去了又瞬间拉满。这种短时波动靠物理辐照模型很难追因为云的运动本身就不是一个确定性问题。这也是为什么近几年的短期和超短期光伏功率预测方案里数据驱动方法逐渐成了主流而 LSTM 又是其中被用得最多的一种。LSTM 能在光伏预测里站住脚原因是它的细胞状态可以跨时间步传递信息既保留了昼夜、季节这种长周期规律又能对分钟级的功率突变做响应。配合 Python 生态里的 TensorFlow/Keras特征工程、训练、部署可以用一套代码串起来。这篇文章就从选型逻辑开始把数据清洗、特征构造、模型搭建、参数设置和评估验证完整走一遍结尾再讲几个工程落地上最容易踩的坑。2. LSTM 时间序列预测的建模逻辑为什么光伏功率适合用门控结构2.1 时序模型选型RNN、LSTM、GRU 在光伏场景下的取舍光伏功率序列本质上是一个带强周期性的非平稳时间序列。拿一个 15 分钟粒度的数据集来说一天 96 个点夜里长时间贴着零白天则是一条随辐照度起伏的曲线。用普通 RNN 去拟合这种序列反向传播时梯度经过多个时间步后会迅速衰减前几小时的云况信息到下午基本就传不过来了预测后段误差会明显变大。LSTM 通过遗忘门、输入门、输出门和贯穿序列的细胞状态解决了梯度传播问题。相比之下GRU 把三个门简化成两个参数量更少在小数据集上训练更快但在光伏功率这种同时存在强周期和随机突变的序列上LSTM 对极端云况的记忆保持能力通常更稳。Transformer 模型也在时序预测里流行但需要的数据量和调参成本都更高做短期光伏预测时LSTM 仍然是最容易跑通且效果有保障的起点。模型结构长期依赖能力参数量光伏功率场景适配度RNN弱梯度易消失最少适合小时级短窗遇到功率骤降骤升误差放大LSTM强细胞状态贯穿全程中等能同时捕捉昼夜周期和分钟级波动短期预测首选GRU较强结构更简中等偏少小数据量下收敛快极端天气下记忆能力略弱选择 LSTM 还有一层工程原因Keras 里 LSTM 层只需要指定单元数和输入形状不需要像 Transformer 那样额外处理位置编码和注意力掩码。对于光伏预测这种输入特征维度不高、序列长度不太长的任务LSTM 的实现成本最低。2.2 遗忘门、输入门、输出门分别对应光伏数据的哪些变化把 LSTM 的三个门对应到光伏功率的物理过程选型理由会清楚很多。遗忘门决定从细胞状态里丢掉多少旧信息。对光伏数据来说这意味着模型要学会在日出后逐步弱化前一夜的功率记忆在持续的阴天里淡出几天前的晴空出力水平。输入门决定当前时间步有多少新信息被写入状态。当辐照度骤降时当前时刻的功率变化和气象特征会通过输入门进入细胞状态成为后续预测的重要依据。输出门则控制当前状态有多少被用于生成这一时刻的输出值。理解了这三个门的含义就能明白为什么 LSTM 对光伏预测是合适的它不需要人工告诉模型“今天是什么天气类型”模型会在训练中自己学会何时保留长周期规律、何时切换到短时突变模式。这也是为什么在特征工程里加入辐照度、温度、湿度等气象变量后预测效果往往比单纯用功率历史值自回归更好。2.3 把算法流程图落到张量形状输入和输出对齐是关键看 LSTM 算法流程图的时候重点是理解数据从输入到输出的形状变化而不是死记神经网络结构图。光伏预测里输入样本的常见组织方式是三维张量[样本数, 时间步数, 特征数]。时间步数是滑动窗口长度特征数包含历史功率、辐照度、温度等变量。一个常见错误是把 LSTM 的输出直接理解成“一个数”忽略了 Keras 中return_sequences参数的影响。只取最后一步输出用于回归时return_sequencesFalse输出形状是[样本数, 单元数]如果需要每个时间步都有输出就必须设成True。在建短期预测模型时一般只在最后一层 LSTM 前使用完整序列输出最终接全连接层得到预测值。3. 光伏数据清洗与特征构造决定预测上限的一步3.1 数据质量检查先看缺测率再看分布是否合理光伏预测项目里最花时间的往往不是模型调参而是数据清洗。电站采集系统传回来的原始数据常见问题包括通讯中断导致的整段缺测、辐照度仪表漂移导致的负值、夜间功率传感器残留的非零读数以及重复时间戳。拿到数据后先做一个快速统计按天统计功率最大值是否超过装机容量、夜间时段是否有持续非零值、缺测占比是否超过 10%。超过 10% 且缺测集中在白天时段时线性插值的可信度会明显下降建议直接考虑删除该时间段或补充气象卫星数据。15 分钟粒度下短时间缺测用前后值线性插值即可连续超过 2 小时的大段缺测不建议强行填充。字段单位常见异常处理方式有功功率 PkW夜间非零、超过装机容量、死值夜间按当地时间置零超容量值剔除死值用邻域中值替换水平辐照度 GHIW/m²负值、缺测、高值异常负值置零缺测线性插值与同时刻功率做散点图检查相关性温度℃传感器漂移、极端值与正常气候范围比对超出 3 倍标准差时标记剔除处理完异常值后用重采样统一时间戳DataFrame.resample(15min).mean()是常见做法重复时间戳取均值而不是直接删除可以尽可能保留有效信息。3.2 归一化与时间编码把物理量转成模型友好输入LSTM 对输入特征的尺度敏感归一化是必须做的。功率和辐照度这类有明确上下界的物理量用 MinMaxScaler 缩放到 0 到 1 之间足够如果数据里存在极端突刺RobustScaler 按分位数缩放会更稳不容易被离群点带偏。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler df pd.read_csv(pv_power.csv, parse_dates[dt]) df df.set_index(dt).resample(15min).mean() # 基础清洗夜间功率归零超过装机容量1.2倍的置为NaN后插值 df.loc[df.index.hour 5, P] 0 df[P] df[P].mask(df[P] 1200).interpolate() # 时间特征用正余弦编码保留周期连续性避免把23点和0点切成两个孤立的类别 hour df.index.hour df[hour_sin] np.sin(2 * np.pi * hour / 24) df[hour_cos] np.cos(2 * np.pi * hour / 24) # 归一化先fit再transform训练后要保存scaler供预测时复用 feat_cols [P, GHI, T, RH, hour_sin, hour_cos] scaler MinMaxScaler() df[feat_cols] scaler.fit_transform(df[feat_cols])这段代码里有几个值得留意的点。重采样把原始数据统一到 15 分钟频率缺测的时段会产生 NaN后续插值把它们补上。时间特征的正余弦编码比直接用 hour 数字更合理因为小时 23 和 0 在数值上差距很大但在物理含义上是连续的。归一化必须在划分训练集和测试集之前先 fit 一次之后用同一个 scaler 去 transform 测试集测试时输入的真实物理值也要经过完全相同的变换。3.3 滑动窗口构造样本对seq_len、label_len 和验证集切分超短期光伏预测的目标是未来 0 到 4 小时也就是 16 个 15 分钟点。实践中并不总是直接预测全部 16 步更常见的做法是用过去 24 步6 小时预测未来 4 步1 小时再通过滚动方式延伸预测时长。def make_sequences(data, seq_len, label_len, feat_cols, target_idx0): X, y [], [] for i in range(len(data) - seq_len - label_len): X.append(data[i:iseq_len]) y.append(data[iseq_len:iseq_lenlabel_len, target_idx]) return np.array(X), np.array(y) SEQ_LEN 24 LABEL_LEN 4 data_arr df[feat_cols].values X, y make_sequences(data_arr, SEQ_LEN, LABEL_LEN, feat_cols) # 按时间顺序切分不能随机打乱 n_train int(len(X) * 0.7) n_val int(len(X) * 0.15) X_train, y_train X[:n_train], y[:n_train] X_val, y_val X[n_train:n_trainn_val], y[n_train:n_trainn_val] X_test, y_test X[n_trainn_val:], y[n_trainn_val:]label_len取 4 而不是 16是短期预测任务里常见的选择预测步数越少误差积累越小模型的输出也更容易收敛。如果想直接预测更长时间可以把 LSTM 的输出维度加大但误差会随预测步长迅速增长不如滚动预测稳定。验证集切分必须按时间顺序完成随机打乱会把未来信息泄漏进训练集导致验证指标虚高模型上线后表现断崖式下跌。4. 用 Python 构建 LSTM 光伏预测模型源代码组织与关键参数4.1 模型定义一个可以直接运行的最小网络有了处理好的样本对模型部分用 Keras Sequential 接口就够了。常见结构是两层 LSTM 加 Dropout再接全连接层输出。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm_model(seq_len, n_features, label_len): model Sequential([ # return_sequencesTrue 时输出每个时间步的隐状态供第二层LSTM继续处理 LSTM(64, input_shape(seq_len, n_features), return_sequencesTrue), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), # 输出维度与预测步数一致单元数越大越容易拟合复杂曲线但也更容易过拟合 Dense(label_len) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), losshuber ) return model model build_lstm_model(SEQ_LEN, len(feat_cols), LABEL_LEN) model.summary()return_sequencesTrue让第一层 LSTM 输出所有时间步的隐状态第二层 LSTM 才能继续处理完整的序列信息。Dropout 放在 LSTM 层之间训练时随机丢弃一部分神经元减少对训练集噪声的拟合。损失函数选了 huber它对功率骤升骤降造成的离群点不像 MSE 那么敏感在光伏数据上通常比 MSE 稳。4.2 训练参数怎么设学习率、批量大小、损失函数和早停LSTM 训练参数的选择虽然有一定经验成分但逻辑是清楚的。Adam 优化器下学习率从 0.001 起步如果验证集 loss 在前几个 epoch 震荡剧烈降一个数量级到 0.0001。批量大小在数据量不大时取 32 即可批量太大模型收敛快但容易收敛到平坦的次优解批量太小训练时间会成倍增加。参数建议起始值调整逻辑learning_rate0.001验证集 loss 震荡则降到 0.0003 或 0.0001batch_size32序列样本数过万时可调到 64加速训练losshuber功率毛刺多的电站用 huber数据干净可用 MSEepochs100配合早停不设上限以验证集 loss 为准patience10验证集 loss 连续 10 个 epoch 不降则停止训练早停是防止过拟合最直接的手段。光伏数据里晴天和阴天的样本分布极不均衡训练到后期模型容易把晴天特征背下来在验证集上表现反而变差。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience5) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size32, callbacks[early_stop, reduce_lr], verbose1 )训练过程中重点观察验证集 loss 和训练集 loss 的差距。训练集 loss 持续下降而验证集 loss 连续多个 epoch 不降反升说明模型开始记忆训练集的噪声早停会自动恢复到最佳权重。如果验证集 loss 从一开始就不下降优先检查归一化是否用了同一个 scaler、样本序列是否构造正确。4.3 调参经验先固定网络结构再动数据一个常见的调参误区是上来就堆参数量。光伏功率序列的复杂度并没有高到需要超大网络训练数据只有几千个样本时单层 LSTM 配 32 或 64 个单元往往就够用了。参数调优的顺序应该是先把滑窗长度和预测步数定下来用一组默认参数跑通全流程确认验证集指标合理后再逐个调整 LSTM 单元数和 Dropout 比例。调参时还要注意shuffle的问题。Keras 的model.fit默认会打乱训练样本的顺序这在一般任务里没问题但时间序列任务里如果样本顺序被打乱模型就失去了利用相邻样本连续性学习的能力。可以在fit里设shuffleFalse或者保持默认但验证集必须严格按时间顺序切片。5. 预测效果验证与工程化落地从评估指标到源码交付5.1 按装机容量归一化的误差指标才有可比性不同光伏电站的装机容量差异很大直接用 RMSE 或 MAE 做横向对比没有意义。工程上更常用的是按装机容量归一的 NMAE 和 NRMSE。from sklearn.metrics import mean_squared_error, mean_absolute_error capacity_kw 1000 # 电站装机容量单位kW y_pred model.predict(X_test) y_true y_test # 预测输出是归一化后的值要反归一化回真实功率才能算误差 y_pred_real scaler.inverse_transform( np.concatenate([y_pred, np.zeros((len(y_pred), len(feat_cols)-1))], axis1) )[:, 0] y_true_real scaler.inverse_transform( np.concatenate([y_true, np.zeros((len(y_true), len(feat_cols)-1))], axis1) )[:, 0] nmae mean_absolute_error(y_true_real, y_pred_real) / capacity_kw * 100 rmse np.sqrt(mean_squared_error(y_true_real, y_pred_real)) print(fNMAE: {nmae:.2f}% RMSE: {rmse:.2f} kW)反归一化是这里最容易出错的环节。MinMaxScaler是在整个特征矩阵上训练的预测的输出只有功率一列其他特征位置要补零才能调用inverse_transform。如果直接把scaler单独 fit 在功率序列上再对多步预测输出做反归一化就会报维度错误或者得到错位的数值。更稳妥的做法是在训练前就为功率列单独建一个 scaler预测时直接用它变换。5.2 模型持久化与滚动预测预测值要能接回滑窗训练完成后的模型要保存下来供定时预测任务加载使用。model.save(lstm_pv.h5)之后用tf.keras.models.load_model加载即可。真正的难点在滚动预测做未来 4 小时预测时如果一次性输出误差会随时间步增大工程上更常见的做法是每次预测 4 步把得到的预测功率补充到滑窗末尾再滑动窗口继续预测。def rolling_predict(model, last_seq, n_steps, step_len4): predictions [] current_seq last_seq.copy() for _ in range(n_steps // step_len): y_step model.predict(current_seq[np.newaxis, :, :], verbose0) predictions.append(y_step[0]) # 构造新窗口丢掉最旧的step_len步把预测值拼接到末尾 next_seq np.vstack([current_seq[step_len:], np.zeros((step_len, current_seq.shape[1]))]) next_seq[-step_len:, 0] y_step[0] next_seq[-step_len:, 1:] 0 # 缺失的气象特征用最近值或0填充 current_seq next_seq return np.concatenate(predictions)滚动预测的关键假设是未来时段的气象特征未知只能用最近观测值或预报值填充。如果项目里能拿到数值天气预报数据把辐照度、温度的预报值填进去预测精度会明显提升。这也解释了为什么单纯依赖功率历史值的自回归模型预测时长超过 1 小时后误差会快速变大。5.3 文档说明里必须写清楚的三个运行细节标题里强调了“源代码文档说明”源码交付时最容易让接手人头疼的往往不是网络结构而是数据流动的细节。第一个要写清楚的是数据格式原始 CSV 的时间列格式、时区是 UTC 还是北京时间、功率单位是 kW 还是 MW这些不写明白换一份数据就跑不通。第二个是归一化和反归一化的完整流程scaler 文件要和模型文件一起交付否则加载模型后无法对新的输入做变换。第三个是天气突变时的评估方式建议在测试集里单独挑多云天和晴天各统计一次 NMAE因为晴天的误差可能只有 5%多云天可能冲到 20%只看总体平均值会掩盖模型在复杂天气下的真实表现。跑通基础模型后可以先集中精力把这几处补齐给训练脚本加一个--input --model_dir参数让代码能接受任意路径的数据文件把预测结果画成曲线图和真实功率叠加对比人工看一眼就能发现预测是否出现了相位偏移或整体偏低。本文还有配套的精品资源点击获取