ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

MATLAB实现ARMA时间序列预测:从原理到定阶与滚动验证实战

MATLAB实现ARMA时间序列预测:从原理到定阶与滚动验证实战

简介:ARMA模型是时间序列分析中经典的数据预测算法,适用于金融、经济、工程等领域的平稳与非平稳序列处理。面向需要掌握MATLAB预测建模的初学者或科研人员,这一项目内含一个完整的ARMA预测工程:压缩包共12个文件、322KB,包含10个txt数据文件(分别记录TCP/UDP/ICMP等流量指标序列)、1个xls数据表,以及1个m脚本(armaarma.m)。脚本覆盖数据加载、ARMA模型构建、参数估计与未来值预测,配合txt与xls中的原始序列,可完整走通差分平稳化、ACF/PACF定阶、模型诊断、误差分析等关键环节。通过该包不仅能理解AR/MA项及ARIMA差分原理,还能直接复用代码处理自定义场景,节省从零搭建环境的时间。目前已有227位用户学习,适合希望结合MATLAB实操快速上手数据预测算法的开发者。

1. 时间序列预测里ARMA数据预测为什么到现在还是硬需求

在时间序列预测这份活儿里,ARMA 经常被当成“老古董”:深度学习都已经卷到 Transformer 了,谁还盯着自回归移动平均?但真正落到工程上,尤其是样本量不大、对可解释性要求高、模型要能快速反复跑的场景,用 Matlab 把 ARMA 数据预测算法完整走一遍,依然是性价比最高的基线方案。它不需要海量数据,不需要 GPU,几分钟就能出一个带置信区间的预测结果;同时它是 ARIMA、SARIMA、GARCH 这一整条时序模型家族的地基——把 ARMA 原理和实现吃透,后面那些“升级版”基本就是加差分、加季节项、加误差方差建模。这篇文章从 ARMA 原理讲起,到 Matlab 里的完整复现代码、定阶方法、常见翻车点,最后给你一个严格的滚动验证流程。无论你是刚拿到 ARMA.rar 这种打包代码想快速读懂并跑通,还是想自己从零写一套数据预测算法,按这个顺序走不会走偏。

2. ARMA模型的建模逻辑:AR项拖住历史,MA项吸掉噪声

2.1 AR项:把过去的观测值当解释变量

ARMA 的全称是 Autoregressive Moving Average,自回归移动平均。名字里先出来的 AR 项解决的是“现在的值依赖过去的值”这件事。用 AR(1) 写出来就是:

y(t) = c + φ₁·y(t-1) + ε(t)

这里 c 是常数项,φ₁ 是自回归系数,ε(t) 是均值为零、方差恒定的白噪声冲击。模型的解释力全在 φ₁ 上:如果 φ₁ 接近 1,那今天的值几乎就是昨天的值加一点噪声,序列会表现出明显的随机游走特征,这种情况下预测很难做;如果 φ₁ 是 0.6,那今天的值会向均值方向回拉,序列在图上呈现“均值回归”的锯齿形态。AR(p) 就是把这种依赖扩展到 p 阶,y(t) 由 y(t-1), y(t-2), …, y(t-p) 的线性组合决定,方程写成:

y(t) = c + φ₁·y(t-1) + φ₂·y(t-2) + … + φ_p·y(t-p) + ε(t)

这是一个纯粹的线性回归形式,好处是参数解释非常直观:φ_j 表示 y 在 j 阶滞后上的偏相关强度。但代价是它只能捕捉线性依赖。如果序列里存在周期性、突变、非线性阈值效应,AR 系数的估计就会失真,预测跟着翻车。

AR 模型的稳定性条件也藏在这条式子里。以 AR(1) 为例,只有当 |φ₁| < 1 时序列才是平稳的;φ₁ ≥ 1 就变成爆炸性增长或者随机游走。对 AR(p),等价条件是特征多项式 1 - φ₁B - φ₂B² - … - φ_pB^p = 0 的所有根都落在单位圆外。这个条件听起来抽象,Matlab 里可以直接对估计后的模型用 isstable(fit_mdl) 判断。但真正在工程上,我更习惯先用 ADF 检验判断数据平稳性,再做参数估计,这两者的顺序不要反过来。

