ARTICLE DETAIL

资讯详情

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

光伏发电量短期预测的SARIMA与Prophet组合方案与实战代码

光伏发电量短期预测的SARIMA与Prophet组合方案与实战代码 光伏发电量短期预测我前前后后做了小半年。刚开始的想法很简单找个模型把历史发电量喂进去直接跑出未来几天的曲线就行。等真拿到电站数据才发现网上教程讲的是模型现实考验的是数据。晴天的时候功率曲线确实光滑得像教科书但一旦碰上多云天云层一挡出力在几分钟内能掉一半再拉回来这种数据用单一模型硬扛基本就是翻车的命。后来我改用SARIMA季节性自回归移动平均模型和Prophet的组合方案才算走通了一条比较稳的路。SARIMA擅长捕捉日周期和小时级的季节性规律Prophet对趋势变化和天气扰动的适应能力更强两个模型一配合能覆盖对方顾不上的误差区间。这篇文就把这套方案的完整流程、Python代码和我在实际项目里踩过的坑全部写出来适合正在做光伏功率预测、电力交易策略或者研究时间序列模型组合的朋友参考也适合刚接触这两个模型的初学者拿去做一个能跑通的完整项目。1. 光伏功率预测的难点与组合拳思路1.1 数据里那种规律与随机并存的拧巴感光伏发电功率主要由三个因素决定辐照度、温度和组件自身状态。三者里辐照度的波动最要命。晴空条件下辐照度变化非常有规律基本跟着太阳高度角走所以功率曲线呈现典型的早晚低、中午高钟形而且日与日之间高度相似。这种强烈的日重复性正好是SARIMA这类强调季节性的模型的拿手好戏——它本质上就是通过季节差分把这种周期规律提炼出来。但多云天气下辐照度受云层移动影响会剧烈波动。云遮住太阳的那几分钟功率能骤降七成云移走后又会快速恢复。这种突变不是周期性规律而是气象过程驱动的。SARIMA很难提前捕捉到因为它是历史数据的线性组合外推遇到非周期性突变就力不从心。Prophet相对好一点它把趋势变化建模成一系列可学习的突变点拟合时会把极端点当成趋势切换信号来处理不至于被瞬时毛刺带得全线失真。所以为什么一定要做组合因为短期预测未来24到72小时的功率曲线本质上就是稳定周期加天气扰动的叠加。SARIMA管周期Prophet管扰动两者误差模式不同融合之后明显比单模型稳。1.2 两个模型各自的脾气与适用边界SARIMA全称季节性自回归移动平均模型是ARIMA的扩展版专门处理带季节性的数据。光伏功率数据天然带多尺度季节性日季节性最明显每天同一时刻的功率模式高度相似如果数据跨度够长还会有天气尺度上的周季节性波动。SARIMA通过差分、季节差分、自回归项和移动平均项的组合把规律压缩成一组参数然后顺着历史轨道往外推。Prophet是Meta开源的时间序列预测工具思路完全不同。它把时间序列拆成趋势项、季节项、节假日项和误差项用加法模型拟合训练速度快对非统计专业背景的人很友好。最关键的差异是Prophet允许你塞进额外回归变量比如辐照度、温度、云量。这些外生变量正好补上SARIMA只看历史功率、不看天气原因的短板。我之前见过不少项目为省事只跑一个模型。说实话电站地理位置好、天气常年稳定的话单用Prophet够用但你要是做电力交易策略或者微网调度预测误差直接跟真金白银挂钩组合方案的价值就不是提升两三个点的精度那么简单了而是它能显著降低大误差出现的概率。哪怕只是让最坏情况从偏差30%降到偏差18%业务上就能接受很多。2. 数据预处理先把电站日志变成一条干净的时间序列2.1 拿到原始SCADA数据先别急着建模电站的SCADA系统导出的数据通常包含时间戳、总有功功率、气象站辐照度、环境温度、组件温度等字段。但原始数据远没想象中干净。我第一次拿到的数据就撞上几个典型问题时间戳不连续传输中断导致某些小时段整段缺失功率值偶尔出现负的是夜间传感器零漂造成的辐照度偶尔超过物理上限明显是仪器异常或积灰干扰。处理原则可以固定下来我一般按小时粒度做始终不建议拿到数据直接丢进模型按固定频率重采样15分钟粒度更精细但模型计算量成倍上涨1小时粒度对72小时尺度的短期预测足够。时间戳缺失用线性插值补。功率值不要用均值填充——夜间功率就是0补一个非0值等于凭空造噪声。辐照度超过理论上限不同地区有差异多数电站设计在1000 W/m²左右的按上限截断或者标记异常后插值替换。夜间辐照度接近0但功率不为0的小幅波动不用特意剔除。这本身就是电站自耗电的真实特性让模型学进去无妨。下面这段是我常用的预处理代码可以直接套import pandas as pd import numpy as np df pd.read_csv(pv_power.csv, parse_dates[time]) df df.set_index(time).sort_index() # 固定为1小时粒度重采样 df df.resample(1H).mean() # 功率下限约束线性插值 df[power] df[power].clip(lower0) df[power] df[power].interpolate(methodlinear, limit48) # 辐照度、温度处理 df[irradiance] df[irradiance].clip(upper1000) df[temperature] df[temperature].interpolate(methodlinear, limit48) # 辐照度几乎为0时认定无光照功率直接归零 df.loc[df[irradiance] 5, power] 0 df df.dropna(subset[power])注意interpolate里的limit参数。限制最大插值距离是为了防止大段缺失被硬生生补出一条虚假曲线。超过48个点连续缺失说明那段数据不值得救直接剔除更省心。2.2 天气数据到底要不要进模型SARIMA的标准形式不直接支持外生变量但它的扩展版SARIMAX可以。不过我自己的经验是做短期预测时SARIMA其实只用历史功率序列就够了加入天气变量反而容易引入未来不可得的麻烦。真正需要外生变量的是Prophet它本来就有专门的外生回归机制把辐照度和温度放进去预测效果提升非常明显。这里有个特别容易踩的坑训练阶段你可以用历史实测天气数据但预测阶段手上只有天气预报数据有些场景甚至连天气预报都没有。所以做验证的时候必须用预测时刻实际能拿到的天气数据来测不能用实测天气数据事后替代否则验证指标会虚高一大截。如果项目确实拿不到未来天气预报那就做个简化版Prophet只加温度回归变量因为温度预报相对靠谱辐照度的预报误差太大反而会把模型拖垮。2.3 训练集和验证集必须按时间切不能随机打乱时间序列模型的验证方式跟普通机器学习完全不同绝对禁止随机shuffle。光伏数据尤其敏感的是季节差异夏天光伏出力峰值远高于冬天如果随机抽样让训练集里混着夏天的数据去预测冬天模型会在季节边界上暴露严重的滞后偏差你还以为是自己模型没调好。正确做法是按时间顺序切分。比如有两年数据用前20个月训练后4个月验证。更严格一点的做法是滚动预测验证也叫时间序列交叉验证每预测完一段就把真实值并入训练集再预测下一段。我在第6章评估部分会专门说这个。先把切分的思路定下来后面所有模型都遵循同一套切分结果才公平。3. SARIMA建模从定阶到滚动预测的完整流程3.1 先看自相关图再决定参数SARIMA有一堆参数要定p自回归阶数、d差分阶数、q移动平均阶数以及季节部分的P、D、Q和季节周期m。很多人一上来就丢给auto_arima跑跑完拿到一组参数也不知道为什么。我的建议是先用眼睛看再让工具帮你确认。光伏功率序列最明显的特征是日季节性。如果按小时采样季节周期m就是24。快速验证的办法把连续七天每天同时刻的功率拉出来画在一起如果曲线高度重合说明日季节性极强季节部分必须有。进一步看周季节性如果工作日和周末的功率形态有明显差异可以考虑m168一周的小时数的周期项。不过对短期光伏预测我一般只保留m24周季节性放到Prophet里用weekly_seasonality去兜。接下来对序列做一阶差分去掉整体趋势再做一次间隔24小时季节周期的季节差分然后去看ACF和PACF图。光伏序列经过这两步差分后ACF往往在滞后24处有明显的拖尾PACF在滞后24附近有截断这说明季节自回归项P1是合适的。非季节部分p和q一般0到2之间就足够。这里的原则是宁可参数少也不要为了完美拟合历史而把模型搞得很复杂——参数越多对未来预测的稳定性越差尤其是超过一个季节周期之后复杂模型特别容易崩。3.2 statsmodels实现与参数含义statsmodels里的SARIMAX类可以直接建模支持SARIMA加外生变量。下面这个例子是hourly数据、季节周期24的标准配置from statsmodels.tsa.statespace.sarimax import SARIMAX model SARIMAX( train[power], order(2, 1, 2), seasonal_order(1, 1, 1, 24), enforce_stationarityFalse, enforce_invertibilityFalse, ) result model.fit(dispFalse, maxiter200) print(result.summary())参数的具体含义order(2,1,2)非季节部分AR阶数2一阶差分MA阶数2。seasonal_order(1,1,1,24)季节部分季节AR阶数1季节差分1次季节MA阶数1季节周期24小时。enforce_stationarityFalse和enforce_invertibilityFalse让优化器放宽平稳性与可逆性限制避免因边界条件拒收收敛。但注意这两个开关不能当成万能药。如果数据本身有明显趋势或者异常段宁可先回头清洗数据也不要靠放宽限制硬凑。fit的时候建议固定maxiter默认值在某些异常数据上会过早停止迭代我习惯给到200到500。训练完先看summary里的系数p值明显不显著比如p0.1的项可以考虑删掉把模型压瘦。3.3 预测阶段直接预测还是滚动预测SARIMA做多步预测时有两种思路。一种是直接调用get_forecast(steps48)一步输出未来48小时所有预测值另一种是滚动预测预测下1小时拿到真实值后更新模型再预测下1小时。直接预测适合做计划排产滚动预测适合在线实时更新。短期光伏预测我更推荐直接预测因为实际场景里你不可能为了预测未来一小时而在线重训模型也没法等到真实值出来再推下一步。forecast result.get_forecast(steps48) forecast_mean forecast.predicted_mean forecast_ci forecast.conf_int()直接预测的局限也很明显超过24小时之后预测曲线会逐渐趋向一个周期性的平均形状对天气突变没有任何反应。SARIMA本质上假设未来是历史的某种重复这个假设在晴天成立在多云转晴或者突降暴雨的场景就彻底失效。这正好是Prophet登场的理由。4. Prophet建模把天气因素塞进去效果直接上一个台阶4.1 输入输出格式与训练过程Prophet要求的输入格式很简单一个DataFrame两列ds必须是datetime类型y是目标值。对光伏数据y就是功率。预测前要构造一个包含未来时间点的futureDataFrame最好从训练集的结束时刻无缝衔接不要随便从中间截断。训练代码基础版长这样from prophet import Prophet df_prophet df[[power]].reset_index() df_prophet.columns [ds, y] df_prophet[irradiance] df[irradiance].values df_prophet[temperature] df[temperature].values model Prophet( yearly_seasonalityFalse, weekly_seasonalityTrue, daily_seasonalityTrue, changepoint_prior_scale0.05, seasonality_prior_scale10.0, ) model.add_regressor(irradiance, standardizeFalse) model.add_regressor(temperature, standardizeFalse) model.add_country_holidays(country_nameCN) model.fit(df_prophet)关于yearly_seasonalityFalse我的理由是光伏功率虽然有季节性但那是太阳高度角的季节性数据长度不够两年的话逐年季节性项很容易和日季节性混淆拟合过度。如果你手里有三年以上数据再考虑打开也不迟。4.2 预测阶段必须处理的外生变量问题Prophet预测时必须为未来时间段的每个回归变量提供值。这里就是前面2.2节说的坑你要用预测时刻实际能拿到的数据而不是事后拿实测值填进去。实际项目里我会用数值天气预报的辐照度和温度没有预报源时退而求其次用气候平均值补但一定要在代码里留下备注说明假设是什么。future model.make_future_dataframe(periods48, freqH) # 从数值天气预报读取未来辐照度与温度 future[irradiance] weather_forecast[irradiance].values future[temperature] weather_forecast[temperature].values forecast model.predict(future)4.3 参数调整的几个实战经验changepoint_prior_scale是Prophet里最需要小心调的参数。光伏序列里的天气突变会导致出力趋势出现阶段性变化设置太小模型把突变当噪声平滑掉设置太大模型过度拟合历史里的随机波动预测曲线毛刺很多观感差而且误差大。我从0.05起步观察预测曲线如果出现锯齿状抖动就往下调如果晴天预测峰值系统性偏低就往上调一点范围控制在0.02到0.08之间。seasonality_prior_scale默认是10控制季节项的灵活度。光伏的日季节性强且形状稳定默认值基本够用。如果你发现预测曲线在晴天中午的峰值明显偏低可以把季节项尺度调大到15左右让它更贴合历史峰值形态。还有一个容易被忽略的默认行为Prophet会自动搜寻突变点changepoint主动把历史切分成多段趋势。这对光伏数据是双刃剑。它能识别出一段连续的阴雨天气对应的低出力趋势但如果识别得太碎会把正常天气波动也当成趋势切换信号导致预测后续出力一路走低。我一般会显式传入n_changepoints控制在5到10之间同时结合前面的changepoint_prior_scale一起做调参。5. 模型融合不是简单加权平均是取长补短5.1 固定权重的问题在哪SARIMA和Prophet各自预测做完最朴素的做法是加权平均final 0.5 * sarima 0.5 * prophet。实操下来你会发现两个模型的误差分布在不同天气场景下差别很大。晴天SARIMA往往更好因为周期性强、噪声小突变天气下Prophet误差更小因为它的趋势突变机制更扛造。固定权重让你在两个场景间都不出错也不出彩但没办法在正确的时间把权重倾斜到更准的那个模型上。所以我想做的不是固定权重而是动态权重用一个滑动窗口统计最近一段时间两个模型各自的预测误差根据误差大小动态调整下一个时段的权重。误差小的权重自然变大误差大的权重变小。这个思路实现起来非常简单效果却非常直观。5.2 动态权重的实现下面这个函数就是核心逻辑。window表示滑动窗口大小我一般取24小时这样权重能跟上一天内的天气变化节奏。import pandas as pd import numpy as np def dynamic_weights(sarima_pred, prophet_pred, actual, window24): # 逐点绝对误差 errors pd.DataFrame({ sarima: np.abs(sarima_pred - actual), prophet: np.abs(prophet_pred - actual), }) # 滚动平均绝对误差 rolling_mae errors.rolling(window).mean() # 权重与误差成反比 weights 1.0 / rolling_mae weights weights.div(weights.sum(axis1), axis0) return weights # 假设在验证集上不停滚动预测得到两个模型的预测序列 weights dynamic_weights(sarima_history, prophet_history, actual_history) final_pred (weights[sarima] * sarima_forecast weights[prophet] * prophet_forecast)注意一个细节动态权重最好在验证集上校准不要直接用训练集数据算权重。否则模型在训练集上的误差分布会过于乐观到了验证集上实际误差放大后权重切换会滞后。我一开始就在训练集上算权重结果验证集效果比预想差后来改成验证集内滚动计算才正常。5.3 残差修正一个进阶方向除了加权另一种主流思路是残差修正。先拿SARIMA或Prophet做一个基准预测然后用机器学习模型去学习预测误差与天气变量的关系最后把预测的误差加回基准预测上。比如拿随机森林去拟合误差项特征是辐照度变化率、云量、温度变化、基准预测值等。这个方向潜力很大但对特征工程的要求也高而且上线后的稳定性风险更大。我自己的项目里动态权重相对单模型大概能把RMSE降3%到5%残差修正再多降1到2个百分点但代码复杂度、线上数据依赖、特征漂移问题都明显上升。如果你刚开始做我建议先把动态权重跑稳再考虑残差修正不要一上来就两头抓。6. 评估指标与实测分天气看误差才是真实水平6.1 评估指标怎么选光伏功率预测常用的指标有MAE、RMSE、MAPE、R²。我的实际建议是MAE直观反映平均偏差水平方便向业务解释。RMSE对大误差惩罚更重。如果业务关注极端偏差用RMSE当核心指标。MAPE对非技术背景的人友好但功率在夜间趋近于0直接算全天MAPE会被极小值放大到离谱。我一般只统计白天时段辐照度大于50 W/m²的MAPE。R²和相关系数时间序列预测里参考价值有限不建议做主要指标。如果是电力交易场景我还会单独看高峰时段的RMSE。高峰时段出力大、误差绝对值也大直接影响交易策略的盈亏跟全天平均误差是两码事。6.2 按天气类型拆分评估光伏预测有个铁律不同天气类型下误差差异极大。晴天的RMSE可能只有装机容量的5%阴雨天能到20%以上。只报一个总的RMSE什么都看不出来。我习惯把验证集按目标天气拆成晴天、多云、雨天三组分别评估。分组方式可以用气象站的云量数据也可以用辐照度曲线的波动率简单聚类实在不行人工逐日标注。下面是我一次验证结果的示意数值仅做演示别当真天气类型SARIMA RMSE (kW)Prophet RMSE (kW)融合模型 RMSE (kW)晴天36.241.533.8多云58.752.147.3雨天79.471.865.2表格里的规律很典型晴天SARIMA强多雨天气Prophet强融合模型在各类天气下都能保持居中偏上的表现。这也是我不建议只看单一模型总指标的原因——总指标好可能只是晴天占比高把多雨天的拉胯藏掉了。6.3 一次完整预测流程的代码骨架把上述步骤串起来主流程大概是这样from statsmodels.tsa.statespace.sarimax import SARIMAX from prophet import Prophet # 1. 数据准备 train_df ... # 含 power, irradiance, temperature test_df ... # 2. SARIMA预测 sarima_model SARIMAX(train_df[power], order(2, 1, 2), seasonal_order(1, 1, 1, 24)) sarima_result sarima_model.fit(dispFalse, maxiter200) sarima_forecast sarima_result.get_forecast(steps48).predicted_mean # 3. Prophet预测 prophet_model Prophet(daily_seasonalityTrue, weekly_seasonalityTrue) prophet_model.add_regressor(irradiance) prophet_model.add_regressor(temperature) prophet_train train_df.rename(columns{power: y})[[ds, y, irradiance, temperature]] prophet_model.fit(prophet_train) future prophet_model.make_future_dataframe(periods48, freqH) future[irradiance] test_df[irradiance].values future[temperature] test_df[temperature].values prophet_forecast prophet_model.predict(future)[yhat].values # 4. 动态权重融合 final_pred (weights[sarima] * sarima_forecast weights[prophet] * prophet_forecast) # 5. 结果约束 final_pred np.clip(final_pred, 0, plant_capacity)最后一步np.clip别嫌多余。预测值出现负的或者超过装机容量在业务汇报时会被立刻质疑模型可靠性。加上上下限约束虽然只是后处理但对外展示专业度高出一截。7. 踩坑记录版本、时区、预测步长的三个大坑7.1 Prophet 的安装与版本演进Prophet这个库改名过多次早期叫fbprophet后来改成prophet。直接pip install的时候经常跟numpy、cmdstanpy的版本打架在Windows上尤其明显。我自己用过最省事的方案是conda建独立环境然后conda install -c conda-forge prophet一套下来基本不会撞依赖。如果你项目里还要跑statsmodels建议两个库在同一个环境里装好之后固定版本不要频繁升级时间序列库的版本变更经常悄悄改默认行为线上复现对不上版本会非常痛苦。新版Prophet还换了后端的编译方式安装时间变长首次训练还会触发Stan编译。第一次跑通别着急等它编译完后续训练速度就正常了。7.2 时区问题导致整个预测曲线偏移光伏电站数据一般记录的是北京时间。Prophet内部处理时间戳时如果不带时区信息或者混入了UTC预测出的时间点就会整体偏移8小时。白天峰值的预测曲线会跟真实曲线错开一大截看起来就像模型完全没学会规律其实是时区没统一。建议在数据加载阶段就强制统一df.index df.index.tz_localize(UTC).tz_convert(Asia/Shanghai)注意如果原始时间戳已经带时区就不要再用tz_localize直接加先检查再处理。我吃过一次亏连续排查了两天才发现是时区混用白白浪费了很多时间。7.3 预测步长越长SARIMA越容易均值回归SARIMA做多步预测时超过一个季节周期后预测值会慢慢收向历史同期的平均值方差越来越小。这意味着你预测未来72小时前24小时还行后48小时基本就是一条平均曲线在天气突变的日子里误差会迅速放大。应对方法有两个一是把融合模型里的Prophet权重在长步长时段提高因为Prophet至少还带着天气回归变量能对突变做出部分反应二是做分段预测比如先预测24小时拿到真实数据更新后再预测下一个24小时效果比一次性预测72小时好很多。这个方法会根据预测时间尺度的不同采取不同的融合策略未来24小时内SARIMA表现好权重高一些。24到72小时Prophet权重逐步提高因为它保留了天气信息。这些权重调节规则可以通过之前提到的动态权重函数实现也可以在后期固定为经验值。如果你做的是每天滚动更新一次的系统我建议直接把近24小时偏SARIMA、远时段偏Prophet这个规则固化成代码运维起来省心不少。我自己的感受是光伏预测这个项目最后拼的不是模型有多先进而是对数据特性有多少敬畏。把数据洗干净把两个模型的适用边界摸透再用一个简单的动态权重去平衡它们已经能跑赢绝大多数直接用单一模型的项目了。如果后续还想进一步提升可以从光伏电站的实况辐照度监测入手把分钟级辐照度变化率做成高频特征做成一个独立的辐照度突变预警模块让光伏发电量预测从统计模型往物理与统计混合的方向再走一步。
返回列表