简介:这份资源是面向高校学生与初学者的光伏功率预测实战项目,基于Python与机器学习方法,解决光伏发电功率的短期预测问题,适合用作毕业设计、期末大作业或课程设计的高分参考方案。压缩包共16个文件,约4.64MB,包含8个csv训练与测试数据集、4个py核心脚本、1个ipynb交互式笔记本、1个md说明文档、1个docx任务说明及gitignore配置,覆盖数据加载、预处理、模型训练与预测的完整流程。代码附有详细注释,新手也能看懂,下载后简单部署即可运行。项目围绕DC_PV_Power_Predict_2018数据集展开,提供多组训练与测试样本,便于对比不同机器学习模型的预测效果,并配有任务说明文档帮助理解实验目标与评估指标。目前已有316人学习下载,适合希望快速掌握光伏功率预测建模思路、需要完整可运行代码与数据支撑的读者参考使用。
1. 光伏功率预测项目到底在做什么:从「靠天吃饭」到可复现的机器学习流水线
光伏电站最头疼的不是组件效率,而是出力忽高忽低——云飘过来功率掉一半,云走了又猛冲,电网调度最怕这种「过山车」。光伏功率预测要解决的就是这件事:用历史发电数据加气象数据,训练一个模型,提前预测未来一段时间电站能发多少电。超短期光伏功率预测通常指未来 0~4 小时、分辨率 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(drop=True) # 按时间顺序切分,前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(drop=True) 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_estimators=800, learning_rate=0.05, num_leaves=63, max_depth=8, min_child_samples=20, subsample=0.8, colsample_bytree=0.8, reg_alpha=0.1, reg_lambda=0.1, random_state=42 ) model.fit(X_train, y_train, eval_set=[(X_test, y_test)], eval_metric="rmse", callbacks=[lgb.early_stopping(50)]) pred = model.predict(X_test) rmse = mean_squared_error(y_test, pred, squared=False) mape = mean_absolute_percentage_error(y_test + 1e-6, pred + 1e-6) print(f"RMSE={rmse:.2f}, MAPE={mape:.4f}")参数说明:n_estimators=800配合early_stopping(50)让模型自己决定何时停;num_leaves=63控制复杂度,光伏数据噪声大,叶子太多容易过拟合;learning_rate=0.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_dim=64, num_layers=2, dropout=0.2): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True, dropout=dropout) 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_len=96): xs, ys = [], [] for i in range(len(features) - seq_len): xs.append(features[i:i+seq_len]) ys.append(targets[i+seq_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_len=96) print(X_seq.shape) # (样本数, 96, 特征数)hidden_dim=64和num_layers=2是中小规模数据的稳妥起点,dropout=0.2防过拟合。make_sequences里targets[i+seq_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], squared=False) 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 通常能涨 1~2 个点,但训练时间翻倍;num_leaves从 31 加到 63 可能过拟合,要看验证集 RMSE 是否同步下降。LSTM 里先调seq_len(96 vs 192),再调hidden_dim(64 vs 128),最后加 dropout。
# 简单的学习率对比,不要用 GridSearchCV 全量搜 for lr in [0.1, 0.05, 0.02]: m = lgb.LGBMRegressor(n_estimators=1000, learning_rate=lr, num_leaves=63, random_state=42) m.fit(X_train, y_train, eval_set=[(X_test, y_test)], eval_metric="rmse", callbacks=[lgb.early_stopping(50)]) p = m.predict(X_test) print(f"lr={lr}, RMSE={mean_squared_error(y_test, p, squared=False):.2f}")这种单变量扫描比网格搜索快得多,也更容易看出趋势。如果 lr=0.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_len=96, horizon=16): """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, dtype=torch.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这段代码是递归多步预测的简化版,horizon=16对应 4 小时。实际工程里不会用预测值填power_lag1,而是用数值天气预报的辐照度预报来驱动,否则误差会累积。校正环节可以用一个简单的线性回归做残差修正:把最近 24 小时的预测误差和真实值拟合一个修正系数,加到下一步预测上。
我自己的习惯是:每次换数据集或换电站,先跑一遍 LightGBM 基线,把分天气 MAPE 记在笔记本上,再决定要不要上 LSTM。这个基线数字就是后续所有优化的参照系,没有它,调参就是玄学。希望帮到你。
本文还有配套的精品资源,点击获取