2.2 MA项:把历史误差当成有效输入

再来看 MA 项,它的形式是:

y(t) = μ + ε(t) + θ₁·ε(t-1) + … + θ_q·ε(t-q)

这里的 ε(t) 是白噪声,θ 是移动平均系数。注意方程右边没有出现任何 y(t-j),它用的是“过去各期的预测误差”来解释当前值。为什么需要误差项的自相关结构?因为有些序列并不是典型的一阶依赖,而是一连串外部冲击的累积结果。比如产品上线活动当天的流量比平时高出一截,这个冲击会通过用户习惯、推荐机制等路径影响之后几天的流量,但又不至于让流量对昨天的流量产生明显的线性依赖。这时你看到的 ACF 图是:一阶、二阶样本自相关偏高,但不是指数式缓慢衰减,而是快速落回置信带。这种模式用高阶 AR 去拟合需要很多参数,改用 MA(1) 甚至 MA(2) 就能干净地描述。

更严谨地说,MA 模型在数学上是 AR(∞) 过程的一种紧凑表达。任何满足平稳性的 ARMA 过程,理论上都可以反过来用无穷阶 MA 展开;同理,很多低阶 MA 在 AR 视角下要很高阶才能近似。所以 MA 项的工程价值是减少参数数量,降低过拟合风险。实际拟合时你会发现,ARMA(1,1) 在很多数据集上比 AR(2) 或 AR(3) 效果还稳,因为 AR(1) 捕捉主要惯性,MA(1) 吸收一阶的冲击残差,两个参数就够了。

2.3 平稳性:ARMA 建模绕不开的硬前提

ARMA 的推导建立在弱平稳假设之上:均值不随时间变化,方差恒定,自协方差只取决于时间间隔而与起点无关。直观一点说,序列必须“看起来在围绕一条水平线波动”,不能有明显上升趋势,不能有周期性季节,也不能波动幅度越来越大。

为什么这个前提这么硬?因为 AR 系数是从历史观测值的统计关系里估出来的。序列有趋势时,y(t) 和 y(t-1) 的关系被趋势主导,φ₁ 会被估成一个接近 1 的大值,模型把“趋势”误当成了“自相关”,预测结果自然南辕北辙。这就是教科书里讲的虚假回归。

判断方式用 Matlab 里的 adftest 函数即可。h = 1 且 p 值小于 0.05 时,我们认为序列平稳;否则先做一阶差分,必要时再做二阶差分。值得提醒的是,ARMA 与 ARIMA 的核心区别就是差分这一步——ARIMA 中的 I 表示 Integrated,对应 arima(p,D,q) 中间那个 D。D=0 就是 ARMA(p,q)。我见过不少人在 Matlab 里写完 diff 之后,顺手把模型写成 arima(2,1,1),等于对差分序列再差分了一次,这种错误出现的概率比我预想的要高得多。

顺便放一个判断表,第 4 章我们还会回头细读它。

观察到的自相关模式建议模型结构
ACF 在 q 阶后快速截尾,PACF 拖尾衰减MA(q)
ACF 拖尾衰减,PACF 在 p 阶后快速截尾AR(p)
ACF 与 PACF 都是拖尾ARMA(p,q)

3. Matlab跑通ARMA数据预测:从数据准备到预测出图的完整代码

3.1 数据导入、缺失值处理与 adftest 平稳性检验

实际落地中,拿到 ARMA.rar 之类的压缩包,第一步永远是看数据,而不是跑模型。解开后通常是 .mat 文件加一个 m 脚本。变量名可能叫 y、data、series,也可能带时间戳;先 load 进来,再 whos 看一眼结构。我一般会直接写成下面这段,把变量统一整理成列向量:

% 从 .mat 文件读取时间序列 load('ARMA_data.mat'); % 变量名不固定,先 whos 查看 y = series(:); % 强制转成列向量,防止行列向量不一致 % 缺失值检查 fprintf('样本量: %d\n', length(y)); fprintf('缺失值个数: %d\n', sum(isnan(y))); % 先画图观察 figure('Name', '原始序列'); plot(y); xlabel('时间序号'); ylabel('观测值'); title('ARMA 输入序列');

这段代码背后有一个容易忽视的细节:arima 的 estimate 函数虽然支持输入带 NaN 的序列并内部用卡尔曼滤波处理缺失,但在做预测之前,我还是推荐先把缺失值插补掉,把对数据的控制权握在自己手里。线性插值适合短期缺口;如果缺失比例超过 5%,擅自插值会在后面的 ACF/PACF 检查里露出假相关,这时候要先分析缺失原因再决定方案。

然后做最关键的平稳性判断:

% ADF 检验:原假设是存在单位根(不平稳) [h, pValue] = adftest(y); fprintf('ADF p 值: %.4f\n', pValue); if h == 0 % 不平稳,做一阶差分 dy = diff(y); fprintf('序列不平稳,已做一阶差分\n'); else dy = y; fprintf('序列平稳,无需差分\n'); end

adftest 里有个默认参数值得改一改:如果你的序列有线性趋势,用 adftest(y, 'Model', 'TS') 更合理。默认的 'AR' 模型不带趋势项,会低估平稳性,明明是一条确定性趋势加随机波动的序列,会被误判成带单位根的不平稳,导致你多做一次无意义的差分。

3.2 用 arima 模型对象拟合 ARMA(2,1)

差分完成后,dy 就是建模对象。用 Econometrics Toolbox 的 arima 构造模型对象并做极大似然估计,代码如:

% 拟合 ARMA(2,1),注意 arima(p,d,q) 中间的 d 必须为 0 mdl = arima(2, 0, 1); fit_mdl = estimate(mdl, dy, 'Display', 'off'); % 提取结果 fprintf('AR 系数: %s\n', mat2str(fit_mdl.AR{:})); fprintf('MA 系数: %s\n', mat2str(fit_mdl.MA{:})); fprintf('常数项: %.6f\n', fit_mdl.Constant);

解释一下参数:mdl 是未估计的模型模板,estimate 把数据传进去做极大似然估计并返回估计后的 fit_mdl。AR 和 MA 是 cell 数组,因为高阶模型会存多个系数,所以用 mat2str 转成可读字符串比较省事。这里的 arima(2,0,1) 表示 AR 阶数 2、差分阶数 0、MA 阶数 1,也就是 ARMA(2,1)。

我更推荐在这里使用手动差分和 ARMA 而不是 arima(2,1,1) 的原因,是想把“差分”这个动作显式地放在你眼前,避免黑匣子误导。如果你不想手动差分,Matlab 也支持直接 arima(2,1,1) 放在原始序列 y 上,工具箱会自动完成积分并返回原始尺度的预测值;但对新手来说,手动差分能让自己清楚每一步在做什么,出问题时也更容易定位。

3.3 多步预测与置信区间可视化

拟合好之后,用 forecast 对未来 N 步做条件期望预测。forecast 同时返回预测误差方差,可以用来画置信区间:

% 预测未来 12 步 horizon = 12; [forecast_vals, forecast_mse] = forecast(fit_mdl, horizon, 'Y0', dy); % 95% 置信区间 z = 1.96; forecast_se = sqrt(forecast_mse); upper = forecast_vals + z * forecast_se; lower = forecast_vals - z * forecast_se; % 可视化 figure('Name', 'ARMA 预测结果'); hold on; plot(dy, 'b-', 'LineWidth', 1.2); plot(length(dy)+1 : length(dy)+horizon, forecast_vals, 'r-o', 'LineWidth', 1.5); plot(length(dy)+1 : length(dy)+horizon, upper, 'r--'); plot(length(dy)+1 : length(dy)+horizon, lower, 'r--'); xline(length(dy), 'k--', '预测起点'); legend('历史值', '预测均值', '95%上界', '95%下界', 'Location', 'best'); hold off;

