☰
光伏功率预测机器学习实战:完整项目包与避坑指南
2026/10/12 12:52:06 网站建设 项目流程

简介:这是一份面向计算机相关专业学生与项目实战学习者的光伏功率预测完整项目包,以Python与机器学习为核心,可用于毕业设计、课程设计或期末大作业。资源包含可直接运行的源码、训练数据与测试数据,覆盖数据加载、预处理、模型训练与预测等完整流程,并配有任务说明文档与Jupyter Notebook,便于理解建模思路与实验步骤。压缩包共16个文件,以8个csv数据文件、4个py脚本为主,另含1个ipynb笔记本、1个md说明、1个docx文档及gitignore配置,整体约4.64MB,结构清晰、轻量易上手。项目经导师指导并获评审99分,代码完整可运行,适合零基础小白按目录逐步复现。目前已有56人学习,读者可借此掌握光伏功率预测的机器学习实现方法,积累数据处理、模型训练与结果验证的实战经验,并作为高分项目参考模板。

1. 光伏功率预测项目拆包:一份能跑通的机器学习实战资源

光伏功率预测这个方向,做的人不少,但真正能拿到一份「数据齐全、代码能跑、流程闭环」的完整项目包,其实没那么容易。很多开源仓库要么只给模型代码不给数据,要么数据是脱敏过的残缺版本,跑起来一堆报错。这份资源不一样的地方在于,它把训练数据、测试数据、数据处理脚本、训练预测脚本、Notebook 实验记录全部打包在一起了,解压就能看到DC_Data目录下按编号分好的train_1.csv到train_4.csv、test_1.csv到test_4.csv,配套Data_Process.py、Train_Predict.py、Load_Save_Data.py三个核心脚本,外加一个DC_PV_Power_Predict_2018.ipynb用来做交互式实验。适合正在做机器学习课程设计、期末大作业或者毕业设计的同学,也适合想找一个完整光伏预测 pipeline 来练手的从业者。下面我按「这份资源是什么 → 怎么跑起来 → 坑在哪 → 怎么改出自己的东西」的顺序,把整个包拆一遍。

2. 数据管线拆解:从 CSV 到模型输入的四步处理链

2.1 数据文件的组织逻辑与字段含义

先看数据。DC_Data目录下的文件命名很直白:train_1.csv到train_4.csv是四组训练集,test_1.csv到test_4.csv是对应的测试集。这种按编号分组的方式,常见做法是不同时间段、不同季节或者不同电站的切片数据,用来做交叉验证或者分场景评估。每组数据内部通常包含时间戳、辐照度、温度、湿度、风速等气象特征列,以及目标列——光伏功率输出。

拿到数据后第一件事不是急着喂模型,而是确认字段类型和缺失情况。我一般会先跑一段探查代码:

import pandas as pd # 读取第一组训练数据,先看结构和缺失 df = pd.read_csv('DC_Data/train_1.csv') print(df.shape) print(df.dtypes) print(df.isnull().sum()) print(df.describe())

这段代码做三件事:看数据规模(行数列数)、看每列的数据类型(有没有把数值列读成 object)、看缺失值分布。光伏数据里最常见的缺失是夜间功率为 0 或者传感器掉线导致的空值,处理方式不同,后面模型表现差异很大。describe()能快速看出有没有异常极值,比如辐照度出现负值或者功率超过装机容量,这些都要在清洗阶段处理掉。

2.2 Data_Process.py 里的清洗与特征工程

Data_Process.py是整个管线的入口脚本,负责把原始 CSV 转成模型能吃的格式。虽然具体实现要看源码,但这类光伏预测项目的处理逻辑基本跑不出几个固定动作:时间戳解析、缺失值填充、异常值剔除、特征归一化、训练测试集划分。

常见的处理顺序是这样的:

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def process_data(filepath): df = pd.read_csv(filepath) # 时间戳解析,按需提取小时特征 df['timestamp'] = pd.to_datetime(df['timestamp']) df['hour'] = df['timestamp'].dt.hour # 夜间功率置零处理,辐照度为 0 时功率强制归零 df.loc[df['irradiance'] <= 0, 'power'] = 0 # 缺失值用前向填充 + 后向填充兜底 df = df.fillna(method='ffill').fillna(method='bfill') # 归一化到 [0,1] scaler = MinMaxScaler() feature_cols = ['irradiance', 'temperature', 'humidity', 'wind_speed'] df[feature_cols] = scaler.fit_transform(df[feature_cols]) return df

