Python机器学习光伏功率预测:时序数据切分与特征工程实战
2026/9/24 18:05:43 网站建设 项目流程

简介:这是一套面向计算机相关专业学生与项目实战学习者的光伏功率预测完整项目,基于Python与机器学习实现,可作为毕业设计、课程设计或期末大作业的高分参考方案。资源包共16个文件,约4.64MB,包含8个csv训练与测试数据集、4个py核心脚本、1个ipynb实验笔记、1个md说明文档及1个docx任务说明,覆盖数据加载、数据处理、模型训练与预测全流程,目录结构清晰,便于按模块理解与复现。项目经导师指导并认可,评审分99分,代码完整可运行,对新手友好。已有56人学习下载。读者可从中获得一套可直接运行的预测方案、配套训练与测试数据、分步实验笔记以及数据预处理与模型调参的排错思路,适合希望快速上手机器学习实战或需要完整项目案例支撑论文与答辩的学习者。

1. 光伏功率预测为什么总在下午三点翻车

做过光伏电站运维的人大多有过类似经历:早上模型预测曲线和实际出力贴合得不错,中午开始飘,到了下午三点前后直接跑偏,晚高峰前又莫名其妙拉回来。这不是模型“玄学”,而是光伏功率预测这件事本身对特征工程和时序切分极其敏感。Python 基于机器学习的光伏功率预测,核心就是用历史辐照、温度、组件出力等数据,训练一个能映射气象条件到发电功率的模型,再拿它去预测未来 15 分钟到 4 小时的出力。它解决的是电站并网调度、储能充放电策略、功率考核罚款这三个真金白银的问题,适合有 Python 基础、手头有逆变器和气象站历史数据的运维或算法同学。标题里说的“源码+训练数据+测试数据”,落地时最关键的不是模型多深,而是数据怎么切、特征怎么造、评估怎么防泄漏。

2. 光伏功率预测的数据集怎么切才不泄漏未来

2.1 训练数据、测试数据不是随机分,是按时间分

很多人拿到一份光伏出力 CSV,第一反应是train_test_split(random_state=42),这是最典型的翻车起点。光伏数据是强时序数据,随机切分会让测试集里的某一天和训练集里的相邻时刻混在一起,模型相当于“偷看”了未来,评估指标好看得离谱,上线就崩。常见做法是按时间顺序切:前 70% 做训练,中间 15% 做验证,最后 15% 做测试,且三段之间留出至少一天的 gap,避免边界泄漏。

import pandas as pd # df 至少包含 timestamp, irradiance, temp, power 四列 df = pd.read_csv("pv_data.csv", parse_dates=["timestamp"]) df = df.sort_values("timestamp").reset_index(drop=True) n = len(df) train_end = int(n * 0.70) val_end = int(n * 0.85) train = df.iloc[:train_end].copy() val = df.iloc[train_end:val_end].copy() test = df.iloc[val_end:].copy() # 打印三段的时间范围,确认没有重叠 for name, part in [("train", train), ("val", val), ("test", test)]: print(name, part["timestamp"].min(), part["timestamp"].max())

这段代码的逻辑是先按时间排序,再按比例硬切,最后打印每段的时间范围做人工确认。参数上,70/15/15 是光伏预测里比较稳的默认值,如果数据只有几个月,可以把验证集比例压到 10%,但测试集不要低于 10%,否则评估方差太大。注意parse_dates必须加,否则 timestamp 是字符串,排序会按字典序,10 月会排在 2 月前面。

2.2 特征工程:把辐照和温度变成模型能吃的滞后项

光伏功率和辐照度几乎是线性关系,但直接拿当前时刻的辐照去预测当前时刻的功率没有意义,因为预测时你拿不到未来的辐照。真正能用的是历史滞后特征和气象预报特征。我一般会造这几类:功率的 lag_1、lag_2、lag_4、lag_96(15 分钟粒度下 96 个点是一天),辐照的 lag_1 和 lag_4,温度的 lag_1,再加上小时、分钟、是否周末这类时间特征。

def add_lag_features(data, cols, lags): for col in cols: for lag in lags: data[f"{col}_lag{lag}"] = data[col].shift(lag) return data lag_cols = ["power", "irradiance", "temp"] df = add_lag_features(df, lag_cols, lags=[1, 2, 4, 96]) # 时间特征 df["hour"] = df["timestamp"].dt.hour df["minute"] = df["timestamp"].dt.minute df["is_weekend"] = df["timestamp"].dt.dayofweek.isin([5, 6]).astype(int) # 造完 lag 后前 96 行会有 NaN,直接丢掉 df = df.dropna().reset_index(drop=True)

