拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

光伏发电量短期预测:SARIMA与Prophet组合模型的工程实践

光伏发电量短期预测:SARIMA与Prophet组合模型的工程实践

1. 做这个预测项目前,先把场景和难点想清楚

做光伏发电量预测这个事,我先说个结论:单纯靠一个模型扛到底,基本都会翻车。我在实际项目里试过只用ARIMA、只用Prophet、也试过深度学习那套LSTM,最后稳定下来用的就是SARIMA和Prophet结合的方式。这个组合的定位很明确——短期预测,也就是未来几小时到未来几天的发电量曲线。

先说清楚这个项目的适用场景。如果你是做光伏电站的运维监控、电力交易申报、或者给微电网做日内调度,那短期发电量预测就是刚需。比如第二天上午十点要上报次日的发电计划,你总得有个能打的模型在背后撑着。如果是做中长期规划、电站选址评估那种按月按年的预测,那这套方案就不太合适,别硬套。

光伏发电量预测之所以难,核心在于它的输出受两个完全不同的因素控制:一个是物理上确定性很强的部分,比如太阳辐照度的日变化规律、季节变化规律,这部分有非常明显的周期性和趋势性;另一个是物理上随机性很强的部分,比如云层遮挡、降雨、温度波动、空气质量变化,这部分几乎无法精确建模。SARIMA的优势在于把第一部分的周期性、趋势性吃得很透,Prophet的优势在于对趋势变化和节假日效应比较敏感,而且对异常值和缺失值有天然的容忍度。两者结合,等于一个负责抓住“规律”,一个负责兜住“意外”。

当然,我得实话实说,这套方案不是所有情况下都最优。如果项目数据量非常大、特征维度很高,比如你有几十个气象站的高频数据要融合进来,那梯度提升树或者深度模型可能会更好。但如果你的场景是:数据量中等、预测周期在1到7天、手头主要就是历史发电量序列加上基础气象数据,那SARIMA加Prophet的组合是目前性价比最高的选择之一,调参工作量可控,代码也不复杂,最重要的是结果可解释。

这项目适合谁来参考?我觉得有三类人:第一类是刚接触时序预测、想找一条完整技术路线的开发者;第二类是在做能源相关项目的工程师,需要快速落地一个预测模块;第三类是学生做课题、写毕业论文时想用真实数据集跑出点成果的。这篇文章我会把从数据清洗到模型调参再到融合预测的全流程都拆开讲,你跟着走一遍就能复现。

2. 数据准备:决定预测上限的第一道关卡

2.1 数据粒度与时间跨度怎么定

很多人一上来就急着调模型参数,但最后发现怎么调都不准,问题多半出在数据上。光伏发电量预测的数据粒度选择,直接决定了后面所有工作的上限。

我的经验是:短期预测以15分钟或1小时为粒度最合适。为什么不用5分钟?因为5分钟级别的数据噪声太大了,逆变器本身的采样波动、局部云层快速移动造成的剧烈抖动,都会让模型去拟合那些没有意义的细节,反而把真正的趋势给淹没掉了。为什么不用1天粒度?因为一天一条数据做短期预测,等于用微信步数去预测一个人接下来三分钟的跑步速度,信息密度完全不够。

在实际操作里,我倾向于用15分钟粒度做训练和预测。15分钟粒度在光伏领域是一个比较折中的选择:太阳能辐照度的变化在这个时间尺度上能够体现出云层平滑效应,又不会丢失日内峰值的形态特征。如果你手头原始数据是分钟级甚至秒级的,处理方式也简单,直接按15分钟重采样取均值就可以。

时间跨度方面,至少要有一年的历史数据,这是硬底线。原因很简单:光伏发电量的季节性至少包含两个维度,一个是一天之内白天和夜晚的日内周期,另一个是春夏秋冬的跨年周期。如果你只有三个月的数据,SARIMA连年周期都辨识不出来,Prophet也会把季节项拟合得歪七扭八。如果数据超过三年,那可以直接做截断处理,取近两年到三年的数据就足够,更早的数据一方面电站设备可能经历过清洗或更换,效率曲线已经变了,另一方面计算开销也会白白增加。

2.2 清洗与特征工程要点

光伏数据最常见的脏数据有这么几类,我挨个说:

