ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数学建模导论:时间序列分析与ARIMA预测模型实战

数学建模导论:时间序列分析与ARIMA预测模型实战

时间序列在我眼里是数学建模里最“讲道理”的一类问题。它不像多目标优化那样需要反复权衡,也不像微分方程建模那样依赖严格的物理假设,只要你手头有一串按时间顺序排好的数据,就有机会用一套相对清晰、可验证的逻辑逼近答案。前两年接手一个模拟销量预测的小项目,我靠着经典的 ARIMA 家族把月度数据拆成趋势、季节项和随机波动,第一次觉得“预测模型”不是玄学,而是一套可以复盘的工程方法。

这篇内容就叫《数学建模导论:时间序列与预测模型》。我会把时间序列模型放在数学建模的整体框架里来讲:从拿到题目怎么理解,到数据类型怎么分辨,再到用 Python 做一个能直接用的 ARIMA 流程,最后聊一聊那些只在实操中才暴露出来的坑。适合刚接触建模的学生,也适合业务里要做小规模预测的开发者。看完之后,你至少能对“我该选什么模型”这个问题有自己的判断,而不是看哪个模型流行就套哪个。

1. 从拿到题目到选定模型:时间序列建模的整体思路

1.1 为什么时间序列会被单独拎出来考

数学建模题目里,时间序列几乎是无处不在的。销售预测、客流预测、水位预测、用电负荷预测、空气质量预报,本质上都是同一个问题:给定一段历史观测值,预测未来若干时间点的取值。这类题和普通回归最大的区别在于,样本点之间存在先后顺序,而且这种顺序本身携带信息。你今天卖了多少,很可能会影响明天卖多少;昨天没下雨,今天的湿度也不会瞬间归零。如果把这些点当作互相独立的样本去回归,等于把时间顺序这个最核心的信息丢掉了。

所以,凡是带有“随时间变化”“预测未来”“趋势分析”这类表述的题目,都可以归入时间序列建模的范畴。这类题在各类建模比赛中出现频率极高,因为数据好找、结果直观、评测维度也清楚,答得对不对基本能看误差大小。对想入门建模的人来说,它是一个性价比很高的切入口。

1.2 数学建模的通用流程在时序问题上怎么落地

大多数建模题的流程可以归纳成四步:问题分析、数据预处理、模型构建、结果评估。放到时间序列场景里,需要再细化一层:

  • 问题分析阶段,要明确预测目标的时间粒度和范围。是预测未来一个月,还是一年?是按天、按周,还是按季度?
  • 数据预处理阶段,除了常规缺失值和异常值,还要处理时间索引的连续性,以及是否补全缺失时间段。
  • 模型构建阶段,要判断序列是否有趋势、是否有周期,然后据此选择模型族。
  • 结果评估阶段,要用多个误差指标结合业务语义去看,而不是只盯一个数字。

这个流程看起来朴素,但我在实际项目中见过太多跳过第一步直接上模型的情况。时间粒度判断错,比如把周数据当成日数据拟合,最终的预测结果会连基本量级都对不上。问题分析不是空话,先把它做扎实,后面每一步都会顺利很多。

1.3 开跑之前先问三个问题

我拿到一份时序数据后,不会急着色画图,而是先问三个问题。

第一个问题:趋势明显吗?如果整体数据明显向上或向下走,那模型必须能处理非平稳性,最简单的做法是差分。

第二个问题:周期长度是多少?月度数据常有12个月的年周期,周度数据常有7天的小周期。周期不确定时,可以画自相关图看哪些滞后期上相关系数异常高。

第三个问题:异常点多不多?业务数据里偶尔会有大促、故障、政策导致的毛刺,这些点如果不处理,会严重影响模型参数估计。

这三个问题决定了后续路线的粗方向。我的经验是,花十分钟回答它们,比花一小时调参更有价值。

2. 把序列拆开看:趋势、季节、周期和噪声

2.1 时间序列的结构化拆解