逻辑说明:shift(lag)把过去第 lag 个时刻的值挪到当前行,lag_96 捕捉“昨天同一时刻”的日周期。参数上,lag 列表不是越多越好,光伏功率的自相关在 4 小时(16 个点)之后衰减很快,lag_96 主要是给模型一个日周期的锚点。丢 NaN 这一步不能省,否则后面 sklearn 会直接报错。注意is_weekend对光伏本身影响不大,但对“周末是否有人工清洗组件”这类隐性规律有间接作用,留着成本很低。

2.3 训练数据和测试数据的分布对齐

训练集和测试集如果季节不同,模型会严重偏。比如用夏天数据训练、冬天数据测试,辐照峰值差一倍,模型直接失效。落地时要么保证训练集覆盖至少一整年,要么在特征里加入“日序数”让模型自己学季节偏移。我一般会加一个day_of_year特征,并检查训练集和测试集的功率均值差异,超过 20% 就要警惕。

df["day_of_year"] = df["timestamp"].dt.dayofyear print("train power mean:", train["power"].mean()) print("test power mean:", test["power"].mean())

如果差异过大,常见做法是做分季节建模,或者用滑动窗口重新切分,让训练集和测试集覆盖相近的月份。这一步没有代码能自动救,必须人工看一眼。

3. 用 Python 机器学习模型跑通光伏功率预测的最小闭环

3.1 选 LightGBM 而不是 LSTM 的理由

光伏功率预测的公开研究和工程实践里,梯度提升树(LightGBM、XGBoost)在中小规模数据上经常打得过 LSTM。原因很实际:光伏特征里大量是滞后项和表格型气象数据,树模型对这类特征的捕捉效率高,训练快,调参少,还不容易过拟合。LSTM 需要更长的序列和更多数据才能体现优势,而且调参成本高。标题里说“高分项目”,如果数据量在几万到几十万行,LightGBM 是性价比最高的选择。