- 夜间零值堆积。光伏电站夜晚发电量必然是0,这些零值本身是正常的,但对模型来说它会造成一种“零膨胀”的假象。处理方式有两种:一是训练时段只取每天6点到20点之间的数据,让模型专心学白天的光伏曲线;二是保留全天数据但把0值作为正常值对待,不额外处理。我建议用第一种,因为SARIMA对序列的均值结构很敏感,如果不分昼夜,模型的季节项会在两个完全不同的状态之间来回拉扯。

- 停机检修和通信故障造成的缺失段。这部分直接做线性插值往往是不准的,因为光伏曲线的形状是两头低中间高的山峰形态,不是直线。更好的做法是按“同时间点的相邻日期”来填充,比如今天是上午10点到11点缺失,就用昨天上午10点到11点的值做基础,再乘一个当天整体辐照度水平的修正系数。这个系数可以简化为两天的最大出力比值。

- 天气因素的引入。很多人在做SARIMA和Prophet时,只用发电量本身这一个单变量,这是可以用的,但也浪费了最有价值的辅助信息。我建议至少引入两个外生变量:水平面总辐照度(GHI)和环境温度。这两个变量对发电量的解释力最强。Prophet里可以直接通过add_regressor方法加入,SARIMA则可以用arima的exogenous参数传入。不过要注意一点:预测未来时,外生变量也需要预测值,所以实际操作中要么用数值天气预报数据,要么用历史同期均值做回退方案。

- 数值归一化。SARIMA和Prophet本身对量纲不敏感,因为它们是线性模型的扩展,不是梯度下降驱动的。但如果你后面要在这个基础上接其他模型做融合,比如LSTM或XGBoost,那归一化就很有必要了。我做项目时习惯统一在预处理阶段就把数据归一化到0到1之间,这样后面换模型、融合、评估指标计算都不用回头改。

清洗完的数据,我最后会先画一张时序图确认形态,再画一张月度热力图看季节规律是否清晰。这一步很便宜,却非常必要,数据形态不对,后续全部白做。

3. SARIMA模型:把季节性和自回归吃透

3.1 先搞清楚SARIMA(p,d,q)(P,D,Q)s的每一层含义

SARIMA的全称是季节性自回归移动平均模型,它本质上是把ARIMA对非平稳序列的处理能力,加上一个针对固定周期周期的季节差分和季节自回归、季节移动平均项。

很多人一看到(p,d,q)(P,D,Q)s这七个参数就头大,我换一种方式讲。你把发电量序列想象成一条水流,AR项(p)就是“看前几个时刻的水位来推测当前水位”,MA项(q)就是“看前几个时刻的冲击误差来修正当前判断”,差分阶数(d)就是“通过做几次减法让水流从有趋势变成平稳”。季节部分同理,(P,D,Q)是对“每年的同一天”这一层格局做的同样操作,s就是周期的长度。

放在光伏场景里,如果你用15分钟粒度数据,日内有96个点,那s=96;如果用小时粒度,s=24。这个s非常关键,很多人在做光伏预测时把s设成7,因为看到有星期周期性,但那是把光伏发电量当成了“居民用电量”来建模,光伏靠天吃饭,没有周末不周末的区别,唯一的周期就是昼夜和季节。当然,有些地区的电价政策会对光伏并网产生一周内的规律性影响,比如某个工业园区的限电措施在工作日和周末不同,这种情况下s=24之外可以再考虑一个周周期项,但这是少数情况,默认先做日内周期。

3.2 定参流程与代码实现

我实际项目里的定参流程是这样的:

第一步,用ADF检验确认序列平稳性。如果p值大于0.05,就对序列做一阶差分再检验,这个确定d。光伏数据因为有极强的日内季节性,直接看原始数据的话ADF结果通常会很不稳定,我的建议是先做季节差分(也就是每条数据减去前s条数据),再做普通差分。

第二步,用ACF和PACF图来初定p、q、P、Q。ACF图在季节延迟处有明显截尾时,说明MA类项起作用;PACF在季节延迟处有截尾时,说明AR类项起作用。这里我给个快速参考:光伏发电量序列的ACF图通常会在s,2s,3s的位置呈现出拖尾衰减的形状,这就是季节MA项的典型表现,也意味着Q通常取1或者2就够了。

