☰
LSTM光伏功率预测实战:从数据对齐到多步预测的Python实现
2026/10/11 11:57:03 网站建设 项目流程

简介:这是一套面向计算机相关专业毕业设计与深度学习实践者的光伏发电预测项目源码,以长短期记忆神经网络为核心,解决短期光伏发电量时间序列预测问题,难度定位中等,适合需要完整课题案例或开展实践训练的学习者。压缩包共38个文件,约3.86MB,以22张png训练曲线与预测对比图、4个py源码文件、1个ipynb交互式笔记本、1个csv光伏数据集及txt依赖说明为主,另含模型与损失模块目录,结构清晰便于按模块查阅。已有78人学习下载。项目涵盖数据预处理、模型搭建、训练与评估的完整流程,数据集经清洗后可直接用于训练,关键算法附有详细注释,便于理解与二次开发;配套图表可直观对照预测效果,requirements与README说明环境依赖与使用方式,为时间序列预测研究提供可靠参考。

1. 光伏功率预测为什么总在午后翻车:从 LSTM 时间序列建模说起

做过光伏电站运维的人大多有个共同体会:早上和傍晚的功率曲线预测得挺准,一到中午前后就集体翻车,误差能飙到 20% 以上。这不是模型不行,而是光伏出力本身受云层移动、组件温度、辐照度突变影响,短时波动极强,传统前馈神经网络把每个时刻当独立样本处理,丢掉了时间维度上的依赖关系。LSTM 神经网络做光伏发电预测,核心价值就在于它用门控机制记住了「过去几个时间步的辐照度和温度是怎么变化的」,从而在功率爬坡或骤降时给出更合理的推断。这套 Python 实现方案适合有基础编程能力、手头有历史发电数据、想快速搭一套可用预测系统的工程师,也适合做毕业设计或课程项目的同学。下面从数据准备到模型调参,把整条链路拆开讲清楚。

2. 数据准备与特征工程:把气象和功率对齐成 LSTM 能吃的格式

2.1 光伏预测到底需要哪些输入特征

LSTM 做时间序列预测,输入不是越多越好,而是要选和功率有物理因果关系的量。我一般会保留以下几类:

特征类别具体字段采样频率说明
气象特征总辐照度、直射辐照度、散射辐照度15min最核心的输入,和功率几乎线性相关
温度特征环境温度、组件背板温度15min组件温度超过 25℃ 后效率下降明显
时间特征小时、分钟、一年中的第几天—帮模型区分季节和日内位置
历史功率前 1~4 个时刻的功率值15min给模型提供自回归信息
统计特征前 1 小时功率均值、方差15min平滑短时波动

注意:风速和湿度对功率的直接影响很小,加了反而增加噪声,除非你的电站有特殊地形导致频繁积灰。

2.2 用 Pandas 做时间对齐和缺失值处理

光伏数据最常见的坑是气象站和逆变器的时间戳对不上,差几分钟就会导致特征错位。下面这段代码把两个来源的数据按 15 分钟粒度对齐,并处理夜间功率为负的异常值。

import pandas as pd import numpy as np # 读取气象数据和功率数据,假设都是 CSV,含 timestamp 列 weather = pd.read_csv('weather.csv', parse_dates=['timestamp']) power = pd.read_csv('power.csv', parse_dates=['timestamp']) # 统一重采样到 15 分钟,气象取均值,功率取均值 weather_15 = weather.set_index('timestamp').resample('15min').mean() power_15 = power.set_index('timestamp').resample('15min').mean() # 按时间戳外连接,保证不丢时刻 df = pd.merge(weather_15, power_15, left_index=True, right_index=True, how='outer') # 夜间功率为负是传感器零漂,直接截断为 0 df['power'] = df['power'].clip(lower=0) # 辐照度缺失用前向填充,温度缺失用线性插值 df['irradiance'] = df['irradiance'].fillna(method='ffill').fillna(0) df['temp_module'] = df['temp_module'].interpolate(method='linear') # 删除仍然缺失的行 df = df.dropna() print(f'对齐后数据量:{len(df)} 条,时间范围:{df.index.min()} ~ {df.index.max()}')

这段代码的逻辑是先用resample把不同频率的数据统一到 15 分钟,再用merge外连接保证时间戳完整。辐照度用前向填充是因为它变化连续,温度用线性插值更平滑。夜间功率截断为 0 是必须的,否则负值会让模型学到错误模式。

2.3 滑动窗口构造和归一化:别让量纲毁了 LSTM

