资讯动态

基于SARIMA的季节性销量预测工具实战:从定参到多步预测

发布时间:2026/10/20 5:53:43 来源:尧图企业网站定制
做预测工具这件事我踩过不少坑也积累了不少心得。前阵子团队要做一个带季节性的销量预测工具数据有明显的年度波动试了好几个模型最后定下来基于 SARIMA季节性差分自回归移动平均模型来实现多步未来值预测。这篇文章就把这个工具从数据准备、定参、训练到多步预测评估的完整过程拆开讲清楚包括里面那些文档里不会写的细节和实战中才会遇到的问题。不管你是刚接触时间序列预测的新手还是想把手头预测脚本升级成完整工具的开发这篇内容都可以直接拿来参考。1. 整体设计与方案选型1.1 为什么最终选了 SARIMA当时摆在我们面前的不止一个方案有长短期记忆网络LSTM有 Facebook 的 Prophet也有传统的 ARIMA 和它的季节性版本 SARIMA。团队里有人倾向直接上 LSTM觉得“深度学习听起来更厉害”但后来仔细想了一下实际场景样本量其实不大大概只有三年多的月度数据业务上需要能解释预测结果而不是给一个“黑盒”输出线上环境要求推理速度快不能每跑一次预测都等很久。LSTM 在小样本下很容易过拟合调参成本高而且结果不稳定同一份数据跑两次可能得到不一样的预测。Prophet 虽然开箱即用但它对季节性的拟合实际上是一种“曲线拟合”思路对于短期波动比较敏感的数据效果反而不如 SARIMA 精细。SARIMA 的优势在于它把趋势、季节性和随机波动三者显式建模参数含义清晰通过残差诊断就能判断模型是否合适。在样本量有限、数据带明显周期性的情况下它表现得非常稳。经验之谈做技术选型不要被“高级模型更好效果”这个观念带偏。先看你的数据量、业务解释需求和部署成本再决定用什么模型。小样本、需要解释、周期性强这三条凑齐了SARIMA 基本就是最优解。1.2 工具核心功能拆解我们最后做出来的这个工具本质上是一个“模型训练 参数自动选择 多步预测 结果输出”的完整流程。具体来说输入一列历史时间序列数据工具自动做缺失值检查、季节性分解判断是否需要差分。自动搜索最优的 SARIMA 参数组合不需要人工盯着一张 ACF/PACF 图去估参数。支持指定预测步长比如预测未来 6 个月、12 个月输出点预测值和置信区间。输出包括预测可视化图、误差指标、模型摘要以及一份可直接读的预测结果表。这里有个很关键的产品决策预测工具不能只是把结果数字丢给业务方一定要带上置信区间。业务方需要知道“下个季度销量大概率落在什么区间”而不是只看一个孤零零的点。后面我们做可视化时也把预测区间画成了阴影带这个细节对使用者理解预测的不确定性帮助很大。1.3 数据准备从原始数据到可用序列任何时间序列模型都有个铁律垃圾进垃圾出。SARIMA 对输入数据的要求不算苛刻但有几件事必须提前做。首先是缺失值处理。月度数据偶尔会缺一两个月最稳妥的办法是用线性插值补齐不要直接把缺失值所在的月份删掉——删除会破坏时间序列的连续性和季节性结构。插值时要注意如果缺失月份恰好处于季节性波峰或者波谷线性插值会低估真实值这时候可以先用同月份历史均值做粗填再看整体趋势微调。其次是异常值处理。如果某个月的数值突然暴涨暴跌比如大促期间销量翻了三倍这种“真实异常”不能说它是噪声就删掉但模型会把这种极端值吸收到残差项里对预测产生污染。我们的处理方式是保留大促月份的原始值但对训练样本里的极端离群点做 Winsorize 处理把超出上下四分位数倍数的值压到边界这样既能保留趋势信息又不会让单个点对模型参数产生过大影响。这里有一个很容易被忽略但极度重要的问题季节周期长度的设定。SARIMA 里的季节性周期 m 必须由你根据业务知识指定模型自己是不会“猜”出来的。月度数据通常是 m12年度周期季度数据是 m4周数据是 m7。有一次我们把月度数据误设成 m6结果模型把半年周期当成了季节周期预测曲线整个乱掉。这个参数错了后面所有结论都是白搭。2. 核心细节解析与实操要点2.1 平稳性检查与差分阶数确定SARIMA 模型的完整形式是 ARIMA(p,d,q)(P,D,Q)×m括号里可以看成两组参数非季节部分 (p,d,q) 和季节部分 (P,D,Q)m 是季节周期。d 和 D 分别代表非季节差分和季节差分的阶数目的是把非平稳序列转成平稳序列。判断平稳性最常用的工具是 ADF 检验Augmented Dickey-Fuller 检验。ADF 的原假设是“序列存在单位根即非平稳”所以 p 值小于 0.05 就说明拒绝原假设序列是平稳的。但只看 ADF 不够最好配合 KPSS 检验它的原假设是“序列平稳”两个检验一起看可以避免误判。如果 ADF 说平稳、KPSS 也说平稳那序列平稳没问题如果两个说法相反一般建议保守一点做一阶差分让序列更稳。实际操作中我一般会画三个图原始序列图、一阶差分后序列图、季节性差分后序列图。看图比看检验更直观——原始序列如果有明显上升趋势先做一阶差分消除趋势如果看到每个周期内走势有“残影”比如每年 12 月都冲高、1 月都回落但趋势项并没有完全抹平再做一次季节差分。通常情况下 d 取 0 或 1D 取 0 或 1 就够了再大就是过差分会把有效信息也差掉。注意不要干“先 ADF 检验不通过就一路差分到检验通过”这种事。过度差分会让序列变成噪声模型看起来挺好残差也白但预测结果完全没意义。2.2 参数定阶从 ACF/PACF 到自动搜索传统教科书会教你用自相关函数图ACF和偏自相关函数图PACF的拖尾/截尾特征来手动定 p 和 q。比如 ACF 拖尾、PACF 在滞后 1 期截尾可能对应 AR(1)。这种方法在小规模实验里能行但在真实项目里非常低效而且季节部分 (P,D,Q) 的阶数靠肉眼看几乎不可能看准。我们最终选择了 pmdarima 库里的 auto_arima 来做自动定参。核心思路是在一个参数候选空间里逐个尝试不同参数组合用 AIC赤池信息准则或 BIC贝叶斯信息准则评估每个模型的拟合优度和复杂度选指标最小的那个。AIC 在拟合优度上加了惩罚项防止参数太多导致过拟合BIC 对参数数量惩罚更重在大样本时倾向选更简单的模型。auto_arima 里的几个关键参数值得讲清楚seasonalTrue开启季节部分搜索m12告诉模型周期长度。stepwiseTrue用逐步搜索代替穷举速度快很多如果数据量小、参数空间不大可以设成 False 做全搜索。max_p、max_q、max_P、max_Q限制搜索范围避免它跑太远。月度数据一般设到 3 就够设太大只会浪费算力。traceTrue打印搜索过程方便观察它在比较哪些参数组合。suppress_warningsTrue收敛告警太多的话可以先压住最后看结果再做判断。auto_arima 给出的参数组合是一个很好的起点但不要无脑相信。有一次它给出的模型过拟合严重样本内拟合误差几乎为零样本外预测一塌糊涂。所以我们的工具做了个机制auto_arima 搜索完成后会把前三个 AIC 最接近的候选模型都保留下来然后拿它们在验证集上做滚动预测对比最终选样本外误差最小的那个而不是直接选 AIC 最小的。这点后面详说。2.3 核心实现完整代码骨架下面这个代码骨架是我们工具的精简版本去掉了一些和业务绑定的复杂逻辑保留了核心流程。语言用 Python核心库是 statsmodels 和 pmdarima。import warnings import numpy as np import pandas as pd from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf from statsmodels.tsa.statespace.sarimax import SARIMAX from statsmodels.tsa.seasonal import STL from pmdarima import auto_arima import matplotlib.pyplot as plt warnings.filterwarnings(ignore) def prepare_series(df, date_col, value_col): 基础数据清洗类型转换、排序、缺失处理 df[date_col] pd.to_datetime(df[date_col]) df df.sort_values(date_col).set_index(date_col) ts df[value_col].astype(float) ts ts.resample(MS).sum() # 统一对齐到月初 if ts.isnull().any(): ts ts.interpolate(methodlinear) # 极端值压缩上下界取 Q1/Q3 自适应 q1, q3 ts.quantile([0.25, 0.75]) iqr q3 - q1 lower, upper q1 - 3 * iqr, q3 3 * iqr ts ts.clip(lower, upper) return ts def stationarity_check(ts): ADF KPSS 双重检验 from statsmodels.tsa.stattools import kpss adf_stat, adf_p adfuller(ts, autolagAIC)[:2] kpss_stat, kpss_p kpss(ts, regressionct, nlagsauto)[:2] print(fADF p-value{adf_p:.4f}, KPSS p-value{kpss_p:.4f}) # 规则ADF 显著KPSS 不显著 平稳否则建议差分 return adf_p 0.05 and kpss_p 0.05 def auto_search_best_params(ts, m12): 自动搜索 SARIMA 参数 model auto_arima( ts, seasonalTrue, mm, stepwiseTrue, traceFalse, max_p3, max_q3, max_P2, max_Q2, d1, D1, # 固定差分阶数节省搜索时间 information_criterionaic, error_actionignore, suppress_warningsTrue, n_jobs-1, ) return model.order, model.seasonal_order def train_sarima(ts, order, seasonal_order): 训练 SARIMA 模型并诊断残差 model SARIMAX( ts, orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse, ) fitted model.fit(dispFalse) print(fitted.summary()) # 残差 Ljung-Box 检验p 值大 残差是白噪声 lb_pvalue fitted.test_serial_correlation(methodljungbox, lags10)[0][-1][1] print(fLjung-Box p-value: {lb_pvalue:.4f}) return fitted def forecast_model(fitted, steps, ts_mean0.0, ts_std1.0): 多步预测返回均值与 95% 置信区间支持只输入历史均值做缩放换算 fc fitted.get_forecast(stepssteps) mean fc.predicted_mean conf_int fc.conf_int(alpha0.05) # 这里根据情况把归一化/差分的量还原简化版直接返回 return mean, conf_int需要说明的是statsmodels 的 SARIMAX 在训练的时候会自动处理数据内部的差分和去均值逻辑所以get_forecast给出的是和原序列同量纲的结果不需要手动还原。但如果你在训练前做过分位数压缩或者标准化那预测值就需要做逆向变换。2.4 关于 enforce_stationarity 和 enforce_invertibility训练 SARIMAX 时有两个参数容易被忽略enforce_stationarity和enforce_invertibility。如果不设成 False模型会强制要求拟合出来的 AR 部分在单位根之内、MA 部分可逆这在大部分情况下没问题。但当我们对数据做了一次差分后又指定了较大的 p 或 q 阶数强制约束可能导致参数估计无法收敛模型直接报错。设成 False 后statsmodels 不再强制约束而是在似然估计时自己处理能避免很大一部分收敛问题。这里有个坑设成 False 不代表模型一定会得到非平稳或不可逆的参数只是取消了硬性约束。如果最终拟合结果里最大 AR 根或 MA 根的模长大于 1这个模型基本不能用需要降低阶数或重新差分。检查方法fitted.polynomial_ar和fitted.polynomial_ma的根。3. 实操过程与核心环节实现3.1 多步预测的策略选择所谓多步预测就是一次预测未来 N 个时间点比如用前 36 个月的数据预测未来 6 个月的销量。实现上有一口直接算多步和“递归预测”两种思路。在 statsmodels 中get_forecast(stepsk)直接给出未来 k 步的点预测和置信区间内部已经处理了预测误差的累积不用你手动滚动。但也有人采用“递归预测”方案先预测第 t1 期然后把预测值作为新输入又去预测 t2 期以此类推。这种方案的坏处是误差会一级一级滚雪球预测步数越多偏差越大。还有一种是“直接多步”方案即对每个要预测的步长单独训练一个模型。比如要预测未来 6 个月就训练 6 个模型每个模型直接输出第 k 个月的预测值。这种方案避免了误差累积但每个模型只针对一个固定步长无法用前一步的真实值修正而且训练代价高。我们最终选择了 statsmodels 的get_forecast。原因很简单SARIMAX 实现的是状态空间模型它在预测未来多步时已经计算了状态协方差矩阵递归式地得到了每一步的预测分布既避免了手动递归的麻烦又能自然输出置信区间。而且实测下来状态空间预测在步数不超过一个季节周期时精度稳定步数拉长到两三个周期后误差虽然变大但置信区间也变宽了业务上看起来反而是诚实的表现。# 输出未来 12 个月预测 steps 12 mean, conf_int forecast_model(fitted, steps) future_index pd.date_range(startts.index[-1] pd.DateOffset(months1), periodssteps, freqMS) result_df pd.DataFrame({ forecast: mean.values, lower_95: conf_int.iloc[:, 0].values, upper_95: conf_int.iloc[:, 1].values, }, indexfuture_index) result_df.head()3.2 模型评估不要在训练集上自嗨时间序列模型的评估难点在于你不能用普通回归模型的 train/test split 方式因为序列有先后顺序随机打散会直接破坏时间依赖结构。正确的做法是“滚动预测验证”也叫 walk-forward validation。具体操作假设有 36 个月数据初始用前 24 个月训练预测接下来 3 个月把预测窗口往后滑 3 个月用前 27 个月重新训练或者继续用原模型但最好重训预测再接下来 3 个月重复这个过程最后把所有样本外预测值和真实值放一起算误差指标。我们在工具里写了个函数来做这件事核心是控制训练窗口和预测窗口的滑动。一次滚动会跑多个模型训练虽然耗时但得到的样本外误差才是真正可信的业务效果评估。误差指标上我们同时用了三个RMSE均方根误差对大误差非常敏感适合判断“是否出现离谱预测”。MAE平均绝对误差更稳健不会因为个别离群点被拉爆。MAPE平均绝对百分比误差业务方最容易理解说“平均偏差百分之十”比“偏差 2000 件”直观得多。但 MAPE 有个致命缺陷当真实值接近 0 时百分比会爆炸。我们的数据里销量每个月至少几百件所以用 MAPE 没问题。如果你处理的数据有大量接近 0 的值建议改成 SMAPE对称平均绝对百分比误差或直接用 MAE。3.3 预测区间别让你的预测看着“太精准”单独一个点预测值业务方往往不理解为什么预测“不准”。其实任何预测都有不确定性多步预测本质上是概率预测。SARIMA 的输出天然包含预测方差95% 置信区间由conf_int直接给出含义是如果模型的假设成立那么真实值有 95% 的概率落在这个区间里。这里要特别提醒一件容易被忽略的事statsmodels 默认汇报的预测区间只考虑了随机波动没有把模型参数的估计误差也放进去。如果数据量小、模型参数本身就不稳真实的不确定性会比置信区间显示的大得多。要做更校准的区间可以用 bootstrap 或者贝叶斯版的 SARIMAX比如通过 MCMC 采样的贝叶斯结构时间序列模型但代价是计算量暴涨。我们的选择是在工具输出里明说“该区间未包含模型参数不确定性”让业务方心里有数。3.4 可视化一张图说清楚全部信息工具最后输出一张预测图图里至少要包含以下元素历史序列曲线、预测曲线、95% 置信区间阴影、历史与预测的分界线。分界线很重要不然看图的人根本不知道从哪一期开始是预测值。plt.figure(figsize(12, 6)) plt.plot(ts.index, ts.values, labelHistorical) plt.plot(result_df.index, result_df[forecast], labelForecast, linestyle--) plt.fill_between( result_df.index, result_df[lower_95], result_df[upper_95], colorgray, alpha0.3, label95% Confidence Interval ) plt.axvline(ts.index[-1], colorred, linestyle:, linewidth1) plt.legend() plt.title(SARIMA Forecast with Confidence Interval) plt.tight_layout() plt.savefig(forecast_plot.png, dpi150)有个细节图的横轴时间刻度一定要对齐不要让历史最后一个月和预测第一个月重叠不然业务方会以为那两个点是同一时间。我们统一采用“历史最后一个完整月之后开始预测”的规则。4. 常见问题与排查技巧实录4.1 典型问题速查表下面这个表格是我们团队在开发这个工具和后续使用中整理的问题清单基本覆盖了 SARIMA 模型最常见的翻车场景。问题现象可能原因解决方案模型训练报收敛错误参数阶数过高或差分过度降低 p/q/P/Q检查 d/D 是否真的需要ADF 检验总是不通过数据存在强趋势或结构突变先尝试一阶差分再看是否要做季节差分结构突变需分段建模auto_arima 搜索时间过长参数空间太大stepwise 没开开启 stepwise限制 max_p/max_q 范围残差 Ljung-Box 检验 p 值极小模型没有完全提取自相关增加 p 或 q 的阶数或者调高季节部分 P/Q预测曲线逐渐变成一条水平线均值回归效应模型把趋势当成了随机部分检查差分阶数确认 d0 时趋势项是否存在考虑在数据变换后加确定性趋势预测区间窄得离谱数据量过小方差估计不准报告 bootstrap 区间或者收集更多历史数据预测值出现负值数据底层存在非负约束模型没感知对数变换或 Box-Cox 变换后再建模某一月预测严重偏离该月历史上存在大促等特殊事件加入节假日虚拟变量回归项或用干预分析4.2 残差诊断模型合格的最后一道防线很多人在做完预测后就直接交差了我强烈建议多看一眼残差图。SARIMA 的一个核心假设是残差也就是模型没解释掉的部分是白噪声没有自相关且均值接近 0。如果残差里还存在明显的模式说明模型没有提取干净信息。诊断有两条路一条是看图——残差序列图有没有规律波动Q-Q 图有没有明显偏斜另一条是用 Ljung-Box 检验数值判断。上面代码里已经写了怎么取 Ljung-Box 的 p 值p 值大于 0.05 就说明残差没有显著自相关模型合格。有一次模型拟合效果看起来很好AIC 也低但我画残差图发现每个月第 9 期都有一个相同的波峰显然是周期性模式没被捕捉到。手动把季节部分的 P 从 0 调到 1 后残差立刻白噪化了。这里想强调不要只看拟合曲线贴得紧不紧残差才是模型是否真正解释数据的关键。4.3 几个我们踩过比较深的坑第一个坑是“一劳永逸式训练”。有些人训练一次模型之后每个月新数据来了不重新训练直接在上一次的模型上继续预测。SARIMA 模型结构没那么脆但长期不更新会越来越跟不上趋势变化。我们的工具设计成每月自动重训一次用截止到上个月的全部历史数据更新参数再输出未来预测。重训策略的危害不在模型出错而在错过最新趋势的信号。第二个坑是“过度相信自动搜索”。auto_arima 是工具不是权威。它在数据集上跑了上千种组合总有概率在某个测试样本上表现得异常好但那只是运气。过拟合的信号很隐蔽——训练集误差极低、滚动验证误差却很高。我们处理的办法是只用 auto_arima 的结果当起点而不是终点每次建模都跑一遍滚动验证。第三个坑和业务行为强相关——忽略数据口径的变化。之前接手过一份数据前两年统计口径是“发货量”后一年改成了“订单量”两条曲线的量级和波动特征完全不是一回事。模型再聪明也处理不了这种口径断裂。这种问题光看序列图很难发现要业务方主动说明。任何预测工具上线之前一定要和业务方确认历史数据的口径是否一致。第四个坑是“时间粒度选错”。月度数据可以做年季节周期周数据可以做周季节。我们早期用过一周粒度的数据配 m7结果模型算出来很慢效果也一般原因是一周 7 天里有工作日和周末的背对背波动这个周期信号太强SARIMA 的线性自回归结构描述这种复杂星期模式效果很差。后来把数据聚合到月度或双周粒度模型表现立刻好了很多。粒度越细不代表模型越强反而可能暴露模型结构能力的短板。4.4 性能优化与上线部署这个工具最终跑成定时任务每天对多个业务线各跑一轮预测。最初版本用 auto_arima 对每条序列全搜索十几条序列跑完要将近一小时业务根本等不起。后来做了三个优化第一个优化是参数缓存。每条序列在每天重训时搜索空间其实变化不大。我们把上次确认的最佳参数作为初值限制搜索范围在初值附近的邻域搜索时间骤降到原来的五分之一。第二个优化是并行。n_jobs-1可以自动使用所有 CPU 核但对于参数搜索这种轻量级任务多进程的开销有时比收益还大。实际测试下来对每条序列分开建独立进程然后在序列之间并行比在参数搜索内部并行更高效。第三个优化是模型压缩。如果只是预测未来几期不需要保留整个完整的训练过程训练完成后把模型摘要、参数、最后的隐含状态存下来预测时直接用这些参数重建状态空间模型就行。这样内存占用低了很多预测函数每次跑完即释放。5. 工具落地后的几点体会从最初一个简单的单序列预测脚本到最后做成一个支持多序列、自动定参、多步预测带置信区间的完整工具这条路走下来最大的体会是时间序列预测这件事模型只占一半剩下的功夫全在数据理解、验证设计和工程化上。数据理解的重要性怎么强调都不为过。你在跑模型之前必须对数据的周期性、趋势、噪声来源、特殊事件影响有基本判断再考虑用什么模型、怎么设参数否则你只会对着输出结果发呆不知道怎么解释。验证设计的价值也容易被低估。样本内拟合得再漂亮都不如一次严格的样本外滚动预测来得真实。把评估逻辑前置到工具流程里能让每一次模型调整都有量化反馈而不是靠感觉调参。最后是工程化上的一点建议预测工具的输出一定要人可读、事可查。每次预测跑完把模型参数、数据范围、训练时间、误差指标都记录到一个日志表里。下次如果预测结果被业务质疑你能迅速定位问题是出在数据、模型还是假设变化上。SARIMA 虽然是个老模型但在小样本、有周期、需要解释的业务场景里它依然是极其可靠的选择。希望这篇文章能帮你在做预测工具时少踩几个坑。如果过程中有什么新的体会也欢迎一起交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