
简介一份基于机器学习的光伏功率预测完整工程项目面向毕业设计、期末大作业及课程设计场景也适合希望入门时间序列预测的初学者。工程将CSV数据读取、数据预处理、特征构建、模型训练与功率预测拆分为独立模块同时提供可交互运行的Notebook、中文任务说明和代码注释结构清晰部署门槛低。压缩包共16个文件包含8个CSV训练/测试数据文件、4个Python核心脚本、1个Jupyter Notebook、1个Markdown说明及1个Word文档整体大小约4.64MB体量轻但功能齐全。目前已有218人学习/下载。借助自带训练集和测试集可以完整跑通“数据清洗—特征工程—模型训练—功率预测”流程也可替换数据、调整模型继续扩展对需要提交高分毕设或课设作品的学生来说是一份可直接运行、便于演示和二次开发的实用参考。1. 光伏功率预测项目源码用 Python 和机器学习能做出的最快交付物拿到一份“Python基于机器学习的光伏功率预测项目源码训练数据测试数据”的高分项目包第一反应别急着翻代码先想清楚它在解决什么问题。光伏电站的功率预测本质上是一个回归任务给定历史发电功率和气象条件预测未来 15 分钟到 72 小时的输出功率。电网调度、电站运维、电力交易都依赖这个数所以这类项目在课程设计、毕业设计和实际工程里出现频率极高也最容易拿高分——因为它不要求你发明新算法而在于你能否把数据清洗、特征工程和模型选型的逻辑说清楚。这份标题里的“项目源码、训练数据、测试数据”三个词其实是三个交付物一个能跑的 Python 工程、一份带标签的历史数据集、一份用于评估模型泛化能力的测试集。我的建议是这样的包拿到手后你要做的第一件事不是补算法而是把四条主线理顺——特征是什么、预测目标是什么、时间序列怎么切分、评价指标选哪个。这篇文章按我实际做过同类项目的顺序来拆解从选型到跑通再到踩坑每一段代码和参数都可以直接抄走改到自己数据集上。适合的人群是刚接触机器学习、手里有一份光伏数据但不知道从哪下手的人以及想把这个项目改造成毕设或竞赛方案的在读学生。2. 建模选型随机森林、XGBoost 还是 LSTM先想清要分钟级还是小时级预测2.1 预测时长决定模型复杂度不是模型越新越好光伏功率预测的建模选型第一个关键因素是预测时效。电网常说“超短期预测”未来 0-4 小时和“短期预测”未来 1-3 天它们在工程实现上是两套完全不同的做法。超短期预测的特征里当前时刻的实时功率和最近一小时的变化趋势占主导——因为云层运动造成的辐照度突变在分钟级别有很强的自相关性。这种场景我用过随机森林、XGBoost 和 LSTM 三类模型实话讲如果特征构造得当XGBoost 在表格型特征上的表现往往不比 LSTM 差而且训练快、调试容易、特征重要性可以直接可视化。短期预测则不同它依赖数值天气预报NWP提供的辐照度、温度、云量预报值特征和预测目标之间的映射关系更复杂这时候梯度提升树仍然够用但如果你有连续多日的气象预报序列LSTM 或 Transformer 这类序列模型就有优势了。我一般会这样跟刚入门的人说如果你的数据只有功率历史序列和简单气象表先用 XGBoost 拿到一个稳定的 baseline再考虑要不要换深度学习模型。用一个玄学一点的说法光伏功率预测的得分瓶颈通常不在模型结构而在特征的时区对齐和清洗质量。2.2 输入特征设计的三个维度气象、历史功率、时间编码功率预测不是“给一堆特征让模型自己学”就完了。特征设计有一个固定的三维框架缺了任何一维模型都会出现严重的偏差。第一维是历史功率这是数据包里最容易拿到也最该放在第一位的特征。常见做法是构造“滞后特征”预测 t1 时刻的功率那就取 t、t-1、t-2 时刻的功率作为特征。对于光伏来说滞后步长不是越大越好一般取当前时刻往前推 6-12 个采样点足够了——因为光伏功率的日周期性很强昨天的同一时刻比昨天前三个小时更有参考价值。第二维是气象特征包括辐照度GHI、温度、湿度、风速、云量。辐照度是最强单特征没有之一。很多光伏项目只给了功率数据没给辐照度这种情况我建议用“反推法”用晴朗天气下的功率曲线反演理论辐照度再构造“功率/理论辐照度”的比值特征作为云层遮挡的代理。第三维是时间编码这一步最容易被忽略但也最能提升分数。光伏功率有强烈的双周期性——日内周期太阳升起落下和年周期季节太阳高度角变化。我习惯把小时、月份做 sin/cos 编码而不是直接用整数值因为直接用 23 和 0 两个数字会让模型误以为它们距离很远实际上只隔了一个小时。import numpy as np def create_time_features(df): 为光伏功率数据添加循环时间特征 hourly_sin np.sin(2 * np.pi * df[hour] / 24) hourly_cos np.cos(2 * np.pi * df[hour] / 24) monthly_sin np.sin(2 * np.pi * df[month] / 12) monthly_cos np.cos(2 * np.pi * df[month] / 12) df[hour_sin] hourly_sin df[hour_cos] hourly_cos df[month_sin] monthly_sin df[month_cos] monthly_cos return df这段代码的要点在于用 sin/cos 把线性时间变成单位圆上的角度映射让模型知道“23 点和 0 点相邻”、“1 月和 12 月相邻”这种周期关系。参数方面24 和 12 对应小时和月份的周期长度如果你的数据是 15 分钟采样一次小时编码应该改成 96一天 96 个采样点否则周期性编码就失效了。2.3 数据包里的模型文件怎么看检查三个东西再决定要不要重训拿到项目源码包通常会有已经训练好的模型权重文件或模型缓存。别急着加载跑预测先做三件事看特征列名列表、看模型是分类还是回归接口有些做成了分类功率离散化这会丢精度、看训练时的数据时间范围。我遇到过一个高频翻车点模型是用夏季数据训练的项目测试数据是冬季的直接跑出来的预测曲线整体偏低——因为冬季日照时长短、组件温度低功率水平整体下移模型没见过这个分布。所以我会先做一个快速验证加载模型对训练数据里的最后一周做一次“伪预测”看误差量级和实际情况是否一致。如果训练集内都拟合得很差那不是预训练模型的问题而是特征构造和加载流程没对齐。from xgboost import XGBRegressor model XGBRegressor( n_estimators300, max_depth5, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42 ) model.load_model(model/xgb_power.json) # 用训练集最后一周做伪预测检查误差基线 train_tail X_test # 假设 X_test 是已加载的测试特征 pred model.predict(train_tail) print(f预测均值: {pred.mean():.2f}) print(f测试集真实均值: {y_test.mean():.2f})这段代码里 n_estimators 和 max_depth 是树模型的核心参数前者控制树的数量后者控制单棵树的复杂度。光伏数据通常有几万行300 棵树加 5 层深度足够max_depth 超过 8 就容易过拟合表现为训练误差极低但测试误差飙升。learning_rate 设 0.05 是为了配合 300 棵树不至于训得太快失稳。如果加载模型后预测均值与真实均值偏差超过 20%大概率是训练集和测试集分布不一致需要重训而不是调整后处理。3. 把源码跑通从数据集划分到第一个可用的功率预测模型3.1 项目文件结构解读拿到包先认清每个文件夹的职责一份合格的光伏预测源码包即使不知道原作者怎么组织的也能通过几个固定目录快速定位核心代码。我见过的项目结构大多长这样data 目录放原始 CSV 和清洗后的数据model 目录放训练好的模型文件和模型结构定义代码utils 目录放数据加载、特征处理的辅助函数train.py 和 predict.py 在根目录。我的建议是不要按“src 全家桶”的方式把所有脚本塞进一个目录而是按数据流切分数据不动、特征逻辑独立、模型版本可回溯。原因很实际——功率预测项目到后期要反复实验不同特征组合和模型参数如果数据清洗和特征处理没有独立成函数你每换一次实验就要改一段又臭又长的脚本且容易污染原始数据。对于只有源码和数据、没有环境配置说明的项目包先跑通的标准做法是手动建一个虚拟环境装核心依赖。光伏功率预测项目的核心依赖其实很收敛numpy、pandas、scikit-learn、xgboost可能还有 matplotlib 用于画预测对比图。3.2 跑通最小训练流程加载数据到输出评估指标以下是一段可以直接套用的最小训练流程。它把数据加载、特征分离、时间序列切分、模型训练和评估串起来是这类项目最通用的主干代码。如果你的数据集列名不是下面的名字改列名映射即可。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, mean_squared_error from xgboost import XGBRegressor # 加载原始数据 df pd.read_csv(data/pv_power.csv, parse_dates[time]) df df.sort_values(time).reset_index(dropTrue) # 构造特征列功率滞后项 辐照度 时间编码 for lag in [1, 2, 6, 12]: df[fpower_lag_{lag}] df[power].shift(lag) df[hour] df[time].dt.hour df[month] df[time].dt.month df create_time_features(df) # 调用上一节的时间编码函数 # 丢弃没有滞后值的前 12 行避免 NaN 传染 df df.dropna().reset_index(dropTrue) feature_cols [c for c in df.columns if c not in [time, power]] X df[feature_cols] y df[power] # 时间序列切分前 80% 训练后 20% 测试不打乱顺序 split_idx int(len(df) * 0.8) X_train, X_test X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test y.iloc[:split_idx], y.iloc[split_idx:] model XGBRegressor(n_estimators300, max_depth5, learning_rate0.05) model.fit(X_train, y_train) pred model.predict(X_test) print(fMAE: {mean_absolute_error(y_test, pred):.2f}) print(fRMSE: {mean_squared_error(y_test, pred, squaredFalse):.2f})这段代码里有三个必须说的细节。第一切分用的是按时间顺序的前 80% 后 20%而不是 train_test_split 默认的随机打乱——光伏数据有强时间相关性随机打乱会暴露未来信息给训练集测试指标虚高这是时间序列预测里最常见的错误。第二滞后特征的构造使用 shift 函数shift(1) 指用上一时刻的功率预测当前时刻shift(6) 在 15 分钟采样下就是 1.5 小时前的功率。第三预测目标列 power 必须从特征列里显式排除否则模型直接拿目标值当特征属于特征泄漏评估结果会不可思议地高而换一批数据立即失效。3.3 用“性能基线”判断项目质量误差不能只看 MAPE评估光伏预测模型最常被提到的指标是 MAPE平均绝对百分比误差但这个指标有陷阱夜间功率接近 0MAPE 会被极小值拉爆。你会看到一种荒诞现象——模型在夜间误差只有几个千瓦但因为分母接近零百分比误差变成几千MAPE 高得离谱。我做这类项目时评估指标以 MAE 和 RMSE 为主MAPE 只对白天时段辐照度大于 50 W/m²单独计算。另外一个同样重要的指标是归一化均方根误差它把 RMSE 除以电站装机容量。例如装机容量 100kWp 的电站RMSE 若为 10kW那么 nRMSE 为 10%这个数在行业里算中等偏上。capacity_kw 100.0 # 电站装机容量按项目实际填 nrmse mean_squared_error(y_test, pred, squaredFalse) / capacity_kw * 100 daytime_mask X_test[irradiance] 50 daytime_mape (abs(y_test[daytime_mask] - pred[daytime_mask]) / y_test[daytime_mask]).mean() * 100 print(fnRMSE: {nrmse:.2f}%) print(f白天MAPE: {daytime_mape:.2f}%)这里的关键是把评估口径拆开nRMSE 看整体水平对装机容量的占比白天 MAPE 看真正的有效发电时段预测准度。我见过一份项目源码MAE 报得漂亮但仔细一查是按随机切分算的、还包含了夜间零值时段这种数字没有参考意义。拿到任何项目包先复算这两个口径再看是否达标。4. 训练数据与测试数据的处理清洗、特征工程和“时间上正确”的划分4.1 数据清洗的四个固定动作限值、连续性、夜间归零、检修剔除光伏功率数据是出了名的“脏”原因是逆变器和传感器的工作状态会直接影响记录值。我做过的数据里至少遇到四种异常功率超过装机容量 1.2 倍数据采集噪声、连续多个时刻功率恒为 0 但辐照度不为 0逆变器限电或停机、夜间功率不为 0传感器零漂、突然出现孤立尖峰云边效应会放大辐照度但功率突刺通常是设备异常。清洗策略是超限值直接裁剪到装机容量夜间辐照度小于 10 W/m²功率强制置 0连续零功率超过 30 分钟且辐照度大于 100 W/m² 的时段标记为“非正常发电”整段剔除而不是填充——因为逆变器限电期间的数据完全不代表真实功率能力拿这种数据训练模型学到的是“晴天也可能发不出电”这是最致命的偏差。def clean_pv_data(df, capacity_kw): 清洗光伏功率数据的固定流程 参数: df: 包含 power(kW) 和 irradiance(W/m²) 的 DataFrame capacity_kw: 电站装机容量 # 1. 功率上限裁剪 df[power] df[power].clip(0, capacity_kw * 1.1) # 2. 夜间归零辐照度极低时功率强制为0 df.loc[df[irradiance] 10, power] 0 # 3. 连续零功率但辐照度高的时段标记为异常并剔除 abnormal (df[power] 1) (df[irradiance] 100) df[is_abnormal] abnormal.astype(int) df[block_id] (df[is_abnormal] ! df[is_abnormal].shift()).cumsum() block_sizes df.groupby(block_id)[is_abnormal].transform(sum) df df[~((block_sizes 2) (df[is_abnormal] 1))] return df.drop(columns[is_abnormal, block_id])这段清洗代码要强调参数选择的原因clip 到 1.1 倍装机容量而不是 1.0 倍是因为光伏逆变器在辐照超强时允许短时过载输出 10%如果卡到 1.0 倍会误杀真实功率峰。夜间归零阈值取 10 W/m² 是行业常用判断——低于这个辐照度光伏组件基本不发电。连续异常判定用 groupby 加 block 编号实现效果等同于把连续异常区间识别出来整体删掉避免零值和真实值混杂在同一段序列里干扰滞后特征。4.2 特征工程辐照度“日外推”和“天气相似日”的构造方法气象特征里有一个高价值处理技巧是辐照度日外推。数据集提供的辐照度如果是历史实测值它在预测 t1 时刻时已经“过时”了——因为预测的意义就在于不知道未来辐照度。常见做法是把辐照度拆成两个角色历史辐照度作为特征未来辐照度作为外部输入来自 NWP 数据或假设晴空模型。对于缺少 NWP 数据的项目我一般用“晴空模型天气倍率”构造未来辐照度近似值先根据经纬度和时间算出理论晴空辐照度然后当前时刻的实测辐照度与晴空值的比值作为天气倍率预测未来的近似辐照度等于未来晴空值乘以最近一小时的平均天气倍率。这个近似不完美但比直接把历史辐照度当期值当未来值要诚实得多——后者是典型的特征泄漏。“天气相似日”是另一个在竞赛方案里出现频率很高的特征从历史数据中挑出与当前时刻天气状态最接近的前 3 天同一时刻功率取均值作为新特征。实现方式是计算当前时刻的辐照度、温度、湿度与历史每日同时段的欧氏距离取最小的几天。这个特征能让模型在缺乏精确未来气象时将当前功率引导到“类似天气曾经发多少电”的合理区间。4.3 训练集测试集划分留最后一整段不留随机样本数据划分是光伏预测项目里“看着容易、出错成本最高”的一步。很多初学拿到的项目包里训练集和测试集是直接文件分开的但划分逻辑如果是随机抽行拿这模型去做时间层面的预测一定露馅——训练时见过测试时段的邻近样本实际部署时未来数据不可见。正确做法是“不留随机样本只留时间尾段”。测试集必须是时间上连续的最后一段时间比如训练集为 2023 年 1 月 1 日到 2023 年 9 月 30 日测试集为 2023 年 10 月 1 日到 2023 年 10 月 31 日。这样模型在测试集上的表现接近于“预测未来”的真实场景而不是同分布抽样。如果项目还要求展示泛化能力可以再做一次滚动验证用 1-3 月训练、4 月测试再用 1-4 月训练、5 月测试……最后把各段误差平均这是时间序列项目的标准玩法。测试集文件里的功率标签必须保留不能为了模拟线上推理而删除。训练阶段用完整标签训练评估阶段再把预测值与标签对比。如果项目包的测试数据没有标签那需要从训练数据尾部切一段出来自建测试集否则无法验证模型好坏。5. 避坑清单光伏预测最容易翻车的 5 个问题及排查路径5.1 特征泄漏预测值出现在特征列指标好到不真实现象测试集 RMSE 低得离谱nRMSE 只有 1%-2%画出来的预测曲线和真实曲线几乎完全重合。原因特征列里包含了未来时刻的信息。最常见的有两种——一是滞后特征方向搞反了shift(-1) 把未来功率拉进来了二是辐照度列是“当天的整体实测值”而不是“截至当前时刻的实测值”模型拿到全天辐照度后自然能推算出全天功率。解决把特征列和预测目标列全部打印出来做核对逐列确认每个特征的“信息时间戳”是否早于预测目标时刻。滞后特征统一用 shift(k)k 为正整数辐照度特征如果是日内累计值改成截至当前时刻的累计值或干脆只用前一采样点的瞬时辐照度。5.2 夜间数据让模型训练失真零值占比太高现象模型整体 MAE 很低但白天峰值时段预测明显偏低预测曲线就像被“削了顶”。原因一天 96 个采样点15 分钟分辨率夜间 40-50 个点功率恒为 0白天的有效训练样本在数量上不占优势。模型发现全部预测为零也能拿到还不错的平均误差于是学会了“躺平”。解决训练时给夜间样本降权或直接只用辐照度大于 0 的时段训练。我用的是第二套策略白天时段正常参与训练夜间时段只保留一小部分用于教会模型“这种条件下应该输出零”比例控制在 10%-15%。评估时则以白天时段为主要口径夜间零值点作为附带参考。5.3 模型在训练集上 MAPE 低但测试集翻车划分方式导致数据漂移现象训练时交叉验证分数很好看但换到测试集误差翻倍预测值整体偏移。原因光伏功率数据存在季节漂移——夏天训练、冬天测试平均功率水平可能差出 40%。如果划分方式是随机抽样模型见过“未来某个时刻”的邻近样本测试失效恰好说明数据分布随时间变了。解决回归到“按时间顺序切分”的原则并在模型里加上“月份”这个显式特征让模型至少有机会学季节规律。如果测试集和训练集跨了季节不要指望模型外推得准要在项目报告里如实说明这是分布差异而不是模型缺陷。5.4 集成模型和深度模型结果一样问题不在模型在特征现象把 XGBoost 换成 LSTM、GRU误差几乎不变研究者怀疑是模型能力不足。原因模型只是拟合器特征里没有的信息它学不出来。光伏功率的强影响因素是辐照度如果辐照度特征精度差或者只给了温度没给云量换任何模型都补不了信息缺口。解决先做一次特征重要性排序XGBoost 的 feature_importances_ 或 SHAP 值可以告诉你模型在用哪些信息。如果排第一的特征是滞后 1 的功率、辐照度排不进去说明辐照度列质量差可能全是 0 或缺失优先清洗辐照度而不是换模型。5.5 预训练模型加载一堆报错版本不匹配与特征列顺序错位现象加载网上下载的模型权重时报“feature_names mismatch”或直接 KeyError训练代码里同样的模型却正常。原因XGBoost 模型文件里保存了训练时的特征列名和顺序如果你加载数据时特征列名或顺序和保存时不一致预测阶段会报参数校验失败。另外 sklearn 版本跨代时 pickle 模型文件也可能出现不兼容。解决不用直接加载模型文件而是用训练代码重新训练或至少在加载后调用 model.feature_names_ 打印特征列表跟当前数据的列名逐一比对。跨版本问题没有后悔药最稳妥的做法是用项目源码里指定的依赖版本重建环境conda 导出 environment.yml 时带上版本号。我习惯在训练脚本里加一行 model.save_model json 格式json 比 pickle 跨版本兼容性高很多。6. 进阶技巧从“能跑”到“高分”评价指标与超短期预测的调参习惯先讲评价指标的进阶用法。常规 MAE 和 RMSE 只能说明整体误差但光伏预测的评分在电力场景里通常是分时段算的——上午爬坡时段7:00-10:00预测偏慢会被考核傍晚降落时段16:00-19:00预测偏早也会被考核。我在调参时会额外画一张“误差-时刻”分布图把 24 小时的误差均值输出成一张表专盯爬坡时段的偏差。如果爬坡时段系统性偏低说明滞后特征权重太高模型反应慢需要增加“辐照度变化率”特征即当前时刻辐照度减去前一时刻辐照度模型就更容易捕捉云层快速遮挡引起的功率骤降。其次是超短期预测的实战调参习惯。做未来 15 分钟-4 小时的超短期预测时我最常用的一组参数组合是 XGBoost、max_depth4、n_estimators500、learning_rate0.03配合早停early_stopping_rounds20。max_depth 从 5 降到 4 是有意为之——超短期预测的特征里强信号集中在最近几个时刻树太深容易把训练集里的气象噪声当作规律。early_stopping 用验证集做早停训练轮数设再多也不会过拟合这是最值得养成的调参习惯。model XGBRegressor( max_depth4, n_estimators500, learning_rate0.03, subsample0.7, colsample_bytree0.7, early_stopping_rounds20, random_state42 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], verboseFalse )这段代码里的 subsample 和 colsample_bytree 都设成 0.7是防止树模型对噪声敏感的有效手段。光伏数据里偶尔会有几分钟的测量抖动降低行采样和列采样比例相当于给模型加了“模糊滤镜”。eval_set 传入验证集后早停会在连续 20 轮验证误差不降时自动停止训练省掉了手动选轮次的过程。这个习惯我沿用了很长时间先固定一个中等的 learning_rate配合早停确定 n_estimators 的大概范围再回头调 max_depth最后调采样比例而不是一上来就同时调四五个参数。最后一个压箱底的习惯是关于“兜底预测”的。光伏功率预测模型再准也会遇到极端天气——暴雨、厚云层、沙尘——模型没见过的场景这时预测曲线会和真实曲线严重偏离。我的做法是给模型输出加一层物理约束预测值不低于 0不高于当前辐照度对应的理论功率上限爬坡时的预测变化幅度不超过装机容量的 10%/15 分钟。这个约束不增加任何计算量但能避免模型输出荒谬的负功率或深夜满功率在高分项目的评估阶段它往往能救回 2%-3% 的指标分。关于这份源码和训练数据值得投入的方向我已经在每一章里铺开了。你能拿到的最值钱的资产不是预训练模型文件而是“清洗逻辑 特征定义 时间序列划分”三板斧——这三样东西才是数据包之外真正能在面试和答辩中讲清楚的东西。我的个人习惯是拿到任何新数据集先写一段数据探查脚本跑出数据覆盖时间范围、缺失率、零值占比再决定后面所有建模动作。完整的探索性分析会把坑提前暴露出来省下的返工时间远超建模本身。希望这篇文章帮到你。本文还有配套的精品资源点击获取