
简介基于MATLAB环境开发的BP神经网络光伏发电功率预测源码包面向能源、电气与计算机相关专业的本科及以上学习者可用于光伏发电功率预测建模、神经网络算法入门、课程设计与毕业设计等场景。压缩包共4个文件包含3个m脚本与1个xlsx数据文件m脚本分别对应BP网络训练主程序以及MSE、RMSE、MBE、MAE、R²等误差评估指标计算xlsx文件提供用于训练与测试的功率样本数据整体体量仅18KB结构精简、注释完整适合快速上手与二次开发。目前已有687人学习下载常用于科研预研和项目扩展。借助该资料读者既能理解BP神经网络建模与评估的完整流程也可调整网络层数、学习率或引入优化算法将模型扩展到多变量输入、分时段预测等更复杂的光伏功率预测任务为后续研究或工程应用打下基础。1. 光伏功率预测为什么绕不开BP神经网络先看清问题再动手光伏发电功率预测这活儿做运维的想提前安排储能和检修做电站的想应付电网调度考核做交易的想少赔点偏差电费——不管哪种诉求落到最后都是同一个问题明天或者下一个小时这块光伏阵列到底能发多少千瓦。影响功率的因素一大堆辐照度、温度、湿度、风速、云量甚至组件积灰程度它们之间还是非线性关系传统物理模型根本算不过来。BP神经网络就是干这个用的它不需要你手工推导光伏板的等效电路方程只需要拿历史数据喂进去网络自己学出一套从气象特征到输出功率的映射关系。这也是它在光伏预测里一直被提起的原因代码结构简单训练逻辑透明改起来方便数据一换就能迁移到另一座电站。但别把这玩意儿想得太神。BP网络本质是个多层感知机加反向传播优点是万能逼近缺点是训练调参带着明显的“玄学”成分——同样的数据网络结构差一层、学习率差一个数量级预测曲线可能就是光滑直线和剧烈抖动的区别。这篇笔记不打算泛泛介绍直接按落地顺序讲清楚数据怎么准备、网络怎么设计、训练怎么调、评估怎么做、有哪些常踩的坑。2. 数据预处理与训练集划分预测精度七成看这里2.1 原始数据长什么样先处理哪些脏数据光伏电站的历史数据一般来自SCADA系统或者气象站以15分钟或1小时为采样粒度字段通常包括时间戳、辐照度W/m²、组件温度℃、环境温度℃、风速m/s、湿度%、实际输出功率kW。理想情况下一年有8760条小时级数据但现场拿到的数据几乎没有干净的夜间辐照度为0但功率可能有待机损耗雨天传感器脏了出现辐照度阶跃跳变通信中断产生整段缺失逆变器限功率导致“辐照度很高但功率上不去”。我用pandas做清洗的一般顺序是先把时间戳列转成datetime并设成索引按7天窗口检测功率跳变、剔除超过阈值3倍的异常点再用前后24个点的线性插值补缺失值。需要特别说明的是夜间数据最好直接丢掉——辐照度低于50W/m²时BP网络学不到有用特征反而会把训练集均值拉低导致白天预测值系统性偏小。插值代码import pandas as pd import numpy as np df pd.read_csv(pv_history.csv, parse_dates[timestamp], index_coltimestamp) df.sort_index(inplaceTrue) # 剔除功率突变点前向差分绝对值超过50kW则置为NaN power_diff df[power].diff().abs() df.loc[power_diff 50, power] np.nan # 剔除辐照度小于50W/m²的夜间无效段 valid_mask df[irradiance] 50 df df[valid_mask] # 线性插值补缺失值限制前后各12个点 df df.interpolate(methodlinear, limit12, limit_directionboth) df df.dropna()这里有一个关键点值得展开剔除异常点要用diff().abs()而不是直接看数值范围因为光伏功率的额定范围从几十千瓦到几百千瓦不等固定阈值换电站就得改差分阈值只需要预估最大变化率比如限功率时的爬坡速率通用性强得多。插值窗口限制在12个点以内是为了防止长时间通信中断时把一大块缺失用假数据填平那种情况宁可直接删掉。2.2 特征归一化不归一化的BP就是个黑匣子BP网络本质在做梯度下降而不同特征的数值范围差距极大——辐照度可能到1000湿度在20到90之间风速在0到15之间额定功率可能上千。如果不做归一化梯度更新方向会被大幅值特征主导网络训练极慢甚至直接不收敛。另一个容易忽略的问题是激活函数如果隐藏层用sigmoid或tanh输入不归一化会导致神经元输出长时间处于饱和区梯度消失。归一化常见做法是min-max缩放到[0,1]公式是x_norm (x - x_min) / (x_max - x_min)。但要注意x_min和x_max必须只用训练集计算然后用这个固定值去转换验证集和测试集。如果整个数据集一起算min和max相当于测试集的信息泄漏到训练过程中验证集评估结果会偏乐观。使用sklearn的MinMaxScaler时代码上就是一个fit和transform分离的操作from sklearn.preprocessing import MinMaxScaler feature_cols [irradiance, module_temp, ambient_temp, wind_speed, humidity] target_col [power] scaler_x MinMaxScaler() scaler_y MinMaxScaler() # 先用训练集fit再transform所有集合 train_x scaler_x.fit_transform(train_df[feature_cols]) train_y scaler_y.fit_transform(train_df[[target_col]]) val_x scaler_x.transform(val_df[feature_cols]) val_y scaler_y.transform(val_df[[target_col]]) test_x scaler_x.transform(test_df[feature_cols]) test_y scaler_y.transform(test_df[[target_col]])scaler_y单独做一个对象是为了后续反变换预测结果时用同一组min和max。实际项目里见过有人图省事只归一化输入不归一化输出结果输出值范围过大损失函数值高到爆炸学习率怎么调都不收敛。2.3 时间序列数据切分不能打乱顺序不能用未来预测过去光伏功率预测本质上属于时间序列预测训练集、验证集、测试集必须按时间顺序切分。假设有一年数据我会按比例切前10个月训练第11个月验证最后1个月测试。有人习惯把数据随机打乱再按比例切分这在分类问题上没问题但在时间序列上会造成严重的数据泄漏——模型见过了测试期附近的样本评估结果虚高一上真实场景就翻车。切分代码和每个集合的用途# 按时间顺序切分不打乱 train_end int(len(df) * 0.7) val_end int(len(df) * 0.85) train_df df.iloc[:train_end] val_df df.iloc[train_end:val_end] test_df df.iloc[val_end:] print(f训练集 {len(train_df)} 条验证集 {len(val_df)} 条测试集 {len(test_df)} 条)注意验证集的作用不是调超参数的工具而是早停的裁判——训练过程中每个epoch结束用验证集算一次误差如果误差持续上升就停止训练。测试集是最后才能用的用于评估模型真实泛化能力。这个顺序一旦反过来训练出来的模型就废了。还有个细节切分完检查一下测试集是否覆盖了晴、阴、雨不同天气类型如果最后一个月的天气异常单一预测结果会有偏差这时可以往后多滑几天。3. BP网络模型结构设计从bp神经网络结构图到具体实现3.1 输入层、隐藏层、输出层怎么定不靠拍脑袋靠经验和实验BP神经网络结构图看起来不复杂输入层、隐藏层、输出层三层每个圆圈代表一个神经元箭头代表权重连接。但放到光伏功率预测里每一层的尺寸都是有讲究的。输入层的维度就是特征数量上面选了5个气象特征输入层就是5个神经元。输出层是唯一需要预测的目标——功率1个神经元。隐藏层的层数和每层神经元数量是BP网络最让人头疼的玄学参数。理论上一个隐藏层就足以逼近任何连续函数这是万能逼近定理告诉我们的但实际做光伏预测时单隐藏层需要非常多的神经元才能达到效果而多层少量神经元的组合往往收敛更快、泛化更好。我尝试过的典型配置是两层隐藏层第一层64个神经元第二层32个神经元激活函数都用ReLU。之所以不选sigmoid是因为BP网络层数多了之后sigmoid的梯度在反向传播中会连续衰减浅层权重几乎更新不动ReLU缓解了这个问题。神经元数量往下减到3216如果验证误差差别不大就选更小的结构参数少、训练快、过拟合风险低。3.2 用Keras实现BP网络从构建到训练的最小代码实际上手用Keras搭网络代码量不大。需要先定义模型结构然后编译、训练。下面这段代码基本是我在小型光伏电站预测中的常用模板from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau from tensorflow.keras.optimizers import Adam model Sequential() model.add(Dense(64, activationrelu, input_shape(train_x.shape[1],))) model.add(Dense(32, activationrelu)) model.add(Dense(1, activationlinear)) model.compile(optimizerAdam(learning_rate0.001), lossmse, metrics[mae]) early_stop EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue) lr_scheduler ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5) history model.fit(train_x, train_y, validation_data(val_x, val_y), epochs500, batch_size64, callbacks[early_stop, lr_scheduler], verbose1)参数说明lossmse即均方误差适合回归任务但需要注意当功率范围很大时mse会放大峰值误差的权重预测曲线会优先拟合高功率段batch_size64是训练速度和稳定性的折中数据量大可以提到128数据量不足时建议降到32EarlyStopping的patience15意味着验证损失连续15个epoch不下降就停restore_best_weights让模型回滚到验证误差最低的权重这两个设置是防止过拟合的关键我见过有人去掉早停跑满500个epoch训练损失几乎为0验证损失却在第60轮之后就一路飙升。ReduceLROnPlateau的作用是学习率在验证损失平稳后自动减半这比手动调学习率高效得多。训练结束后可以用model.save保存整个模型下次预测直接load_model加载。3.3 输入特征能不能只靠辐照度单变量模型的边界在哪一个见了很多次的误区是只拿辐照度预测功率理由是辐照度和功率相关性最高散点图几乎一条直线但温度影响也不能忽略——同一辐照度下组件温度每升高1℃多晶硅组件输出功率大约下降0.4%~0.5%夏天中午组件温度可能到60℃以上这会造成高辐照度时段预测偏高的误差。湿度影响雨水和云层遮挡风速影响组件散热这类特征虽然单独拿出来与功率的相关性不高但组合起来对模型精度的提升是实打实的。不过特征也不是越多越好。把天气描述、日历特征星期几、是否节假日塞进去模型参数变多、训练变慢如果数据量只有几千条网络可能直接记住训练集噪声。我的做法是先用相关性矩阵筛一遍剔除与功率相关系数低于0.1的弱特征再看加了某个特征后验证集误差有没有下降。特征筛选这件事上没有捷径每次加一个特征重新跑一轮训练记录验证集MAE多试几次就知道哪些特征值得留。4. 模型训练与评估指标训练曲线怎么看预测结果怎么算准4.1 训练过程监控loss下降和收敛状态怎么看训练时每次看终端输出的loss值很多人只关心它降不降忽略了更重要的东西——train loss和val loss之间的差距。正常情况下两条曲线都下降间距很小。如果train loss持续下降但val loss在第几十轮开始反弹就是典型的过拟合信号意味着网络开始背训练集而不是学内在规律。早停机制会在这种情况下自动截断训练ReduceLROnPlateau会在学习率过大导致loss震荡时接管局面。绘图看训练曲线是常规操作import matplotlib.pyplot as plt plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(MSE loss) plt.legend() plt.title(Loss Curve) plt.subplot(1, 2, 2) plt.plot(history.history[mae], labeltrain_mae) plt.plot(history.history[val_mae], labelval_mae) plt.xlabel(epoch) plt.ylabel(MAE (kW)) plt.legend() plt.title(MAE Curve) plt.tight_layout() plt.savefig(training_curve.png, dpi150)看这张图需要关心的点val_loss有没有明显低于train_loss如果低太多数据泄漏的可能性很大val_loss的曲线如果锯齿明显batch_size可能太小或学习率偏大最后的val_loss值相对于功率额定值的比例是否在可接受范围内。如果额定功率为100kWval_loss对应的RMSE应该折算成功率来看不能只看loss的数字本身。4.2 评估指标RMSE、MAE、MAPE各代表什么什么时候用哪个光伏功率预测的评估指标和普通回归任务略有区别行业里比较看重几个指标的组合。MAE平均绝对误差反映平均预测偏差直观易懂但所有误差同等对待无法突出大误差点RMSE均方根误差对大误差更敏感适合评估预测曲线的峰值偏差——电网考核中最怕的就是午间高功率时段预测偏差过大RMSE能把这个风险暴露出来MAPE平均绝对百分比误差则是把误差归一到实际值上。MAPE有一个坑在光伏预测里特别明显当实际功率接近0时比如清晨和傍晚一个很小的绝对误差换算成百分比就会变成几百个百分点拉高整体MAPE。所以在算MAPE前先过滤掉低功率样本用test_y 5kW的掩码筛选或者改用sMAPE对称平均绝对百分比误差。计算代码from sklearn.metrics import mean_absolute_error, mean_squared_error # 反归一化预测值 pred_y scaler_y.inverse_transform(pred_y_normalized) true_y scaler_y.inverse_transform(test_y) # 功率大于阈值的样本才参与MAPE计算 valid_idx true_y.flatten() 5 mae mean_absolute_error(true_y[valid_idx], pred_y[valid_idx]) rmse np.sqrt(mean_squared_error(true_y[valid_idx], pred_y[valid_idx])) mape np.mean(np.abs((true_y[valid_idx] - pred_y[valid_idx]) / true_y[valid_idx])) * 100 print(fMAE: {mae:.2f} kW, RMSE: {rmse:.2f} kW, MAPE: {mape:.2f}%)如果MAPE在20%以下模型算能用了15%以下是日常稳定的水平10%以内相当不错通常需要数据质量高、天气波动小。如果MAPE超过30%先别看模型结构回头查数据清洗和特征归一化。4.3 用真实数据反推预测曲线画出来才能发现问题数值指标只能告诉你偏差有多大不能告诉你偏差长什么样。把测试集某三天的真实功率和预测功率画在同一张图上能直观看到问题比如辐照度突降时段预测值滞后——因为模型是在用同类辐照度下的历史功率做映射但云层遮挡导致的骤降是分钟级事件历史数据里没有足够多的相似样本再比如阴天整体预测偏高——阴天辐照度低模型容易把输出压得太低但实际阴天光伏仍有30%左右的出力。画图代码plt.figure(figsize(12, 5)) plt.plot(true_y[:72], labelactual power, linewidth1.5) plt.plot(pred_y[:72], labelpredicted power, linewidth1.5, linestyle--) plt.xlabel(time step (15min)) plt.ylabel(power (kW)) plt.legend() plt.title(3-Day Prediction vs Actual) plt.savefig(prediction_curve.png, dpi150)这种图最大的价值在于识别系统偏差——如果预测值整体比实际值低应该是特征中没有考虑组件效率衰减或逆变器损耗如果预测曲线比实际平滑很多说明模型没有学到辐照度的快速变化特征需要把辐照度的一阶差分或滑动窗口均值加进去当特征。5. BP神经网络在光伏预测中的避坑指南五条血泪经验5.1 数据泄漏看起来精度很高一上线就废现象训练集、验证集、测试集一起归一化测试集误差低得离谱模型部署到实际电站后误差翻倍。原因是测试集的min和max信息通过归一化过程泄漏给了训练过程。解决MinMaxScaler只能fit训练集transform验证集和测试集。另外还有一类更隐蔽的泄漏数据清洗时的插值跨越了训练集和测试集的边界比如把第10个月末到第11月头的缺失值用两端数据一起做线性插值等于测试信息渗透进训练数据。解决先切分再清洗或者清洗时确保每段插值数据只在单一集合内。5.2 模型预测负功率现象夜间或凌晨预测结果出现负值比如-3.6kW从数值上看不大但在评估指标里会变成很大的百分比误差。原因是BP网络输出层是线性激活权重和偏置的组合可以产生负数。解决输出层用activationlinear没错但可以在预测阶段加一个np.clip(pred_y, 0, None)把负值截断为0更稳定的办法是把训练目标归一化时把min值设成0而不是实际最小值预测后在反变换时改回真实量纲。5.3 晴天准、阴天差模型学的是平均天气现象测试集指标尚可但按天气类型拆分误差时晴天MAPE为8%阴天MAPE为42%。原因是训练集中晴天样本占比远高于阴天模型天然倾向于预测晴天出力。解决按天气类型对训练集做分层采样确保每种天气类型都有足够样本或者干脆按天气分别训练多个模型判断明天是阴天就用阴天模型。后一种做法的缺点是需要在预测时先做天气分类分类错误的话模型选择也会错但整体上比分不开的模型要好。5.4 隐藏层神经元越多效果不一定越好现象隐藏层从32加到128个神经元训练集loss下降很多测试集误差反而变大。原因是网络容量过大把训练集的噪声也背了下来泛化能力下降。解决遵循一个经验法则——神经元数量从输入特征数量的2倍开始尝试比如5个特征就从10个神经元起步逐次加倍直到验证集误差不再下降就回头选之前的配置。加入Dropout层也能缓解在隐藏层之间加Dropout(0.2)让网络在训练时随机丢弃20%的神经元等于在训练多个子网络求平均。5.5 不同季节预测偏差大现象用前一年全量数据训练模型预测第二年夏天的功率时误差明显变大。原因是光伏组件的效率受季节温度和太阳高度角影响模型学过的那种温度-功率映射关系在冬季和夏季并不一致冬天组件温度低效率高夏天高温下效率衰减同一辐照度对应的功率不同。解决用滑动窗口训练策略——训练数据只用最近3~6个月而不是全部历史数据每隔一段时间用新数据微调模型比如每周重新训练一次如果条件允许把季节或者月份作为一个特征输入模型让网络自己学季节变化规律。6. 进阶多站点模型迁移与天气分型预测的实际操作BP网络的预测模型不会一劳永逸电站换了、组件换新了、数据分布漂移了模型都需要调整。最后这部分讲两个我实际操作中有收益的进阶方向一个是多站点迁移一个是天气分型预测。多站点迁移的背景是这样的新建电站没有足够的历史数据但临近地区有一座数据丰富的电站。直接把模型拿过来用效果一般因为地理位置的辐照度、温度差异使输入输出映射关系有偏差。常见做法是在已有数据电站上预训练BP网络然后用新电站的一个月数据做微调——把预训练模型的权重作为初始值用低学习率比如0.0001在新数据上继续训练迭代50轮以内。这种方式比从零训练收敛快得多也可能让模型在数据量不足时直接不可用。model.load_weights加载预训练权重然后重新编译设置更低的学习率# 预训练模型保存后加载 from tensorflow.keras.models import load_model pretrained_model load_model(pretrained_pv_bp.h5) # 冻结前几层只训练最后层减少参数更新量 for layer in pretrained_model.layers[:-1]: layer.trainable False pretrained_model.compile(optimizerAdam(learning_rate0.0001), lossmse, metrics[mae])新电站数据喂进去之前必须先做同样的归一化参数如果新电站的功率范围不同scaler_y要重新fit再transform。冻结前几层的作用是保留先前电站学到的底层特征提取能力只微调输出层适配新电站的量纲这样小样本情况下更不容易过拟合。天气分型预测是在预处理阶段多做一个动作把历史数据按晴天、多云、阴天、雨天分成四个子集各训练一个BP网络。预测当天先看气象预报的天气类型选对应模型预测。这个方案在天气多变的地区作用明显代价是训练时间翻四倍、模型维护量增加。如果把天气类型做成模型输入特征效果介于单模型与分模型之间实践下来方式更省事。训练完成的评估不只看误差指标建议把降雨前后、日出日落等时间段的预测误差单独算一遍这类边界时段的预测质量才真正决定该系统在电网考核中的表现。我自己的经验是模型结构再精致不如把数据清洗做好、天气类型分清楚、评估指标选对一半的收益大。做光伏预测这条路上没有一劳永逸的模型只有持续观察误差分布、持续喂新数据、持续微调参数才能把精度稳定在可用水平。希望这篇笔记能让你在BP神经网络预测光伏功率的落地上少踩几个坑。本文还有配套的精品资源点击获取