这里有几个参数值得注意。MinMaxScaler对光伏数据是合理选择,因为辐照度和功率都有明确物理边界,不像金融数据那样存在长尾分布。夜间功率置零这一步很关键——如果不做,模型会学到「辐照度为 0 但功率不为 0」的矛盾样本,预测时容易出现夜间输出虚高。缺失值填充用前向+后向组合是保守做法,更精细的方案是按天气类型分组填充,但对课程设计来说够用了。

2.3 Load_Save_Data.py 的读写封装

Load_Save_Data.py从命名看是负责数据加载和结果保存的封装模块。这类脚本的价值在于把重复的 IO 操作抽出来,避免在每个实验脚本里写一遍pd.read_csv。典型实现会包含两个函数:一个负责按编号加载指定组的训练/测试数据,另一个负责把预测结果写回 CSV 或者 pkl 文件。

import pandas as pd import os DATA_DIR = 'DC_Data' def load_group(group_id): """按编号加载一组训练和测试数据""" train_path = os.path.join(DATA_DIR, f'train_{group_id}.csv') test_path = os.path.join(DATA_DIR, f'test_{group_id}.csv') train_df = pd.read_csv(train_path) test_df = pd.read_csv(test_path) return train_df, test_df def save_predictions(preds, filename='predictions.csv'): """保存预测结果""" pd.DataFrame(preds, columns=['predicted_power']).to_csv(filename, index=False)

这种封装看起来简单,但实际做实验时能省很多事。比如你想跑四组数据的对比实验,只需要循环调用load_group(i),不用手动改路径。save_predictions统一了输出格式,后面画对比图或者算误差指标时直接读这个文件就行。

2.4 训练测试集的划分策略

四组 train/test 的划分方式决定了你后面评估模型时的可信度。如果train_1和test_1是随机切分的,那评估结果会偏乐观;如果是按时间顺序切的(比如前 80% 训练、后 20% 测试),那更接近真实部署场景。光伏功率预测里,时间序列切分是更合理的做法,因为随机切分会导致未来信息泄漏——模型在训练时见过了「未来」的天气模式,测试时自然表现好,但上线就翻车。

判断方法很简单,看测试集的时间戳范围是不是紧接在训练集之后:

train_df, test_df = load_group(1) print('训练集时间范围:', train_df['timestamp'].min(), '至', train_df['timestamp'].max()) print('测试集时间范围:', test_df['timestamp'].min(), '至', test_df['timestamp'].max())

如果测试集的起始时间晚于训练集结束时间,说明是按时间切的,评估结果更可信。如果两组时间范围重叠,那就要注意过拟合风险,后续调参时不能只看测试集指标。

3. 模型训练与预测:Train_Predict.py 的完整执行链路

3.1 模型选型与 sklearn 接口约定

Train_Predict.py是核心训练脚本。从项目定位(机器学习课程设计/毕业设计)来看,模型选型大概率落在随机森林、梯度提升树(GBDT/XGBoost/LightGBM)或者支持向量回归(SVR)这几类。光伏功率预测这个任务,特征维度不高、样本量中等,树模型通常是性价比最高的选择——不用太多调参就能出合理结果,而且特征重要性可以直接解释。

sklearn 风格的接口约定是fit(X_train, y_train)→predict(X_test),整个流程很标准:

from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np # 假设 X_train, y_train, X_test, y_test 已经准备好 model = RandomForestRegressor( n_estimators=200, # 树的数量,200 是常用起点 max_depth=12, # 限制深度防止过拟合 min_samples_leaf=5, # 叶节点最少样本数 random_state=42 # 固定随机种子保证可复现 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) # 三个核心指标 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}, RMSE: {rmse:.4f}, R2: {r2:.4f}')

参数说明:n_estimators从 100 到 500 都常见,200 是平衡训练时间和效果的起点;max_depth不设的话树会一直长到叶子纯净,光伏数据噪声不小,限制到 10-15 层通常更稳;min_samples_leaf设 3-5 能防止模型记住个别异常样本。random_state一定要固定,不然每次跑出来的指标都不一样,调参时根本分不清是参数变了还是随机波动。

