
简介基于Python与Jupyter搭建的光伏发电功率预测项目面向毕业设计、课程设计及项目开发场景适合需要完整算法方案与实践参照的开发者。项目通过学习历史数值天气数据与对应发电功率训练模型结合未来天气数据预测光伏功率并围绕辐照度阈值处理、太阳高度与赤纬角特征构造、fbprophet时间序列拟合等关键点展开算法解析。资源包共37个文件以30个csv数据集、3个ipynb分析/建模笔记本、2个py脚本为主附带md说明文档与license压缩包大小48.23MB结构便于按数据处理、特征工程、建模预测流程逐步研读。已有1397人学习使用。内容包含数据探索、太阳功率特征构造、数据处理与模型建立等模块可直接在Jupyter中运行验证并在此基础上扩展改进适合作为课程汇报或毕设答辩的完整参考。1. 光伏发电功率预测这套 PythonJupyter 方案到底值不值得拿去做毕设很多人第一次接触光伏发电功率预测以为难点在模型——Stacking、Attention、Transformer 轮番上阵结果跑出来的精度还不如线性回归。真正做过的人都知道光伏预测 80% 的坑在数据侧辐照度传感器漂移、云层突变导致的时间序列非平稳、天气类型与功率的强耦合关系。这套基于 PythonJupyter 的光伏发电功率预测项目把数据清洗、特征构造、多模型对比和误差分析完整串成了一条可复现的流水线源码配好了可直接运行的 Jupyter Notebook 和对应的光伏电站历史数据集适合毕业设计、课程设计也适合刚入门时序预测的开发者做基线参考。项目最大的价值不是某一套模型而是它把「从原始数据到预测结果」的完整链路做了标准化处理缺失值什么策略补、辐照度为什么要做时序滞后特征、训练集和测试集怎么切才不会数据泄漏这些在代码里都有明确注释。哪怕你最后决定换模型这套数据预处理框架也能直接搬走复用。2. 光伏功率预测的建模思路为什么数据特征比模型选择更影响精度2.1 光伏发电的物理特性与预测难点光伏电站的输出功率主要由组件表面的太阳辐照度决定但实际功率曲线远不是一条平滑正弦曲线。云层遮挡会让辐照度在几十秒内剧烈波动温度升高会降低组件转换效率灰尘堆积会造成系统性偏差。这意味着光伏功率时间序列是非平稳的仅靠历史功率做单变量预测在晴天尚可在阴雨天或阵雨天气会快速失效。这个项目的做法是把气象因素显式纳入特征体系。数据集里通常包含辐照度、组件温度、环境温度、风速、风向、湿度等字段配合历史功率一起构成多变量输入。模型要学习的是「当前气象条件下组件能发出多少电」而不是单纯拟合历史功率曲线。这一点在建模前必须想清楚否则后续加多少层网络都改善有限。2.2 项目采用的算法方案与选型理由项目主体基于 LSTM 和随机森林两条路线展开对比。LSTM 适合捕捉辐照度变化的时序依赖随机森林则擅长处理特征间的非线性交互且对离群值鲁棒。两者互补正好覆盖光伏预测的两类典型场景连续晴天的平稳时段和云层快速变化的波动时段。代码中同时给出了两个模型的完整实现训练完成后自动输出 R2、MAE、RMSE 等指标还绘制了预测值与真实值的对比曲线。用同一份数据和同一套特征去跑两个模型能直观看到不同算法在同场景下的行为差异——这也是答辩时最容易讲清楚的工作量。如果你后续想换成 XGBoost 或 Prophet只需要替换模型实例化部分的代码前面的数据处理和后面的评估逻辑都能直接复用。2.3 项目目录结构与核心文件职责解压后建议先按职责梳理文件不要急着跑代码。这个项目的核心结构大致如下光伏预测项目/ │ data/ # 光伏电站原始数据集CSV │ notebook/ │ 01_数据清洗.ipynb # 缺失值处理、异常值剔除、时间戳规范化 │ 02_特征工程.ipynb # 辐照度滞后特征、温度交互特征、天气类型编码 │ 03_模型训练.ipynb # LSTM与随机森林训练与评估 │ 04_结果分析.ipynb # 误差分布图、场景切片分析 │ src/ │ data_loader.py # 数据加载与预处理函数封装 │ features.py # 特征构造工具函数 │ models.py # 模型定义与训练封装 │ requirements.txt # 依赖库版本清单先跑 01 再跑 02顺序不能乱。数据处理 Notebook 会生成中间文件后面的特征工程脚本要依赖这些中间结果。如果你改了数据路径或文件名字段需要同步修改 data_loader.py 里的路径配置和列名映射否则会直接报 KeyError。3. 级数据清洗与特征构造可复用的核心代码与参数说明3.1 数据清洗缺失值插补与异常辐照度剔除光伏数据最常见的两个脏数据问题夜间辐照度传感器归零正常但白天瞬时跳变到异常高值是传感器故障功率记录偶发缺失不能直接删行否则会破坏时间序列连续性。项目中针对这两个问题分别做了规则处理。import pandas as pd import numpy as np df pd.read_csv(data/pv_farm.csv, parse_dates[timestamp]) df.set_index(timestamp, inplaceTrue) # 剔除辐照度超过物理上限的异常点太阳常数约1367W/m2实际地表通常1200 df df[df[irradiance].between(0, 1200)] # 功率为负且辐照度接近0时属于正常的夜间状态不处理 # 但功率为负且辐照度100时属于传感器或记录异常按缺失处理 mask (df[power] 0) (df[irradiance] 100) df.loc[mask, power] np.nan # 时序缺失值采用线性插值适合辐照度和功率这类连续变化量 df[power] df[power].interpolate(methodlinear, limit_directionboth) df[irradiance] df[irradiance].interpolate(methodlinear, limit_directionboth) # 对功率做滚动中位数滤波压制单点毛刺 df[power_smooth] df[power].rolling(window5, centerTrue, min_periods1).median()插值前先做异常值屏蔽是这里的关键顺序。如果你先插值再剔除异常点异常值会参与插值计算把周围的正常数据一起带偏。limit_directionboth保证序列开头和结尾的缺失值也能被补上否则前几个时间点的数据会变成 NaN训练时直接被丢弃。滚动中位数窗口设为 5 是经验值对 15 分钟粒度的数据来说能有效压制单点毛刺同时不会过度平滑掉真实的功率波动。处理完的数据会保存在一个新 CSV 里供特征工程环节读取。建议在清洗后打印一份数据质量报告确认每列的缺失率、取值区间和时间跨度避免带着脏数据进入建模阶段。3.2 特征工程滞后特征与交互特征构造特征工程是整个项目里投入产出比最高的一步。光伏功率预测中当前时刻的辐照度当然重要但前 15 分钟、前 30 分钟的辐照度变化趋势同样关键——它反映了云层是正在靠近还是已经过境。项目构造了多阶滞后特征并加入了辐照度与温度、风速的交互项。def build_features(df, lag_steps[1, 2, 3, 6]): df df.copy() # 滞后特征前n个时间步的辐照度和功率 for lag in lag_steps: df[firradiance_lag_{lag}] df[irradiance].shift(lag) df[fpower_lag_{lag}] df[power_smooth].shift(lag) # 滚动统计特征过去1小时辐照度的均值与标准差反映云层波动强度 df[irradiance_roll_mean] df[irradiance].rolling(window4).mean() df[irradiance_roll_std] df[irradiance].rolling(window4).std() # 交互特征温度过高时光电转换效率下降用辐照度与温度乘积捕捉该效应 df[irradiance_temp_interact] df[irradiance] * df[module_temp] # 时间特征小时和月份光伏出力有日内和季节性规律 df[hour] df.index.hour df[month] df.index.month # 删除构造特征时引入的NaN行前几行没有滞后数据 df.dropna(inplaceTrue) return df feature_df build_features(df)滞后步长的选择直接影响模型效果。数据是 15 分钟粒度[1, 2, 3, 6]对应的是前 15 分钟到前 90 分钟的状态。如果你换成了小时粒度数据这个列表要相应调整否则滞后 6 步就变成了前 6 小时特征意义完全不同。滚动窗口为 4 同样基于 15 分钟粒度设计代表过去 1 小时。dropna这一步会丢掉数据前几行在时间序列场景下是正常的因为最早的几个时间步没有足够的滞后信息。但如果丢掉的行数远超预期需要检查是不是原始数据本身有缺口。3.3 数据集切分防止数据泄漏的关键一步时序预测的数据切分和普通机器学习完全不同不能随机打乱。项目代码中明确了这一点训练集只使用前 80% 的时间段测试集为最后 20%。这样才能模拟「用过去预测未来」的真实场景。from sklearn.model_selection import train_test_split X feature_df.drop(columns[power_smooth]) y feature_df[power_smooth] # 时序数据必须按时间顺序切分禁止shuffle train_size int(len(feature_df) * 0.8) X_train, X_test X.iloc[:train_size], X.iloc[train_size:] y_train, y_test y.iloc[:train_size], y.iloc[train_size:] # 特征缩放LSTM对尺度敏感标准化后训练更稳定 from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 保存scaler参数预测新数据时需要做同样的变换 import joblib joblib.dump(scaler, scaler.pkl)fit_transform只能用在训练集上测试集必须用训练集的均值和标准差做transform两者的参数不能混用。如果你先对整个数据集做标准化再切分测试集的信息就泄漏到了训练过程中评估指标会虚高答辩时容易被追问。scaler 保存下来是实际部署时的关键步骤新数据进来必须先做同样的标准化再输入模型。4. 模型训练与对比分析LSTM 和随机森林的完整实现4.1 LSTM 模型结构与训练参数LSTM 部分项目用的是 Keras 实现。光伏功率预测的输入是二维表格数据需要先重塑成三维张量才能送入 LSTM 层。序列长度这里设为滞后步数加上其他特征的总维度每个样本都是一个时间窗口内的特征序列。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping # 重塑为LSTM输入格式(样本数, 时间步长, 特征数) timesteps 6 n_features X_train_scaled.shape[1] X_train_3d X_train_scaled.reshape((X_train_scaled.shape[0], timesteps, n_features // timesteps)) X_test_3d X_test_scaled.reshape((X_test_scaled.shape[0], timesteps, n_features // timesteps)) model Sequential([ LSTM(units64, return_sequencesTrue, input_shape(timesteps, n_features // timesteps)), Dropout(0.2), LSTM(units32, return_sequencesFalse), Dropout(0.2), Dense(units16, activationrelu), Dense(units1, activationlinear) ]) model.compile(optimizeradam, lossmse, metrics[mae]) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train_3d, y_train.values, validation_split0.1, epochs100, batch_size32, callbacks[early_stop], verbose1 )reshape 时有个容易踩坑的地方n_features // timesteps必须能整除否则会报错。LSTM 第一层设 64 个单元是相对保守的起始值光伏数据的复杂度撑不起太大网络反而容易过拟合。Dropout 设为 0.2 用来抑制对训练集噪声的拟合训练早期如果发现验证集损失持续上升可以适当增加到 0.3。EarlyStopping 的patience10表示连续 10 个 epoch 验证损失没有改善就提前停止restore_best_weights会回滚到最优权重。训练完成后用同一套训练好的模型对测试集做预测得到的预测值要先做逆标准化才能跟真实功率对比——这一点在评估部分尤其重要否则误差数量级完全不正确。4.2 随机森林模型与特征重要性分析随机森林部分的实现更直接适合做对照实验。它不需要特征缩放对异常值也相对不敏感训练速度远快于 LSTM是快速验证特征工程是否有效的理想工具。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error rf_model RandomForestRegressor( n_estimators200, max_depth15, min_samples_split5, min_samples_leaf2, random_state42, n_jobs-1 ) rf_model.fit(X_train_scaled, y_train.values) rf_pred rf_model.predict(X_test_scaled) # 评估指标 rf_r2 r2_score(y_test, rf_pred) rf_mae mean_absolute_error(y_test, rf_pred) rf_rmse np.sqrt(mean_squared_error(y_test, rf_pred)) print(f随机森林 R2: {rf_r2:.4f}, MAE: {rf_mae:.2f} kW, RMSE: {rf_rmse:.2f} kW) # 特征重要性排序 feature_importance pd.DataFrame({ feature: X.columns, importance: rf_model.feature_importances_ }).sort_values(importance, ascendingFalse) print(feature_importance.head(10))n_estimators200在多数光伏数据集上已经收敛继续增大收益很小但训练时间线性增长。max_depth15防止单棵树过深导致过拟合对中小规模数据集是稳妥选择。特征重要性输出能直观看到哪些特征贡献最大通常排名靠前的是辐照度滞后项和滚动统计量这也反过来验证了特征工程方向没有做偏。从实测经验看随机森林在数据量小、特征工程充分的情况下精度往往逼近甚至超过 LSTM且训练时间只有后者的几十分之一。这并不意味着 LSTM 无用——在数据量大、时序依赖强的场景下LSTM 的上限更高。项目让两个模型并行对比正好能给出这个多维度的结论。4.3 预测结果可视化与误差分析评估一个光伏预测模型的核心指标是 R2、MAE 和 RMSE。R2 反映整体拟合优度MAE 反映平均绝对偏差RMSE 对大幅误差更敏感能暴露模型在剧烈天气变化下的失效情况。项目代码中生成了预测值与真实值的时序对比曲线。import matplotlib.pyplot as plt plt.figure(figsize(14, 6)) plt.plot(y_test.index, y_test.values, labelActual, linewidth1.5) plt.plot(y_test.index, rf_pred, labelRandom Forest Pred, alpha0.8) plt.plot(y_test.index, lstm_pred, labelLSTM Pred, alpha0.8) plt.xlabel(Time) plt.ylabel(Power (kW)) plt.title(PV Power Prediction Comparison) plt.legend() plt.grid(alpha0.3) plt.tight_layout() plt.show() # 按辐照度分箱统计误差定位模型薄弱场景 df_result pd.DataFrame({ actual: y_test.values, rf_pred: rf_pred, lstm_pred: lstm_pred, irradiance: X_test[irradiance].values }) df_result[abs_error_rf] (df_result[actual] - df_result[rf_pred]).abs() # 低辐照度200、中辐照度、高辐照度三组分别计算MAE low_mae df_result[df_result[irradiance] 200][abs_error_rf].mean() mid_mae df_result[(df_result[irradiance] 200) (df_result[irradiance] 600)][abs_error_rf].mean() high_mae df_result[df_result[irradiance] 600][abs_error_rf].mean() print(f低辐照度MAE: {low_mae:.2f} kW, 中辐照度MAE: {mid_mae:.2f} kW, 高辐照度MAE: {high_mae:.2f} kW)按辐照度分箱统计误差这个做法非常实用能精确定位模型失效场景。实际运行中常见的结论是晴天高辐照度时段误差最小阴雨天低辐照度时段误差被大幅拉高——这不是特征工程的问题而是阴雨天功率本身随机性强物理上就难预测。写论文时这段分析很有价值它证明了模型的问题边界所在。5. 常见问题与避坑从数据泄漏到维度不匹配的排查记录5.1 训练指标很高但测试指标崩塌大概率数据泄漏现象训练集 R2 达到 0.97测试集 R2 却只有 0.4两者差距悬殊。原因最常见的是数据切分前对整个数据集做了标准化或归一化scaler 的统计量包含了测试集信息。另一种常见情况是特征构造时使用了未来数据比如用当天平均辐照度去预测当天每个时刻的功率这在训练时表现优异因为目标信息和特征信息高度重合但真实场景根本拿不到当日均值。解决严格执行先切分、后标准化的顺序。特征工程只使用当前时刻和过去时刻的数据凡是需要「未来窗口」的统计量一律禁止。检查特征列里有没有_target 的同源衍生字段确认滞后特征的 shift 方向正确。5.2 程序报错 reshape 无法整除时间步长与特征数配置冲突现象运行 LSTM 训练代码时X_train_3d X_train_scaled.reshape(...)这一行报错cannot reshape array。原因特征总数除以时间步长不是整数。通常是你新增了一列特征比如风速但忘记同步更新timesteps参数导致 n_features 不再是 timesteps 的整数倍。解决在 reshape 前打印n_features根据实际特征维度调整timesteps或特征列数。我一般会把n_features X_train_scaled.shape[1]打印出来确认无误后再 reshape避免一头扎进维度泥潭。5.3 预测值整体低于真实值尤其在晴天正午现象测试集整体 R2 还不错但查看对比曲线发现正午时段预测值系统性偏低早晚时段反而没问题。原因正午辐照度高、组件温度也高光伏组件存在温度折减效应——组件温度每升高 1 度输出效率下降约 0.4%。模型如果没有捕捉到这个交互效应会在高温时段高估功率反之如果你用的是功率平滑后的序列正午的短时云遮挡被平滑掉模型学到的就是一个偏低的下限。解决确认特征中有irradiance_temp_interact交互项。如果模型还是没有吃进去这个规律可以单独构造「组件温度与标准温度 25 度的偏差」作为特征让模型更直接地感知折减效应。5.4 随机森林预测结果远差于线性回归现象随机森林跑完R2 只有 0.6还不如简单线性回归。原因大概率是特征缩放出了问题。随机森林不需要标准化但如果特征包含无序原始量纲比如辐照度 800 和 hour 23树模型对特征尺度的敏感性虽然低但极端量纲差异会导致分裂点选取偏向大数值特征。更常见的原因是滞后特征没带上模型只能用当前时刻的辐照度预测当前时刻的功率无法利用趋势信息。解决建特征前先检查build_features是否完整执行。随机森林对这种场景最吃特征质量建议按第 3 章的特征列表逐项核对尤其确认滞后项和滚动标准差存在。5.5 模型输入维度对不上训练用 3D 预测用 2D现象训练正常但调用模型做预测时报错维度不匹配。原因LSTM 训练时输入是(样本数, 时间步长, 特征数)预测时必须使用同样的 3D 结构。很多人在预测阶段直接传 2D 的X_test_scaled导致维度校验失败。解决预测之前先用reshape转换测试数据与训练输入保持完全一致。代码里建议把 reshape 封装成独立函数训练和预测统一调用就不会出现两边维度不一致的问题。6. 项目化落地把 Jupyter Notebook 改造成可独立运行的预测脚本Notebook 适合演示和调参但真要交付或者做扩展还是得抽成脚本。项目里 src 目录已经做了初步封装我的建议是完全脱离 Notebook 环境跑一遍全流程这能暴露隐藏的路径依赖和全局状态问题。# run_predict.py —— 独立可执行的预测入口 from src.data_loader import load_data from src.features import build_features from src.models import train_models import joblib import sys def main(config_path): cfg load_config(config_path) # 第一步加载与清洗 df load_data(cfg[data_path]) print(f数据加载完成共 {len(df)} 条记录) # 第二步特征工程 feature_df build_features(df, lag_stepscfg[lag_steps]) print(f特征构造完成当前特征维度 {feature_df.shape[1]}) # 第三步训练与保存 models train_models(feature_df, model_listcfg[models]) for name, model in models.items(): joblib.dump(model, fmodel_{name}.pkl) print(f模型 {name} 已保存) if __name__ __main__: main(config.yaml)脚本化的核心是消除 Notebook 的全局变量污染。Notebook 里如果在前面某格定义了一个变量后面换数据重跑时很容易用错旧值。独立脚本强制你把所有参数通过配置文件传入每次运行都是干净状态。对毕设答辩来说这一版脚本还能直接演示「给一段新数据模型自动输出预测曲线」的完整过程比翻 Notebook 截图更有说服力。验证预测效果时我习惯把测试期最后一周的数据单独抽出模拟真实的上线流程。用前 80% 的数据训练后 20% 做滚动预测并把每天的预测和实际功率曲线画在同一张图上逐小时观察偏差。只有这时候你才会发现夜间时段功率归零模型偶尔会预测出小幅负值——物理上不可能。处理方式是在输出层加max(0, pred)约束几行代码就能修复。从那以后我每次做光伏预测项目都会强制走一遍「先画真实曲线、再对照预测曲线、最后按场景切片看误差」的流程。这一步能拦住大部分自欺欺人的高精度——R2 再好看如果阴雨天误差远超晴天两倍工程上就得明确告知使用方这个边界。希望这篇拆解能帮你在毕设或项目开发中少走几步弯路也让这份源码和数据集的真正价值尽早释放出来。本文还有配套的精品资源点击获取