恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python机器学习光伏功率预测:从数据清洗到LSTM的完整项目实战
首页
资讯中心
/
Python机器学习光伏功率预测:从数据清洗到LSTM的完整项目实战
Python机器学习光伏功率预测:从数据清洗到LSTM的完整项目实战
发布时间:2026/9/26 14:12:33
简介这份资源是面向高校学生与初学者的光伏功率预测实战项目基于Python与机器学习方法解决光伏发电功率的短期预测问题适合用作毕业设计、期末大作业或课程设计的高分参考方案。压缩包共16个文件约4.64MB包含8个csv训练与测试数据集、4个py核心脚本、1个ipynb交互式笔记本、1个md说明文档、1个docx任务说明及gitignore配置覆盖数据加载、预处理、模型训练与预测的完整流程。代码附有详细注释新手也能看懂下载后简单部署即可运行。项目围绕DC_PV_Power_Predict_2018数据集展开提供多组训练与测试样本便于对比不同机器学习模型的预测效果并配有任务说明文档帮助理解实验目标与评估指标。目前已有316人学习下载适合希望快速掌握光伏功率预测建模思路、需要完整可运行代码与数据支撑的读者参考使用。1. 光伏功率预测项目到底在做什么从「靠天吃饭」到可复现的机器学习流水线光伏电站最头疼的不是组件效率而是出力忽高忽低——云飘过来功率掉一半云走了又猛冲电网调度最怕这种「过山车」。光伏功率预测要解决的就是这件事用历史发电数据加气象数据训练一个模型提前预测未来一段时间电站能发多少电。超短期光伏功率预测通常指未来 04 小时、分辨率 15 分钟级别的预测这个尺度上云层移动是主导因素也是机器学习最能发挥价值的地方。这个标题里的「Python 基于机器学习的光伏功率预测项目源码训练数据测试数据」本质是一套完整的监督学习工程输入是历史功率、辐照度、温度、湿度、风速等特征输出是未来时刻的功率值。它适合三类人想找一个真实时序回归项目练手的机器学习入门者、需要给电站做功率预测基线方案的工程人员、以及想理解「数据清洗→特征工程→模型训练→评估」全链路的学生。下面我按自己搭这套流水线的顺序把每一步的参数、坑和验证方法讲清楚。2. 数据准备与特征工程训练数据决定模型上限2.1 训练数据和测试数据怎么切分才不泄漏光伏功率预测是典型时序问题很多人第一反应是train_test_split随机切分这是最常见的翻车点。随机切分会让未来数据混进训练集模型在测试集上表现虚高上线后直接崩。正确做法是按时间顺序切前 80% 做训练后 20% 做测试中间留一段 gap 避免相邻时刻泄漏。import pandas as pd import numpy as np # 读取训练数据假设列包含 timestamp, power, irradiance, temp, humidity, wind_speed df pd.read_csv(pv_train.csv, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 按时间顺序切分前80%训练后20%测试 split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx].copy() test_df df.iloc[split_idx:].copy() # 检查时间是否有重叠 assert train_df[timestamp].max() test_df[timestamp].min(), 时间顺序切分错误 print(f训练集 {len(train_df)} 条测试集 {len(test_df)} 条)这段代码的关键是sort_values加iloc顺序切分assert那行是后悔药防止后续改动把顺序打乱。参数上 80/20 是常见起点如果数据量少于 5000 条可以调到 70/30但绝不能随机打乱。2.2 特征工程辐照度和历史功率是两大主力光伏功率的核心驱动因素是辐照度其次是组件温度和历史功率的滞后项。原始数据里往往只有总辐照度需要拆出直射和散射或者至少做归一化。时间特征也很关键小时、月份、太阳高度角。下面是我常用的特征构造脚本。def build_features(df): df df.copy() # 时间特征 df[hour] df[timestamp].dt.hour df[month] df[timestamp].dt.month df[dayofyear] df[timestamp].dt.dayofyear # 周期编码避免23点和0点距离被拉大 df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) # 历史功率滞后特征注意用shift避免未来信息 df[power_lag1] df[power].shift(1) df[power_lag4] df[power].shift(4) # 15分钟粒度下约1小时前 df[power_roll_mean4] df[power].shift(1).rolling(4).mean() # 辐照度与温度交互 df[irradiance_temp] df[irradiance] * df[temp] df df.dropna().reset_index(dropTrue) return df train_feat build_features(train_df) test_feat build_features(test_df)shift(1)是防止标签泄漏的生命线所有滞后特征必须先移位再计算。hour_sin和hour_cos解决的是周期特征的「23 点和 0 点明明相邻数值却差 23」问题。power_roll_mean4用 4 个 15 分钟点做滑动平均相当于 1 小时趋势。参数上滞后阶数根据数据粒度定15 分钟粒度用 1、4、96一天1 小时粒度用 1、3、24。注意如果训练数据里夜间功率全为 0不要直接删掉夜间样本能帮助模型学会「无辐照度则无功率」的边界删了反而容易在清晨黄昏时段预测出负值。3. 模型选型与训练从 LightGBM 基线到 LSTM 对比3.1 为什么先用 LightGBM 而不是直接上深度学习光伏功率预测的数据量通常在几万到几十万条这个规模下梯度提升树LightGBM/XGBoost往往比 LSTM 更稳、更快、更好调。树模型对缺失值和异常值鲁棒特征重要性可解释训练一次几分钟。我一般先用 LightGBM 跑一个基线把 RMSE 和 MAPE 记下来再决定要不要上深度学习。如果基线 MAPE 已经低于 8%深度学习提升空间有限不值得投入调参成本。import lightgbm as lgb from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error feature_cols [irradiance, temp, humidity, wind_speed, hour_sin, hour_cos, month, dayofyear, power_lag1, power_lag4, power_roll_mean4, irradiance_temp] X_train train_feat[feature_cols] y_train train_feat[power] X_test test_feat[feature_cols] y_test test_feat[power] model lgb.LGBMRegressor( n_estimators800, learning_rate0.05, num_leaves63, max_depth8, min_child_samples20, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda0.1, random_state42 ) model.fit(X_train, y_train, eval_set[(X_test, y_test)], eval_metricrmse, callbacks[lgb.early_stopping(50)]) pred model.predict(X_test) rmse mean_squared_error(y_test, pred, squaredFalse) mape mean_absolute_percentage_error(y_test 1e-6, pred 1e-6) print(fRMSE{rmse:.2f}, MAPE{mape:.4f})参数说明n_estimators800配合early_stopping(50)让模型自己决定何时停num_leaves63控制复杂度光伏数据噪声大叶子太多容易过拟合learning_rate0.05是精度和速度的平衡点subsample和colsample_bytree都设 0.8 做行采样和列采样进一步抗过拟合。MAPE 计算时加1e-6是防止夜间功率为 0 导致除零。3.2 LSTM 什么时候值得上序列长度和输入维度怎么定当你有至少一年的 15 分钟粒度数据约 3.5 万条并且希望模型自动捕捉时序依赖时LSTM 或 GRU 值得一试。关键参数是序列长度seq_len太短抓不到趋势太长训练慢且容易梯度消失。光伏场景下我一般用 96一天或 192两天。输入维度是特征数输出是单步功率。import torch import torch.nn as nn class PVLSTM(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_dim, 1) def forward(self, x): # x: (batch, seq_len, input_dim) out, _ self.lstm(x) out out[:, -1, :] # 取最后一个时间步 return self.fc(out).squeeze(-1) def make_sequences(features, targets, seq_len96): xs, ys [], [] for i in range(len(features) - seq_len): xs.append(features[i:iseq_len]) ys.append(targets[iseq_len]) return np.array(xs), np.array(ys) # 标准化后再构造序列避免量纲影响 from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_all scaler.fit_transform(train_feat[feature_cols]) y_all train_feat[power].values X_seq, y_seq make_sequences(X_all, y_all, seq_len96) print(X_seq.shape) # (样本数, 96, 特征数)hidden_dim64和num_layers2是中小规模数据的稳妥起点dropout0.2防过拟合。make_sequences里targets[iseq_len]表示用前 96 步预测第 97 步这是单步预测如果要预测未来 4 小时16 步把输出改成nn.Linear(hidden_dim, 16)并调整标签切片。标准化必须在构造序列之前做且 scaler 只能 fit 训练集再 transform 测试集否则又是泄漏。提示LSTM 训练时如果 loss 震荡不降先检查序列构造有没有把测试集数据混进训练序列再检查学习率是否超过 1e-3。4. 评估与调参RMSE、MAPE 和「晴天/多云」分场景看4.1 别只看一个总体指标分天气类型评估光伏预测最容易骗自己的地方是总体 MAPE 很好看一到多云天就崩。因为晴天功率曲线平滑模型随便拟合都能对多云天功率剧烈波动才是真正考验。我一般把测试集按天气类型晴、多云、阴雨分组分别算 RMSE 和 MAPE如果多云天 MAPE 超过 15%说明模型对波动捕捉不够需要加辐照度变化率特征或换模型。# 假设 test_feat 里有 weather_type 列0晴 1多云 2阴雨 for wt in [0, 1, 2]: mask test_feat[weather_type] wt if mask.sum() 0: continue rmse_wt mean_squared_error(y_test[mask], pred[mask], squaredFalse) mape_wt mean_absolute_percentage_error(y_test[mask] 1e-6, pred[mask] 1e-6) print(f天气类型{wt}: 样本{mask.sum()}, RMSE{rmse_wt:.2f}, MAPE{mape_wt:.4f})这段代码的价值在于暴露短板。如果多云天样本太少模型学不好可以考虑对多云样本过采样或者在损失函数里给多云样本更高权重。参数上分组阈值按业务定装机容量 10MW 以下RMSE 控制在 1MW 以内算合格MAPE 总体低于 10%、多云天低于 18% 是可以接受的基线。4.2 调参顺序先树模型后网络先学习率后复杂度调参不要一上来就网格搜索浪费时间。我的顺序是先固定一组合理默认值跑通再按「学习率 → 树数量/层数 → 正则化 → 特征」的顺序调。LightGBM 里learning_rate从 0.1 降到 0.05 通常能涨 12 个点但训练时间翻倍num_leaves从 31 加到 63 可能过拟合要看验证集 RMSE 是否同步下降。LSTM 里先调seq_len96 vs 192再调hidden_dim64 vs 128最后加 dropout。# 简单的学习率对比不要用 GridSearchCV 全量搜 for lr in [0.1, 0.05, 0.02]: m lgb.LGBMRegressor(n_estimators1000, learning_ratelr, num_leaves63, random_state42) m.fit(X_train, y_train, eval_set[(X_test, y_test)], eval_metricrmse, callbacks[lgb.early_stopping(50)]) p m.predict(X_test) print(flr{lr}, RMSE{mean_squared_error(y_test, p, squaredFalse):.2f})这种单变量扫描比网格搜索快得多也更容易看出趋势。如果 lr0.05 和 0.02 的 RMSE 差距小于 1%就选 0.05省训练时间。5. 避坑与排查光伏功率预测里最容易翻车的 5 件事5.1 现象测试集 MAPE 只有 3%上线后误差翻倍原因随机切分导致时间泄漏或者标准化时用了全量数据 fit。解决改成时间顺序切分scaler 只在训练集 fit测试集 transform。检查方法是看训练集和测试集的时间戳是否有重叠。5.2 现象模型在夜间预测出负功率原因回归模型没有输出约束夜间样本少或特征区分度不够。解决在预测后加np.clip(pred, 0, capacity)或者在训练时对夜间样本加权。更彻底的做法是加一个「是否白天」的二分类特征。5.3 现象LSTM 训练 loss 不降RMSE 比 LightGBM 还差原因序列构造时把不同天的数据连在一起跨天边界产生虚假时序或者学习率太大。解决构造序列时按天分组每天单独滑窗不要跨天拼接学习率从 1e-3 降到 1e-4 试。5.4 现象特征重要性里power_lag1一家独大其他特征几乎没用原因滞后特征太强模型直接抄昨天同一时刻的值没有学到气象关系。解决做消融实验去掉power_lag1看 RMSE 涨多少如果涨得不多说明模型确实在偷懒需要加更多气象特征或改用能捕捉长期依赖的模型。5.5 现象训练数据里缺失值填 0 后模型在缺失时段预测异常原因光伏夜间功率本来就是 0但白天缺失填 0 会被模型当成「真实出力为 0」学出错误模式。解决缺失值用前后时刻插值或者加一个「是否缺失」的指示特征让模型自己判断。注意这 5 条里第 1 条和第 3 条最隐蔽建议每次改完数据管道都重新跑一遍时间戳检查和序列边界检查。6. 把模型用起来滚动预测和「预测-校正」闭环训练完模型不是终点真正落地要做滚动预测每 15 分钟用最新实测数据更新输入预测未来 4 小时然后等真实值回来做校正。我一般会维护一个误差缓冲区如果连续 3 个点预测偏差超过 20%就触发模型重新校准或切换备用模型。下面是一个简化的滚动预测骨架。def rolling_forecast(model, scaler, history_df, feature_cols, seq_len96, horizon16): history_df: 最近 seq_len 条真实数据horizon: 预测步数 preds [] buf history_df[feature_cols].values.copy() for step in range(horizon): x scaler.transform(buf[-seq_len:]).reshape(1, seq_len, -1) with torch.no_grad(): p model(torch.tensor(x, dtypetorch.float32)).item() preds.append(p) # 用预测值填充下一步输入递归预测实际中可用气象预报替代 next_row buf[-1].copy() next_row[feature_cols.index(power_lag1)] p buf np.vstack([buf, next_row]) return preds这段代码是递归多步预测的简化版horizon16对应 4 小时。实际工程里不会用预测值填power_lag1而是用数值天气预报的辐照度预报来驱动否则误差会累积。校正环节可以用一个简单的线性回归做残差修正把最近 24 小时的预测误差和真实值拟合一个修正系数加到下一步预测上。我自己的习惯是每次换数据集或换电站先跑一遍 LightGBM 基线把分天气 MAPE 记在笔记本上再决定要不要上 LSTM。这个基线数字就是后续所有优化的参照系没有它调参就是玄学。希望帮到你。本文还有配套的精品资源点击获取