LSTM 对输入尺度敏感,辐照度可能上千,温度只有几十,不归一化的话梯度会被大数值主导。我一般用 MinMax 归一化到 [0,1],然后按滑动窗口切样本。

from sklearn.preprocessing import MinMaxScaler # 选择建模用到的列 feature_cols = ['irradiance', 'temp_module', 'temp_ambient', 'hour', 'dayofyear', 'power'] data = df[feature_cols].values # 归一化:注意 scaler 要保存,预测时要反变换 scaler = MinMaxScaler(feature_range=(0, 1)) data_scaled = scaler.fit_transform(data) # 滑动窗口:用过去 8 个时刻(2 小时)预测下一个时刻功率 def create_sequences(data, lookback=8): X, y = [], [] for i in range(len(data) - lookback): X.append(data[i:i+lookback, :-1]) # 特征列 y.append(data[i+lookback, -1]) # 功率列 return np.array(X), np.array(y) lookback = 8 X, y = create_sequences(data_scaled, lookback) print(f'样本形状:X={X.shape}, y={y.shape}')

lookback=8表示用过去 2 小时的数据预测下一时刻,这个值可以根据你的采样频率调整。如果数据是 1 分钟粒度,lookback 可以设 60 左右。归一化时一定要把 scaler 存下来,预测完功率后要反变换回真实值,否则输出永远是 0 到 1 之间的小数。

3. 搭一个能跑的 LSTM 模型:Keras 实现与训练参数怎么定

3.1 网络结构设计:几层 LSTM、多少隐藏单元