第三步,用AIC或BIC做网格搜索,把候选参数组合跑一遍。我通常搜的范围是p和q在0到5之间,P和Q在0到2之间,d取0或1,D取0或1。这个网格规模大概几百个组合,SARIMA拟合一次从几十毫秒到几秒不等,全部跑完也就几分钟,完全可接受。

第四步,回测验证。拿最近14天的数据做滚动预测,每天预测未来24小时,算RMSE和MAE。这一步效果比任何一项检验指标都直观。

下面是一段我从实际项目中抽出来的核心代码,用的是statsmodels库:

import pandas as pd import numpy as np from statsmodels.tsa.statespace.sarimax import SARIMAX from statsmodels.graphics.tsaplots import plot_acf, plot_pacf from pmdarima import auto_arima from sklearn.metrics import mean_absolute_error, mean_squared_error # 数据预处理:假设df已经清洗完毕,索引为15分钟粒度DatetimeIndex # df['power'] 是归一化后的发电量 train = df.loc[:'2024-06-30', 'power'] test = df.loc['2024-07-01':, 'power'] # 先用auto_arima粗筛,限定季节性周期为96(15分钟粒度) auto_model = auto_arima( train, seasonal=True, m=96, start_p=0, max_p=5, start_q=0, max_q=5, d=0, max_d=2, D=0, max_D=1, trace=False, error_action='ignore', suppress_warnings=True, stepwise=True, n_fits=50 ) print(auto_model.order, auto_model.seasonal_order) # 手动拟合精细模型 order = auto_model.order seasonal_order = auto_model.seasonal_order model = SARIMAX( train, order=order, seasonal_order=seasonal_order, enforce_stationarity=False, enforce_invertibility=False ) result = model.fit(disp=False) print(result.summary())

这里有一个我在项目里反复踩过的坑:statsmodels的SARIMAX在enforce_stationarity和enforce_invertibility同时开启时,很多参数组合会直接报错或者收敛失败。尤其当季节周期s比较大时,模型的参数矩阵维度会比较高,默认的约束条件过于严格。所以我在拟合前都会把这两项关掉,拟合完再通过result.check_innovations去查看残差是否满足白噪声假设。

再看预测部分:

# 滚动预测未来24小时(96个点) forecast_steps = 96 pred = result.get_forecast(steps=forecast_steps) pred_mean = pred.predicted_mean pred_ci = pred.conf_int(alpha=0.05) # 反归一化 pred_mean_original = pred_mean * (power_max - power_min) + power_min

SARIMA的输出是带置信区间的,这个很有用。实际业务中,电力交易申报需要的是一个确定值,但并网调度希望你给出区间,SARIMA天然支持这个。我在给电站做方案时,一般把置信区间的上下界直接映射成“乐观场景”和“悲观场景”,这比单一预测值有说服力得多。

4. Prophet模型:让趋势和节假日自己说话

4.1 Prophet的核心机制与适用边界

Prophet是Meta开源的时间序列预测工具,它在工业界的口碑是“开箱即用,参数少,出图漂亮”。但说句公道话,Prophet不是万能的,它更适合那些有强趋势、有周期性并且有少量异常值的业务型时间序列,比如电商销量、网站流量。光伏发电量和这类序列正好有几分类似,但也有本质不同。

Prophet的原理是把你看到的时间序列拆成四个部分:趋势项、季节项、节假日项、误差项。趋势项默认是分段线性趋势,也可以切换成logistic增长趋势;季节项用傅里叶级数来拟合,所以你实际上是在用一组正弦和余弦的组合去逼近季节形态;节假日项是一个稀疏脉冲矩阵的回归。

在光伏场景里,Prophet能做因为季节项能够刻画“白天高、夜晚低”的形态,傅里叶级数的阶数越高,对曲线细节的拟合能力越强。但这里有个关键点:Prophet的默认季节项是年周期和周周期,没有日内周期。如果直接用日粒度数据还好,但如果你用15分钟粒度数据,就需要自己配置日内周期。