经典时间序列分析会把一个观测序列拆成几个组成部分:趋势项(Trend)、季节项(Seasonal)、周期项(Cyclical)和随机波动(Irregular)。以销量预测为例,长期增长来自市场扩张,这是趋势;每年双十一或者过年期间销量冲高,这是季节;经济环境变化带来的两三年波动,这是周期;剩下那些说不清楚的原因,就是噪声。

数学建模里最常用的拆法,是加性模型和乘性模型。加性模型写作

( y(t) = T(t) + S(t) + R(t) )

乘性模型写作

( y(t) = T(t) \times S(t) \times R(t) )

加性模型适合季节波动幅度不随趋势变化的情况,乘性模型适合波动幅度随水平同步放大的情况。举个粗糙的例子:如果月销量在1000和1500之间波动,幅度基本不变,用加性;如果淡季5000、旺季8000,旺季明显比淡季多出更多绝对值,用乘性或做对数变换。

这个拆分不是纯理论,它直接告诉你建模的切入点:先提取趋势和季节,剩下的随机部分再用平稳序列模型去拟合。

2.2 平稳性:很多模型的前提门槛

AR、MA、ARMA这些经典模型,理论推导时都默认序列是平稳的。所谓平稳,可以通俗理解成序列的统计性质不随时间改变:均值稳定、方差稳定、自相关结构稳定。反过来,如果数据整体在涨,均值一直在变,模型就很难抓住一个固定的规律来外推。

判断平稳性有两个层面。第一个层面是肉眼观察:看曲线是否围绕一条水平线震荡,但肉眼判断不可靠;第二个层面是统计检验,最常用的是 ADF 单位根检验。

ADF 检验的原假设是“序列非平稳”,也就是存在单位根。p 值小于显著性水平(通常 0.05),就拒绝原假设,认为序列平稳。我这里给一张常用的解读表:

ADF 检验 p 值结论处理方式
p < 0.05平稳可直接建模
p >= 0.05非平稳差分或做变换后再检验

需要提醒的是,ADF 不是万能的。某些结构突变明显的序列,检验结果可能模棱两可,这时候还要结合业务理解和可视化一起判断。

2.3 非平稳序列的两条处理路径

处理非平稳序列,常见做法是差分。一阶差分能去掉线性趋势,二阶差分能处理曲率更强的趋势。比如原始序列是 ( y_t ),一阶差分序列就是 ( d_t = y_t - y_{t-1} )。差分次数在 ARIMA 里对应参数 d,一般取 0、1、2,取大了会过度差分,反而把信息差没了。

另一条路径是做变换,比如取对数或开根号,目的是稳定方差。如果数据明显呈指数增长,先取对数再做差分,效果通常比直接差分好。我做用水量预测时,数据跨度大且存在明显的成倍增长,直接一阶差分后方差还是不稳定,取对数后再差分,序列看起来就干净多了。

3. 从 AR 到 ARIMA:经典预测模型的逻辑链

3.1 AR 与 MA 分别捕捉什么

自回归模型 AR(p) 的思想很直观:当前值等于过去 p 个值的加权和,再加上噪声。它能捕捉“惯性”,比如今天气温高,明天往往也高。

滑动平均模型 MA(q) 听起来有点绕,但它的思路其实也不复杂:当前值由过去 q 个预测误差的加权和决定。误差在这里代表“新信息”,比如突发的库存缺货造成销量波动,这种冲击的影响会延续几期。

AR 和 MA 适合的场景不太一样。AR 适合自相关衰减平缓的序列,MA 适合自相关只在最初几期明显、之后快速截断的序列。实际应用中,我们很少只用其中一个,而是把它们组合成 ARMA(p,q)。ARMA 又能通过差分推广到 ARIMA(p,d,q),用来处理带趋势的非平稳数据。这套模型的完整名称是差分自回归移动平均模型,也是时间序列建模入门一定要吃透的核心模型。

3.2 ACF 和 PACF:定阶的地图