这里有三个关键点。第一,forecast 的 'Y0' 参数指定预测起点的历史样本,如果不传,Matlab 默认用拟合时那整段序列,这在滚动预测场景下会让你不自觉地把测试集数据喂进初始条件。第二,forecast_mse 是逐步累积的均方误差,随步数增大而增大,所以置信区间呈扇形展开,这个扇形不是模型缺陷,而是多步预测的不确定性在累积,看到后期区间宽到离谱是正常现象。第三,如果你做了 diff,这里的预测值是差分层面的,必须还原成原始量级再画图,否则预测线跟原始序列不在一个坐标系上。还原公式是最后一个原始观测值加差分预测的累积和,第 5 章第一个坑我们会专门讲。

4. ARMA定阶的三种方法:读图、信息准则和循环搜索

4.1 ACF与PACF:截尾和拖尾到底怎么读

定阶的核心工具是两个图:自相关函数 ACF 和偏自相关函数 PACF。读图经验是:MA(q) 的 ACF 在 q 阶后截尾,AR(p) 的 PACF 在 p 阶后截尾;而 AR 的 ACF、MA 的 PACF 都呈现拖尾。于是组合出现:ACF 拖尾加 PACF 截尾选 AR(p),ACF 截尾加 PACF 拖尾选 MA(q),两者都拖尾选 ARMA(p,q)。Matlab 里画这两个图非常简单:

figure('Name', 'ACF与PACF'); subplot(2,1,1); autocorr(dy, 'NumLags', 30); title('ACF'); subplot(2,1,2); parcorr(dy, 'NumLags', 30); title('PACF');

NumLags 控制显示多少期延迟,太小看不出截尾,太大尾端估计误差大,一般取序列长度的 10% 到 20% 比较常见。图中蓝色竖线是样本自相关,两条红色虚线是 95% 置信边界,超出虚线的位置才有统计意义。

读图这件事有一半是玄学:样本有波动,截尾的位置不容易一眼定死,尤其当 p 和 q 都不大时,图上的“尾巴”拖得很长。我的习惯是读图只用来缩小范围,比如第一阶明显突出、后面全部贴近零,那 p 或 q 大概率是 1;如果前几阶都超出红线但幅度递减,就把 p 或 q 的候选限定在 3 以内。真正决定用哪个阶数,交给信息准则。

4.2 AIC/BIC信息准则:让Matlab帮你比较候选模型

AIC(赤池信息准则)和 BIC(贝叶斯信息准则)都在“拟合优度”和“参数数量”之间找平衡。公式大致是:

AIC = -2·log(L) + 2·k
BIC = -2·log(L) + k·log(T)

其中 L 是极大似然值,k 是估计的参数个数,T 是样本数。BIC 对参数惩罚更重,所以同数据下 BIC 选出的模型通常比 AIC 更小。在 Matlab 中可以直接用 aicbic 函数:

% fit_mdl 是 estimate 返回的已估模型 k = fit_mdl.NumEstimatedParameters; T = length(dy); logL = fit_mdl.LogLikelihood; [aic_val, bic_val] = aicbic(logL, k, T); fprintf('AIC=%.2f, BIC=%.2f\n', aic_val, bic_val);

这里要注意,fit_mdl.LogLikelihood 是极大似然估计的对数似然值,不是每个样本的平均值,所以第三个参数必须传样本数 T,否则 aicbic 算出来的值没有可比性。这个细节在帮助文档里写得不算显眼,但很多报错和数值异常都源于此。