光伏功率预测不需要太深的网络,我试过 1 层和 3 层 LSTM,效果差别不大,但训练时间差一倍。常用配置是 2 层 LSTM,每层 64 个隐藏单元,后面接一个全连接层输出单值。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam model = Sequential([ # 第一层 LSTM,return_sequences=True 才能接第二层 LSTM(64, return_sequences=True, input_shape=(lookback, X.shape[2])), Dropout(0.2), # 第二层 LSTM,只返回最后一个时间步 LSTM(64, return_sequences=False), Dropout(0.2), # 全连接层输出功率 Dense(1) ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae']) model.summary()

return_sequences=True是第一层必须加的,否则第二层 LSTM 收不到序列输入。Dropout 设 0.2 是为了防止过拟合,光伏数据季节性重复强,过拟合很常见。学习率 0.001 是 Adam 的默认值,如果 loss 震荡厉害可以降到 0.0005。

3.2 训练集验证集划分与早停策略

时间序列不能随机打乱划分,否则未来信息会泄露到训练集。我一般按时间顺序取前 70% 做训练,中间 15% 做验证,最后 15% 做测试。

from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau # 按时间顺序划分 train_size = int(len(X) * 0.7) val_size = int(len(X) * 0.15) X_train, y_train = X[:train_size], y[:train_size] X_val, y_val = X[train_size:train_size+val_size], y[train_size:train_size+val_size] X_test, y_test = X[train_size+val_size:], y[train_size+val_size:] # 早停:验证 loss 连续 10 轮不降就停 early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) # 学习率衰减:验证 loss 不降时减半 reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=100, batch_size=32, callbacks=[early_stop, reduce_lr], verbose=1 )

patience=10表示验证 loss 连续 10 轮不下降就停止,restore_best_weights=True保证模型恢复到最优状态。ReduceLROnPlateau是防止学习率过大导致在最优解附近震荡。batch_size 设 32 是折中值,数据量大的话可以加到 64 或 128。

3.3 预测结果反归一化和误差评估

训练完不能直接看 loss,要把预测值反变换回真实功率,再算 MAE 和 RMSE。

# 预测 y_pred_scaled = model.predict(X_test) # 反归一化:构造一个和原始数据同宽度的矩阵 # 功率在最后一列,其他列填 0 不影响反变换 dummy = np.zeros((len(y_pred_scaled), data.shape[1])) dummy[:, -1] = y_pred_scaled.flatten() y_pred = scaler.inverse_transform(dummy)[:, -1] # 真实值反归一化 dummy_true = np.zeros((len(y_test), data.shape[1])) dummy_true[:, -1] = y_test y_true = scaler.inverse_transform(dummy_true)[:, -1] # 计算误差 from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(y_true, y_pred) rmse = np.sqrt(mean_squared_error(y_true, y_pred)) print(f'MAE: {mae:.2f} kW, RMSE: {rmse:.2f} kW')

反归一化时构造 dummy 矩阵是因为 scaler 是按所有列拟合的,单独反变换一列会报维度错误。MAE 反映平均偏差,RMSE 对大误差更敏感,光伏预测里 RMSE 通常比 MAE 高 30% 到 50%,因为中午波动大。

4. 避坑与排查:光伏 LSTM 预测最常见的 5 个翻车现场

4.1 预测曲线整体平移:时间戳错位了

现象:预测功率曲线形状对,但整体比真实值提前或滞后一两个时刻。原因:气象数据和功率数据的时间戳没有严格对齐,或者重采样时用了不同的时区。解决:统一用 UTC 时间戳,重采样前先检查两个表的timestamp列是否都是datetime64类型,用pd.to_datetime强制转换。

4.2 夜间预测出负功率:归一化范围没截断

现象:模型在夜间输出负的功率值,物理上不可能。原因:训练数据里夜间功率有负的零漂值,归一化后模型学到了负值模式。解决:在数据预处理阶段就把功率clip(lower=0),预测后再做一次np.maximum(y_pred, 0)。

4.3 验证 loss 比训练 loss 低很多:数据泄露了

现象:验证集 loss 比训练集还低,看起来「泛化很好」。原因:滑动窗口构造时,训练集和验证集之间有重叠样本,未来信息泄露。解决:划分数据集时要在滑动窗口之前按时间切分原始数据,而不是切分已经构造好的 X 和 y。

4.4 模型只输出一条直线:学习率太大或特征没归一化

现象:预测值几乎不变,接近功率均值。原因:学习率过大导致模型跳过最优解,或者输入特征量纲差异太大。解决:先把学习率降到 0.0001 试一轮,确认所有特征都做了 MinMax 归一化,检查scaler.fit_transform是否用在了整个数据集上。

4.5 换一个电站效果就崩:模型过拟合了本地气象模式

现象:在 A 电站训练好的模型,拿到 B 电站 MAE 翻倍。原因:模型学到了 A 电站特有的云层移动规律和组件衰减特性。解决:要么在 B 电站数据上做微调,要么在训练时加入多个电站的数据做混合训练,并增加 Dropout 比例到 0.3。

5. 把 LSTM 预测推到可用:多步预测与在线更新的几个技巧

单步预测只能提前 15 分钟知道功率,对调度来说远远不够。实际项目里更常用的是多步预测,比如一次输出未来 4 个时刻(1 小时)的功率。做法有两种:直接多输出和滚动预测。直接多输出是把 Dense 层改成Dense(4),一次预测 4 个值;滚动预测是把预测值当作输入再喂给模型,逐步推。我一般用直接多输出,因为误差不会累积。

# 直接多输出:Dense 层输出 4 个时刻 model_multi = Sequential([ LSTM(64, return_sequences=True, input_shape=(lookback, X.shape[2])), Dropout(0.2), LSTM(64), Dropout(0.2), Dense(4) # 一次预测未来 4 个时刻 ]) model_multi.compile(optimizer=Adam(0.001), loss='mse') # 构造多步标签:y 的形状从 (N,) 变成 (N, 4) def create_multi_sequences(data, lookback=8, horizon=4): X, y = [], [] for i in range(len(data) - lookback - horizon + 1): X.append(data[i:i+lookback, :-1]) y.append(data[i+lookback:i+lookback+horizon, -1]) return np.array(X), np.array(y) X_multi, y_multi = create_multi_sequences(data_scaled, lookback=8, horizon=4) print(f'多步样本形状:X={X_multi.shape}, y={y_multi.shape}')

horizon=4表示预测未来 4 个时刻,如果你的采样是 15 分钟,就是提前 1 小时。多步预测的误差评估要分时刻算,通常第 1 步 MAE 最低,第 4 步会高 20% 到 40%。

在线更新是另一个实用技巧。电站运行一段时间后,组件会积灰、逆变器效率会变化,模型需要定期用新数据微调。我一般每周用最近一个月的数据做一次增量训练,学习率设小一点(0.0001),只训练 10 到 20 轮,避免把旧知识覆盖掉。

# 增量训练:加载已有模型,用小学习率微调 model_multi.compile(optimizer=Adam(0.0001), loss='mse') model_multi.fit(X_recent, y_recent, epochs=15, batch_size=32, verbose=0)

最后说一个我踩过的坑:不要用测试集去调超参数。我早期做光伏预测时,反复在测试集上试 lookback 和隐藏单元数,结果上线后误差比测试时高了 50%。后来改成从训练集里再切一小块做验证,测试集只在最终评估时用一次,模型泛化能力才稳定下来。希望帮到你。

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

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

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

立即咨询