ARIMA 模型有三个参数:p 是自回归阶数,d 是差分阶数,q 是移动平均阶数。d 通常由差分次数决定,p 和 q 则需要借助自相关函数 ACF 和偏自相关函数 PACF 来定。

ACF 描述的是当前值与 k 期前取值的相关性,中间隔着很多其他期。PACF 则是在“剔除中间变量影响之后”的相关性。判断规则很简单但需要经验:

  • 如果 PACF 在滞后 p 期后明显截尾,ACF 拖尾,倾向 AR(p)
  • 如果 ACF 在滞后 q 期后明显截尾,PACF 拖尾,倾向 MA(q)
  • 如果两者都拖尾,考虑 ARMA(p,q)

我用一个生活类比帮助理解:你判断某个人今天的心情,ACF 是看他前几天的心情都有没有影响,PACF 是只看“昨天”的独立影响,把更早几天的间接影响都排除掉。

不过,现实数据往往不像教科书那么干净,ACF 和 PACF 的截尾并不总是清晰。这时候就别硬盯图了,可以用信息准则 AIC 或 BIC 遍历不同的 p、q 组合,选数值最小的。这样定阶更客观,也更快。

3.3 从 ARIMA 到 SARIMA 和更多选择

ARIMA 能处理趋势,但处理不了季节性。如果数据存在稳定的年周期、周周期,就要在模型里加入季节项,变成了 SARIMA(p,d,q)(P,D,Q,s)。其中 s 是季节周期长度,月度数据通常是12,周数据通常是7。

除了 SARIMA,还有几类常用的预测模型值得了解:

模型捕捉什么适用场景上手难度
指数平滑法对历史值加权平均,近期权重更高波动较小、无明显季节的短期预测低
ARIMA线性自相关结构 + 趋势无季节性的单变量序列中
SARIMAARIMA + 季节项有明显固定周期的单变量序列中偏上
Prophet趋势、季节、节假日回归业务数据,可解释性要求高中
状态空间模型多来源信息融合复杂结构性序列高
LSTM 等深度模型非线性长期依赖数据量大的复杂序列高

我的建议是,入门优先掌握 ARIMA 和指数平滑。它们解释性强,不用大量数据就能跑,适合比赛评审和业务解释。深度模型不是不能用,但数据量不足时很容易过拟合,产出也只是某个点预测,不容易讲清楚为什么。

4. 实操:一套可以直接复用的 Python 预测流程

4.1 数据准备与时间索引处理

我一般用 pandas 处理数据,用 statsmodels 做建模。第一步是把时间列转成 datetime 类型,并设置成索引。这里有一个容易被忽略的细节:数据可能不是严格连续的,缺了一两个月,需要先重采样补上空缺时间,否则后续模型会把它当成连续序列处理,产生误导性的自相关。

import pandas as pd import numpy as np from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf from statsmodels.tsa.arima.model import ARIMA # 读取数据 df = pd.read_csv('sales.csv', parse_dates=['date']) df = df.set_index('date').sort_index() # 重采样到月度,并补全缺失月份 df = df.resample('MS').sum() # 看趋势 df['value'].plot(title='Original Series')

这里我用的是模拟项目 X 的销售数据,字段就两个,date和value。resample这步要看清楚,MS表示月初频率,如果你数据是日频,就改成D,周频可以改成W-MON。补全后,再检查有没有空值,最简单的处理是用前一期填充,或者在差分之重新生成索引。

4.2 平稳性检验与差分

接下来对原始序列做 ADF 检验,判断是否需要差分。

result = adfuller(df['value'].dropna()) print(f'ADF Statistic: {result[0]:.4f}') print(f'p-value: {result[1]:.4f}')

p 值如果大于 0.05,做一阶差分再检验:

df['diff1'] = df['value'].diff() result = adfuller(df['diff1'].dropna()) print(f'First Difference p-value: {result[1]:.4f}')

如果一阶差分后还不平稳,可以尝试二阶差分,或者在差分前先取对数。