我的建议是:Prophet在光伏预测里最适合的是“日粒度或小时粒度的发电量总量预测”,特别是用来做未来3到7天的日发电量趋势预判。而SARIMA更适合做日内逐点的精细曲线拟合。两者在这个层面的分工刚好互补。

让我给一个直观对比:

维度SARIMAProphet
日内周期精细拟合强,s参数直接控制弱,需要手动加周期项
趋势突变适应中,差分结构对突变滞后强,变点检测自动识别
缺失值处理差,需要提前清洗插值好,自带容错机制
外生变量支持支持,但需要额外处理支持,add_regressor方便
可解释性中,参数含义专业强,plot组件直观
计算速度慢,网格搜索耗时长快,参数少

4.2 参数配置与代码实现

我用Prophet时,最关注的是三组参数:growth、seasonality、changepoint。

growth我用linear。光伏发电量在没有新增装机容量变化时,不呈现饱和增长形态,所以logistic不太合适。如果你的电站中间有扩建,发电量序列会有一个突越,这时你需要手动传入capacity的上下限,用logistic也能处理,但复杂度上来了,我这里不做展开。

seasonality方面,我搭配了三个周期项。第一个是日内周期,周期长度为1天,傅里叶阶数设为6到10之间,阶数太低拟合不了光伏的尖峰形态,太高会过拟合到云层的细节抖动。第二个是年周期,傅里叶阶数设为10,用来捕捉不同季节日照时长的差异。第三个是周周期,我默认不开,除非实际数据显示出明显的周期性并网限制。

changepoint_prior_scale这个参数值得单独说一下。它控制的趋势变点的灵敏度,默认是0.05。光伏电站的发电量趋势在一年内相对稳定,不像电商大促前后那样剧烈波动,所以我一般调大到0.1到0.2之间,让模型能捕捉到因为设备更换、组件衰减导致的长周期趋势漂移。相反,如果调得太小,比如0.01以下,模型的趋势项基本就是一条直线,失去了变点检测的意义。

核心代码如下:

from prophet import Prophet import pandas as pd # 准备Prophet格式的数据:ds是时间戳,y是归一化发电量 df_prophet = df.reset_index()[['ds', 'power']].rename(columns={'power': 'y'}) model_p = Prophet( growth='linear', seasonality_mode='multiplicative', yearly_seasonality=10, weekly_seasonality=False, daily_seasonality=False, changepoint_prior_scale=0.15, seasonality_prior_scale=10.0 ) # 添加日内周期,周期为一天 model_p.add_seasonality(name='daily_custom', period=1, fourier_order=8) # 添加外生回归变量:辐照度和温度 model_p.add_regressor('ghi') model_p.add_regressor('temp') train_prophet = df_prophet[['ds', 'y', 'ghi', 'temp']].iloc[:len(train)] model_p.fit(train_prophet) future = model_p.make_future_dataframe(periods=forecast_steps, freq='15min') # 未来外生变量用实况预报或者历史均值填充 future['ghi'] = ghi_forecast future['temp'] = temp_forecast forecast = model_p.predict(future)

这里需要提醒一点,Prophet的multiplicative seasonality通常用来处理“季节波动的幅度随趋势水平变化”的序列。光伏发电量正好符合这个特点:夏天的峰值不仅平均出力高,而且峰值的日波动也更剧烈。如果用additive模式,模型会假定每天的偏差幅度是恒定的,这对光伏来说太强了,我用multiplicative模式的效果明显好一截。

预测结果出来之后,我会把prophet输出的yhat、yhat_lower、yhat_upper都留下来,这三个值分别对应预测均值、下限和上限。配合绘图组件plot_components,你可以直接看到日内周期项、年周期项和趋势项各自的形状,这在实际项目汇报里是非常好的可视化素材,比直接甩一个RMSE数字更有说服力。

5. 两个模型怎么结合:加权融合与兜底策略

5.1 融合权重怎么定

模型结合这件事,最大的误区是拿两个模型的预测结果直接取平均。取平均在大多数情况下都有用,因为它天然让系统的方差降低了,但这不是最优策略。SARIMA和Prophet的结构差异决定了它们在同一个数据集上的表现分布也是不同的,更好的做法是动态权重。

我用的方法是:用过去N天(比如14天)的滚动回测来评估两个模型各自在“当前近期段”的表现,然后按误差倒数分配权重。核心逻辑很直接:谁过去表现好,谁在未来贡献更多。