3.2 特征与目标的拆分逻辑

训练之前要把 DataFrame 拆成特征矩阵 X 和目标向量 y。光伏预测里,目标列就是功率,特征列是气象因子加上时间特征。拆分时最容易犯的错是把目标列也塞进特征里,或者把时间戳直接当数值特征喂进去。

# 假设 df 已经过 Data_Process 处理 target_col = 'power' drop_cols = ['timestamp', 'power'] # 时间戳不直接作为数值特征 feature_cols = [c for c in df.columns if c not in drop_cols] X = df[feature_cols].values y = df[target_col].values # 按时间顺序切分,前 80% 训练 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:]

时间戳不直接作为特征,但可以从里面提取小时、月份、季节等周期特征。hour对光伏预测特别重要,因为它隐含了太阳高度角信息。如果Data_Process.py里已经做了这步,这里就不用重复。按 80/20 切分是常见做法,样本量大的话可以调到 90/10,样本少就 70/30。

3.3 预测结果的反归一化与保存

如果训练前对目标列做了归一化,预测出来的是 [0,1] 区间的值,必须反归一化才能还原成实际功率。这一步经常被漏掉,导致预测结果看起来「小得离谱」。

# 假设 scaler_y 是训练时对 power 列拟合的 scaler y_pred_original = scaler_y.inverse_transform(y_pred.reshape(-1, 1)).flatten() y_test_original = scaler_y.inverse_transform(y_test.reshape(-1, 1)).flatten() # 保存结果 result_df = pd.DataFrame({ 'actual': y_test_original, 'predicted': y_pred_original }) result_df.to_csv('DC_Data/prediction_result.csv', index=False)

反归一化要用训练集拟合的 scaler,不能用测试集重新拟合,否则等于引入了测试集信息。保存结果时把实际值和预测值放在一起,后面画对比曲线或者算分时段误差都方便。

3.4 Notebook 实验记录的使用方式

DC_PV_Power_Predict_2018.ipynb是交互式实验记录,通常包含数据探索、特征可视化、模型对比、调参过程这些内容。Notebook 的价值在于它保留了中间输出——你能看到每一步的图表和数值,而不是只拿到一个最终脚本。

打开 Notebook 后建议按顺序执行,不要跳着跑。如果某个 cell 报错,先检查前面的数据加载 cell 是否成功执行。Notebook 里常见的坑是变量名和脚本文件不一致,比如 Notebook 里叫df_train,脚本里叫train_df,混着用会报 NameError。我一般会先把 Notebook 完整跑一遍,确认所有 cell 都能通过,再对照Train_Predict.py看两者的逻辑差异。

4. 避坑与排查:跑这份源码时最容易翻车的五个地方

4.1 路径问题导致 FileNotFoundError

现象:运行Data_Process.py或Train_Predict.py时报FileNotFoundError: [Errno 2] No such file or directory: 'DC_Data/train_1.csv'。

原因:脚本里的相对路径是相对于当前工作目录的,不是相对于脚本文件所在目录。如果你在项目根目录的上一级运行python 项目文件夹/Train_Predict.py,工作目录就不对了。

解决:要么cd到项目根目录再运行,要么在脚本开头加一段路径修正:

import os os.chdir(os.path.dirname(os.path.abspath(__file__)))

这行代码把工作目录切到脚本所在目录,后面所有相对路径就都对了。

4.2 pandas 版本差异导致 fillna 报错

现象:df.fillna(method='ffill')报TypeError: fillna() got an unexpected keyword argument 'method'。

原因:pandas 2.0 之后废弃了method参数,改用ffill()和bfill()方法。

解决:把df.fillna(method='ffill')改成df.ffill(),df.fillna(method='bfill')改成df.bfill()。如果代码里还有inplace=True,也建议改成赋值形式,因为 pandas 3.0 会进一步限制 inplace 行为。

4.3 归一化后目标列反归一化遗漏

现象:模型输出的预测值都在 0 到 1 之间,和实际功率(几百到几千 kW)完全对不上。

原因:训练时对目标列做了归一化,预测后忘了反归一化。

解决:检查Data_Process.py或Train_Predict.py里是否对power列做了fit_transform。如果有,预测后必须用同一个 scaler 做inverse_transform。注意 scaler 要保存下来,不能重新拟合。

