光伏功率预测机器学习实战:从特征工程到随机森林模型

📅 发布时间:2026/9/11 14:56:37
光伏功率预测机器学习实战:从特征工程到随机森林模型
简介这是一份基于机器学习的光伏功率预测项目完整工程包面向需要完成毕业设计、期末大作业或课程设计的高校学生也适合想入门回归预测任务的开发者项目覆盖数据处理、特征构建、模型训练与预测输出等完整流程代码附有注释关键模块简单修改即可适配其他新能源功率预测场景。压缩包共16个文件小巧易传约4.64MB其中8个csv文件为训练集与测试集数据4个py文件包含数据加载、预处理、训练预测等核心逻辑另有ipynb交互式笔记本、说明文档和任务书docx便于快速理解项目结构并运行调试目前已有218人学习下载。从内容预览看代码以光伏直流功率预测为场景包含完整的训练集与测试集划分用户在安装基础Python库后即可直接跑通结果作为导师认可的高分项目其整体功能完整、界面清爽简洁既能直接作为毕设演示也能在此基础上扩展做对比实验或学术分析实际应用价值较高。1. 为什么光伏功率预测要落到机器学习上光伏电站的出力曲线看起来是“日出而作、日落而息”的确定性过程真正做过的才知道一块云飘过来功率能瞬间掉 40%再飘走又弹回去。电网调度要的是未来 15 分钟到 4 小时的功率曲线靠物理模型做辐照度反演误差大且部署成本高靠持续预测persistence在晴天还行多云天几乎失效。于是机器学习成了这类功率预测项目的现实解用历史发电功率加气象特征训练回归模型直接拟合“辐照度→功率”的映射关系。这个项目是一套完整可跑的 Python 光伏功率预测工程结构里有 DC_Data 下的 train_1 到 train_4、test_1 到 test_4 共八份 CSV配合 Data_Process.py 做数据清洗、Train_Predict.py 做模型训练与评估、Load_Save_Data.py 做序列化持久化主入口 main.py 和 DC_PV_Power_Predict_2018.ipynb 方便你命令行跑或 notebook 逐步调。适合两类人一是做毕业设计、期末大作业需要一个能讲清数据流和模型对比的完整项目二是刚接触时序预测的工程师想找一个能替换特征和模型的最小骨架。下面按数据、特征、训练、评估、部署的顺序拆开讲。2. 数据预处理与特征构造从 CSV 到模型输入2.1 训练数据长什么样拿到 train_1.csv 这类文件先别急着训练。光伏功率预测的数据集一般包含时间戳、环境辐照度、组件温度、环境温度、风速、湿度以及实际发电功率。先读出来看一眼结构import pandas as pd df pd.read_csv(DC_Data/train_1.csv, encodingutf-8) print(df.info()) print(df.head(10)) print(df.isnull().sum())这段代码做了三件事打印字段类型与缺失数量、预览前 10 行、统计每列空值。光伏采集设备偶尔掉线时间戳不连续和功率列为 0 是常见现象缺失值比例超过 5% 的列要做标记不要直接 fillna。2.2 清洗策略异常值和夜间数据光伏功率预测有个容易忽略的问题夜间数据全是 0占了样本总量近一半。直接用全量数据训练模型会被大量零值带偏。我处理这类序列的常见做法是先按辐照度阈值过滤掉夜间样本再对功率列做 3σ 去异常import numpy as np # 过滤夜间辐照度低于 10 W/m² 的点视为无意义样本 df df[df[irradiance] 10].copy() # 功率列 3σ 去异常 mean_p, std_p df[power].mean(), df[power].std() df df[(np.abs(df[power] - mean_p) 3 * std_p)].copy() # 时间戳解析并排序 df[time] pd.to_datetime(df[time]) df df.sort_values(time).reset_index(dropTrue)过滤夜间数据能显著减少模型对零样本的过拟合3σ 去异常去掉的是传感器抖动或逆变器限功率导致的跳变点。注意逆变器限功率curtailment产生的“平台期”是真实物理现象不全是噪声异常检测后要人工抽查一下被删样本是否连续成段。2.3 特征工程滞后特征比什么都管用光伏功率预测中的最大信息来源不是天气变量而是功率自身的历史值。因为辐照度传感器和组件实际接收到的能量之间隔着积灰、遮挡、温度系数等一堆干扰而历史功率是这些因素综合作用后的最终结果。我一般至少构造三个维度的特征df[hour] df[time].dt.hour df[month] df[time].dt.month df[power_lag_1] df[power].shift(1) # 上一时刻功率 df[power_lag_2] df[power].shift(2) df[power_rolling_30min] df[power].rolling(30, min_periods1).mean() df[irradiance_lag_1] df[irradiance].shift(1) df df.dropna().reset_index(dropTrue)滞后 1 时刻和 2 时刻的功率对齐的是未来值本身的短时惯性30 分钟滑动平均可以平滑云的瞬时遮挡辐照度滞后一时刻是因为热惯性——组件温度变化滞后于辐照度变化功率响应也滞后。这个特征组合在后面的模型对比中会比单纯用天气变量高不少。2.4 归一化与数据集切分机器学习模型输入量纲不一致会拖慢收敛树模型虽然不敏感但如果后面要换 LSTM 或 MLP归一化是必选项。切分时要注意时间序列不能随机打乱再切否则训练集“偷看”了未来。from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split feature_cols [hour, month, power_lag_1, power_lag_2, power_rolling_30min, irradiance_lag_1] X df[feature_cols].values y df[power].values scaler_x MinMaxScaler() scaler_y MinMaxScaler() X_scaled scaler_x.fit_transform(X) # 按时间顺序切分前 80% 训练后 20% 验证 split_idx int(len(X_scaled) * 0.8) X_train, X_valid X_scaled[:split_idx], X_scaled[split_idx:] y_train, y_valid y[:split_idx], y[split_idx:]这里没有用 train_test_split 的随机切分参数而是按索引位置硬切。时序预测中 RandomState 固定但 shuffleTrue 同样会造成数据泄漏写代码时最容易踩。如果你要对比不同模型的表现这套归一化和切分逻辑要保持完全一致。3. 模型选型与训练为什么随机森林是可靠的基线3.1 基线模型选择逻辑光伏功率预测项目里见过有人直接上 LSTM结果训练时间长、调参困难效果还不如决策树模型。原因是这类数据集通常只有几万到十几万行深度网络的优势发挥不出来。我给这个项目定的基线是随机森林回归器非线性拟合能力强能捕捉辐照度与功率之间的分段关系对异常值鲁棒不需要精细调参就能拿到可用的分数。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error model RandomForestRegressor( n_estimators300, max_depth15, min_samples_leaf3, random_state42, n_jobs-1 ) model.fit(X_train, y_train) y_pred model.predict(X_valid) print(R2:, r2_score(y_valid, y_pred)) print(MAE:, mean_absolute_error(y_valid, y_pred)) print(RMSE:, np.sqrt(mean_squared_error(y_valid, y_pred)))n_estimators 设 300 是精度与耗时的折中超过 500 收益很小max_depth15 防止单棵树过深导致过拟合min_samples_leaf3 保证叶节点有足够样本支撑预测稳定性n_jobs-1 用满所有 CPU 核心。对几万行数据量这个配置训练时间在几十秒量级。3.2 特征重要性分析验证你的特征工程训练完成后第一件事是看特征重要性这能验证滞后特征的设计是否有效。如果 hour 和 month 排在前面说明数据里可能没有包含天气预报值模型在用周期性硬扛如果 power_lag_1 重要性显著领先说明短时惯性是主要预测来源符合光伏出力的物理特性。importances pd.Series(model.feature_importances_, indexfeature_cols) print(importances.sort_values(ascendingFalse))随机森林的特征重要性基于基尼不纯度减少量它告诉你的是“这个特征在减少预测误差中的参与度”不是因果贡献。比如 irradiance_lag_1 重要性低不代表辐照度没用可能只是被 time 相关特征吸收了共线性。做特征筛选时不要只砍掉低重要性特征要结合删除后验证集分数变化来定。3.3 扩展到 XGBoost随机森林之外的另一个选择随机森林是 bagging 思路XGBoost 是 boosting 思路后者在同样数据上往往能再提升一点精度代价是需要调学习率和树深度。XGBoost 对缺失值有内建处理如果原始数据清洗想偷懒可以依赖它但我还是建议把清洗做在前面模型才能专注拟合有效模式。import xgboost as xgb xgb_model xgb.XGBRegressor( n_estimators500, max_depth6, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42 ) xgb_model.fit(X_train, y_train, eval_set[(X_valid, y_valid)], verboseFalse) print(XGB R2:, r2_score(y_valid, xgb_model.predict(X_valid)))learning_rate0.05 配合 n_estimators500是 XGBoost 常见的“小步慢走”配置subsample 和 colsample_bytree 都设为 0.8让每棵树只看到 80% 的样本和 80% 的特征降低过拟合。XGBoost 的优势在于它对特征之间的交互项拟合更细但如果你发现验证集分数和训练集分数差得远优先降 max_depth 或增大 min_child_weight。4. 项目代码结构与再训练流程从 ipynb 到生产脚本4.1 文件职责划分这个项目的文件组织方式是典型的“数据处理与模型训练分离”main.py 是总入口Data_Process.py 里是清洗与特征构造逻辑Train_Predict.py 里是模型训练与评估Load_Save_Data.py 负责把处理好的数据和训练好的模型落盘。这种分层的意义在于换数据集时只改 Data_Process.py换模型时只动 Train_Predict.py评估指标变了也只动最后一段。# main.py 中典型的调度逻辑 from Data_Process import build_dataset from Train_Predict import train_model, evaluate_model from Load_Save_Data import save_model, load_model if __name__ __main__: df build_dataset(DC_Data/train_1.csv) X_train, X_valid, y_train, y_valid, scaler_y train_model(DC_Data/train_1.csv) pred, metrics evaluate_model(X_valid, y_valid) save_model(model, pathoutput/model.pkl)入口脚本里我只写了四个函数的调用实际使用时每个函数内部会打印日志和数据形状方便追查哪一步出了问题。我自己跑这类项目时习惯在 build_dataset 里返回一个 dict 而不是多个变量否则函数参数会越改越长。4.2 DC_PV_Power_Predict_2018.ipynb 适合什么场景notebook 文件和纯脚本的区别在于迭代速度。如果你还在摸索特征组合、对比模型阶段建议用 notebook 的方式每个 cell 只做一件事变量都在内存里改完特征立即可见。文件里已经是按流程排好的 cell从读数据到出指标一路跑下来最后可以导出 HTML 放到毕设附录里当作实验记录。真正要批量预测多个文件时再退回 main.py 用命令行跑。4.3 多文件训练数据的处理差异项目里 train_1 到 train_4 是四份独立数据每份对应的电站、季节、天气条件可能不一样。不要简单地把四个文件纵向堆在一起训练——如果各文件采样频率或功率单位不一致堆起来的模型反而会被数据量大的那份主导。我建议的做法是每个文件单独训练一个模型预测时按电站编号路由到对应模型。项目里测试集 test_1 到 test_4 与训练集编号对应设计上就是这个思路。以 train_1.csv 作为基准模型test_1.csv 作为泛化验证# 加载已保存模型并对新数据预测 model load_model(output/model_1.pkl) test_df pd.read_csv(DC_Data/test_1.csv) test_df[power_lag_1] test_df[power].shift(1) test_df test_df.dropna().reset_index(dropTrue) X_test test_df[feature_cols].values X_test_norm scaler_x.transform(X_test) test_pred model.predict(X_test_norm)加载模型时也要一起加载 scaler_x 和 scaler_y很多人保存模型时漏了标准化器预测阶段对测试数据重新 fit 了一遍分数看起来还行实际一上线就崩。这个坑在时序预测项目里出现频率极高。5. 误差分析与模型调优预测曲线对不上真实功率时怎么办5.1 晴天 vs 多云天的分段评估光伏功率预测里全局 R² 好看不代表模型可用。多云天突变点的预测误差会被晴天样本稀释。我把验证集按天气分段评估做法是按辐照度变化率把样本分成稳定段和波动段df_valid df.iloc[split_idx:].copy() df_valid[irr_diff] df_valid[irradiance].diff().abs() df_valid[segment] np.where(df_valid[irr_diff] 50, cloudy, stable) for seg in [stable, cloudy]: mask df_valid[segment] seg y_seg y_valid[mask] pred_seg model.predict(X_scaled[split_idx:][mask]) print(seg, R2:, r2_score(y_seg, pred_seg), MAE:, mean_absolute_error(y_seg, pred_seg))这段代码里 irr_diff 大于 50 W/m² 视为云的快速移动。实际操作中这个阈值要根据你的数据采样频率调整分钟级数据 50 合适秒级数据可能要放大。分段评估的目的是定位误差来源——如果波动段误差远大于稳定段说明模型缺少“变化趋势”类特征。5.2 误差时间分布与偏置修正画出误差随小时变化的曲线常常能看到固定规律上午 8 点系统性低估下午 16 点系统性高估。这是模型对辐照度爬坡和衰减阶段曲率拟合不足的表现。一个简单的修正是按小时学习误差偏置然后叠加到预测值上errors y_valid - model.predict(X_scaled[split_idx:]) hours_valid df[hour].iloc[split_idx:].values bias_df pd.DataFrame({hour: hours_valid, error: errors}) hourly_bias bias_df.groupby(hour)[error].mean() def unbiased_predict(model, X, hours): raw_pred model.predict(X) return raw_pred hours.map(hourly_bias).values偏置修正的本质是让模型在某个固定时段的高频误差被统计平均抵消。它能提升 MAE对 R² 的影响通常有限因为 R² 看的是方差解释度偏置修正不改变化形状。我一般只在最后的集成模型上做这一步在调参阶段就做会掩盖真实问题。5.3 超参数调优的边界网格搜索跑多久才算合理用 RandomForestRegressor 时n_estimators 从 100 调到 1000 耗时线性增长但精度提升很小max_depth 从 5 到 15 效果明显超过 20 开始过拟合。XGBoost 的调参顺序有讲究先定 learning_rate 和 n_estimators再调 max_depth 和 min_child_weight最后调 subsample 和 colsample_bytree。不要一上来就跑 GridSearchCV 的全参数组合几百个组合跑完可能花了几个小时收获却是“和默认参数差不多”。对于十万行以内的数据先手动把树模型跑通再考虑要不要上 LSTM。深度学习在时序预测上不是银弹——光伏功率预测的主要挑战是云的随机移动这一部分即使 LSTM 也学不会除非引入数值天气预报NWP数据作为额外特征。项目里没有包含气象预报文件所以现有模型做到 R² 0.95 以上、MAE 在额定功率的 5% 以内就已经是数据质量支撑的合理上限了。6. 超短期预测的进阶验证跑通测试集只是开始项目交付的测试集 train_1 到 test_4 是同一个时间段内切出来的还是另一个季节的这是验证模型泛化能力的关键。把 test_1.csv 跑出 R² 后拿 train_2.csv 训练出的模型去预测 test_2.csv同时用 train_1.csv 的模型去预测 test_2.csv两个结果做对比能看出模型是否过拟合到训练集的特定天气形态。这一步在很多课程设计里没有做但它恰恰是答辩时最能体现工程判断力的部分。最后分享一个我在这类项目上最常用的验证技巧预测步长拉伸测试。训练阶段构造 power_lag_1、power_lag_2 做特征模型学会的其实是“一步超前”的映射但实际调度要的是未来 1 小时甚至 4 小时的功率曲线。这时候用模型递归预测——把模型输出的预测值当作下一次预测的 power_lag_1 输入连续迭代 4 次到 12 次观察误差累积速度。def recursive_forecast(model, last_power, irradiance_seq, n_steps): preds [] current_power last_power for i in range(n_steps): feat np.array([[hour_seq[i], month, current_power, current_power, current_power, irradiance_seq[i]]]) feat scaler_x.transform(feat) current_power scaler_y.inverse_transform( model.predict(feat).reshape(-1, 1))[0, 0] preds.append(current_power) return preds如果递归预测 6 步之后误差从 3% 膨胀到 15%说明模型依赖的是短时惯性没有学到辐照度曲线形状的先验。这个结果本身不是“项目不合格”而是告诉你这类机器学习方案适用于 15 分钟以内的超短期预测更长的时间尺度需要引入外部辐照度预报或用 sequence-to-sequence 结构重构输入输出窗口。能说清楚这个边界比模型分数多 0.01 更值钱。本文还有配套的精品资源点击获取