简介:这是一份面向高校学生与机器学习入门者的光伏功率预测实战项目,以Python为实现语言,围绕历史发电数据完成从训练到预测的完整流程,适合用作毕业设计、期末大作业或课程设计选题。压缩包共19个文件,约4.64MB,包含8个csv训练与测试数据、4个py核心脚本、1个ipynb交互式笔记本、1个md说明文档及1个docx任务说明,另有若干备份文件,数据与代码分层清晰,便于按模块查阅。项目代码附带详细注释,新手可对照理解数据加载、特征处理与模型训练预测各环节,训练集与测试集分离,方便验证模型准确性与泛化能力。目前已有65人学习下载。整体而言,读者可获得一套可直接部署运行的预测方案、配套数据集与任务说明,既能快速复现实验,也能在此基础上调整算法、优化特征,积累从数据到结果的完整实践思路。
1. 光伏功率预测项目拆包:一份能跑通的毕业设计资源长什么样
光伏功率预测这个方向,每年毕业季都有大量同学在找能直接跑的源码。原因很现实:光伏出力受辐照度、温度、云层遮挡影响,波动大、随机性强,自己从零搭一套特征工程加模型调参的流程,时间成本太高。这份「基于机器学习的Python光伏功率预测项目源码及数据集」就是冲着这个痛点来的——它把数据加载、预处理、模型训练、预测评估整条链路都写好了,还配了训练集和测试集,下载解压后改改路径就能出结果。
它适合三类人:一是做毕业设计或课程设计的学生,需要一套结构完整、有注释、导师能看懂的项目;二是刚接触机器学习想找一个真实时序预测场景练手的开发者;三是需要快速验证光伏预测baseline的从业者。资源里包含main.py、Data_Process.py、Train_Predict.py、Load_Save_Data.py四个核心脚本,一个DC_PV_Power_Predict_2018.ipynb交互式笔记本,以及DC_Data目录下的多组train_*.csv和test_*.csv。下面按实际拆包顺序,把每个环节讲透。
2. 数据管道拆解:Load_Save_Data.py 与 Data_Process.py 怎么配合
拿到这份源码,第一件事不是急着python main.py,而是先搞清楚数据是怎么从 CSV 流进模型的。这个项目的设计思路很清晰:Load_Save_Data.py负责 I/O,Data_Process.py负责清洗和特征构造,两者解耦,方便替换数据集。
2.1 数据加载脚本的职责边界
Load_Save_Data.py通常承担三件事:读取DC_Data下的 CSV、做基础的类型转换、把处理好的数据保存成中间格式供训练脚本调用。常见做法是用 pandas 的read_csv配合parse_dates参数直接解析时间列,避免后续再转换。
import pandas as pd import os def load_csv(data_dir, filename): """ 读取指定目录下的CSV文件 data_dir: 数据文件夹路径,如 'DC_Data' filename: 文件名,如 'train_1.csv' """ filepath = os.path.join(data_dir, filename) # 尝试用utf-8读取,失败则回退gbk,避免中文列名乱码 try: df = pd.read_csv(filepath, encoding='utf-8') except UnicodeDecodeError: df = pd.read_csv(filepath, encoding='gbk') # 统一列名去除首尾空格,防止后续按列名索引时报KeyError df.columns = [c.strip() for c in df.columns] return df def save_processed(df, out_path): """保存处理后的数据,不保留索引列""" df.to_csv(out_path, index=False, encoding='utf-8-sig') print(f"saved: {out_path}, shape={df.shape}")这段代码的关键参数是encoding。光伏数据集的列名有时含中文(如“辐照度”“温度”),用 utf-8 读可能报UnicodeDecodeError,回退 gbk 是血泪经验。index=False避免保存时多出一列无用索引,utf-8-sig则保证用 Excel 打开时不乱码。
2.2 预处理脚本里的特征工程逻辑
Data_Process.py是整条管道里最值得细看的部分。光伏功率预测的输入特征一般包括:时间戳、辐照度、环境温度、组件温度、历史功率。这个脚本大概率做了以下几类操作:
- 缺失值处理:对传感器掉线导致的空值,用前向填充或线性插值
- 时间特征提取:从时间戳拆出小时、月份、季节
- 归一化/标准化:把不同量纲的特征缩放到同一区间
- 滑动窗口构造:用过去 N 个时刻的功率预测下一时刻
import numpy as np import pandas as pd def add_time_features(df, time_col='time'): """从时间列提取小时、月份等周期特征""" df[time_col] = pd.to_datetime(df[time_col]) df['hour'] = df[time_col].dt.hour df['month'] = df[time_col].dt.month # 小时的正弦余弦编码,保留周期性,避免23点和0点距离被拉大 df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24) return df def fill_missing(df, method='interpolate'): """缺失值填充,默认线性插值""" if method == 'interpolate': df = df.interpolate(method='linear', limit_direction='both') elif method == 'ffill': df = df.fillna(method='ffill').fillna(method='bfill') return df def normalize(df, cols, method='minmax'): """按列归一化,返回归一化后的df和参数""" params = {} for c in cols: if method == 'minmax': mn, mx = df[c].min(), df[c].max() df[c] = (df[c] - mn) / (mx - mn + 1e-8) params[c] = (mn, mx) elif method == 'zscore': mu, sigma = df[c].mean(), df[c].std() df[c] = (df[c] - mu) / (sigma + 1e-8) params[c] = (mu, sigma) return df, paramshour_sin和hour_cos这组编码是光伏预测里的常见操作。如果直接把小时当成 0-23 的数值喂给模型,模型会认为 23 点和 0 点相差很远,但实际上它们在时间上是相邻的。正弦余弦编码把周期信息保留下来,对树模型和神经网络都有帮助。归一化时加1e-8是防止某列最大值等于最小值时除零,这个细节很多新手会忽略。
提示:
Data_Process.py里如果用了fillna(method='ffill'),注意 pandas 新版本已弃用该写法,改成df.ffill()即可,否则会报 FutureWarning。
3. 模型训练与预测:Train_Predict.py 和 main.py 的调用链
预处理做完,接下来就是模型部分。这个项目把训练和预测放在同一个脚本Train_Predict.py里,main.py作为入口串联整个流程。这种设计对毕业设计来说很友好——答辩时老师问“你的模型在哪”,直接指Train_Predict.py就行。
3.1 训练脚本里的模型选型与参数
从项目定位看,Train_Predict.py大概率用了随机森林、梯度提升树或简单的神经网络作为 baseline。光伏功率预测在学术场景下常见的选择是:随机森林(RF)、XGBoost、LSTM。考虑到这份资源面向新手且强调“简单部署”,RF 或 XGBoost 的可能性最大,因为它们对超参数不敏感、训练快、不需要 GPU。
from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np def train_model(X_train, y_train, n_estimators=100, max_depth=None): """ 训练随机森林回归模型 n_estimators: 树的数量,越大越稳但越慢,100是常用起点 max_depth: 树的最大深度,None表示不限制,容易过拟合 """ model = RandomForestRegressor( n_estimators=n_estimators, max_depth=max_depth, random_state=42, # 固定随机种子,保证结果可复现 n_jobs=-1 # 用满所有CPU核心 ) model.fit(X_train, y_train) return model def evaluate(model, X_test, y_test): """输出MAE、RMSE、R2三个指标""" pred = model.predict(X_test) mae = mean_absolute_error(y_test, pred) rmse = np.sqrt(mean_squared_error(y_test, pred)) r2 = r2_score(y_test, pred) print(f"MAE={mae:.4f}, RMSE={rmse:.4f}, R2={r2:.4f}") return pred, {'mae': mae, 'rmse': rmse, 'r2': r2}random_state=42是必须的。光伏数据本身波动大,如果不固定种子,每次跑出来的 R2 可能差好几个百分点,答辩时被问到“你这个结果稳定吗”就不好回答。n_jobs=-1让模型用满 CPU 核心,训练时间能缩短不少。评估指标里 R2 最直观,一般光伏预测能做到 0.85 以上就算不错,具体看数据质量和预测步长。
3.2 main.py 的串联逻辑与运行方式
main.py是整个项目的入口,典型结构是:加载数据 → 预处理 → 划分训练测试集 → 训练模型 → 预测 → 保存结果。运行方式通常是命令行直接执行:
# 在项目根目录下运行 python main.py # 如果依赖缺失,先安装 pip install pandas numpy scikit-learn matplotlib如果main.py里用了 argparse 接收参数,可能还支持指定数据文件:
import argparse parser = argparse.ArgumentParser() parser.add_argument('--data', default='DC_Data/train_1.csv', help='训练数据路径') parser.add_argument('--test', default='DC_Data/test_1.csv', help='测试数据路径') parser.add_argument('--n_estimators', type=int, default=100) args = parser.parse_args()这种写法方便切换不同的train_*.csv和test_*.csv。项目里提供了 train_1 到 train_4、test_1 到 test_4 共八组数据,可能是不同季节或不同站点的数据。建议先用 train_1/test_1 跑通流程,再换其他组对比模型泛化能力。
3.3 Notebook 的辅助价值
DC_PV_Power_Predict_2018.ipynb是交互式笔记本,适合用来做数据探索和结果可视化。常见用法是在里面画功率曲线、特征相关性热力图、预测值与真实值对比图。这些图直接放进毕业设计论文里,比纯文字描述有说服力。Notebook 里如果已经预置了输出,打开就能看到图表,省去自己调 matplotlib 的时间。
注意:Notebook 和
.py脚本可能共用同一套函数,如果修改了Data_Process.py里的逻辑,记得重启 Notebook 内核重新导入,否则跑的还是旧代码。
4. 避坑与排查:跑这份源码时最容易翻车的五个地方
这份资源虽然对新手友好,但环境配置和数据路径这两块该踩的坑一个不少。下面五条是按实际复现时出问题概率从高到低排的。
4.1 现象:ModuleNotFoundError: No module named 'sklearn'
原因:当前 Python 环境没装 scikit-learn,或者装到了另一个解释器里。常见于系统同时有 Python 3.8 和 3.11,pip 和 python 指向不同版本。
解决:先确认解释器路径,再用对应 pip 安装。
# 查看当前python路径 which python # Linux/Mac where python # Windows # 用当前python的pip安装 python -m pip install scikit-learn pandas numpy matplotlib用python -m pip而不是直接pip,能保证装到当前解释器下,这个习惯能省很多事。
4.2 现象:FileNotFoundError: [Errno 2] No such file or directory: 'DC_Data/train_1.csv'
原因:main.py里的路径是相对路径,但运行时的工作目录不是项目根目录。比如在DC_Data文件夹里执行python ../main.py,相对路径就找不到了。
解决:要么cd到项目根目录再运行,要么在代码里用os.path.dirname(__file__)拼绝对路径。
import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) data_path = os.path.join(BASE_DIR, 'DC_Data', 'train_1.csv')4.3 现象:CSV 读取后列名带\ufeff前缀,KeyError
原因:CSV 文件保存时带了 BOM 头,pandas 用 utf-8 读取时会把 BOM 当成列名的一部分。
解决:读取时指定encoding='utf-8-sig',或者读完后统一清洗列名。
df = pd.read_csv(path, encoding='utf-8-sig') # 或者 df.columns = [c.replace('\ufeff', '').strip() for c in df.columns]4.4 现象:模型 R2 为负数或极低
原因:训练集和测试集的特征分布差异大,或者归一化参数用了测试集的数据(数据泄露),或者时间序列没有按时间顺序划分而是随机打乱。
解决:时间序列预测必须按时间切分,不能train_test_split(shuffle=True)。归一化参数只能从训练集计算,再应用到测试集。
# 正确做法:先按时间排序,前80%训练,后20%测试 df = df.sort_values('time').reset_index(drop=True) split = int(len(df) * 0.8) train_df, test_df = df.iloc[:split], df.iloc[split:] # 归一化参数只从训练集算 train_df, params = normalize(train_df, feature_cols) # 用同样的参数处理测试集 for c in feature_cols: mn, mx = params[c] test_df[c] = (test_df[c] - mn) / (mx - mn + 1e-8)4.5 现象:Notebook 里图表不显示或中文乱码
原因:matplotlib 默认字体不支持中文,且 Notebook 需要%matplotlib inline才能内嵌显示。
解决:在 Notebook 开头加两行配置。
%matplotlib inline import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows用黑体 plt.rcParams['axes.unicode_minus'] = False # 负号正常显示Mac 用户把SimHei换成Arial Unicode MS,Linux 用户换成WenQuanYi Micro Hei,具体看系统装了哪些中文字体。
5. 从跑通到跑好:换数据、调参和写进论文的三个进阶技巧
把main.py跑出结果只是第一步。这份资源真正的价值在于它提供了一个可扩展的框架,你可以在这个基础上做自己的实验。下面三个技巧是我在实际用类似项目时总结出来的,能让你的毕业设计从“能跑”变成“有亮点”。
5.1 换自己的数据时怎么改
项目自带的train_*.csv和test_*.csv是固定格式。如果你手上有别的光伏电站数据,需要对齐列名。常见做法是写一个映射字典,把自家数据的列名转成项目期望的列名。
col_map = { '时间': 'time', '辐照度': 'irradiance', '环境温度': 'temp_ambient', '组件温度': 'temp_module', '功率': 'power' } df = df.rename(columns=col_map) # 检查必需列是否齐全 required = ['time', 'irradiance', 'temp_ambient', 'power'] missing = [c for c in required if c not in df.columns] if missing: raise ValueError(f"缺少列: {missing}")时间列格式也要统一。如果原始数据是2018/1/1 0:00这种,pd.to_datetime一般能自动解析;如果是20180101 0000这种紧凑格式,需要加format参数指定。
5.2 调参的优先级和范围
随机森林的主要超参数就三个:n_estimators、max_depth、min_samples_leaf。按影响程度排序,max_depth最关键——不限制深度几乎一定过拟合,训练集 R2 能到 0.99,测试集掉到 0.6。建议从max_depth=10开始试,逐步加到 20、30,看测试集 R2 什么时候不再提升。
from sklearn.model_selection import TimeSeriesSplit # 时间序列交叉验证,不能用普通KFold tscv = TimeSeriesSplit(n_splits=5) for depth in [5, 10, 15, 20, 30]: scores = [] for train_idx, val_idx in tscv.split(X): model = RandomForestRegressor(max_depth=depth, n_estimators=100, random_state=42) model.fit(X[train_idx], y[train_idx]) scores.append(r2_score(y[val_idx], model.predict(X[val_idx]))) print(f"depth={depth}, mean R2={np.mean(scores):.4f}")TimeSeriesSplit是时间序列专用的交叉验证,它保证训练集的时间永远在验证集之前,不会出现“用未来数据预测过去”的玄学问题。普通KFold随机打乱后做光伏预测,R2 会虚高,答辩时被懂行的老师一眼看穿。
5.3 论文里怎么呈现结果
毕业设计论文里,光伏功率预测章节通常需要三张图:原始功率曲线、预测值与真实值对比、误差分布直方图。用 Notebook 生成后导出 PNG,分辨率设 300dpi。
fig, axes = plt.subplots(2, 1, figsize=(12, 8)) # 上图:预测vs真实 axes[0].plot(y_test.values[:200], label='真实值', alpha=0.8) axes[0].plot(pred[:200], label='预测值', alpha=0.8) axes[0].set_ylabel('光伏功率') axes[0].legend() axes[0].set_title('预测值与真实值对比(前200个样本)') # 下图:误差分布 errors = y_test.values - pred axes[1].hist(errors, bins=50, edgecolor='black') axes[1].set_xlabel('预测误差') axes[1].set_ylabel('频数') axes[1].set_title('误差分布') plt.tight_layout() plt.savefig('result.png', dpi=300, bbox_inches='tight')表格方面,把不同模型的 MAE、RMSE、R2 列在一起对比,比只放一个模型有说服力。如果时间充裕,可以加一个 persistence 模型(直接用上一时刻功率作为预测值)作为 baseline,你的机器学习模型只要比它好,就能说明“机器学习确实学到了东西”。
从那以后我每次拿到这类时序预测项目,都强制先跑一遍 persistence baseline,再动模型。这个习惯帮我避免了好几次“模型看起来很复杂但实际没学到规律”的翻车。希望这份拆解能帮你顺利跑通这份光伏功率预测资源,把毕业设计稳稳拿下。
本文还有配套的精品资源,点击获取