4.4 随机种子未固定导致结果不可复现

现象:同样的代码跑两次,R2 一次 0.92 一次 0.87,差距明显。

原因:模型初始化、数据切分、特征采样里只要有一处没固定random_state,结果就会有随机波动。

解决:在模型初始化、train_test_split、任何涉及随机采样的地方都加上random_state=42。如果是深度学习框架,还要固定 numpy 和框架自身的随机种子。

4.5 测试集评估指标异常好

现象:测试集 R2 超过 0.98,但画预测曲线发现模型在夜间也输出了高功率。

原因:训练集和测试集随机切分导致时间泄漏,模型学到了「未来」的信息。或者测试集里混入了训练集样本。

解决:改成按时间顺序切分,确保测试集时间戳全部晚于训练集。同时检查数据去重,确认没有同一条记录同时出现在 train 和 test 里。

5. 从跑通到改出自己的一套:特征构造与模型对比的实操建议

跑通这份源码只是起点,真正让它变成你自己的东西,得在特征和模型上做文章。光伏功率预测的精度瓶颈往往不在模型本身,而在特征质量。原始数据里的辐照度、温度、湿度是最基础的,但太阳高度角、云量变化率、历史功率滑动平均这些衍生特征,对提升预测精度帮助很大。

一个我常用的特征构造套路是加「滞后特征」和「滑动窗口统计」:

# 滞后特征:前一时刻的功率 df['power_lag1'] = df['power'].shift(1) df['power_lag2'] = df['power'].shift(2) # 滑动窗口:过去 3 个时刻的均值和标准差 df['power_roll_mean3'] = df['power'].rolling(window=3).mean() df['power_roll_std3'] = df['power'].rolling(window=3).std() # 辐照度变化率 df['irradiance_diff'] = df['irradiance'].diff() # 去掉因 shift/rolling 产生的首行空值 df = df.dropna()

滞后特征让模型能看到「上一时刻功率是多少」,这对超短期光伏功率预测特别有用,因为功率变化有惯性。滑动标准差捕捉的是波动剧烈程度,云层快速移动时这个值会变大,模型可以据此调整预测策略。辐照度变化率是判断天气突变的一个简单指标。

模型对比方面,建议至少跑三个基线:随机森林、梯度提升树、和一个线性模型(岭回归或 Lasso)。线性模型虽然精度通常不如树模型,但它的系数能告诉你哪些特征最重要,对理解数据有帮助。对比时统一用同一组训练测试数据、同一套评估指标,不要这个模型用 MAE 那个用 RMSE,不然没法比。

from sklearn.linear_model import Ridge from sklearn.ensemble import GradientBoostingRegressor models = { 'Ridge': Ridge(alpha=1.0), 'RandomForest': RandomForestRegressor(n_estimators=200, max_depth=12, random_state=42), 'GBDT': GradientBoostingRegressor(n_estimators=200, max_depth=5, random_state=42) } for name, model in models.items(): model.fit(X_train, y_train) pred = model.predict(X_test) r2 = r2_score(y_test, pred) print(f'{name} R2: {r2:.4f}')

如果 GBDT 明显好于随机森林,可以进一步试 XGBoost 或 LightGBM,但要注意安装依赖。如果三个模型差距不大,说明特征信息量已经榨得差不多了,该回去补特征而不是换模型。

还有一个容易被忽略的点:分时段评估。光伏功率在早晨、正午、傍晚的预测难度完全不同,正午辐照度稳定时预测很准,日出日落时段功率爬坡快,误差会大很多。把测试集按小时分组算 MAE,能看出模型在哪些时段最弱:

test_df['hour'] = pd.to_datetime(test_df['timestamp']).dt.hour test_df['pred'] = y_pred_original test_df['actual'] = y_test_original hourly_mae = test_df.groupby('hour').apply( lambda g: mean_absolute_error(g['actual'], g['pred']) ) print(hourly_mae)

这个分时段误差表比一个总体 R2 有用得多,答辩时拿出来也能说明你对问题有深入理解。从那以后我每次拿到一个新的预测项目,都会先跑一遍分时段误差,确认模型不是只在「好预测」的时段表现好,而是全时段都稳。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询