df['log_value'] = np.log(df['value']) df['log_diff'] = df['log_value'].diff()

这一步我建议把原始序列、一阶差分序列、二阶差分序列画在一起看。图不一定要上交,但对自己判断很有用。比如某段时间出现明显的尖峰,一眼就能看出来。

4.3 用 ACF/PACF 定阶并拟合 ARIMA

平稳之后,画出差分序列的 ACF 和 PACF 图。如果 PACF 在一阶后截尾,可以考虑 AR(1);ACF 在一阶后截尾,则考虑 MA(1)。图和信息准则结合使用,更稳妥。

plot_acf(df['diff1'].dropna(), lags=20) plot_pacf(df['diff1'].dropna(), lags=20)

然后遍历几组 p、q 组合,看哪个 AIC 最小:

results = [] for p in range(0, 4): for q in range(0, 4): try: model = ARIMA(df['value'], order=(p, 1, q)) fit = model.fit() results.append((p, 1, q, fit.aic)) except Exception: continue best = min(results, key=lambda x: x[3]) print(f'Best model: ARIMA({best[0]},{best[1]},{best[2]}), AIC={best[3]:.2f}')

这里 d 固定为 1,因为前面差分检验已经确定需要一阶差分。选 AIC 最小只是参考,不能盲目套用,我在第 5 部分会展开说。

选定模型后,直接拟合并输出摘要:

final_model = ARIMA(df['value'], order=(best[0], best[1], best[2])) result = final_model.fit() print(result.summary())

result.summary()里除了参数估计值,还有 AIC、BIC、残差相关的检验指标。我一般重点看参数对应的 p 值,如果某个参数不显著,就考虑降阶重新拟合,而不是保留一堆无脑参数。

4.4 残差诊断与预测

模型拟合完,最讲究的一步是残差诊断。如果残差还有明显自相关,说明模型没有把信息榨干净,需要增加阶数或者换模型。

resid = result.resid # 看残差是否像白噪声 plot_acf(resid, lags=20)

然后对未来做预测。这里要注意forecast(steps=n)得到的是点估计,如果比赛要求给出置信区间,可以再加上误差范围:

forecast = result.forecast(steps=12) print(forecast)

我经常看到有人在这一步直接把预测结果画成一条平滑曲线就交差。更负责任的做法是把历史数据的后 20% 拿出来做回测,把预测值和真实值画在一起,计算误差指标,心里有底再提交。

5. 预测效果怎么评估:指标选择与调参经验

5.1 常用误差指标怎么算

模型好不好,不能只看“准不准”,还要看“在哪个尺度上准”。不同指标侧重点不同,这里列出最常用的几个:

指标计算公式特点
MAE(\frac{1}{n}\sumy_i - \hat{y}_i
RMSE(\sqrt{\frac{1}{n}\sum (y_i - \hat{y}_i)^2})对大误差更敏感
MAPE(\frac{1}{n}\sum\frac{y_i - \hat{y}_i}{y_i}

RMSE 由于平方操作,会把个别特别离谱的预测点突出出来;MAPE 则对量纲不敏感,适合比较不同量级序列的预测效果。但 MAE 和 RMSE 的单位和原始数据一致,业务人员更好懂。

我自己的习惯是:比赛报告里 MAE、RMSE、MAPE 三个都列出来。如果 RMSE 明显大于 MAE,说明存在少量大误差点,这时候可以检查是不是某些时间点有突变;如果 MAPE 偏大,说明预测在小值区间上误差严重,可以做单独分析。只看一个指标,容易掩盖模型在特定场景下的缺陷。

5.2 区分“拟合好”和“预测好”

模型在训练集上的一步预测误差很小,不代表多步预测就好。很多人把拟合和预测混淆,结果在提交阶段被打回原形。

判断预测能力最有效的办法是时间序列交叉验证:把数据按时间顺序切分成多段,用前段训练、后段验证,不断滑动窗口重复验证。这和普通机器学习里的 k 折交叉验证不同,不能打乱样本顺序,否则未来信息会漏进训练集,造成看起来非常好的假象。

举个例子,一份 60 个月的数据,可以先用前 48 个月训练、预测第 49 个月;然后前 49 个月训练、预测第 50 个月;依次类推。最后把所有验证预测的误差汇总。这样得到的结论才接近真实场景。

5.3 参数调整里的两个陷阱

第一个陷阱是无脑选 AIC 最小的模型。AIC 是模型拟合优度和复杂度的折中,但它不是万能准则。数据量小的时候,AIC 可能选出阶数虚高的模型,预测时表现反而不稳定。我一般会在 AIC 最小的前后两三个候选里,再看残差的白噪声检验和预测稳定性,最后综合决定。

第二个陷阱是忽略业务上的预测周期。如果你需要在第 1 天就预测第 30 天的值,那么训练时也应该用 30 步滞后的误差来评估模型。用一步预测误差选出的模型,可能做长期预测时完全没有竞争力。这个点看似细节,却直接影响最终结果的可信度。

6. 常见问题排查表与实战避坑心得

6.1 一个速查表,覆盖绝大部分入门问题

下面这张表是我自己在实战中反复遇到、也帮别人排查过多次的问题清单。遇到预测不对时,先按这张表逐项自查:

现象可能原因处理思路
ADF 检验一直不平稳有异常值导致单位根误判先去除异常值,再做检验
预测结果是一条平直线差分次数过高或模型阶数不合理降低 d,重新看 ACF/PACF
预测比真实值滞后模型过于依赖近期惯性增加 p 阶数或考虑引入外生变量
AIC 很小但预测误差很大过拟合,模型记住了噪声用时间序列交叉验证重新评估
残差还有明显自相关模型未捕捉到全部信息提高 p/q 阶数,或检查周期性
季节项没被识别季节周期设定错误确认数据频率,检查 ACF 滞后处的尖峰

这张表解决的是“症状”,真正的病根往往出在数据预处理阶段。很多模型表现不佳,不是模型不够高级,而是数据没洗干净或者根本没检查连续性。宁可花一半时间在数据上,也别把时间全堆在调参上。

6.2 不要盲目堆模型:先建立可解释的基准

在投入到 SARIMA、Prophet 甚至 LSTM 之前,先做一个简单基准模型,比如“预测值等于上一期观测值”或者“等于过去若干期均值”。这个基准不一定能赢,但它能告诉你:你的复杂模型到底有没有带来真实提升。如果连最简单的基准都跑不赢,说明问题出在理解层面,而不是模型层面。

我曾在某模拟项目中一度执着于 SARIMA 的参数组合,试了十几种组合,AIC 越来越小,但预测走势始终不对。后来退回基准模型才发现,数据里存在一个业务结构调整导致的断点,之前所有模型都把断点前后的数据混在一起拟合。处理掉断点后,连指数平滑都能跑出不错的结果。

6.3 预测不是“算一次就完事”,要持续回测

时间序列题目经常附带“动态更新”的要求:有了新的观测值,预测也需要滚动更新。成熟的流程是设计好回测框架,每来一期真实值,就重新估计一次参数,然后预测下一期。这个滚动预测流程看着繁琐,但其实最抗风险,也能在比赛答辩时给出更可信的实验过程。

我个人最深的感受是,时间序列建模比很多其他建模更依赖对数据细节的耐心。它没有太多花哨的数学技巧,真正区分高下的地方在于:你是否理解数据里每一个变化的来源,是否愿意花时间做残差诊断,是否能在指标和业务之间找到平衡。这些功夫看似笨拙,但在真需要预测结果的时候,它们是最靠得住的东西。

最后分享一个小小的经验:每次建模前,把数据按时间轴完整画出来,然后在图上标注出你认为是异常的值和可能的原因。这一张图加几条注释,会在你后续做模型选择、写报告甚至答辩的时候,成为最扎实的支撑。毕竟时间序列的规律再复杂,起点永远是先老老实实看清数据长什么样。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进