4.3 定阶循环脚本:一次性跑完候选参数组合

手动一个个试太慢,工程做法是写一个双层循环,把 p、q 限定在 0 到 5 之间穷举,AIC 最小的模型胜出。代码如下:

% 定阶循环:穷举 ARMA(p,q),p,q 从 0 到 5 max_p = 5; max_q = 5; best_aic = inf; best_bic = inf; best_model = []; best_pq = []; for p = 0:max_p for q = 0:max_q % 纯白噪声模型不参与比较 if p == 0 && q == 0 continue; end mdl = arima(p, 0, q); try fit_mdl = estimate(mdl, dy, 'Display', 'off'); logL = fit_mdl.LogLikelihood; k = fit_mdl.NumEstimatedParameters; [aic_val, bic_val] = aicbic(logL, k, length(dy)); if aic_val < best_aic best_aic = aic_val; best_bic = bic_val; best_model = fit_mdl; best_pq = [p, q]; end catch % 某些参数组合会估计失败,比如参数过辨识 continue; end end end fprintf('最优模型: ARMA(%d,%d), AIC=%.2f, BIC=%.2f\n', ... best_pq(1), best_pq(2), best_aic, best_bic);

这段代码有三个要注意的边界。一是 try-catch 不能省,arima 在 p 或 q 较大时会报“数值估计不收敛”之类的错误,如果不用 try-catch 包住,整个循环会中断。二是 ARMA(0,0) 是纯白噪声,没有参数可估,要跳过。三是 AIC 最小和 BIC 最小的结果可能不同,实践中 AIC 更偏预测导向,BIC 更偏解释导向。我的习惯是:如果两者选出的阶数差得不多,就取阶数更高的作为候选,再结合残差白噪声检验做最终判断。

5. ARMA预测避坑指南:五个最常见翻车点

5.1 翻车点一:差分后忘了还原预测结果

现象:对非平稳序列做 diff 后拟合 ARMA,预测图画出来是一条与原始序列完全不在同一数量级的线,或者直接是围绕 0 波动的水平线。

原因:forecast 输出的是差分序列的预测值,代表“相对于上一期的增量”,不是原始水平值。你画图时把增量直接叠在原始序列上,自然对不上。

解决:用原始序列最后一个观测值加上差分预测的累积和。代码补在 forecast 之后:

if h == 0 last_obs = y(end); forecast_levels = last_obs + cumsum(forecast_vals); else forecast_levels = forecast_vals; end

5.2 翻车点二:把不平稳序列直接丢给 arima

现象:plot(y) 明显有上升趋势,没做 adftest 就直接 arima(1,0,0) 拟合,拟合图画得挺好看,但预测完全偏向均值,甚至越往后越像一条直线。

原因:模型在非平稳数据上强行估计,趋势被当成自相关,φ₁ 被估成接近 1 的数值,预测结果没有统计意义,这就是虚假回归。

解决:估计前必须执行 adftest;是趋势型非平稳就先 diff。另外注意 arima(p,D,q) 里的 D 不会替你检查数据是否需要差分,你要先自己判断再设 D,或者先手动 diff 并把 D 设为 0。

5.3 翻车点三:ACF/PACF 读图读错了还硬解释

现象:ACF 第一阶超出置信带,后面逐渐衰减,被解读成“截尾”所以选了 MA(1);结果 AIC 选出来的是 ARMA(1,1),两者预测差异还不小。

原因:截尾和拖尾在小样本下本来就难分辨,样本量小于 50 时几乎不可能凭肉眼定准,读图本身有很强的不确定性。

解决:读图只用来缩减候选范围,决定权交给 AIC/BIC 循环和残差检验。另外可以适当提高 NumLags,让更多延迟暴露在置信带里;但别加到序列长度的三分之一以上,尾端样本自相关方差太大会干扰判断。

5.4 翻车点四:数据泄漏——用整段序列拟合再“预测”

