简介这份资源是面向高校学生与机器学习初学者的光伏功率预测实战项目以Python为实现语言可用于毕业设计、期末大作业或课程设计等场景。项目围绕历史光伏功率数据展开通过机器学习算法挖掘发电规律完成对未来功率输出的预测帮助读者理解从数据加载、处理到模型训练与测试的完整流程。压缩包共19个文件约4.64MB包含8个csv训练与测试数据、4个py源码脚本、1个ipynb交互式笔记本、1个docx任务说明及README等文档源码附带详细注释新手也能逐步读懂。目前已有65人学习。读者可获得一套可直接部署运行的预测方案借助训练集与测试集验证模型准确性和泛化能力并通过任务说明与笔记本快速梳理项目结构适合作为入门机器学习与可再生能源方向课题的实践参考。1. 光伏功率预测项目为什么值得用 Python 机器学习重做一遍做光伏电站运维或者新能源功率预测的工程师大概率都经历过这样的场景下午两点云层飘过来逆变器出力从额定功率的 80% 直接掉到 20%调度端电话立刻打过来问为什么上报的预测曲线和实际差这么多。传统物理模型依赖数值天气预报和组件参数遇到局部云遮挡、阵风降温这类分钟级波动基本束手无策。基于机器学习的 Python 光伏功率预测项目源码及数据集解决的正是这个问题用历史发电数据加气象观测数据训练模型让预测曲线能跟上真实出力的变化节奏。这套方案适合三类人一是新能源场站做功率预测系统维护的工程师想从物理模型切换到数据驱动模型二是做能源方向课程设计或毕业设计的学生需要一套能跑通的完整代码和数据集三是想入门机器学习落地应用的开发者光伏功率预测是一个数据规整、评价指标明确、业务价值清晰的好场景。整套流程用 Python 实现从数据清洗、特征工程、模型训练到超短期光伏功率预测部署每一步都有可复现的代码和参数说明。2. 光伏功率预测的数据集怎么选、怎么读、怎么洗2.1 三类常用数据集及字段结构光伏功率预测的数据集通常来自三个渠道场站 SCADA 系统导出的历史发电数据、气象站实测数据、以及公开的科研数据集。常见做法是把这三类数据按时间戳对齐形成一个包含气象特征和目标功率的宽表。数据来源典型字段采样间隔获取难度场站 SCADA有功功率、辐照度、组件温度、环境温度1min 或 5min低场站直接导出气象站实测总辐照、直射辐照、散射辐照、风速、湿度1min中需硬件支持公开科研数据集功率、辐照、温度、时间戳5min 或 15min低网络可下载我一般会优先用场站 SCADA 数据因为功率字段最真实辐照度如果有配套传感器就更好。如果场站没有辐照度传感器可以用卫星反演辐照数据替代但精度会打折扣。公开数据集适合做算法验证但直接迁移到实际场站往往需要重新标定。读取数据用 pandas 是最稳妥的选择下面是一段标准的读取和初步探查代码import pandas as pd import numpy as np # 读取 SCADA 导出的 CSV指定时间列解析为 datetime df pd.read_csv(pv_scada_2023.csv, parse_dates[timestamp], encodingutf-8) # 统一列名方便后续处理 df.columns [timestamp, power_kw, irradiance_wm2, module_temp_c, ambient_temp_c, wind_speed_ms] # 按时间排序并设为索引 df df.sort_values(timestamp).set_index(timestamp) # 查看缺失情况 print(df.isnull().sum()) print(df.describe()) # 查看采样间隔是否均匀 time_diff df.index.to_series().diff().value_counts() print(time_diff.head(10))这段代码做了四件事解析时间列、统一列名、按时间排序、检查缺失和采样间隔。参数上注意parse_dates要指定正确的时间列名encoding根据实际文件编码调整国内场站导出的 CSV 常见 gbk 编码。describe()能快速发现异常值比如功率出现负值或者辐照度超过 1500 W/m²这些都需要在清洗阶段处理。2.2 缺失值填充与异常值剔除的实操参数光伏数据缺失是常态逆变器通讯中断、传感器故障、限电都会导致数据断档。处理方式取决于缺失比例和缺失时段连续缺失少于 3 个点线性插值df.interpolate(methodlinear, limit3)连续缺失 3 到 12 个点用同时刻前后天的均值填充连续缺失超过 12 个点直接标记为 NaN训练时丢弃该时段异常值剔除用物理约束加统计方法双管齐下。物理约束包括功率不能为负、功率不能超过装机容量、辐照度夜间应为零。统计方法用 IQR 或者 3σ但要注意光伏出力本身波动大3σ 容易误杀真实波动我一般用 IQR 的 1.5 倍作为阈值。# 物理约束剔除 capacity 10000 # 装机容量 kW df df[(df[power_kw] 0) (df[power_kw] capacity)] df df[(df[irradiance_wm2] 0) (df[irradiance_wm2] 1500)] # 夜间辐照度归零处理 night_mask (df.index.hour 6) | (df.index.hour 19) df.loc[night_mask (df[irradiance_wm2] 10), irradiance_wm2] 0 # IQR 剔除功率异常 Q1 df[power_kw].quantile(0.25) Q3 df[power_kw].quantile(0.75) IQR Q3 - Q1 df df[~((df[power_kw] Q1 - 1.5 * IQR) | (df[power_kw] Q3 1.5 * IQR))] # 线性插值补短缺失 df df.interpolate(methodlinear, limit3) df df.dropna()这里的关键参数是capacity必须和场站实际装机一致limit3控制插值最大连续点数。夜间归零的判断阈值 10 W/m² 是经验值不同传感器暗电流不同可以适当调整。IQR 系数 1.5 是标准做法如果数据质量差可以放宽到 2.0但会保留更多异常点。2.3 特征工程从时间戳和气象量里榨出有效信息原始字段直接喂给模型效果一般光伏功率预测的特征工程有几个固定动作第一时间特征。小时、分钟、一年中的第几天、星期几这些能帮助模型捕捉日周期和季节周期。小时和分钟做 sin/cos 编码避免 23 点和 0 点被模型认为距离很远。第二滞后特征。功率和辐照度的前 1 到 4 个时刻值对超短期预测特别有用。采样间隔 5 分钟的话前 4 个点就是过去 20 分钟的信息。第三滑动窗口统计。过去 30 分钟、1 小时的功率均值、标准差、最大值、最小值能反映云层移动带来的波动趋势。# 时间特征 df[hour] df.index.hour df[minute] df.index.minute df[dayofyear] df.index.dayofyear df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) # 滞后特征 for lag in [1, 2, 3, 4]: df[fpower_lag_{lag}] df[power_kw].shift(lag) df[firradiance_lag_{lag}] df[irradiance_wm2].shift(lag) # 滑动窗口统计 df[power_roll_mean_6] df[power_kw].rolling(window6).mean() df[power_roll_std_6] df[power_kw].rolling(window6).std() df[irradiance_roll_mean_6] df[irradiance_wm2].rolling(window6).mean() # 丢弃因滞后和滑动窗口产生的 NaN df df.dropna()滞后阶数选 4 是因为 5 分钟采样下 20 分钟内的功率变化对当前时刻影响最大再远的滞后特征重要性会明显下降。滑动窗口 6 对应 30 分钟这个尺度能覆盖一次云层过境的典型时长。如果采样间隔是 1 分钟窗口大小要相应调整到 15 到 30。3. 用 Python 机器学习模型跑通光伏功率预测3.1 训练集测试集划分与评价指标选择光伏功率预测不能随机划分训练集和测试集因为时间序列有自相关性随机划分会导致信息泄露。正确做法是按时间顺序切分比如前 80% 做训练后 20% 做测试。更严格的做法是用滚动窗口交叉验证但计算量大实际项目中按时间切分已经够用。评价指标方面RMSE 和 MAE 是基础但光伏领域更常用的是归一化均方根误差 nRMSE用装机容量归一化后不同场站之间可以横向对比。还有一个关键指标是准确率按国家标准定义预测偏差在装机容量 15% 以内算合格合格点占比就是准确率。from sklearn.metrics import mean_squared_error, mean_absolute_error def evaluate(y_true, y_pred, capacity): rmse np.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) nrmse rmse / capacity # 准确率偏差在 15% 容量以内算合格 accuracy np.mean(np.abs(y_true - y_pred) 0.15 * capacity) return {RMSE: rmse, MAE: mae, nRMSE: nrmse, Accuracy: accuracy}capacity参数必须用场站实际装机容量不能用测试集最大值代替否则准确率会虚高。nRMSE 一般要求低于 10%准确率要求高于 85%这是调度考核的常见门槛。3.2 从线性回归到 XGBoost模型选型与参数配置光伏功率预测的模型选型有一条清晰的演进路线线性回归做基线随机森林和 XGBoost 做主力LSTM 和 Transformer 做超短期预测。实际项目中 XGBoost 性价比最高训练快、调参少、对特征工程依赖适中。先用线性回归跑一个基线确认特征和标签之间确实存在线性关系同时得到一个最差性能参考。然后上 XGBoost重点调三个参数n_estimators控制树的数量max_depth控制单棵树复杂度learning_rate控制每棵树的贡献权重。import xgboost as xgb from sklearn.linear_model import LinearRegression # 特征列 feature_cols [irradiance_wm2, module_temp_c, ambient_temp_c, wind_speed_ms, hour_sin, hour_cos, dayofyear, power_lag_1, power_lag_2, power_lag_3, power_lag_4, irradiance_lag_1, irradiance_lag_2, power_roll_mean_6, power_roll_std_6, irradiance_roll_mean_6] X df[feature_cols].values y df[power_kw].values # 按时间切分 split_idx int(len(X) * 0.8) X_train, X_test X[:split_idx], X[split_idx:] y_train, y_test y[:split_idx], y[split_idx:] # 线性回归基线 lr LinearRegression() lr.fit(X_train, y_train) lr_pred lr.predict(X_test) print(Linear Regression:, evaluate(y_test, lr_pred, capacity)) # XGBoost model xgb.XGBRegressor( n_estimators500, max_depth6, learning_rate0.05, subsample0.8, colsample_bytree0.8, early_stopping_rounds50, eval_metricrmse ) model.fit(X_train, y_train, eval_set[(X_test, y_test)], verboseFalse) xgb_pred model.predict(X_test) print(XGBoost:, evaluate(y_test, xgb_pred, capacity))参数配置的逻辑n_estimators500配合early_stopping_rounds50让模型在验证集误差不再下降时自动停止避免过拟合。max_depth6是光伏数据的经验值再深容易记住噪声。learning_rate0.05偏小需要更多树来补偿但泛化更好。subsample和colsample_bytree都设 0.8增加随机性防止过拟合。如果 XGBoost 的 nRMSE 还在 12% 以上优先检查特征工程而不是换模型。辐照度滞后特征和滑动窗口统计对精度影响最大这两个没做好换 LSTM 也救不回来。3.3 超短期光伏功率预测的滚动预测实现超短期预测一般指未来 0 到 4 小时、分辨率 15 分钟的预测。实际部署时不能用测试集一次性预测必须滚动预测用当前时刻之前的真实数据预测下一时刻然后把预测值填入历史窗口再预测下下时刻。def rolling_forecast(model, df, feature_cols, horizon16): 滚动预测未来 horizon 个点每个点间隔 5 分钟 history df.copy() predictions [] for step in range(horizon): # 取最后一行作为当前特征 current_features history[feature_cols].iloc[-1:].values pred model.predict(current_features)[0] pred np.clip(pred, 0, capacity) # 物理约束 predictions.append(pred) # 构造下一时刻的特征行 next_time history.index[-1] pd.Timedelta(minutes5) new_row history.iloc[-1:].copy() new_row.index [next_time] new_row[power_kw] pred # 更新滞后特征 for lag in [4, 3, 2, 1]: if lag 1: new_row[fpower_lag_{lag}] pred else: new_row[fpower_lag_{lag}] history[fpower_lag_{lag-1}].iloc[-1] history pd.concat([history, new_row]) return predictions滚动预测的关键是每预测一步就把预测值写回历史同时更新滞后特征。np.clip做物理约束防止模型输出负功率或超过装机容量。horizon16对应未来 80 分钟如果要做 4 小时预测就设 48。这个函数在部署时每个时刻调用一次计算量很小XGBoost 单次预测在毫秒级。注意滚动预测的误差会累积预测步数越多精度越差。实际项目中 4 小时预测的 nRMSE 通常比 15 分钟预测高 3 到 5 个百分点这是正常的。如果误差累积太快可以在每步预测后做偏差校正用最近几个时刻的预测偏差均值来修正。4. 光伏功率预测项目落地避坑与排查4.1 数据时间戳不对齐导致模型学偏现象模型训练集 RMSE 很低但测试集 RMSE 突然翻倍预测曲线整体滞后实际功率 1 到 2 个时刻。原因SCADA 功率数据和气象数据的时间戳没有严格对齐。SCADA 记录的是采样时刻的瞬时功率气象数据可能是前一个时刻的观测值两者差一个采样间隔。模型学到的是错位的关系。解决统一时间戳基准所有数据按同一时刻对齐。如果气象数据有延迟用shift(-1)把气象数据前移一个时刻。对齐后重新训练滞后现象会消失。4.2 限电时段数据未剔除污染训练集现象模型在中午时段预测值明显偏高但实际功率被限电压得很低准确率骤降。原因训练数据里包含了限电时段功率被人为压低但辐照度是正常的。模型学到的是「高辐照度对应低功率」的错误映射。解决从调度日志或 SCADA 告警记录里标记限电时段训练时剔除这些样本。如果无法获取限电标记用辐照度和功率的比值做异常检测比值明显偏离正常范围的时段标记为限电。4.3 特征穿越导致离线指标虚高现象离线评估 nRMSE 只有 5%上线后实际 nRMSE 超过 15%。原因特征工程里用了未来信息。比如滑动窗口统计用了centerTrue或者滞后特征方向搞反了把未来时刻的功率当成了历史特征。解决检查所有特征生成代码确保每个特征只依赖当前时刻及之前的数据。滑动窗口用默认的centerFalse滞后特征用shift(positive)。上线前用最近一周的真实数据做一次模拟滚动预测验证离线指标和在线指标是否一致。4.4 模型更新频率过低导致精度衰减现象模型上线前三个月精度达标第四个月开始 nRMSE 从 8% 涨到 14%。原因光伏组件衰减、季节变化、周边新增建筑遮挡这些因素会让数据分布发生漂移。模型没有更新学的还是旧分布。解决建立定期更新机制每月用最近三个月数据重新训练一次。如果场站有新增遮挡或组件更换立即触发重新训练。更新后先用一周数据做影子测试确认精度恢复再切换。4.5 逆变器通讯中断导致实时特征缺失现象滚动预测运行时突然报错提示特征列有 NaN。原因某个逆变器通讯中断SCADA 数据出现断档滞后特征和滑动窗口统计无法计算。解决在滚动预测函数里加缺失处理逻辑。如果当前时刻功率缺失用上一时刻值填充同时把该时刻标记为低置信度。如果连续缺失超过 3 个点暂停预测并告警等数据恢复后重新初始化历史窗口。5. 把光伏功率预测模型塞进生产环境的几个硬技巧模型训练完只是第一步真正落地还要解决部署和监控的问题。我一般用 Flask 或 FastAPI 把模型包成 HTTP 接口输入是最近一段时间的 SCADA 和气象数据输出是未来 16 个点的功率预测值。接口本身很简单关键是输入数据的校验和输出结果的物理约束。from fastapi import FastAPI import numpy as np app FastAPI() model xgb.XGBRegressor() model.load_model(pv_xgb_model.json) app.post(/forecast) def forecast(data: dict): # 校验输入字段 required [irradiance_wm2, module_temp_c, ambient_temp_c, wind_speed_ms, power_lag_1, power_lag_2, power_lag_3, power_lag_4] for field in required: if field not in data: return {error: fmissing field: {field}} # 构造特征向量 features np.array([[data[f] for f in required]]) pred model.predict(features)[0] # 物理约束 pred float(np.clip(pred, 0, 10000)) return {forecast_kw: pred, timestamp: data.get(timestamp)}这个接口的输入校验不能省生产环境里上游系统传错字段是常事。物理约束用np.clip兜底防止模型输出离谱值。接口返回里带上时间戳方便和调度系统对齐。监控方面我习惯记录三个指标接口响应时间、预测偏差滚动均值、输入数据缺失率。响应时间超过 500ms 就要查是不是模型文件太大或者并发太高。预测偏差滚动均值连续 6 个点超过 15% 容量就触发告警。输入缺失率超过 5% 说明上游数据源有问题。还有一个容易被忽略的点模型文件版本管理。每次重新训练后模型文件要带版本号和训练日期接口加载时记录当前版本。出问题时能快速回滚到上一个版本这个后悔药一定要提前准备好。最后说一个我踩过的坑模型在测试环境跑得好好的上线后第一天就翻车。排查半天发现是生产环境的 Python 版本和训练环境不一致XGBoost 版本也不同模型加载后预测结果有细微差异滚动预测几步后误差就被放大了。后来我固定了训练和推理的依赖版本用 Docker 镜像统一环境这个问题再没出现过。做光伏功率预测这类对数值精度敏感的项目环境一致性比模型调参重要得多。希望帮到你。本文还有配套的精品资源点击获取