1. 为什么我又翻出了SARIMA:时间序列预测不是非要上深度学习
这几年时间序列预测的圈子几乎被LSTM、Transformer刷屏了,打开论文和开源项目,十有八九都是神经网络那一套。但我自己在实际做项目时,尤其是数据量不大、周期性规律明显的场景,反而更愿意先跑一遍SARIMA。原因很简单:它快、稳、可解释性强,而且在某些结构化数据上,预测精度一点都不比深度学习差。
SARIMA全称是Seasonal Autoregressive Integrated Moving Average,中文一般叫季节性差分自回归移动平均模型。它本质上是在经典ARIMA模型的基础上,额外显式建模了时间序列中的季节性成分。这个“显式”很关键——ARIMA只能处理非平稳序列的均值漂移,但遇到明显的月度、季度、周度周期时,如果不做季节性处理,残差里会一直留着周期性信息,模型拟合效果和预测精度都会大打折扣。
这篇博文我就以MATLAB为例,从零开始走一遍SARIMA建模的完整流程:包括数据预处理、平稳性检验、阶数识别、参数估计、残差诊断、模型评估,以及预测。全部代码都用MATLAB的Econometrics Toolbox实现,我会尽量把每一步背后的原因讲清楚,也会把我在实际建模过程中踩过的坑一并交代。适合刚接触时间序列分析、或者想用MATLAB做预测任务但还没系统梳理过SARIMA流程的读者。就算你用的是Python或R,建模思路同样通用,只是API和函数名不同而已。
我最早接触SARIMA是做一个电商平台的销量预测项目,当时数据就是典型的“周内周期性明显、节假日冲高回落”的形态。我也试过LSTM,调参调得头皮发麻,最后用SARIMA反而稳定拿到了一个不错的基线。从那之后我就养成了一个习惯:任何时间序列预测任务,先用SARIMA把天花板摸清楚,再考虑要不要上复杂度更高的模型。
2. SARIMA模型的核心拆解:它到底比ARIMA多了什么
2.1 ARIMA模型的基本逻辑
聊SARIMA之前,必须先说清楚ARIMA。ARIMA(p,d,q)由三部分组成:AR是自回归项,I是差分次数,MA是移动平均项。用一句通俗的话解释:它假设当前时刻的值,可以由过去若干个时刻的值(AR部分)和过去若干个时刻的预测误差(MA部分)线性组合而成,同时通过差分把非平稳序列变成平稳序列。
公式上写出来就是:
(1 - φ₁B - ... - φₚBᵖ)(1-B)ᵈyₜ = c + (1 + θ₁B + ... + θ_qB^q)εₜ其中B是滞后算子,φ和θ分别是AR和MA系数,εₜ是白噪声误差项。
这个模型在处理纯趋势型数据时非常好用,比如股票的日收益率(虽然实际预测很难)、气温的日均值(如果只考虑长期趋势)。但一旦数据中存在“每年同一时期都会重复出现的模式”,ARIMA就露馅了。你可以强行加大p和q的阶数去“覆盖”周期性,但代价是参数爆炸、过拟合风险急剧上升,而且预测效果并不好。
2.2 季节性分量是如何被建模的
SARIMA在ARIMA的基础上,增加了一组季节性AR和MA项,同时支持季节性差分。完整写法是SARIMA(p,d,q)(P,D,Q)ₛ,其中s是季节周期长度。比如月度数据有明显年度周期,s=12;周数据有周内周期,s=7;季度数据有年度周期,s=4。
背后的数学直觉很简单:除了用过去的近期值(比如昨天、前天)来预测今天,你还可以用去年同月、前年同月的值来捕捉季节性规律。于是,模型对滞后s期、2s期等季节性滞后位置上的自相关关系也做了显式建模。
写成公式就是:
Φ(B^s)φ(B)(1-B^s)^D(1-B)^d yₜ = Θ(B^s)θ(B)εₜ其中Φ和Θ是季节性多项式,s就是之前说的季节周期。
这里要特别注意D的选择,也就是季节性差分的次数。绝大多数场景下D取0或1就够了。D=1表示把“今年1月和去年1月”的差值作为输入,这一操作能直接抹掉数据里固定的季节性偏移。但D取2就很容易造成过度差分,把数据里的有效信息“差没”了,还会引入额外的负自相关,导致后续参数估计失真。
2.3 为什么选择SARIMA而不是直接用Prophet或LSTM
我并不否定深度学习模型的价值,但在MATLAB环境里做时间序列预测时,SARIMA有它独有的优势。
首先,可解释性。SARIMA的参数都对应明确的统计含义:φ₁告诉你昨天的值对今天的影响强度,季节性MA项告诉你上个月误差的影响。这在做业务汇报时特别有用,老板问你“这个预测凭什么这么做”,你能给出逻辑自洽的答案,而神经网络只能给出一堆无法解读的权重矩阵。
其次,数据需求低。LSTM类模型往往需要成千上万条数据才能训练出稳定效果,但SARIMA在几百条数据上就能工作得很好。电商新品的销量预测、新店开业初期的客流预测、疫情后数据的快速重建,这些场景下SARIMA几乎是唯一能稳定输出结果的方法。
再次,计算开销小。MATLAB里用最大似然估计拟合SARIMA,通常几秒到几十秒就收敛了,而训练一次LSTM至少是分钟级起步,还要调学习率、调层数、调dropout,折腾半天不一定比SARIMA好。实际项目中“先用SARIMA拿到基线”这个策略,能帮你省下大量试错时间。
2.4 模型适用边界的理性判断
不过SARIMA也不是万能的。它对数据有几个明确要求:一是序列长度必须足够支撑季节性周期的辨识,比如s=12时至少要有2-3个完整周期,也就是24-36个月的数据,否则季节性估计极不稳定;二是数据生成过程大致是线性的,如果存在明显的阈值效应、突变点、复杂交互关系,SARIMA很难刻画;三是对缺失值和异常值比较敏感,需要先做插补和清洗。
我的建议是:拿到一个预测任务时,先做时间序列可视化,观察是否存在明显的趋势、周期、突变;再快速跑一遍SARIMA作为baseline;如果后续发现SARIMA在验证集上误差较大,再逐步引入回归变量(比如SARIMAX)、Prophet或LSTM等更灵活的模型。这样做既不会错过简单高效的机会,也不至于在复杂问题上硬套线性模型导致翻车。
3. MATLAB环境下SARIMA建模的完整实操流程
3.1 工具箱准备与数据准备阶段
MATLAB做SARIMA建模主要依赖Econometrics Toolbox,核心函数是arima。这个函数支持指定AR、MA、SAR、SMA的阶数以及差分阶数,也支持把外生回归变量一并纳入(对应SARIMAX)。如果你的MATLAB没装Econometrics Toolbox,可以在当前工程环境下用ver命令检查一下,我用的版本是R2021a,后面所有代码默认在这个环境下运行。
准备好工具箱之后,第一步永远是数据探索。下面是一段我经常用来做快速可视化的模板代码:
%% 加载数据并快速可视化 data = readtable('sales_data.csv'); t = data.time; y = data.sales; figure; plot(t, y, 'LineWidth', 1.2); grid on; xlabel('时间'); ylabel('销量'); title('原始时间序列图');看图的几个关键观察点:
- 序列是否存在明显的上升或下降趋势。有趋势意味着大概率需要差分,也就是d至少取1。
- 序列是否存在固定的周期性波动。比如每年旺季都集中在3月和9月,说明s可能存在。
- 方差的波动是否随时间变化。如果锥形波动明显,说明方差非平稳,SARIMA虽能处理部分情况,但更彻底的做法是先做Box-Cox变换。
在实际项目里我会把这张图存在本地,后续做模型对比时要反复回来看。很多时候模型效果不好,回头一看就能发现数据本身有问题,比如某个月份有促销导致的极端值,或者有大额异常订单混入,这些都会在图上以刺眼的尖峰出现。这类异常值如果不处理,SARIMA的参数估计会被严重拉偏。
3.2 平稳性检验与必要的差分处理
SARIMA要求输入序列是平稳的,或者经过差分后平稳。判断平稳性的两种主流方法是ADF检验和KPSS检验。MATLAB中adftest函数直接封装了ADF检验的完整流程,KPSS检验也有对应的kpsstest函数。
%% 平稳性检验 [h_adf, p_adf] = adftest(y); disp(['ADF检验结果:p值为 ', num2str(p_adf)]); % 如果不平稳,做一阶差分 if h_adf == 0 dy = diff(y); disp('原序列不平稳,进行一阶差分'); end这里要提醒初次接触的人:ADF检验的原假设是“存在单位根”,也就是序列不平稳。所以p值小于0.05时拒绝原假设,说明序列平稳。很多新手记反了,一看p值大就以为序列没问题,结果建出来的模型一团糟。
差分阶数的选择不宜过度。绝大多数经济和管理类数据都是d=0或d=1,d=2很少见。判断差分是否过度的最简单方法:差分后序列的标准差。如果差分后标准差反而比差分前更大,说明你这个差分做亏了,已经损坏了原始信息。这个经验虽然朴素,但在实际项目中帮我挡掉了很多“看似更平稳、实则更糟”的陷阱。
季节性差分在MATLAB中也是手动完成的,用diff(y, s)即可,只要s与实际周期一致。我见过很多人拿不准这个s怎么确定,其实最可靠的方法仍然是看数据背景:月度数据看12,季度数据看4,周数据看7。不要盲目用统计方法去“猜”周期,业务先验比统计检验更关键。
3.3 模型阶数识别:ACF和PACF的配合
差分处理完成后,下一步就是确定p、q、P、Q的阶数。标准工具是自相关函数ACF和偏自相关函数PACF。MATLAB中分别用autocorr和parcorr函数:
%% 绘制ACF和PACF figure; subplot(2,1,1); autocorr(dy); title('差分后序列的自相关函数ACF'); subplot(2,1,2); parcorr(dy); title('差分后序列的偏自相关函数PACF');识别的原则:
- ACF在滞后k处截尾(即从某个滞后开始迅速落入置信区间内),PACF呈拖尾衰减,则考虑MA(k)或SMA(k)。
- PACF在滞后k处截尾,ACF拖尾,则考虑AR(k)或SAR(k)。
- 两者都拖尾,则可能需要ARMA项的组合。
如果序列存在季节性,你会发现ACF在s、2s、3s等季节性滞后位置出现明显的尖峰,而不是只在短期滞后处显著。这就是你决定要不要加入SAR和SMA项的直接证据。
实际数据分析中,ACF和PACF的形态经常不是“教科书式”的干净截尾或拖尾,而是一堆滞后同时显著。这时候我不建议纯靠肉眼判断,可以借助信息准则做自动化筛选。这也是下面一节的核心内容。
3.4 基于信息准则的自动选阶策略
MATLAB的Econometrics Toolbox提供了aicbic函数,可以计算AIC和BIC值。我常用的策略是:先手动设定一个候选参数范围,比如p ∈ {0,1,2}、q ∈ {0,1,2}、P ∈ {0,1}、Q ∈ {0,1},然后遍历所有组合,选出AIC或BIC最小的模型。
%% 参数网格搜索示例 bestAIC = inf; bestBIC = inf; results = []; for p = 0:2 for q = 0:2 for P = 0:1 for Q = 0:1 try Mdl = arima(p, 1, q); Mdl.Seasonality = 12; Mdl.AR = NaN(p,1); Mdl.MA = NaN(q,1); Mdl.SAR = NaN(P,1); Mdl.SMA = NaN(Q,1); EstMdl = estimate(Mdl, y, 'Display', 'off'); [aic, bic] = aicbic(EstMdl.LogLikelihood, numel(EstMdl.Variance)+...); catch continue; end end end end end这段代码里有个关键点:arima函数中Seasonality属性指定了季节周期s,同时还需要把AR、MA、SAR、SMA对应的系数全部设为NaN,表示“这些参数需要估计”。如果不设成NaN,arima会默认这些项系数为0,模型就不对了。
经验之谈:BIC通常比AIC更强调模型简洁性,在样本量较大时选BIC更稳妥;样本量较小且预测为主要目标时,AIC的效果也还不错。实际项目里我会把AIC和BIC一起输出,两者选出的模型如果一致,那基本稳了;如果不一致,我会对比两者在验证集上的预测误差再定。
3.5 参数估计与常见坑
选定阶数后就可以正式估计了。estimate函数内部用的是最大似然估计,配合数值优化算法求解。我在实践中发现,估计阶段最容易遇到的坑是优化器不收敛,或者收敛到明显的局部最优。应对方法有三个:
第一,检查数据预处理是否充分。如果原始数据的量级特别大(比如销售额是百万级别),而方差初始化又不太合理,MLE很容易在初始几步就发散。最省事的做法是把数据做标准化,估计完再还原预测结果。MATLAB里直接用zscore就好。
第二,观察估计结果的Standard Error。如果某个参数的方差异常大,说明该参数几乎不可辨识,很可能是阶数选高了。这时候宁可牺牲一点AIC,也要把不显著的高阶项去掉。
第三,警惕估计结果的Warning信息。MATLAB在估计过程中遇到非平稳或非可逆解时会弹警告,最常见的是“Non-stationary AR part”或“Non-invertible MA part”。一旦出现这种警告,说明模型虽然收敛了,但得到的解没有统计意义,必须调整阶数或检查差分是否足够。
%% 模型估计 Mdl = arima(p, d, q); Mdl.Seasonality = 12; Mdl.SAR = NaN(P,1); Mdl.SMA = NaN(Q,1); EstMdl = estimate(Mdl, y, 'Display', 'params');3.6 残差诊断与模型验证
参数估完不等于模型建完。我必须对残差序列做诊断,确保残差满足白噪声假设——如果残差里还有可辨识的结构,说明模型漏掉了有效信息。
诊断的三板斧:
%% 残差诊断 res = infer(EstMdl, y); % 1. 残差ACF figure; autocorr(res); title('残差自相关函数'); % 2. 残差正态性Q-Q图 figure; qqplot(res); % 3. Ljung-Box Q检验 [hLB, pLB] = lbqtest(res, 'Lags', [5 10 15]); disp(['Ljung-Box检验p值: ', num2str(pLB)]);Ljung-Box Q检验的原假设是残差序列不存在自相关。p值小于0.05说明残差还有显著自相关,模型不合格。我遇到过不少次这样的场景:AIC选出来的模型,残差检验不过关;反而是稍微“丑”一点的次优模型,残差干干净净。这时候我毫不犹豫选残差诊断更好过的模型。因为AIC是统计指标,而残差白噪声是建模的基本底线。
还有一点:不要把残差诊断当作“一次性必过”的检验。如果残差ACF在某个高频滞后处仍然显著,有可能是季节周期设定错了。举个真实案例,我之前做某系统的日流量预测,业务说是周周期,s=7,但残余ACF在滞后14、21处仍然显著,后来仔细排查发现实际上存在明显的双周周期,改s=14之后残差立刻通过检验。这个坑极其隐蔽,如果没有认真看残差,根本发现不了。
4. 预测效果评估:从点预测到置信区间
4.1 预测函数与基本流程
模型通过诊断后就可以做预测了。MATLAB用forecast函数来生成未来n期的预测值,同时还能给出预测区间(置信区间)。对于时间序列预测来说,我强烈建议不仅输出点预测,还要看区间预测——因为业务决策往往需要知道“最坏情况”和“最好情况”。
%% 预测未来12期 numPeriods = 12; [YF, YMSE] = forecast(EstMdl, numPeriods, 'Y0', y); YSD = sqrt(YMSE); % 构造95%置信区间 lower = YF - 1.96 * YSD; upper = YF + 1.96 * YSD; figure; plot(t, y, 'LineWidth', 1.2); hold on; plot((length(y)+1):(length(y)+numPeriods), YF, 'r-', 'LineWidth', 1.5); plot((length(y)+1):(length(y)+numPeriods), lower, 'r--'); plot((length(y)+1):(length(y)+numPeriods), upper, 'r--'); hold off; grid on; legend('历史数据', '预测值', '95%置信区间', 'Location', 'best');这里有个细节:forecast函数的'Y0'参数必须指定历史数据的最后一段,用来作为预测的初始状态。如果省略,MATLAB会默认使用模型估计时的整个样本作为初始状态,一般效果也还行,但遇到数据结构复杂时容易出错。显式指定更稳妥。
4.2 预测误差评估指标
真实项目里,我们最关心的是预测准不准。常用指标有MAE、RMSE、MAPE。我用一段代码在验证集上计算这些指标:
%% 在验证集上评估预测效果 test_start = length(y) - 11; test_true = y(test_start:end); % 重新拟合前段数据 EstMdl_train = estimate(Mdl, y(1:test_start-1), 'Display', 'off'); [YF_test, ~] = forecast(EstMdl_train, 12, 'Y0', y(1:test_start-1)); MAE = mean(abs(YF_test - test_true)); RMSE = sqrt(mean((YF_test - test_true).^2)); MAPE = mean(abs((YF_test - test_true) ./ test_true)) * 100; fprintf('MAE: %.4f\n', MAE); fprintf('RMSE: %.4f\n', RMSE); fprintf('MAPE: %.2f%%\n', MAPE);评估时有一个容易犯的错误:直接用全样本拟合的模型去预测“已经见过的”样本点,这样得出的误差严重偏小,不能反映真实预测能力。正确做法是滚动预测——只用t时刻之前的数据训练模型,然后预测t+1,再把t+1的真实值纳入训练集,继续预测t+2。这才是“上帝视角”的真实测试。代码实现时用一个for循环配合逐步更新Y0参数即可。
4.3 SARIMA预测的局限性——长期预测会向均值靠拢
用SARIMA做长期预测时有一个显著特征:预测步数越多,预测值越趋向于序列的均值。这不是bug,是模型内在逻辑决定的。因为平稳时间序列的长期条件期望必然收敛到无条件均值,所以如果你预测未来24期,第20期到第24期的预测值基本会变成同一条水平线,置信区间也会越拉越宽。
这一点在给业务方做汇报时一定要提前讲清楚,否则对方看到预测曲线变成一条“死线”会以为模型坏了。实际处理中我一般建议:SARIMA适合做短中期预测,步数不超过1-2个完整季节周期。如果业务确实需要长期预测,可以尝试和其他模型组合使用,比如“SARIMA预测趋势 + 专家修正”或者“SARIMA线性部分 + 机器学习残差修正”。
5. 常见问题与排查技巧实录
5.1 参数估计不收敛怎么处理
这个问题的出现频率极高,尤其在数据波动大或阶数选择不合理时。我总结了一个排查顺序:
第一,看数据量。样本太少是估计不收敛的头号原因,SARIMA的阶数之和(p+q+P+Q)最好不超过样本量的十分之一。如果样本只有50条,就别硬上SARIMA(2,1,2)(1,1,1)₁₂,大概率不是最优解,只是数值上碰巧跑得动。
第二,看数据尺度。把y标准化到均值为0、方差为1再估计,很多收敛问题会莫名其妙消失。标准化只影响优化路径,不影响模型结构,估计完把预测结果乘回原标准差加上原均值即可。
第三,看季节差分。季节性差分之后序列的可预测性往往会下降很多,方差变大。如果D=1之后的ACF不是“干净”地衰减,而是一个大负尖峰在滞后1处,说明你做了过度差分,应该考虑调回D=0。
5.2 如何判断自己的模型是否过度拟合
过度拟合在时间序列里很隐蔽,因为时间序列不能像交叉验证那样随机打乱。我的经验是看参数显著性:如果模型里有任何一个参数的t统计量绝对值小于1.96,它就不显著,应该考虑去掉。一个只有5个参数的模型如果和8个参数的模型预测效果差不多,我永远选5个参数的。简化为项目里常用的说法就是:能少一个参数就少一个参数,除非预测精度有实质提升。
另外可以做一个简单的稳定性检验:把数据拆成前80%和后20%,用前80%训练,后20%验证,如果验证集误差显著大于训练集误差,大概率过拟合。
5.3 预测结果全是历史均值翻版
这个现象通常是由于AR项和MA项的阶数选择不平衡造成的。比如模型几乎只依赖常数项和季节性哑变量,而忽略了最近几期的动态信息,导致预测看起来像“把往年同期平均值搬过来”。解决方法:检查ACF在滞后1-3处的衰减速度,如果ACF在滞后1处就掉到置信区间内,说明模型对近期信息的依赖过低。适当增加p或q的阶数,让模型把“最近的变化”纳入进来。
5.4 常见错误速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 估计结果出现大量NaN | 数据存在缺失值或Inf | 前处理补齐,用fillmissing |
| 估计Warning提示非平稳AR | 差分不足 | 增大d或D |
| 估计Warning提示非可逆MA | 差分过度或阶数过高 | 减小q或Q |
| 残差诊断不过 | 季节性周期判断错误 | 重新观察残差ACF的尖峰位置 |
| 预测置信区间过宽 | 数据波动大或模型不确定性高 | 考虑方差建模(如GARCH组合) |
| forecast报维度不匹配 | Y0长度小于指定初始状态长度 | 检查'Y0'是否传入了足够长的数组 |
5.5 给新手的三个实操建议
第一,一定要先画图再建模。这句话我重复无数次也不嫌多——直接跑模型是新手最容易犯的致命错误,时间序列第一次就要从图上看结构,肉眼确认周期和趋势比任何统计检验都更直观。
第二,不要死磕一个模型。SARIMA只是起点,如果数据有明显的节假日效应或者外部干预事件,你应该考虑SARIMAX,也就是在SARIMA基础上加入外生回归变量。MATLAB里arima函数支持'X'参数直接传入外生变量矩阵。
第三,保持代码的可重复性。把所有步骤写成一个脚本,包括数据读取、预处理、建模、预测、绘图。项目做到后期你会发现,绝大多数时间不是在建模本身,而是在反复调整数据、对比方案。一套好用的脚本能帮你节省至少50%的项目时间。
6. 从SARIMA到更多扩展:这个模型还能怎么用
SARIMA的价值不止于“跑出一个预测值”。实际业务中,它经常被用作复杂项目的基础模块。比如在库存管理系统中,SARIMA负责给出每个SKU未来14天的销量预测;在能源调度中,SARIMA预测未来一周的用电负荷;在运维监控中,SARIMA对服务器指标做异常检测——如果实际值超出95%预测区间,就判定为异常告警。这些都是我在不同项目里实际验证过的落地场景。
另外结合MATLAB生态,你可以在SARIMA之后继续做很多事:把残差序列喂给机器学习模型(用回归树或随机森林捕捉残差中的非线性成分),或者把SARIMA预测值作为特征输入到更复杂的模型中。这种“统计模型打底、机器学习补残差”的混合架构,在很多比赛和工业项目里表现都相当能打,而且比纯神经网络更容易调试。
我自己实操下来最大的体会是:时间序列预测的核心不在模型多花哨,而在你对数据的理解和建模流程的严谨程度。数据有没有清干净?周期判断对不对?差分做多还是做少了?残差有没有过检验?把这些问题逐一想清楚,SARIMA已经能帮你解决绝大多数中短期预测需求。即使后续要做深度学习,这个流程积累下来的数据认识和分析习惯,也一点都不会浪费。