简介:这是一套面向高校学生与初学者的光伏功率预测实战项目,基于Python与机器学习方法,围绕光伏发电功率的建模与预测展开,适合用作毕业设计、期末大作业或课程设计的高分参考方案。资源包共16个文件,包含8个csv训练与测试数据集、4个py源码脚本、1个ipynb交互式笔记本、1个md说明文档、1个docx任务说明及gitignore配置,压缩包约4.64MB,结构清晰、便于按模块查阅。代码含详细注释,新手也能看懂,下载后简单部署即可运行。项目覆盖数据加载与保存、数据预处理、模型训练与预测等完整流程,配套多组训练与测试数据,方便读者复现实验、对比模型效果并理解光伏功率预测的关键环节。目前已有316人学习下载,可作为机器学习入门与光伏预测方向实践的有力参考。
1. 光伏功率预测项目拆解:从源码到数据,一套能跑通的机器学习方案
光伏功率预测这件事,真正动手做过的人都知道,难点从来不在模型本身,而在数据怎么处理、特征怎么构造、评估怎么做才不骗自己。我拿到「Python基于机器学习的光伏功率预测项目源码+训练数据+测试数据」这个标题的时候,第一反应是:这大概率是一个面向课程设计或入门实战的完整方案,包含数据读取、特征工程、模型训练、结果评估几个环节。它适合谁?适合刚学完机器学习基础、想找一个有真实物理背景的数据集练手的人,也适合做新能源方向课程设计、需要快速搭出一套可复现baseline的工程师。这篇文章不讲空泛概念,直接按「数据长什么样 → 特征怎么造 → 模型怎么选 → 代码怎么写 → 坑在哪」的顺序,把一套能跑通的光伏功率预测方案讲清楚。你跟着走,至少能拿到一个不丢人的预测结果,并且知道每一步为什么这么做。
2. 光伏功率预测的数据长什么样:训练集与测试集的字段拆解
2.1 典型光伏数据集的字段构成与物理含义
光伏功率预测的数据集,不管来自哪个公开源或自采系统,核心字段基本逃不出这几类:时间戳、辐照度、温度、湿度、风速、历史功率。时间戳决定采样粒度,常见的是15分钟或1小时。辐照度是最强特征,没有之一,因为光伏出力本质上就是辐照度的线性映射加上一堆损耗。温度影响组件效率,高温会降出力,这个在夏天中午特别明显。湿度、风速属于辅助特征,风速大了可能吹走云层,也可能带来灰尘,看具体场景。
训练数据和测试数据的划分,常见做法是按时间顺序切分,而不是随机打乱。为什么?因为光伏功率是典型的时间序列,随机打乱会让模型「偷看」未来信息,评估结果虚高。我一般会按7:2:1切成训练、验证、测试,或者按季节切分,比如用春夏秋训练、冬天测试,看模型的泛化能力。
注意:拿到数据先画功率曲线,看有没有夜间负值、白天削顶、连续零值这些异常。夜间功率理论上为零,如果出现负值,多半是逆变器自耗电或传感器漂移,需要截断处理。
2.2 数据清洗与缺失值处理的实操步骤
光伏数据最烦人的就是缺失和异常。云层遮挡会导致功率骤降,传感器故障会导致辐照度读数卡死。下面这段代码是我常用的清洗流程,直接可以抄。
import pandas as pd import numpy as np # 读取原始数据,假设字段为 timestamp, irradiance, temp, humidity, wind_speed, power df = pd.read_csv('pv_data.csv', parse_dates=['timestamp']) df = df.sort_values('timestamp').set_index('timestamp') # 1. 夜间功率截断:辐照度低于阈值时,功率强制置零 df.loc[df['irradiance'] < 5, 'power'] = 0 # 2. 异常值处理:功率超过装机容量视为削顶,用容量值替换 CAPACITY = 100.0 # 单位kW,根据实际装机修改 df['power'] = df['power'].clip(upper=CAPACITY) # 3. 缺失值插补:辐照度和温度用线性插值,功率用前向填充 df['irradiance'] = df['irradiance'].interpolate(method='linear', limit=4) df['temp'] = df['temp'].interpolate(method='linear', limit=4) df['power'] = df['power'].fillna(method='ffill', limit=4) # 4. 删除仍然缺失的行 df = df.dropna() print(f'清洗后数据量:{len(df)},时间范围:{df.index.min()} 至 {df.index.max()}')这段代码的逻辑是:先处理物理上不可能的夜间功率,再处理超容量异常,然后对连续缺失不超过4个点的做插值,最后删掉补不回来的。参数limit=4对应1小时(15分钟粒度),超过1小时的缺失,插值意义不大,不如删掉。CAPACITY必须按实际装机填,填错了削顶处理就失效。
2.3 训练数据与测试数据的切分策略
切分不是随便train_test_split一下就完事。光伏数据有强日周期和季节周期,切分时要保证训练集覆盖足够多的天气类型。我一般用两种切法做对比:一种是按时间顺序前80%训练、后20%测试;另一种是按月份分层抽样,保证每个季节都有样本进训练集。第一种更接近实际部署场景,第二种能检验模型对季节变化的适应能力。
# 按时间顺序切分 split_idx = int(len(df) * 0.8) train_df = df.iloc[:split_idx] test_df = df.iloc[split_idx:] # 检查训练集和测试集的功率分布 print('训练集功率均值:', train_df['power'].mean()) print('测试集功率均值:', test_df['power'].mean())如果两者均值差太多,说明切分点选得不好,可能把整个高辐照季节切进了测试集。这时候要调整切分点,或者改用分层抽样。
3. 特征工程怎么做:从原始字段到模型能吃的输入
3.1 时间特征与辐照度衍生特征的构造
原始字段直接丢给模型,效果通常一般。光伏功率预测里,时间特征和辐照度衍生特征是最值得花时间的部分。时间特征包括小时、分钟、星期几、月份,这些能帮模型捕捉日周期和季节周期。辐照度衍生特征包括辐照度的滑动平均、差分、与理论晴空辐照度的比值。
# 时间特征 df['hour'] = df.index.hour df['minute'] = df.index.minute df['dayofweek'] = df.index.dayofweek df['month'] = df.index.month # 辐照度滑动平均:窗口取4个点(1小时) df['irradiance_ma1h'] = df['irradiance'].rolling(window=4, min_periods=1).mean() # 辐照度差分:反映云层变化速度 df['irradiance_diff'] = df['irradiance'].diff().fillna(0) # 理论晴空辐照度:用简单正弦模型模拟 df['hour_angle'] = (df['hour'] + df['minute']/60 - 12) * 15 # 角度制 df['clear_sky'] = np.maximum(0, np.cos(np.radians(df['hour_angle']))) * 1000 df['irradiance_ratio'] = df['irradiance'] / (df['clear_sky'] + 1)irradiance_ma1h平滑了短时波动,irradiance_diff捕捉云层移动的剧烈程度,irradiance_ratio反映天气晴朗程度。这三个特征加进去,模型对突变天气的响应会好很多。clear_sky的计算是简化版,实际项目可以用pysolar或pvlib算更准的晴空辐照度,但入门方案用这个够用。
3.2 滞后特征与滑动窗口的取舍
光伏功率预测本质上是时间序列预测,滞后特征(lag feature)是标配。但滞后多少步、窗口开多大,需要根据预测 horizon 来定。如果是超短期预测(未来15分钟到4小时),滞后1到4个点足够;如果是日前预测,滞后特征意义不大,反而要靠天气预报数据。
# 滞后特征:前1到4个时刻的功率 for lag in range(1, 5): df[f'power_lag{lag}'] = df['power'].shift(lag) # 滑动窗口统计:过去1小时的功率均值和标准差 df['power_ma1h'] = df['power'].rolling(window=4, min_periods=1).mean() df['power_std1h'] = df['power'].rolling(window=4, min_periods=1).std() # 删除因shift产生的NaN df = df.dropna()滞后特征有个坑:如果数据里有缺失值,shift之后NaN会扩散。所以清洗要在特征工程之前做完。另外,power_lag1和power_ma1h相关性很高,树模型能自动处理,线性模型可能需要做共线性诊断。
3.3 特征筛选:哪些特征真正有用
特征不是越多越好。我见过有人把能造的特征全塞进去,结果模型过拟合,测试集一塌糊涂。筛选特征有两个实用方法:一是看树模型的特征重要性,二是看特征与功率的相关系数。
from sklearn.ensemble import RandomForestRegressor import matplotlib.pyplot as plt feature_cols = ['irradiance', 'temp', 'humidity', 'wind_speed', 'hour', 'month', 'irradiance_ma1h', 'irradiance_diff', 'irradiance_ratio', 'power_lag1', 'power_lag2', 'power_ma1h', 'power_std1h'] X = df[feature_cols] y = df['power'] rf = RandomForestRegressor(n_estimators=100, random_state=42) rf.fit(X, y) importances = pd.Series(rf.feature_importances_, index=feature_cols) importances.sort_values(ascending=False).plot(kind='barh') plt.title('特征重要性排序') plt.show()跑完这张图,你会发现辐照度、irradiance_ratio、power_lag1通常排前三。如果某个特征重要性接近零,直接删掉,减少训练时间和过拟合风险。但注意,特征重要性低不代表没用,可能是被其他相关特征替代了,删之前最好做一次消融实验。
4. 模型选型与训练:从线性回归到梯度提升树
4.1 为什么光伏功率预测首选树模型
光伏功率与特征之间的关系是非线性的:辐照度低时功率接近零,辐照度到一定程度后功率增长放缓,温度高时效率下降。线性回归拟合这种关系很吃力,除非做大量多项式展开。树模型天然处理非线性,而且对特征缩放不敏感,缺失值也能处理(XGBoost和LightGBM原生支持)。我一般先用随机森林或梯度提升树(GBDT)搭baseline,效果不够再上深度学习。
常见做法是:随机森林做快速验证,XGBoost或LightGBM做精细调参。如果数据量超过10万条,LightGBM训练速度优势明显;如果数据量小,随机森林更稳,不容易过拟合。
4.2 用LightGBM训练光伏功率预测模型的完整代码
下面这段代码是我常用的LightGBM训练流程,包含训练、验证、早停和模型保存。
import lightgbm as lgb from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np # 切分特征和标签 X_train = train_df[feature_cols] y_train = train_df['power'] X_test = test_df[feature_cols] y_test = test_df['power'] # 构造LightGBM数据集 train_data = lgb.Dataset(X_train, label=y_train) valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data) # 参数设置 params = { 'objective': 'regression', 'metric': 'mae', 'boosting_type': 'gbdt', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': -1, 'random_state': 42 } # 训练 model = lgb.train( params, train_data, num_boost_round=1000, valid_sets=[valid_data], callbacks=[lgb.early_stopping(stopping_rounds=50), lgb.log_evaluation(period=100)] ) # 预测 y_pred = model.predict(X_test, num_iteration=model.best_iteration) # 评估 mae = mean_absolute_error(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) r2 = r2_score(y_test, y_pred) print(f'MAE: {mae:.4f}') print(f'RMSE: {rmse:.4f}') print(f'R2: {r2:.4f}') # 保存模型 model.save_model('pv_power_lgbm.txt')参数说明:num_leaves=31控制树复杂度,光伏数据一般不超过63,再大容易过拟合。learning_rate=0.05配合num_boost_round=1000和早停,是比较稳的组合。feature_fraction=0.8和bagging_fraction=0.8引入随机性,提升泛化。early_stopping(50)表示验证集MAE连续50轮不下降就停,避免无效训练。
4.3 评估指标怎么选:MAE、RMSE和R2的适用场景
光伏功率预测的评估,MAE和RMSE最常用。MAE反映平均绝对误差,单位是kW,直观;RMSE对大误差惩罚更重,如果出现极端天气预测翻车,RMSE会明显恶化。R2反映拟合优度,但光伏数据里R2容易虚高,因为夜间功率为零,模型只要预测零就能拿到不错的R2。所以我一般以MAE为主,RMSE为辅,R2只做参考。
还有一个指标叫归一化均方根误差(nRMSE),用RMSE除以装机容量,方便不同电站之间对比。如果装机容量是100kW,RMSE是10kW,nRMSE就是10%。
提示:评估一定要在测试集上做,而且测试集不能参与任何训练过程,包括特征筛选和超参数调优。我见过有人用全部数据调参,然后拿同一批数据报指标,结果上线后误差翻倍。
5. 避坑与排查:光伏功率预测项目里最容易翻车的五个地方
5.1 数据泄漏:滞后特征把未来信息带进了训练集
现象:模型在训练集和测试集上表现都很好,MAE低到离谱,但上线后预测完全不准。
原因:构造滞后特征时,用了shift(-1)而不是shift(1),或者滑动窗口的center=True,导致当前时刻的特征包含了未来信息。还有一种隐蔽情况:先对全量数据做了归一化,再切分训练测试,归一化参数里包含了测试集的统计信息。
解决:所有滞后和滑动操作只允许向过去看。归一化必须在切分之后,用训练集的均值和方差去变换测试集。代码审查时重点检查shift的方向和rolling的center参数。
5.2 夜间功率处理不当导致模型学偏
现象:模型在白天预测还行,但夜间偶尔预测出正功率,或者清晨功率爬升预测滞后。
原因:夜间功率理论上为零,但如果训练数据里夜间有微小负值或噪声,模型会学到「夜间功率在零附近波动」的模式。另外,如果简单把夜间功率全置零,模型可能学不到「功率从零爬升」的过渡过程。
解决:夜间功率截断阈值不要设太高,辐照度小于5W/m²置零即可。保留清晨和傍晚的过渡样本,这些样本对模型学习爬坡很重要。如果夜间噪声大,可以用中值滤波平滑,而不是直接置零。
5.3 特征重要性高但物理上说不通的特征
现象:某个特征重要性排前三,但你看不懂它为什么有用,比如「分钟数」比「辐照度」还重要。
原因:数据里存在时间相关的系统性偏差。比如某个月份传感器校准漂移,导致「月份」特征意外地能解释功率变化。或者数据采集系统在整点时有规律地丢包,「分钟数」变成了缺失指示器。
解决:对任何重要性异常的特征,做一次单特征消融实验:去掉它重新训练,看MAE变化。如果去掉后MAE没恶化甚至更好,说明这个特征在拟合噪声。同时检查数据采集日志,看有没有系统性偏差。
5.4 训练集和测试集分布不一致
现象:交叉验证分数很高,但留出测试集分数明显低一截。
原因:光伏数据的分布随季节变化很大。如果训练集全是夏季数据,测试集是冬季数据,模型没见过冬季的低辐照度和低温度组合,自然预测不准。
解决:切分时做分层抽样,保证每个季节都有样本进训练集。如果数据量允许,用滚动预测的方式评估:用前一年数据训练,预测下一年,逐月滚动。这样得到的评估结果更接近实际部署。
5.5 模型保存与加载时的特征顺序错乱
现象:训练时MAE是0.05,加载模型重新预测,MAE变成0.5。
原因:LightGBM保存模型时只保存树结构,不保存特征名称和顺序。加载后如果特征列顺序和训练时不一致,预测结果会完全错乱。这个问题在特征多的时候特别隐蔽,因为列名可能一样,但顺序变了。
解决:训练时把feature_cols列表保存到文件,加载模型后按同样的顺序重排特征列。或者用model.feature_name()检查特征名,和当前DataFrame的列名做对比。
import json # 保存特征列表 with open('feature_cols.json', 'w') as f: json.dump(feature_cols, f) # 加载时 with open('feature_cols.json', 'r') as f: loaded_cols = json.load(f) X_new = df[loaded_cols] # 按保存的顺序取列 y_new_pred = model.predict(X_new)6. 进阶技巧:用滚动预测和在线学习提升超短期精度
超短期光伏功率预测(未来15分钟到4小时)是实际调度最关心的场景。这个场景下,模型不能只靠历史功率和辐照度,还要考虑云层移动的实时影响。我一般用滚动预测:每15分钟用最新数据重新预测未来4小时,而不是一次性预测一整天。
滚动预测的实现要点是:维护一个滑动窗口,每次新数据到来时,更新特征并重新推理。如果模型是LightGBM,推理速度很快,15分钟一次完全没问题。如果要做在线学习,可以用model.refit或者增量训练,但要注意灾难性遗忘——新数据不能完全覆盖旧模型。
# 滚动预测示例 def rolling_forecast(model, df, feature_cols, horizon=16): """horizon: 预测步数,16步对应4小时(15分钟粒度)""" predictions = [] current_df = df.copy() for step in range(horizon): # 取最新一行做特征 latest = current_df.iloc[[-1]][feature_cols] pred = model.predict(latest)[0] predictions.append(pred) # 把预测值作为下一步的滞后特征(简化处理) new_row = current_df.iloc[-1:].copy() new_row['power'] = pred new_row.index = new_row.index + pd.Timedelta(minutes=15) current_df = pd.concat([current_df, new_row]) return predictions这个简化版滚动预测把预测值直接当作真实值回填,实际项目中要用天气预报的辐照度和温度来更新特征,而不是靠模型自己「编」未来天气。但作为入门方案,这个逻辑能帮你理解滚动预测的框架。
验证滚动预测效果,不能只看单步MAE,要看多步累积误差。我一般画一张「预测步数 vs MAE」的曲线,看误差随步数增长的速度。如果第4步MAE就翻倍,说明模型对天气变化的捕捉不够,需要加入云层运动特征或卫星辐照度数据。
最后说一个我踩过的坑:有次调参调了一整天,MAE从0.08降到0.06,结果换了一个月的数据重新测试,发现原来的参数还不如默认参数。光伏数据的季节敏感性太强,任何调参都要在多个时间段上验证,别被单月数据骗了。希望帮到你。
本文还有配套的精品资源,点击获取