权重计算公式我简化成这样:

w_sarima = (1 / rmse_sarima) / (1 / rmse_sarima + 1 / rmse_prophet) w_prophet = 1 - w_sarima

在代码里的实现如下:

from sklearn.metrics import root_mean_squared_error # backtest_results 里存放两个模型在近14天的每日误差 sarima_rmse = root_mean_squared_error(backtest_sarima['y_true'], backtest_sarima['y_pred']) prophet_rmse = root_mean_squared_error(backtest_prophet['y_true'], backtest_prophet['y_pred']) w_sarima = (1 / sarima_rmse) / (1 / sarima_rmse + 1 / prophet_rmse) w_prophet = 1 - w_sarima final_pred = w_sarima * sarima_forecast + w_prophet * prophet_forecast

这段逻辑看起来简单,但我实际项目中还有几个细节要注意:

第一,回测窗口的长度不能太长也不能太短。太短比如3天,权重会跟着某一次天气过程剧烈震荡;太长比如60天,又对最近的气象形势变化反应太慢。14天是我试下来比较稳的选择。

第二,权重计算的基准应该用“归一化后的误差”,因为SARIMA和Prophet如果分别在原始量纲上预测,数值尺度不同会导致误差倒数的尺度过分偏向某一个模型。把所有预测都映射到0到1区间之后再算RMSE,这样权重才有可比性。

第三,融合不只是加权平均。我在业务落地时还加了一层“悬崖式兜底”:如果SARIMA的预测值和Prophet的预测值在某一时刻的偏差超过总装机容量的15%,说明两个模型对当前天气形势出现了根本性分歧,这个时候不急着融合,而是触发异常检测逻辑,检查输入数据是不是有误、外生变量是不是有一方填充异常。等排查清楚再继续往下走。

5.2 完整预测流程与代码骨架

整套流程我总结成五个阶段:数据加载与清洗、特征构建、SARIMA训练与预测、Prophet训练与预测、融合输出与评估。下面给一个完整骨架,方便你直接抄:

def predict_solar_fleet(df, steps=96): # 阶段1:数据预处理 df = preprocess(df) # 阶段2:特征构建 df = build_features(df) # 阶段3:SARIMA sarima_model = train_sarima(df) sarima_forecast, sarima_ci = sarima_predict(sarima_model, steps) # 阶段4:Prophet prophet_model = train_prophet(df) prophet_forecast = prophet_predict(prophet_model, steps) # 阶段5:动态权重融合 weights = compute_weights(backtest_records) final = weights[0] * sarima_forecast + weights[1] * prophet_forecast return final, sarima_forecast, prophet_forecast

从这个骨架可以看到,整个模块的核心只暴露了一个入口函数。在项目里我是把它封装成定时任务的,每天早上6点自动拉取最近30天的历史数据和当天的天气预报数据,重新训练一次两个模型,然后输出未来24小时的预测结果到数据库。

这里说一下为什么每天重训而不是每周重训:光伏发电有很强的季节变化,设备效率也会因为清洗和老化缓慢漂移,模型如果每天都跟着最新数据微调一次,就能始终贴合当前的电站状态。实测对比下来,日更训练比周更训练的RMSE能降低3%到6%,看上去不多,但在电力交易场景里,3%的预测偏差可能就是真金白银的偏差考核费用。

6. 常见问题与排查实录

6.1 SARIMA调参踩坑

SARIMA最让人头疼的两个问题,一个是拟合警告频发,一个是预测结果发散。

拟合警告方面,最常见的提示是“ConvergenceWarning: Maximum likelihood estimation failed to converge”。这个在多数情况下不是模型有问题,而是参数搜索空间太大或者初值设置不合理。我的处理办法是先把auto_arima的结果作为初值,再用statsmodels手动拟合,并且把maxiter从默认50提高到200,置信度会好很多。另外一个细节是,seasonal_order里的s越大,模型的状态空间维度就越高,收敛难度越大。所以如果你用15分钟粒度数据s=96,拟合时间会比小时粒度s=24慢很多。

