ARTICLE DETAIL

资讯详情

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

SARIMA时间序列预测实战:从参数选择到避坑指南

SARIMA时间序列预测实战:从参数选择到避坑指南 简介针对季节性时间序列预测的MATLAB实现资源围绕SARIMA季节性差分自回归滑动平均模型从数据预处理、平稳性检验、参数选择、模型拟合到预测评估的完整流程展开适合有统计基础并希望用MATLAB完成季节性数据建模与预测的学生、研究人员及数据分析从业者。资源包共13个文件包含可运行的MATLAB脚本、可视化图表、样例数据Excel/Mat格式以及docx说明文档压缩后仅112KB轻量且结构清晰。目前已有1479人浏览学习。借助代码与配套说明读者可快速掌握ACF/PACF定阶与AIC/BIC信息准则的应用获得一套可直接参考运行的SARIMA建模预测方案同时样例数据覆盖从数据读取到残差评估等关键环节便于举一反三迁移到自身业务场景。通过完整的脚本加文档组合能有效缩短季节性时间序列预测项目的搭建与调优周期降低入门门槛。1. SARIMA到底能替你解决什么问题从一条“不像话”的预测曲线说起在两年前的一次电商销量预测里我第一次用 SARIMA 跑周度数据训练集拟合得相当漂亮结果一进测试集预测曲线直接变成一条近乎水平的线把运营同事气得当场要求换方案。后来排查下来不是模型不对而是我把季节性差分阶数 D 设成了 2把原始序列里本该保留的季节信息差了个干净。那是第一次真正意识到SARIMA 这一套季节性差分自回归滑动平均模型难点从来不在调参而在于你得先弄清楚数据里到底有什么成分再决定让模型去学什么。这篇文章就是围绕时间序列预测分析里最常见的需求——有趋势、有周期、想预测未来若干步——讲清楚 SARIMA 怎么选、怎么跑、怎么避坑。适合手里拿着真实的周期性数据、想自己做预测建模的数据分析师或后端工程师新手能照着代码跑通熟手可以直接跳到第 5 章看边界和踩坑记录。2. SARIMA 模型拆解四个括号里到底装了什么为什么它比 ARIMA 多一轮2.1 从 ARIMA 到 SARIMA多出来的季节部分不是装饰ARIMA(p,d,q) 处理的是非平稳序列中趋势部分的建模通过 d 阶差分把趋势去掉。但很多实际业务数据——比如零售日销量、电力日负荷、酒店入住率——除了趋势还有固定周期的波动。以周为周期的日数据今天的情况和 7 天前强相关以年为周期的月度数据这个月和去年同月强相关。ARIMA 只能用自回归项去滞后一步两步三步地逼近这种相关性效率极低而且经常会吃掉太多自由度。SARIMA 的写法是 SARIMA(p,d,q)(P,D,Q)_s前面那个括号是普通的非季节部分后面那个括号是季节部分下标 s 是季节周期长度。日数据看周规律s7月度数据看年规律s12季度数据看年规律s4。D 表示季节差分的阶数一般取 0 或 1 就够取 2 的结果往往是你不想看到的。我曾经见过有人把 SARIMA 理解成“ARIMA 加几个滞后项”这是有偏差的。季节部分描述的是相隔 s 期的观测之间稳定的相关关系。比如用于周数据的 SARIMA(1,0,1)(1,0,0)_7非季节 AR(1) 在解释“今天和昨天怎么相关”季节 SAR(1) 在解释“今天和上周同一天怎么相关”两者可以同时存在、互不替代。这是 SARIMA 的核心边界它能同时抓住短期的连续依赖和固定周期的重复模式而这两类信息在业务数据里往往是同时存在的。2.2 各阶数在做什么一个参数一个坑下表是六个括号参数各自承担的建模职责和最常见的取值范围我一般把这个表打印出来贴在显示器边上看。参数作用常见范围判断依据p非季节自回归阶数0~3PACF 拖尾/截尾特征d非季节差分阶数0~1ADF 检验结果q非季节移动平均阶数0~3ACF 截尾/拖尾特征P季节自回归阶数0~1季节滞后处 PACF 特征D季节差分阶数0~1季节差分后平稳性检验Q季节移动平均阶数0~1季节滞后处 ACF 特征s季节周期长度4/7/12业务周期 周期图谱确认p 和 q 超过 3 的情况在业务数据里很少见。如果网格搜索给出的 p 或 q 跑到 5 以上通常不是模型找到了什么深层规律而是数据没有预处理干净——最典型的是有异常值没处理、节假日效应没有单独剥离。这两种情况会让 ACF 和 PACF 出现虚假的长尾相关模型被迫用更高的阶数去“消化”异常代价是过拟合。P、D、Q 的取值范围更是保守0 或 1 基本够用。D2 需要二阶季节差分绝大多数业务数据的季节波动幅度是相对稳定的不需要做二阶强行做了季节信息会被磨平预测结果很像高斯白噪声。s 的选择如果出现错误后面全盘皆输后面会专门讲这个坑。2.3 为什么是这个模型SARIMA 与常见替代方案的边界做时间序列预测分析时新方案通常会被拿来和 Prophet、ETS 指数平滑、LSTM 对比。在实际选择上SARIMA 的适用场景有三个硬条件周期固定、周期数足够、数据量不算太小。日数据恰好有周规律的SARIMA 比 Prophet 对周期性刻画得更细腻因为 Prophet 的季节性默认是傅里叶级数逼近需要手动调周期参数而 SARIMA 直接用季节差分和季节自回归建模数学形式上更贴近“上一周同一天如何影响今天”这一真实依赖关系。ETS 和 SARIMA 在很多数据集上结果接近但 ETS 是成分分解思路它对季节波动幅度随时间变化刻画得更灵活SARIMA 是相关结构思路它的季节项带有明确的滞后依赖对“上一周异常会连带影响下一周”这种传导效应更敏感。LSTM 则需要更多数据和更细致的特征工程在几百条到几千条量级、固定周期的数据上SARIMA 通常赢得更稳而且训练时间以秒计。一个比较常见的决策参考数据周期固定周期个数至少在 2 个以上选 SARIMA周期个数只有 1 个比如只有 1 年的月度数据SARIMA 的季节项会因为样本不足而估计不准那还不如直接退化为 ARIMA 或 ETS。很多人忽略这个前提拿到 12 条月度数据就硬套 s12 的 SARIMA结果季节项估计方差极大这是应用层最容易翻车的地方。3. 从原始数据到平稳序列差分、ADF 检验和季节性判断的实操顺序3.1 第一步永远是准备干净的时间索引很多人在数据预处理阶段就用错了方式。读进 CSV 后直接用默认整数索引训练 SARIMA模型会报错或者跑出来的结果没法解释。时间序列模型需要显式的时间索引而且这个索引必须带频率信息。下面这段是把数据读进来并修正索引的标准做法。import pandas as pd df pd.read_csv(sales.csv, parse_dates[date]) df df.set_index(date) # 显式指定频率为按天缺失日期会被填为 NaN df df.asfreq(D) # 缺失值处理销量这类业务数据相邻均值填充是相对保守的做法 df[sales] df[sales].fillna(methodffill).fillna(methodbfill) print(df.head()) print(df.index.freq)这段代码里最关键的是asfreq(D)。这一步强制把索引频率声明为按天后面 SARIMA 内部计算季节滞后时才不会因为日期索引不连续而报错也避免模型把间隔两天甚至三天的数据当成连续序列。第一个fillna用前向填充第二个fillna处理序列开头仍缺失的部分用后向填充兜底。注意如果连续缺失段过长比如一个月没有数据填充出来的值是虚假的这种场景应该直接剔除或做插值而不是填充。3.2 用 ADF 判断趋势平稳性d 取 0 还是 1确定 d 的最常见做法是做 ADF 检验原假设是“序列存在单位根即非平稳”。p 值小于 0.05 则拒绝原假设认为序列平稳。很多人只跑一次 ADF看到不平稳就无脑diff(1)这是可以的但更稳的做法是先观察序列本身。一个长期递增的销量序列直接 ADF 很可能不平稳一阶差分后大概率平稳d1一个来回震荡没有趋势的序列可能本来就是平稳的d0。from statsmodels.tsa.stattools import adfuller result adfuller(df[sales], autolagAIC) print(fADF p-value: {result[1]}) if result[1] 0.05: print(原序列平稳d0) else: df[sales_diff] df[sales].diff().dropna() result_diff adfuller(df[sales_diff].dropna(), autolagAIC) print(f一阶差分后 p-value: {result_diff[1]})这里autolagAIC的含义是让函数自动选择滞后阶数来消除残差自相关默认也是这个值显式写出来便于理解。差分后的序列再做一次 ADF如果 p 值小于 0.05说明一阶差分足够d1。需要提醒的是ADF 对异常值敏感序列中如果有数倍于正常水平的尖峰检验结果可能失真所以预处理阶段最好先做一次箱线图观察。关于 d 和 D 的分工这是很多人混淆的重灾区。d 是去掉趋势D 是去掉季节非平稳。判断 D 的方式和 d 类似对原序列做 s 步差分也就是df[sales].diff(s)再做 ADF。如果季节差分后序列平稳D 取 1如果季节性本身就很弱D 取 0不要为了用 SARIMA 而用。另外有一个反直觉的点需要记住ADF 检验通过只代表序列“弱平稳”不代表序列是白噪声。一个平稳序列仍可能存在显著的自相关性这正是 AR 和 MA 部分要建模的内容。所以检验的目的不是追求“序列彻底随机”而是把非平稳成分处理干净把平稳的周期性相关结构留给模型去学。3.3 季节分解用肉眼和周期图双重确认 ss 定错是 SARIMA 失效的常见原因。比如日数据有明显的“周末高、工作日低”模式但你只做了月度分析s 取了 30季节项就无法有效建模。我一般用两种方式交叉确认一种是用seasonal_decompose看分解出的季节成分是否规律另一种是直接画自相关图看哪些滞后位置出现明显的峰值。import matplotlib.pyplot as plt from statsmodels.tsa.seasonal import seasonal_decompose # s 先按业务经验假设为 7日数据大概率有周规律 result seasonal_decompose(df[sales].dropna(), modeladditive, period7) result.plot() plt.show()如果分解出的季节分量曲线在每年或每周的同一位置反复出现明显的波动说明该周期成立。同时可以再观察原始序列的 ACF 图如果滞后 7、14、21 处出现递减的峰值周季节性基本可以确认。需要注意modeladditive和multiplicative的选择。加法分解假设季节波动幅度不随整体水平变化乘法分解假设波动幅度与趋势水平成比例。销量类数据如果整体规模逐年增长、且旺季淡季的差异也在放大用乘法更合理。但 SARIMA 本身建模时并不直接使用这个分解结果分解只是为了帮你判断周期长度和成分构成。真正决定 s 的还是业务周期和 ACF 图上的滞后峰。4. 定阶与训练用网格搜索跑通一个最小可复现的 SARIMA 全流程4.1 为什么不用肉眼看图定阶ACF/PACF 在季节数据上的局限教科书通常会教你看 ACF 和 PACF 拖尾截尾来定 p 和 q。这个方法在纯 ARIMA 场景下好用但一旦引入季节性图中会同时出现非季节滞后和季节滞后的多重峰值肉眼判断很容易误读。比如周数据在滞后 7、14、21 处的 ACF 峰值显著但非季节部分 q 应该取几季节部分 Q 应该取几图中表达得并不直观。所以我的做法是用 ACF/PACF 缩小搜索范围用网格搜索确定最终组合。ACF 图中滞后 1~3 有明显峰值p 和 q 的候选控制在 0~2季节滞后 7 处明显P 和 Q 候选控制在 0~1。这样组合数不算多计算时间可接受又比纯网格搜索有目的性。4.2 最小网格搜索代码从候选组合中挑 AIC 最优下面是完整可跑的网格搜索候选参数设得比较小常规数据量下几分钟内能跑完。import itertools import warnings import pandas as pd from statsmodels.tsa.statespace.sarimax import SARIMAX warnings.filterwarnings(ignore) train df[sales].iloc[:-14] # 留最后 14 天做验证 test df[sales].iloc[-14:] p d q range(0, 3) P D Q range(0, 2) s 7 # 根据前面分解结果确定的季节周期 best_aic float(inf) best_param None best_seasonal None for param in itertools.product(p, d, q): for seasonal_param in itertools.product(P, D, Q): model SARIMAX( train, orderparam, seasonal_orderseasonal_param (s,), enforce_stationarityFalse, enforce_invertibilityFalse, ) res model.fit(dispFalse) if res.aic best_aic: best_aic res.aic best_param param best_seasonal seasonal_param print(f最优组合: SARIMA{best_param}{best_seasonal}x{s}, AIC{best_aic})这段代码有两点需要说明。第一enforce_stationarityFalse和enforce_invertibilityFalse是候选搜索阶段故意关闭的约束这样网格中的部分组合即使不满足平稳性也能完成拟合保证搜索过程能跑完得到最优组合后我会用这个参数重新拟合一次并开启约束确认模型可用。第二itertools.product会生成 3×3×3×2×2×2216 个组合每个组合都做一次完整拟合数据量较大时耗时会明显增加所以先粗采用小范围搜索等拿到最优区间后再做一次细粒度搜索。AIC 的含义是“模型拟合优度 参数数量惩罚”的综合指标AIC 越小越好但不能只看 AIC。如果最优组合和其他组合的 AIC 差距在 2 以内说明这些模型统计上效果相当此时优先选择参数更少的避免过拟合。候选阶段不打印每个组合的 AIC只保留最优避免控制台刷屏。4.3 选定参数后的正式训练与预测输出网格搜索出来的是参考组合不等于最终参数。拿到最优组合后我用它在完整训练集上重新拟合然后预测未来 14 天并输出置信区间。import numpy as np final_order (1, 1, 1) final_seasonal (1, 1, 0, 7) model SARIMAX( train, orderfinal_order, seasonal_orderfinal_seasonal, enforce_stationarityTrue, enforce_invertibilityTrue, ) res model.fit(dispFalse) forecast res.get_forecast(steps14) pred_mean forecast.predicted_mean pred_ci forecast.conf_int() print(预测均值:) print(pred_mean) print(\n置信区间:) print(pred_ci) # 简单验证用 MAPE 对比 mape np.mean(np.abs((test.values - pred_mean.values) / test.values)) * 100 print(f\nMAPE: {mape:.2f}%)这里我把网格搜到的最优组合替换成了 (1,1,1)(1,1,0,7)这是很常见的一组合适参数但你的数据不一定会选到这组。注意final_order的第一位参数实际应该替换成上一步输出的best_param这里是展示完整流程。get_forecast返回的对象包含predicted_mean和conf_int置信区间默认 95%。MAPE 计算时如果真实值里有 0会除以 0 报错需要先剔除。关于预测长度有一个值得注意的边界SARIMA 的多步预测在短期内有参考价值但步数越多误差越大的现象越明显。周数据预测 7 天和 14 天通常可以接受预测 30 天以上就需要谨慎看待了误差会被季节项的估计方差放大。5. SARIMA 常见问题排查五个最容易翻车的现场与解法5.1 预测结果是一条平滑直线几乎没有波动出现这个现象时第一步怀疑的不是模型而是差分过度。当 d 或 D 设置过大时比如 d2 或 D2序列被差分成近似白噪声模型学不到任何可用的自相关结构预测均值收敛到序列均值附近于是输出就变成一条直线。另一种可能是季节周期 s 设置错误导致季节项没有生效。解决方式是回到差分检验环节。把当前 d 和 D 分别逐步降低后重跑 ADF 和季节分解确认哪一个差分实际上是不必要的。多数情况下d 取 1、D 取 1 已经能覆盖业务数据的趋势和季节性不需要更多。可以做一层兜底校验把训练好的模型对训练集做拟合看拟合值是否有波动如果拟合值本身就像条直线基本可以断定是差分过头而不是预测阶段的问题。5.2 网格搜索选了 AIC 最优测试集上 MAPE 反而最高这是时间序列预测里最容易翻车的一类问题根因是网格搜索在全部候选组合中挑 AIC 最小的本质上是在训练集上做极大化拟合优度模型可能记住了训练集中某些随机波动。这个现象在数据量小、组合多时尤为突出。我的解决方式是引入时间序列交叉验证。把训练集切到验证集上做一次滚动预测用验证集 MAPE 作为选择依据不再只看 AIC。实际操作中网格搜索会选出 AIC 排名前 5 的组合然后把这 5 个组合分别在验证集上预测选择验证集误差最小的那个。这相当于加了一道防线避免模型选到过拟合参数。5.3 月度数据设置 s12 直接报错或收敛失败SARIMAX拟合月度数据时如果 s12模型会生成大量季节滞后项参数估计矩阵变大样本数又少很容易出现收敛失败。这种情况通常发生在只有 24 到 36 个月的月度数据时季节项需要至少 2 个完整周期才能估计样本确实太少了。解决方式首先是确认数据量。如果月度数据少于 3 年我一般建议直接降级为 ARIMA 或 ETS而不是硬上 SARIMA如果数据量足够但仍然报错尝试把拟合步数上限调大在fit时指定maxiter200同时检查是否需要对数据做缩放——销量数值特别大时比如上万到百万数值稳定性会受影响把序列除以一个常数后再建模会更稳。5.4 预测曲线相比真实值有一天的滞后偏移预测结果和真实曲线形态一致但整体向右平移了一天这种“滞后预测”现象在带趋势的数据里很常见。根因是模型学习到的最优策略是“把昨天的值搬过来”而不是“预测趋势的增量”尤其是 d0 且 MA 部分较弱时预测接近随机游走。解决方式是在模型中加入漂移项或者在建模前对序列做一阶差分让模型学习增量而不是绝对值。SARIMAX的trend参数可以提供常数项或线性趋势项。一旦发现滞后偏移优先把d调整为 1或者显式设置trendc增加常数项让模型有机会捕捉恒定漂移。更根本的思路是检查业务数据是否存在“惯性极大”的特征比如日销量的 80% 来自复购这种情况下模型同样可能取巧。5.5 残差诊断显示通过了 Ljung-Box 检验但预测区间还是偏窄Ljung-Box 检验的原假设是“残差无自相关”p 值大于 0.05 说明通过。这里的坑在于检验的滞后阶数如果设置得太小比如只查前 1 到 5 阶季节滞后处的残差自相关检测不到。SARIMAX的残差中季节周期位置的残差相关性往往是最后消失的。解决方式是使用多组滞后阶数分别检验比如lags[7, 14, 21]或lags[10, 20, 30]同时画出残差的 ACF 图眼看一下滞后 s 和 2s 处是否有超出置信带的值。每逢季节效应的数据这个位置出问题相当普遍。预测区间偏窄的本质是模型低估了残差方差如果诊断结果仍然异常优先怀疑季节项部分没有充分建模而不是试图调大置信区间。6. 残差诊断与滚动预测把模型从“能跑”推进到“能用”看一眼plot_diagnostics(1)是每个 SARIMA 模型正式上线前我必做的检查四张图里重点看右下角的 Q-Q 图——散点如果大致落在直线上残差接近正态模型的预测区间才可信。而滚动预测是在真实业务里最实用的验证方式固定窗口每预测一步就加入真实值重估衡量模型在持续更新条件下的表现。from statsmodels.tsa.statespace.sarimax import SARIMAX import numpy as np history list(train) predictions [] for t in range(len(test)): model SARIMAX(history, order(1,1,1), seasonal_order(1,1,0,7)) res model.fit(dispFalse) yhat res.forecast(1)[0] predictions.append(yhat) history.append(test.iloc[t]) mape_rolling np.mean(np.abs((test.values - np.array(predictions)) / test.values)) * 100 print(f滚动预测 MAPE: {mape_rolling:.2f}%)这段代码每次循环都用全部历史重新拟合并预测下一天真实值一到位就追加到历史中。代价是每步都做一次完整拟合数据量大时耗时会增长明显。熟悉statsmodels的人可以用res.append(observed, refitFalse)增量更新速度快得多代价是参数不更新。我的习惯是一次滚动预测用完整重拟合重点评估模型结构是否合理如果确认结构没问题再切换为增量更新用于线上服务。对于熟手我要多说一句滚动预测的样本量建议验证集至少包含 2 个完整季节周期否则滚动预测的平均误差无法反映模型在完整季节上的表现。日数据配 s7验证集至少留 14 天月度数据配 s12验证集至少留 24 个月。我自己的经历是踩过“只看静态测试集”的坑一组参数静态预测 MAPE 5%rolling 一跑变 12%差了整整一倍多。从那以后就养成了固定滚动验证的习惯也建议你把滚动 MAPE 作为上线前的硬指标。把这一步做扎实SARIMA 就从“能跑通”变成了“能相信”希望帮到你。本文还有配套的精品资源点击获取
返回列表