现象:用前 90% 数据拟合,后 10% 看着预测曲线和真实曲线几乎重合,信心满满上线,一上线预测就散。

原因:如果拟合时把后 10% 的真实值也通过 'Y0' 参数喂给了 forecast,模型已经“看过”答案,评估阶段的误差完全失真。这是很多人用 forecast 时最容易踩的数据泄漏点。

解决:训练集和测试集严格分离。训练只用前 80% 到 90%,预测起点 Y0 只取训练集末端的历史观测,测试集只用于和预测结果对比。代码上把 Y0 参数显式写成 dy(1:train_end),不要省略。

train_end = round(length(dy) * 0.8); fit_mdl_tmp = estimate(mdl, dy(1:train_end), 'Display', 'off'); [yf, ~] = forecast(fit_mdl_tmp, horizon, 'Y0', dy(1:train_end));

5.5 翻车点五:残差自相关没查,模型参数白估

现象:AIC 选出了最小模型,预测平均误差也不大,但残差图呈波浪状或能看出明显周期。

原因:ARMA 模型假设残差是白噪声,如果残差不白,说明信息没提干净,模型阶数不足或结构不对,现有参数估计不能反映真实依赖关系。

解决:用 lbqtest 做 Ljung-Box 检验,检查残差在滞后 10 到 20 阶有无自相关:

res = infer(fit_mdl, dy); [h_lb, p_lb] = lbqtest(res, 'Lags', 20); if h_lb == 0 fprintf('残差通过白噪声检验,p=%.3f\n', p_lb); else fprintf('残差仍有自相关 p=%.3f,需要提高阶数或换结构\n', p_lb); end

如果检验不过,常见做法是增大 p 或 q,或者检查是否漏掉了确定性趋势项和季节项。还可以把 lag 从 20 换成其他值,但 20 是时序样本里比较通用的折中,滞后太多会稀释检验效力。

6. 用滚动预测验证ARMA模型:比一次预测更可靠的验收方式

最后这个技巧,是我自己在每个 ARMA 项目里都会跑的滚动预测验证。它跟单次切分不一样:单次切分是随机性和运气,滚动预测是把测试窗口一步步往后滑,每次都只用预测起点之前的数据拟合模型,然后预测未来 1 步或 3 步,记录误差,窗口滑过去再重复。这一步相当于是给模型上了一个“后悔药”的最小成本环节,能在上线前把大多数隐藏问题暴露出来。

代码写起来不复杂:

% 滚动预测验证:固定回看窗口,步长 1 T = length(dy); train_len = round(T * 0.7); horizon = 1; errors = []; mdl_temp = arima(2, 0, 1); % 这里的阶数用你定阶后的结果 for start = train_len : T - horizon fit_mdl = estimate(mdl_temp, dy(1:start), 'Display', 'off'); [yf, ~] = forecast(fit_mdl, horizon, 'Y0', dy(1:start)); errors(end+1) = yf(end) - dy(start+horizon); end rmse = sqrt(mean(errors.^2)); mae = mean(abs(errors)); fprintf('滚动验证 RMSE=%.4f, MAE=%.4f\n', rmse, mae);

这段代码里有几个参数可以直接改着用:train_len 控制起点,建议不小于 70% 的样本量,当样本长时,回看窗口可以固定成滑动窗口,而不是从 1 一直累加;horizon 改成 3 或 7 就能验证多步能力;mdl_temp 的阶数直接替换成你第 4 章定出来的最优组合。

滚动预测的误差分布能揭示很多一次切分看不见的问题:如果误差在某个时间点突然变大,说明模型对局部结构不稳定,这时该回头查数据里是否有离群点或结构性断点,而不是继续调 p、q。ARMA 在 Matlab 里的实现成本很低,会用之后你会发现它能稳定服务很多“黑匣子”场景——不是所有预测都要上神经网络,先跑通这个经典方案,再判断值不值得升级到更复杂的模型。希望这份实战经验能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

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