预测结果发散这个坑更隐蔽。有时候训练集拟合得很好,但预测未来24小时时,曲线后半段突然振荡幅值巨大,这就多半是参数的AR或MA多项式的根靠近单位圆了,模型处于临界稳定状态。解决办法是回到enforce_stationarity和enforce_invertibility这两个参数,把它们打开重新拟合,虽然可能牺牲一部分拟合精度,但预测稳定性会大幅提升。我一般这样处理:先用宽松约束找到候选参数组合,再用严格约束做最终模型。

还有一个我在做差分时经常遇到的问题:多差分会把序列里的真实信号也削弱。光伏发电量在晴天时特别平滑,做一次普通差分和一次季节差分之后,剩下的部分噪声占比会很大,这时候模型很容易把噪声当成信号来学。所以d和D的取值并不是越大越好,我见过不少案例是d=0、D=1就已经能通过检验了,硬要加d=1反而让AIC变差。

6.2 Prophet与SARIMA结果差异过大的处理

当你把两个模型融合在一起,最直观的问题是:如果某一个时段的预测差值超过阈值,说明什么?我的经验是,这大概率不是模型本身的错,而是输入数据有问题。

举个例子,有一次我发现某个电站的SARIMA预测值比Prophet高出将近20%,查了半天发现是那几天的辐照度数据出现了连续缺测,我用的回退策略是用历史同期均值填充,而SARIMA没有用到辐照度作为外生变量,它对这组缺失值毫无感知,自然就只按历史发电量的季节规律往前走;Prophet用了辐照度回归项,看到辐照度均值偏低,就自动向下修正了。这两个模型的预测方向完全相反,最终融合值被拉到了一个中间位置,反而成了一个不好不坏的预测。

这种场景下我的建议是:融合之前先做一个一致性检查,如果偏差超限,输出预测的同时把自己标记为“低置信度”,并附带原因代码。这样下游业务系统就可以根据置信度决定是否启用手动决策流程。

6.3 天气突变等特殊情况的兜底

SARIMA和Prophet本质上是基于历史统计规律的模型,它们对“历史中没见过的天气过程”无能为力。比如一次突如其来的强对流天气,温度骤降10度,辐照度在半小时内从800W/m²跌到100W/m²,这时候任何统计模型输出都会失准。

兜底方案有三层。第一层是外生变量修正:把数值天气预报的辐照度转换成一个“天气折算系数”,在融合预测结果上直接乘这个系数。这个系数的计算方式可以简单理解成:预测时段的辐照度预报值除以历史同期的气候平均辐照度,结果直接缩放发电量预测。第二层是分时段置信度调整:如果天气预报显示未来24小时有降雨云团活动,就把预测区间的宽度扩大,宁可牺牲一点精确度也要覆盖住风险。第三层是短期滚动修正:当实际发电量开始偏离预测时,用最近2到3小时的实际出力斜率来动态调整接下来几小时的输出,这相当于一个简单的人工PID,虽然不优雅,但在突发天气时比任何模型都好用。

我在项目验收报告里额外提到过一个经验:短期预测系统的核心不在于单点误差有多小,而在于误差是否可控、是否可解释。SARIMA加Prophet这套组合的意义也在这里——模型的任何一个输出异常都能被追溯到具体原因,这是黑盒模型很难做到的。

7. 一点个人体会

做光伏发电量预测这几年,我最大的体会是:模型是配角,数据和场景的理解才是主角。SARIMA和Prophet都是很成熟的工具,但把它们用对地方,需要对电站的物理特性、数据采集的细节、甚至当地天气系统的脾气有足够了解。

如果你想在现有项目里切入这套方案,我给你一个最小可行路径:先用小时粒度、只做未来24小时预测、只跑不融合的两模型对比,把预测误差和业务容忍度对上号,再迭代加入外生变量、动态权重和异常兜底。一上来就追求全功能往往会把问题复杂度推高到没法调试。

最后说个小细节:预测结果输出时,建议同时保存模型版本号、数据版本号和预测时间戳。有一次我们排查一个突然变差的预测,最后定位到是前一天的清洗脚本改了填充策略,导致输入数据均值整体偏移了。如果没有数据版本记录,这种问题排查起来基本靠猜。预测系统能不能长期稳定运行,很多时候就靠这些不起眼的工程细节。

返回列表