ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

分布式光伏出力预测:气象因子处理与模型实战避坑指南

分布式光伏出力预测:气象因子处理与模型实战避坑指南 简介面向电力系统调度与新能源功率预测研究的分布式光伏发电功率预测 MATLAB 资源包聚焦气象因子对出力预测的影响提出基于经验模态分解EMD、主成分分析PCA和长短期记忆神经网络LSTM的组合预测模型以提升光伏功率预测精度。资源共 8 个文件以 4 个 .m 脚本和 3 个 .mat 实测数据文件为主另含 1 个基于气象影响因子的配套资源包整体仅 116KB代码覆盖风速、温度、风向等多变量序列的分解、特征提取、预测计算与结果评价已有 208 人学习浏览。读者可获得完整研究思路与可运行工程含基于 5 分钟间隔风速、温度、功率实测数据的 EMD 分解、PCA 特征提取、LSTM 动态建模及误差指标计算脚本实测数据来自山西某电站 8 个月记录可直接替换验证。适合作为光伏功率预测课题的基线实现或算法对比参考也可用于相关课程论文的代码支撑与进一步改进。1. 分布式光伏发电出力预测气象因子处理不好精度从 8% 掉到 35%做分布式光伏发电出力预测的人十有八九栽在多云天气上晴天辐照度曲线平滑线性模型都能把误差压在 8% 以内云一过来出力十分钟内能从 80% 掉到 20%气象因子处理不到位预测曲线就在考核指标上翻车。我接过不少并网电站的气象与出力数据这类标着“计及气象因子及出力预测方法”的研究包解压后基本都是数据、特征脚本、模型代码三件套。值钱的地方不在某个模型的精度而在气象因子怎么清洗、怎么对齐、怎么和出力建立可信映射。下文从因子物理作用拆到预处理、特征构造、模型对比和坑位最后给一套可落地的晴空约束与验收口径适合正在做功率预测、交易申报或算法验证的工程师照着跑一遍。2. 气象因子怎么影响出力先把辐照度、温度与滞后关系理清2.1 为什么辐照度是主变量从物理关系到分钟级波动光伏组件的出力本质上是一个能量转换过程组件把水平面总辐照度GHI按一定效率转换成电功率简化公式可以写成 P GHI × A × η其中 A 是组件面积η 是综合效率。工程上没人去量组件面积通常直接用“装机容量 × 效率系数 × 辐照度/参考辐照度”来折算。不管怎么折算辐照度都是驱动出力的一阶变量其他气象因子都只是修正项。辐照度本身有三层概念大气层外辐照度太阳常数约 1367 W/m²、晴空辐照度无云时到达地面的值、实际辐照度受云、气溶胶、水汽影响后的地面实测值。预测要用的输入是实际辐照度但实际辐照度恰恰最难拿到——地面辐照仪和气象站只覆盖局部区域数值天气预报NWP给的是格点预报两者之间的偏差就是预测误差的主要来源之一。分钟级波动的根源在云。薄云对辐照度的衰减可能在 20% 以下厚积雨云能把辐照度从 900 W/m² 直接压到 100 W/m² 以下。云的移动方向、速度和厚度叠在一起出力曲线的斜率就没法用简单函数描述。这也是为什么在特征工程里辐照度的短期滑动均值往往比瞬时值更稳定、更好用——它相当于给剧烈波动的辐照度做了一次低通滤波帮模型过滤掉那些难以预测的高频抖动。理解了这层物理关系就不会在后续建模时浪费精力去调一个“万能模型”而是先解决辐照度质量问题。2.2 温度、风速、湿度、气压修正项和边界条件温度是第二重要的气象因子而且方向是负的。光伏组件标称工作温度是 25℃实际运行中组件温度常常到 50~60℃温度系数一般在 -0.3%/℃ 到 -0.5%/℃ 之间。这意味着夏天的强辐照下组件温度升高带来的效率损失可能抵消掉一部分辐照增益出力曲线在正午出现一个“诡异的凹陷”。所以有组件温度背板温度数据就优先用组件温度没有就用环境温度加辐照度去估算热惯性滞后。风速在这里的作用是冷却组件。高辐照、高风速的日子组件温度上不去实际出力会比无风时高几个百分点。风速对出力的影响不是线性的低辐照时几乎可忽略高辐照时才明显建模时把风速和辐照度的交互项放进去比单独塞一个风速特征更有效。湿度和降雨的影响更间接湿度高时气溶胶吸湿增长散射辐照占比变大总辐照量下降降雨本身就是云层覆盖的结果直接反映在辐照度里所以湿度在特征列表里属于弱特征有就放没有也不用刻意补。气压对光伏出力的直接影响很小它主要影响大气质量和空气密度多数场景下可以忽略。真正值得关注的是滞后效应组件有热惯性出力对辐照度的响应不是瞬时的逆变器也有几秒到几十秒的响应时间。叠加到 15 分钟级数据上辐照度当前时刻的值和出力当前时刻的值之间存在相位差滚动均值特征就是用来吸收这部分滞后的。把物理关系理到这里特征怎么构造就顺理成章了。2.3 第一版特征表与构造代码第一版特征不需要追求复杂把主变量、修正项、时间编码三块配齐就可以起步。特征名物理含义建议来源作用ghi_now当前时刻水平面辐照度辐照仪 / 气象站实测主变量ghi_avg_15/30/60过去 15/30/60 分钟辐照度滑动均值由 ghi 计算平滑波动引入滞后env_temp环境温度气象站修正效率module_temp背板温度SCADA比环境温度更直接可选wind_speed风速气象站冷却修正humidity相对湿度气象站弱特征hour_sin / hour_cos一天内时刻的周期编码时间戳捕捉昼夜节律特征构造的代码不长但有几个细节要抠import pandas as pd import numpy as np def make_features(df: pd.DataFrame) - pd.DataFrame: 输入 DataFrame 须包含 ghi, env_temp, wind_speed, humidity 列索引为 DatetimeIndex out df.copy() # 滚动窗口单位是分钟要求索引必须是 DatetimeIndex且数据已重采样到分钟级 for win in [15, 30, 60]: # min_periods3 避免序列头部产生大量 NaN out[fghi_avg_{win}] out[ghi].rolling(f{win}min, min_periods3).mean() # 时间周期编码用 sin/cos 而不是 0-23 整数避免 23 点与 0 点之间的不连续跳变 out[hour] out.index.hour out[hour_sin] np.sin(2 * np.pi * out[hour] / 24) out[hour_cos] np.cos(2 * np.pi * out[hour] / 24) # 有组件温度时建议优先使用组件温度否则继续用环境温度 if module_temp in out.columns: temp_col module_temp else: temp_col env_temp return out[[ghi, ghi_avg_15, ghi_avg_30, ghi_avg_60, temp_col, wind_speed, humidity, hour_sin, hour_cos]]这里滚动的min_periods3是为了防止序列前几个窗口全是 NaN不然后续合并样本时要把这些行全部丢掉。时间编码用 sin/cos 而不是直接放 hour 整数值是因为树模型会把 23 和 0 当作两个距离很远的类别实际对应的是同一个夜间时段。组件温度列做一次存在性判断能省掉后面数据处理阶段的不少麻烦。3. 从原始数据到可训练样本清洗、对齐与防泄漏切分3.1 时间对齐重采样、合并与两类常见错位电站 SCADA 和气象站的数据采样周期几乎不可能一致。出力数据可能是 1 分钟一条辐照度是 5 分钟一条气象预报是 1 小时一条。做预测的第一步不是写模型而是把所有数据统一到同一个时间基准上。电网调度和功率预测的惯例是 15 分钟一个点一天 96 个点所以一般把数据重采样到 15 分钟整点。# 出力数据重采样到 15min用均值聚合 power power.resample(15min).mean() # 辐照度同样重采样连续量用均值短时缺失仅前向填充 2 个点 ghi ghi.resample(15min).mean().ffill(limit2) # 合并时用 inner join宁可少样本也不要错位样本 df power.join(ghi, howinner)resample(15min).mean()是连续量的标准做法取均值比取末值稳定能压掉一部分采样噪声。ffill(limit2)只允许补 30 分钟以内的缺口超过就不补——辐照度的缺测往往伴随设备故障补太长会把坏数据带进来。howinner的意义在于如果辐照仪掉线一小时那一段的出力数据即使完整也不能用来训练因为特征缺失了。比采样周期不一致更隐蔽的错位是气象预报数据的时间语义。NWP 数据里通常有issue_time起报时刻和lead_time预报时效真正的目标时刻是两者相加。很多人直接把预报文件里写的时间列当成目标时间结果整个数据集平移了几个小时。对齐代码如下# 用起报时刻加时效算出真正的有效时刻 nwp[valid_time] nwp[issue_time] pd.to_timedelta(nwp[lead_time], unitmin) nwp nwp[[valid_time, ghi_fcst, temp_fcst]].rename(columns{valid_time: time}) # 按时间就近匹配容差 7.5 分钟保证一个 15min 点只匹配到一条预报 nwp nwp.set_index(time).sort_index() df df.sort_index() aligned pd.merge_asof(df, nwp, left_indexTrue, right_indexTrue, directionnearest, tolerancepd.Timedelta(7.5min))merge_asof是做时序就近关联最合适的工具directionnearest允许前后各找 7.5 分钟正好覆盖一个 15 分钟桶的边界。这个容差不能放大放大了会匹配到相邻时段的预报等于又引入一次错位。3.2 清洗规则哪些样本会教坏模型整理数据阶段最容易翻车的不是格式而是坏样本。分布光伏的数据里常见四类污染辐照度超物理上限、夜间零漂、停机检修、限电弃光。这四类样本如果直接喂给模型模型学到的不是物理规律而是电站的异常运行状态。辐照度超上限比较容易判断地表水平面总辐照度理论上很难超过 1500 W/m²高海拔地区晴天峰值约在 1100~1300 W/m²超过设成异常。夜间零漂是传感器在辐照度接近零时输出一个很小的非零值这类样本的特征基本无效。停机检修和限电弃光在数据上表现为“白天高辐照 出力几乎为零”但它们是完全不同的两个原因限电样本尤其要小心——它不代表电站的物理出力能力训练模型如果拟合了限电时段预测结果会系统性偏低。def clean_data(df: pd.DataFrame, capacity_kw: float) - pd.DataFrame: 按物理规则清洗训练样本capacity_kw 为电站装机容量 mask pd.Series(True, indexdf.index) # 辐照度物理上限超过判定为仪器故障 mask df[ghi].between(0, 1500) # 夜间辐照接近 0 但出力占比 2%判定为零漂噪声 night df[ghi] 5 mask ~(night (df[power] 0.02 * capacity_kw)) # 白天辐照 200 但出力占比 1%标记为停机/限电样本先剔出训练集 day df[ghi] 200 fault day (df[power] 0.01 * capacity_kw) mask ~fault df_clean df[mask].copy() # 单独留一个标签列便于后续做分类或按状态分桶训练 df_clean[is_fault] fault[mask].astype(int) return df_clean阈值按 15 分钟平均数据设计辐照度低于 5 W/m² 视为夜间出力低于 1% 装机容量视为停机/限电。如果换到 5 分钟或 1 分钟数据出力占比阈值要相应放大因为瞬时低出力更常见。停机和限电样本不要直接删除完事建议打标签保留在数据集里后续可以训练一个“是否可发电”的分类器或者用分桶模型分别处理正常和异常时段。3.3 切分与归一化时序任务不要用随机抽样光伏出力预测是严格的时间序列任务训练集、验证集、测试集必须按时间顺序切分。train_test_split默认的shuffleTrue在这里是致命的——它会把未来样本混进训练集模型“偷看”了未来信息验证集上的指标虚高上线后立刻打回原形。我一般按自然时间边界切分训练集用前 70% 的时间验证集用紧随其后的 15%测试集用最后 15%并且要保证验证集和测试集覆盖完整的春夏秋冬避免模型没见过的季节工况。归一化是另一个高频泄漏点。光照数据有尖峰MinMaxScaler或StandardScaler对离群点敏感所以我习惯用RobustScaler它对辐照度偶尔出现的仪器跳变更鲁棒。关键不是选哪个 scaler而是 scaler 只能在训练集上fit验证集和测试集只能用同一套参数transform。如果对全量数据先 fit 再切分验证集和测试集的均值和分位数已经参与过变换等于数据泄漏。from sklearn.preprocessing import RobustScaler train df.loc[:valid_start] valid df.loc[valid_start:test_start] test df.loc[test_start:] feat_cols [c for c in df.columns if c not in [power, is_fault]] # scaler 只在训练集上拟合验证/测试集只用 transform scaler RobustScaler() X_train scaler.fit_transform(train[feat_cols]) X_valid scaler.transform(valid[feat_cols]) X_test scaler.transform(test[feat_cols]) y_train train[power].values y_valid valid[power].values y_test test[power].values树模型不要求特征归一化但这一步做了也没有坏处。真正需要的场景是神经网络和相似度计算如果后面要上 LSTM这里的RobustScaler参数可以直接复用。一个重要提醒如果特征里有ghi_avg_15这类滚动特征切分时滚动窗口只能取切分点之前的数据不能偷偷用测试集的数据去算滚动均值否则又构成一次未来信息泄漏。4. 模型选型与训练从持久性基线到 LightGBM 与 LSTM4.1 先做持久性模型把精度下限定下来很多项目直接上深度学习跳过了基线模型这一步这是本末倒置。光伏出力预测有一个很强的先验相邻两个 15 分钟的出力量高度相关晴天更是如此。所以最朴素的持久性模型——用当前时刻出力预测下一时刻出力——在很多场景下已经能取得不错的 MAE。它存在的意义不是拿来交差而是给所有后续模型划一条精度下限如果复杂模型连持久性都跑不过要么特征有问题要么模型结构不合适。我一般做两个版本的持久性基线。第一个是纯出力持久化y_hat(t1) y(t)。第二个是带辐照度折算的持久化用当前辐照度与当前出力的比值去推算下一时刻出力y_hat(t1) y(t) * ghi(t1) / ghi(t)这相当于假设系统效率在短时间内不变。两者的差异能侧面反映辐照度数据的质量。import numpy as np def evaluate(y_true: np.ndarray, y_pred: np.ndarray, capacity_kw: float None) - dict: mae float(np.mean(np.abs(y_true - y_pred))) rmse float(np.sqrt(np.mean((y_true - y_pred) ** 2))) result {mae: mae, rmse: rmse} if capacity_kw: result[nmae] mae / capacity_kw * 100 # 归一化到装机容量 result[nrmse] rmse / capacity_kw * 100 return result # 持久性基线示例 y_hat_persist np.roll(y_test, 1) # 用 t 时刻出力预测 t1 y_hat_persist[0] y_test[0] # 首位无法预测用真值填充 base_metrics evaluate(y_test, y_hat_persist, capacity_kwcapacity_kw)np.roll实现的是序列平移预测值和真实值之间天然错开一个时间点。归一化指标 nMAE、nRMSE 除以装机容量后可以跨电站横向比较——一个 10 MW 电站的 MAE 是 500 kW和 1 MW 电站的 MAE 是 500 kW意义完全不同。这个基线值要记下来后面的 LightGBM 如果不比它好 10% 以上说明模型没学到位。4.2 梯度提升气象回归任务里性价比最高的一段代码梯度提升树是 tabular 气象数据的首选LightGBM 和 XGBoost 都可以我常用 LightGBM。它对特征尺度和缺失值不敏感训练快还能直接输出特征重要性方便反推气象因子贡献。光伏出力预测本质是回归任务损失函数用 MAE 比 MSE 更贴合业务——调度考核看的是偏差大小MAE 不放大个别离群点。import lightgbm as lgb dtrain lgb.Dataset(X_train, labely_train) dvalid lgb.Dataset(X_valid, labely_valid, referencedtrain) params { objective: regression, metric: mae, boosting_type: gbdt, num_leaves: 31, # 树复杂度数据量小就降到 15防过拟合 learning_rate: 0.05, # 配合 n_estimators 一起调越小越稳但训练越长 feature_fraction: 0.8, # 每棵树随机抽 80% 特征提升泛化 bagging_fraction: 0.8, # 每棵树随机抽 80% 样本 bagging_freq: 1, verbose: -1, } model lgb.train( params, dtrain, num_boost_round2000, valid_sets[dvalid], callbacks[lgb.early_stopping(100), lgb.log_evaluation(200)], ) # 输出特征重要性按 gain 排序看哪些气象因子真正起作用 importance sorted( zip(feat_cols, model.feature_importance(gain)), keylambda x: x[1], reverseTrue )num_leaves31是默认值如果训练集只有几万条建议降到 15 以下learning_rate0.05配上early_stopping(100)一般 300~800 轮就收敛。特征重要性按gain排序比默认的split更能反映特征对精度的实际贡献——如果排名前三是ghi_avg_15、ghi_now、ghi_avg_30物理上说得通模型也大概率没问题如果温度排第一而辐照度排不上号先回头查数据清洗对不对而不是继续调参。4.3 时序模型LSTM 什么时候值得换上去梯度提升最大的短板是依赖手工滞后特征。滚动均值窗口设成 15、30、60 分钟本质上是在猜物理过程的记忆长度云层推移、热惯性这些时间尺度未必是这三个窗口能覆盖的。LSTM 和 GRU 这类循环网络能自动学习时间依赖但付出的代价是数据量要求高、训练速度慢、结果难解释。我的判断标准很简单样本量低于 10 万条优先用 LightGBM样本量超过 10 万条且有日内滚动预测需求才考虑 LSTM。所谓滚动预测不是一次预测未来 24 小时而是每 15 分钟滚动预测未来 1~4 小时LSTM 的状态记忆在这种场景才有发挥空间。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping # X_seq 的 shape 为 (n_samples, 24, n_features)表示每个样本用过去 24 个点预测下一点 # 滑动窗口构造在数据预处理阶段完成窗口长度可调 model Sequential([ LSTM(64, return_sequencesTrue, input_shape(X_seq.shape[1], X_seq.shape[2])), Dropout(0.2), LSTM(32, return_sequencesFalse), Dense(16, activationrelu), Dense(1), ]) model.compile(optimizeradam, lossmae) model.fit( X_seq_train, y_train, validation_data(X_seq_valid, y_valid), epochs50, batch_size256, callbacks[EarlyStopping(patience5, restore_best_weightsTrue)], )窗口长度取 24对应过去 6 小时15 分钟一个点这个长度能覆盖云层通过和组件热惯性的主要时间尺度。Dropout(0.2)放在第一个 LSTM 层后面防止时序依赖被过度记忆。注意 LSTM 的输入特征必须是归一化后的树模型可以不归一化神经网络不行。如果 LSTM 在验证集上只比 LightGBM 好一点点而部署环境是嵌入式设备或老旧服务器果断退回 LightGBM——在光伏预测这种场景里模型的可维护性比零点几个百分点的精度更值钱。5. 避坑与排查光伏出力预测常见的 5 个坑5.1 归一化与滚动特征造成的数据泄漏现象训练集上验证指标很好测试集上一落千丈误差翻倍。原因最常见的是 scaler 用全量数据fit或者滚动特征在切分时跨过切分点往后取数。这两个操作都会让模型“偷看”未来信息。另一个隐蔽来源是特征工程在切分之前统一做完滚动均值里已经包含了验证集和测试集的数据。解决先切分后做特征工程scaler 只在训练集上fit滚动特征只能从历史方向取值并检查特征矩阵的行索引与时间索引严格对齐。5.2 多云天和阴天预测系统性偏低现象模型在晴天精度尚可一到多云天气预测值普遍低于实测且低辐照时段偏差更大。原因不一定是模型的问题。数值天气预报对云的描述本身就有误差辐照度预报偏低模型再放大这个误差低辐照区间出力对辐照度的敏感度低同样的辐照度偏差产生的出力偏差又被非线性压缩。解决先单独统计ghi_fcst与ghi_obs的分桶偏差曲线对预报辐照度做校准引入总云量或云类型作为离散特征按分桶建模。校准这一步做好比换模型有效得多。5.3 凌晨和傍晚的“对称误差”零出力段学歪了现象日出前预测值出现小幅负功率或反光日落出力预测曲线拖尾不为零。原因回归模型在零出力边界处平滑拟合损失函数对 0 附近的误差处理不当夜间样本和白天样本混在一起训练模型在边界处取了一个折中值。解决把预测值加np.clip(0, cap)物理约束更推荐的做法是先训练一个昼夜分类器只在白天时段训练出力回归模型夜间直接置零。5.4 限电和停机样本混入训练集导致预测长期偏低现象模型整体预测值偏低尤其高辐照时段预测曲线始终低于实际发电能力。原因训练集里混入了大量“白天高辐照、出力接近 0”的限电和检修样本模型学到的不是物理规律而是统计平均——把高辐照时段出力拉低了。解决按第 3.2 节的规则清洗将限电/停机样本剔除或打is_fault标签如果电站限电频繁建议单独训练“可发电状态”分类模型再用回归模型预测正常状态下的出力。5.5 zip 包解压异常或文件不全伪加密、乱码与坏包处理现象下载的研究包解压弹密码错误或解压出来缺文件、文件名乱码、数据读不进来。原因一是压缩包在传输中断导致文件体损坏unzip在读取时发现局部数据缺失二是部分压缩包设置的是“伪加密”——文件标记了加密位但实际内容没加密常见于打包工具导出时的标记异常三是中文文件名在 Windows 上用 GBK 编码存储Linux 下按 UTF-8 解压直接乱码。解决先用unzip -t 文件名.zip测试包完整性报错就是文件损坏要重新下载Linux 下解压中文文件名带unzip -O GBK参数Windows 下用 7-Zip 打开压缩包如果能直接预览内容说明是伪加密把文件拖出来就能用真加密就查看压缩包附带的说明文件找密码。验证解压出的 CSV 能正常pd.read_csv并检查行列数再往下走数据处理流程。6. 进阶验证用晴空模型约束预测曲线按考核口径验收6.1 用晴空辐照度给预测加物理上限晴空辐照度是无云条件下到达地表的辐照度由太阳高度角、经纬度、海拔和大气透明度决定可以用 pvlib 的ineichen模型直接估算。这个值有两个用途一是作为出力上限约束预测值超过装机容量 × 晴空辐照度折算系数直接裁剪二是计算 clear-sky indexkt ghi / ghi_clear它是比出力本身更干净的预测目标——去掉了效率系数和温度修正尺度始终在 0 到 1 之间晴空时接近 1多云时在 0.3~0.7 波动阴天趋近于 0。很多预测方案先预测kt再乘回ghi_clear得到出力比直接回归出力更稳定就是这个道理。6.2 误差回补与滚动预测滚动预测场景里前一步的残差有很强的自相关性可以用一阶回补来修正下一步预测y_hat_adj(t1) y_hat(t1) alpha * (y_obs(t) - y_hat(t))。晴天残差小alpha取 0.3多云天残差大且自相关强alpha取 0.8。按kt值分桶动态调整alpha可以在不增加模型复杂度的情况下把滚动预测的 MAE 再压几个百分点。这个技巧对任何模型都有效。6.3 验收口径按电网考核指标来别自嗨自己评估看 MAE、RMSE项目验收要看调度侧的考核口径。国内并网功率预测常见的考核方式是统计预测曲线与实测曲线的偏差在允许范围内的点数占比允许偏差通常在 ±10% 或 ±15% 左右。即使不并网也应该用相同的口径自我评估才能在项目交付时有说服力。指标计算方式用途MAEmean(y_true - y_predRMSEsqrt(mean((y_true - y_pred)^2))放大较大偏差抓异常时段nMAE / nRMSE除以装机容量跨电站横向比较合格率偏差在 ±10% 内的点数 ÷ 总点数调度考核常用口径验收看这个我自己的习惯是任何光伏预测项目都先跑一遍晴空模型和分桶校准再上机器学习。这道工序不性感但能避掉一半的玄学翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表