
简介这套基于 Python 与 Jupyter Notebook 的光伏发电功率预测项目专为毕业设计、课程设计或项目开发准备。项目通过学习历史一段时间的数值天气预报数据与对应发电功率训练模型再结合未来天气数据预测该时间点的功率源码经严格测试附有数据集和详细算法解析文档可直接运行并延伸使用。资源压缩包共37个文件约48MB以csv数据集、ipynb代码、py脚本和md文档为主30个csv提供训练与测试气象数据3个ipynb覆盖数据探索、太阳功率特征构造与外部运行程序2个py负责数据预处理和模型建立md文档则说明项目设计及算法细节。算法解析中除了常规的辐照度阈值修正还利用R语言OCE包提取太阳高度角、赤纬角、预测辐照度等特征并采用fbprophet对具有日周期和季度规律的光伏功率进行时间序列拟合。目前已有1397人学习下载模块化的notebook与py脚本完整呈现数据探索、特征构造、建模验证的全流程适合需要完整算法思路和可复用代码的开发者对照学习、二次开发。1. 光伏发电功率预测为什么我推荐用 PythonJupyter 做毕业设计和项目开发光伏发电功率预测说白了就是提前告诉调度或业主明天上午九点你这套电站大概能发多少千瓦。别小看这句话——并网电站要按预测申报出力报多了要花钱买备用容量报少了要交偏差考核费工商业分布式则靠它决定储能几点充、几点放。用 PythonJupyter 做这个方向最大价值是把数据清洗、特征构造、模型训练、结果可视化全放进一个 notebook改一行就能重跑试错成本比传统脚本低一个量级。这个方向也非常适合毕业设计、课程设计或第一版项目开发数据集公开可得算法套路成熟图表能直接交代工作量。下面从数据集构造开始把完整路径拆开讲你按顺序执行就能复现。2. 数据集怎么造从原始气象记录到可训练的功率样本2.1 预测目标先定清楚短时预测和短期预测用的不是同一套特征光伏功率预测按时间尺度分三类超短期、短期和中期。超短期是未来0到4小时粒度一般用15分钟特征以最近几小时的辐照度实测、功率变化为主服务储能充放电和AGC自动发电控制短期是未来1到3天粒度小时级或15分钟加数值天气预报用于电站的日前申报中期到周级别纯数据驱动很难做因为天气系统性的不确定性不是历史统计能抓住的。落到建模层面这个标题下的主流任务就是回归问题给定特征向量X预测下一时刻或未来某时刻的功率y。你需要先决定“预测哪个时刻”和“用多少历史窗口”这两个决定直接决定后面的数据粒度和特征清单。我的做法是先定预测步长再选数据。见过有人做未来24小时预测却把当天中午的实测辐照度当作输入评估结果漂亮得离谱一落地就翻车因为部署时未来实测值根本拿不到。这一点在第4章会详细展开。数据的时间跨度直接决定模型能学到多少天气模式。只有一个月的数据模型看到的都是单一季节的太阳轨迹测试时遇到冬季阴雨直接崩。一年数据是最低要求能覆盖四季如果能拿到两年就可以放心做季节对比和超短期滚动预测。2.2 用公开数据源搭训练集解析、对齐与重采样常见做法是找一份带时间戳的电站运行记录CSV包含辐照度、温度、风速和实发功率时间跨度至少一年粒度15分钟。15分钟是光伏行业惯用粒度既能抓住云层变化又不至于让数据量压垮训练。下面是建模的基本字段含义先对齐字段含义单位建议粒度timestamp时间戳-15minghi水平总辐照度 GHIW/m²15mintemp环境温度℃15minwind_speed风速m/s15minpower并网功率kW15min原始SCADA数据有三个常见毛病时间戳缺失跳变、同一时刻重复记录、传输丢包导致间隔不均匀。下面这段代码把原始记录清洗成一条规整的时间序列import pandas as pd # 原始记录CSV里至少包含时间戳、辐照度、温度、风速、功率五列 df pd.read_csv(pv_plant_data.csv, parse_dates[timestamp]) # 按时间排序并去重SCADA偶尔会重传同一帧数据 df df.set_index(timestamp).sort_index() df df[~df.index.duplicated(keepfirst)] # 统一重采样到15分钟短时间缺失用前向填充 df df.resample(15min).ffill() # 只保留建模需要的列看一眼每列缺失率 cols [ghi, temp, wind_speed, power] df df[cols] print(df.isnull().sum() / len(df))parse_dates[timestamp]让pandas在读取时直接转成datetime类型后面所有时间操作才走得通。resample(15min)把不规则间隔统一成15分钟网格ffill()按上一个有效值填充适合几十分钟内的短缺失。如果缺失超过两个小时纯ffill会把滞后偏差带进模型这时应该改用interpolate()线性插值。print那行是快速体检任一列缺失率超过5%说明数据源质量差继续往下做只会得到一堆噪音。清洗完的数据我会存一份train_data.csv之后所有notebook都从这份干净数据开始不要在每天跑实验时重复清洗否则特征工程改一处就要重新排查一遍原始数据。提示清洗后检查df.index.freq是否为15min。如果仍为None说明还有重复索引或时间跳变重采样并没有真正落实。2.3 特征工程三件套辐照度、滞后量、时间描述光伏发电的物理规律其实很直观晴天功率曲线像钟形早上爬升、中午见顶、下午回落阴天辐照度剧烈波动功率跟着抖。功率变化不是瞬时的组件有热惯性所以t时刻功率和t-1、t-2时刻强相关。这三条直接转化为三类特征。滞后特征表达“惯性”shift(1)、shift(2)、shift(3)分别给出前15、30、45分钟的功率和辐照度。滑动平均表达“趋势”过去6个采样点即1.5小时的辐照度均值能刻画一大片云正飘过来还是已经飘走。时间特征用hour和month近似太阳轨迹虽然不如太阳高度角精确但对线性回归和树模型足够。# 滞后特征前15/30/45分钟的功率与辐照度 for lag in [1, 2, 3]: df[fpower_lag_{lag}] df[power].shift(lag) df[fghi_lag_{lag}] df[ghi].shift(lag) # 滑动平均过去6个点1.5小时的辐照度均值 df[ghi_ema] df[ghi].rolling(6).mean() # 时间特征小时和月份能粗略刻画太阳轨迹 df[hour] df.index.hour df[month] df.index.month # 前几行因为没有滞后历史会出现NaN丢掉 df df.dropna()shift(lag)产生的前几行是NaN所以最后必须dropna()。如果你的数据从7月开始month特征会丢失6月的对比信息这不是错误是时间序列的天然代价。如果任务改成超短期预测特征窗口要缩短滞后特征用最近的4到6个点滑动平均窗口缩到30分钟如果是短期预测滞后特征可以放宽到1到2小时同时加入小时级的气象预报替代实测值。注意不要在特征里放未来信息。比如用当天中午的气温预测上午的功率训练时模型会觉得“气温好准”但部署时你早上拿不到中午的气温这就是典型的look-ahead bias。3. 算法解析与建模从线性基线到 LightGBM 的升级路径3.1 线性回归当基线先知道自己输在哪光伏组件的短路电流和辐照度近似线性开路电压随温度升高略微下降所以功率与辐照度不是纯直线但线性回归能在十分钟内给出一个可参考的下界。先跑通它你才知道后面换复杂模型到底值不值。如果线性模型的MAE已经让甲方满意其实没必要上更复杂的模型。光伏功率预测里线性模型的误差主要来自阴天突变这类误差是物理不可约的——云什么时候遮住太阳数据驱动模型永远猜不准。后面换LightGBM能缩小误差但不可能归零。明白这一点你对复杂模型的期望值会更合理。import numpy as np from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error # 按时间顺序切分注意不要shuffle split_idx int(len(df) * 0.8) train df.iloc[:split_idx] test df.iloc[split_idx:] features [ghi, temp, wind_speed, power_lag_1, power_lag_2, power_lag_3, ghi_lag_1, ghi_lag_2, ghi_lag_3, ghi_ema, hour, month] model LinearRegression() model.fit(train[features], train[power]) pred model.predict(test[features]) mae mean_absolute_error(test[power], pred) rmse np.sqrt(mean_squared_error(test[power], pred)) print(fMAE{mae:.2f} kW, RMSE{rmse:.2f} kW)这段代码最关键的是切分方式iloc[:split_idx]严格按时间顺序训练集全是过去的样本测试集全是未来的样本。千万别用train_test_split默认参数它会乱序打散数据光伏功率具有很强的自相关性一旦混进未来样本滞后特征直接“抄袭”下一时刻的答案验证集MAE低到你怀疑人生部署时立刻现原形。结果怎么看对额定功率100kW的电站MAE在1到2kW说明模型已经抓到主干MAE超过5kW先别急着换算法回去查数据里的ghi字段是不是大量缺失或填充不当。还可以打印线性模型的系数正常情况下ghi系数最大power_lag_1系数接近0.95就要警惕说明模型主要靠抄上一时刻的值天气突变时预测会迟钝。记住MAE和RMSE的单位都是kW不同装机容量的电站不能直接比后面用nMAPE归一到百分比才有参照系。3.2 LightGBM 拟合非线性关键参数与早停设置换树模型不是赶时髦。云层遮挡会产生明显的非线性组件温度高时效率反而下降辐照度和温度之间还有交互效应这些线性模型都刻画不了。LightGBM作为梯度提升树的代表能自动学这些交互又不需要对特征做标准化在中小规模表格数据上比深度学习更稳。import lightgbm as lgb model lgb.LGBMRegressor( n_estimators500, # 最大树数通常配早停一起用 learning_rate0.05, # 步长越小越稳但越慢 num_leaves31, # 叶子数上限越大模型越复杂 max_depth6, # 树深度限制防过拟合 subsample0.8, # 每棵树用80%样本 colsample_bytree0.8,# 每棵树用80%特征 random_state42 ) model.fit( train[features], train[power], eval_set[(test[features], test[power])], callbacks[ lgb.early_stopping(50), # 50轮没提升就停 lgb.log_evaluation(50) # 每50轮打印一次日志 ] ) pred model.predict(test[features])参数的经验值放在这里按这个起点调能省很多时间参数作用经验值调参方向n_estimators最大树数500-1000配早停不用太纠结learning_rate步长0.05-0.1过拟合降欠拟合升num_leaves叶子数上限31-63过大过拟合max_depth最大深度6-8配合num_leavessubsample行采样0.8过拟合降colsample_bytree列采样0.8过拟合降early_stopping(50)的意思是验证集连续50轮没有改善就中断训练这样把n_estimators设成1000也不用担心跑太久。调参顺序我一般是先把learning_rate固定为0.05让早停决定树数然后看训练集和测试集MAE差距差距大就降num_leaves、降subsample差距小还欠拟合才考虑加大num_leaves。训练完打印特征重要性for name, imp in zip(features, model.feature_importances_): print(name, round(imp, 2))。LightGBM的重要性是“这个特征被用来分裂的总次数”不直接等于对预测的贡献大小但排序仍有参考价值。光伏预测里ghi和ghi_ema排前面是正常的hour、month靠后也正常。如果ghi排到了最末尾说明数据里的辐照度字段可能有问题比如探针故障导致大量常数填充这种情况换什么算法都救不回来。3.3 LSTM 要不要上序列模型的适用边界LSTM不是这个项目的默认答案但在特定条件下值得上。数据量两年以上、粒度15分钟、任务明确是超短期滚动预测未来0到4小时这时候序列结构能利用长窗口里的天气演变模式比单帧特征更合理。反过来如果只有几个月数据、目标是交一份预测报告LSTM在小样本上很难打过调好参的LightGBM反而消耗大量时间调隐藏层和训练轮数。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense # 把数据切成(样本数, 时间步, 特征数)的形状 def make_sequences(data, features, target, steps12): X, y [], [] for i in range(steps, len(data)): X.append(data[features].iloc[i-steps:i].values) y.append(data[target].iloc[i]) return np.array(X), np.array(y) X, y make_sequences(df, features, power, steps12) split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] model Sequential([ LSTM(32, input_shape(X.shape[1], X.shape[2])), Dense(1) ]) model.compile(optimizeradam, lossmse) model.fit(X_train, y_train, epochs20, batch_size64, validation_split0.1, verbose0)steps12表示用过去12个采样点即3小时的数据预测下一个15分钟点输入形状是(样本数, 12, 特征数)LSTM要求的就是这个三维结构。LSTM(32)里的32是记忆单元数量起步够用数据量大可以加到64但单元越多越容易在小样本上过拟合。epochs20配validation_split0.1建议训练时保留loss曲线如果验证loss几轮不降就直接改用LightGBM别在LSTM上恋战。注意LSTM对输入尺度敏感特征先做MinMaxScaler归一化否则训练初期很难收敛。这个细节可以单独放在notebook的cell里别写进生产预测函数。4. 避坑专题Jupyter 里跑光伏功率预测的 5 个翻车现场4.1 数据泄露验证集 MAE 很漂亮一上线就崩现象验证集MAE只有0.3kW你几乎要宣布项目完成结果拿到现场真实数据一测误差翻了好几倍。打印特征重要性发现power_lag_1高得离谱。原因train_test_split默认shuffleTrue把未来样本混进了训练集或者交叉验证用了默认KFold同样是乱序。模型学到的不是“根据天气预测功率”而是“照着上一时刻答案抄”。解决时间序列一律按时间顺序切分用TimeSeriesSplit所有交叉验证显式设置shuffleFalse。滞后特征可以保留但要确保验证集严格晚于训练集。做特征选择时如果发现滞后特征的重要性异常高先在小数据集上反复检查切分逻辑这个坑几乎每个做时序预测的都会踩一遍属于最典型的血泪经验。4.2 Jupyter 环境起不来找不到指定程序和内核卡在 connecting现象命令行敲jupyter notebook直接报“找不到指定的程序”Anaconda里点Launch没反应或者浏览器能打开但内核一直显示connecting。原因最常见是多套Python并存——系统自带的、Anaconda的、虚拟环境的——内核注册路径指向了一个已经被卸载或升级过的解释器其次是ipykernel版本和Jupyter版本不匹配pip和conda混装特别容易触发。这种情况有时候看起来很玄学但绝大多数是环境路径问题。解决在目标环境里重装内核python -m ipykernel install --user再启动。用conda list | grep jupyter核对版本尽量别混装。真正有效的排错方式是直接在终端启动jupyter看完整堆栈而不是双击图标错误信息全写在堆栈里。顺手把默认目录改到项目文件夹jupyter notebook --generate-config生成配置文件改c.NotebookApp.notebook_dir然后把notebook直接创建在项目文件夹里别让数据和脚本散在用户目录的各个角落。4.3 夜间零值把误差指标“洗白”了现象整体MAE只有0.5kW看着模型性能优秀但一到白天时段误差大得离谱业主一看曲线就摇头。原因夜间功率恒为0占全天样本一半以上。模型只需要把夜间全预测成0MAE就被拉低一大截白天的真实误差全被零值稀释了。解决评估时按白天时段单独算比如筛选df.index.hour.between(6, 18)再计算MAE。训练时可以保留夜间样本模型需要学会0但汇报指标必须分白天和全天两套。更规范的做法是用nMAPE也就是误差除以装机容量的百分比来汇报这样夜间零值的稀释效应会被明显削弱。4.4 递归多步预测误差滚雪球现象预测未来24小时前4小时曲线贴合后面越来越偏甚至预测出负功率。原因常见的递归策略把“上一步的预测值”当作下一步的输入特征。树模型对输入分布敏感预测值一旦偏离真实值下一步的输入就进入了训练分布之外的区域误差被一步步放大越滚越离谱。解决多步预测别用递归改用直接多步策略。最简单的做法是为未来每个时点训练一个独立模型比如0到24小时就训练24个模型每个模型预测固定时点或者用多输出回归一次输出未来N步的向量。如果有数值天气预报提供未来辐照度直接把它作为输入效果最好比任何递归技巧都可靠。4.5 内存爆炸几行代码把 Kernel 干死现象读入三年15分钟粒度记录构造几十个特征后Jupyter kernel无响应保存的notebook打不开只能强制杀死进程。原因pandas默认float64每个数占8字节滚动窗口、滞后特征一多中间DataFrame成倍占用内存Jupyter又不像普通脚本那样退出即释放变量全堆在内存里。三年15分钟数据约10万行本身不大但每个特征都构造一份中间副本内存就爆了。解决读CSV时指定dtypenp.float32内存直接减半用完的中间变量及时del加gc.collect()不要一次性把几十个特征全构造出来先用小样本验证代码逻辑再全量跑。30万行以内的数据float32加精简特征普通笔记本完全能扛住。另外notebook里每跑一遍就累积一份变量建议定期重启kernel清空环境这也是排查“越跑越慢”的第一招。5. 验证模型到底行不行时序 K 折、误差指标与一张预测对比图一次train_test_split只给一次结论改个特征就得重跑一遍很难判断模型是真好还是碰巧。我一般先跑时序K折5折每一折训练集在前、验证集在后from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) mae_list [] for train_idx, val_idx in tscv.split(df): train_fold df.iloc[train_idx] val_fold df.iloc[val_idx] # 每个折里跑同一套模型配置记录验证集MAE mae_list.append(...) print(fMAE mean{np.mean(mae_list):.2f}, std{np.std(mae_list):.2f})n_splits5会把数据切成5段每次用前k段训练、第k1段验证所有验证集都严格晚于训练集这才是时间序列该有的评估方式。标准差大说明模型在某个时间段比如连续阴雨天特别不稳这时候先别调参回去看那段数据的特征分布十有八九是缺了某个能刻画天气状况的变量。指标建议三个一起看MAE看平均偏差RMSE把大的误差放大看极端情况nMAPE归一化到装机容量方便和同类研究对比。光伏预测里尤其要按白天时段单算一版避免被夜间零值“洗白”。rated_power 100.0 # 电站额定功率单位kW nmape np.mean(np.abs(test[power] - pred) / rated_power) * 100 print(fnMAPE{nmape:.2f}%)最后选连续3天的数据画一张对比图预测曲线和真实功率曲线叠在一起白天跟得住、波形不错相位、夜间不虚标这才是能交付的模型import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(12, 4)) ax.plot(test.index, test[power], labelactual, alpha0.8) ax.plot(test.index, pred, labelpred, alpha0.8) ax.legend() plt.show()还有一个加分技巧画图时把预测区间也画出来。光伏预测特别是短期预测天然带不确定性阴天比晴天难得多。如果模型能输出分位数LightGBM的objective可以设成quantile把10%到90%区间画成阴影一眼就能看出你对不确定性有意识。我自己的习惯是每次跑完先出图再下结论光看指标很容易被各种平均值骗过去。这张图既是验证工具也是整个项目最直接的交付物答辩或者汇报时摆出来比十行指标都管用。沿着这条路径把数据、建模、验证走完你会得到一套可靠的光伏发电功率预测流程后续换数据、换电站、加气象预报都只是替换输入的问题。希望帮到你。本文还有配套的精品资源点击获取