import lightgbm as lgb from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np feature_cols = [c for c in df.columns if c not in ["timestamp", "power"]] X_train, y_train = train[feature_cols], train["power"] X_val, y_val = val[feature_cols], val["power"] X_test, y_test = test[feature_cols], test["power"] model = lgb.LGBMRegressor( n_estimators=800, learning_rate=0.05, num_leaves=63, min_child_samples=20, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit( X_train, y_train, eval_set=[(X_val, y_val)], eval_metric="mae", callbacks=[lgb.early_stopping(50)] ) pred = model.predict(X_test) mae = mean_absolute_error(y_test, pred) rmse = np.sqrt(mean_squared_error(y_test, pred)) print(f"MAE={mae:.3f}, RMSE={rmse:.3f}")

逻辑说明:early_stopping(50)表示验证集 MAE 连续 50 轮不下降就停,防止过拟合。参数上,num_leaves=63是中等复杂度,数据量小于 5 万行可以降到 31;learning_rate=0.05配合 800 棵树是比较稳的组合,想更快收敛可以提到 0.1,但容易震荡。subsamplecolsample_bytree都设 0.8 是常规防过拟合手段。评估指标用 MAE 而不是 MSE,因为光伏考核通常看平均偏差,MAE 更贴近业务。

3.2 归一化与反归一化:别在树模型上画蛇添足

树模型不需要归一化,这是常识,但很多人从 LSTM 教程里抄来 MinMaxScaler,把特征缩到 0-1 再喂给 LightGBM,结果模型性能没提升,反而因为 scaler 在训练集上 fit、在测试集上 transform 引入了额外的数据依赖。如果一定要用神经网络做对比实验,归一化必须只在训练集上 fit,然后 transform 验证集和测试集。

from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() # 只在训练集上 fit scaler.fit(X_train) X_train_scaled = scaler.transform(X_train) X_val_scaled = scaler.transform(X_val) X_test_scaled = scaler.transform(X_test)

注意fit只能碰训练集,这是防泄漏的铁律。树模型流程里我一般直接跳过这一步,省事且不出错。

3.3 预测结果的后处理:把负功率砍掉

光伏功率物理上不可能为负,但模型在夜间或低辐照时可能输出负值。直接提交负值会被业务方当成 bug。常见做法是pred = np.clip(pred, 0, None),把负值截断到 0。如果电站有额定容量,还可以加上限np.clip(pred, 0, capacity)

capacity = df["power"].max() # 用历史最大出力近似额定容量 pred = np.clip(pred, 0, capacity)

这一步看起来简单,但在实际考核里能救回不少分数。注意 capacity 不要用测试集的最大值,要用训练集或已知的装机容量,否则又是泄漏。

4. 光伏功率预测的避坑与排查清单

4.1 现象:验证集 MAE 很低,测试集 MAE 翻三倍

原因:训练集和测试集时间分布不一致,或者随机切分导致泄漏。解决:按时间切分,检查两段的功率均值和辐照均值差异,必要时做分季节建模或滑动窗口重切。

4.2 现象:模型在中午出力峰值处系统性偏低

原因:训练数据里峰值样本少,树模型对极端值不敏感。解决:对峰值样本做加权,或者用objective="regression_l1"替代默认的 L2,L1 对极端值更鲁棒。也可以单独训练一个峰值时段的模型。

4.3 现象:lag 特征造完后数据量骤减

原因:lag_96 会让前 96 行变成 NaN,如果数据本身只有几天,丢完就没剩多少。解决:确认数据至少覆盖两周以上;如果数据短,把 lag_96 换成 lag_48 或去掉,优先保数据量。

4.4 现象:预测曲线整体平移,形状对但时间对不上

原因:时间戳时区或对齐问题,常见于逆变器数据和气象站数据来自不同系统。解决:统一转成同一时区,检查两套数据的采样间隔是否一致,必要时做重采样对齐。

4.5 现象:LightGBM 训练报错 “Input contains NaN”

原因:lag 特征或时间特征里还有缺失值,dropna没覆盖到。解决:在 fit 之前加assert not X_train.isnull().any().any(),定位到具体列再处理。常见漏网之鱼是气象数据里的辐照缺失,需要插值或前向填充。

5. 把光伏功率预测从 demo 推到可用的三个进阶技巧

5.1 用滑动窗口做在线更新

电站数据是每天新增的,模型不能一训永逸。我一般会保留最近 30 天的数据做增量训练,或者每周用全部历史重新训一次。滑动窗口的代码不复杂,关键是维护一个滚动的时间边界。

def rolling_train(df, window_days=30, model_params=None): end = df["timestamp"].max() start = end - pd.Timedelta(days=window_days) recent = df[df["timestamp"] >= start] X = recent[feature_cols] y = recent["power"] model = lgb.LGBMRegressor(**(model_params or {})) model.fit(X, y) return model

参数上,window_days 取 30 是经验值,夏天可以短一点,冬天建议长一点,因为冬季出力低、噪声占比大。注意这个函数没有验证集,适合已经调好参后的在线更新,不适合首次选型。

5.2 用预测区间代替单点预测

业务方越来越不满足于一个数,他们想知道“明天下午三点出力大概率在 3MW 到 4MW 之间”。LightGBM 可以通过分位数回归给出区间。

lower = lgb.LGBMRegressor(objective="quantile", alpha=0.1) upper = lgb.LGBMRegressor(objective="quantile", alpha=0.9) lower.fit(X_train, y_train) upper.fit(X_train, y_train) pred_lower = lower.predict(X_test) pred_upper = upper.predict(X_test)

alpha=0.10.9给出 80% 预测区间。注意分位数模型训练比普通回归慢,且区间宽度需要人工检查是否合理,太宽没有参考价值。

5.3 特征重要性排查:别让模型学错东西

训练完一定要看model.feature_importances_,如果day_of_yearhour排在最前面,而irradiance_lag1排在后面,说明模型可能在靠时间“背答案”,而不是学物理关系。这种情况在训练集和测试集季节重叠时特别危险。

importance = pd.Series(model.feature_importances_, index=feature_cols) print(importance.sort_values(ascending=False).head(15))

我自己的习惯是每次训练完先看这 15 个特征,如果 lag 特征没进前五,就会回头检查数据对齐和切分。这个动作花不了一分钟,但能挡住大部分“指标好看、上线翻车”的情况。希望帮到你。

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

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

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

立即咨询