
简介面向Java开发者与时间序列分析学习者的ARMA、ARIMA模型实现例程重点解决在业务项目中复用、调试自回归与移动平均建模代码的问题。包内以两个独立工程组织源码提供Eclipse工程配置、Java实现类、编译产物及Jama等依赖库并附带示例数据和说明文档便于从入口程序运行并观察模型拟合、差分处理与预测流程配套教程与讲义可辅助理解自回归阶数、移动平均阶数及平稳化等概念。全包共43个文件以12个java源码、12个class、5个jar依赖为主辅以md说明、txt数据及少量文档压缩后约8.83MB目录紧凑适合边读代码边验证。已有1679人学习下载可作为课程实验、算法复现或项目原型的参考素材也方便替换自有序列做调试与结果对照。1. 从一段销量序列说起为什么要在 Java 里手搓 ARMA 和 ARIMA手上的 Java 服务要做周度销量预测离线侧的分析工具一行 ARIMA 就出结果可线上不可能为了一个预测接口再挂一套分析运行时。这才是大部分人搜「ARMA ARIMA 模型 Java 实现」的真实处境不是不会建模而是要把建模过程搬进 JVM。这份例程走的是一条不依赖建模框架的路用 JDK 原生数组把差分、平稳性检验、ACF/PACF 定阶、参数估计、预测还原和滚动回测串成一条能跑通的链路。每一行参数都能改改完也知道后果适合两类人——已经在用现成统计库但说不清系数怎么来的以及想把定阶、约束、批量建模嵌进现有工程里的。2. 差分与平稳性检验d 定错后面所有参数都是白算ARIMA 里最容易被轻视的一步就是 d。很多人拿到序列先画个图看趋势往下走就随手做一阶差分接着定阶、估参、出预测最后发现预测曲线在尾部一路往下冲。这类问题九成不在 ARMA 部分而是差分阶数选错导致模型把「趋势」当成了「记忆」去外推。这一章干的事就一件把 d 从拍脑袋变成一个能算、能验证的决策。2.1 差分的本质是把水平信息挪到增量上原始序列里的趋势项是确定性的ARMA 模型假设的是弱平稳——均值和自协方差都不随时间平移。做一次差分等价于给序列套一个 $(1-B)$ 算子把线性趋势变成常数、把二次趋势变成线性。问题是差分不是越多越好每做一次差分就丢一个观测同时放大高频噪声差分过头序列会变得不可逆预测阶段就会出现「还原后越走越偏」的玄学现象。我一般会先把差分写成独立工具类不和建模代码耦合因为后面还原时要靠它逐层累加。下面这段是整个例程里最短、但用得最多的代码。public final class Differencer { /** d 阶差分返回长度 n-d 的序列 */ public static double[] diff(double[] x, int d) { double[] cur x.clone(); for (int k 0; k d; k) { double[] next new double[cur.length - 1]; for (int i 1; i cur.length; i) { next[i - 1] cur[i] - cur[i - 1]; // 一阶差分本期减上期 } cur next; } return cur; } /** tail[k] 第 k 层差分序列k0 为原序列的最后一个观测值还原时当锚点用 */ public static double[] tail(double[] x, int d) { double[] t new double[d]; double[] cur x.clone(); t[0] cur[cur.length - 1]; for (int k 1; k d; k) { double[] next new double[cur.length - 1]; for (int i 1; i cur.length; i) next[i - 1] cur[i] - cur[i - 1]; cur next; t[k] cur[cur.length - 1]; // 每层只留末值够还原用 } return t; } }diff的循环写法比递归省事cur每轮缩短 1最后自然只剩最深层序列。tail单独抽出来是因为还原时你一定要知道「预测起点之前每一层序列停在哪个数上」这个数一旦取错整个预测曲线会整体平移——这是后面避坑章节里最典型的一条。参数上没什么可调的真正要留意的是d的取值上限样本量 200 以内的序列d超过 2 基本没有统计意义三层差分后剩下的点太少ADF 回归的自由度都不够用。2.2 ADF 回归用二十行最小二乘就能跑起来判断平稳性最常用的是 ADF 检验。它的形式是一个带滞后差分项的回归$$\Delta x_t \alpha \beta t \gamma x_{t-1} \sum_{i1}^{p}\delta_i \Delta x_{t-i} \varepsilon_t$$关心的只有 $\gamma$ 的 t 统计量。原假设是 $\gamma 0$存在单位根序列不平稳统计量越负越倾向拒绝。这个回归本身就是普通最小二乘不需要拉任何统计包自己写个求逆就能算。public final class AdfTest { public static final class Result { public final double stat; // gamma 的 t 统计量 public final double sigma2; // 回归残差方差 public final int n; // 有效样本量 Result(double stat, double sigma2, int n) { this.stat stat; this.sigma2 sigma2; this.n n; } /** 5% 显著性、含常数项不含趋势的临界值n 在 100 附近时约 -2.89 */ public boolean stationaryAt5() { return stat -2.89; } } /** * param x 原始序列 * param p 增广阶数粗略取 floor(4*(n/100)^0.25)一般 1~4 足够 * param trend 是否加入确定性时间趋势项 */ public static Result test(double[] x, int p, boolean trend) { int n x.length; int cols 2 (trend ? 1 : 0) p; int rows n - 1 - p; if (rows cols 2) { throw new IllegalArgumentException(样本太短ADF 回归自由度不足); } double[][] X new double[rows][cols]; double[] y new double[rows]; for (int i 0; i rows; i) { int t i p 1; y[i] x[t] - x[t - 1]; // 被解释变量一阶差分 int c 0; X[i][c] 1.0; // 常数项 if (trend) X[i][c] t; // 时间趋势 X[i][c] x[t - 1]; // 水平项 x_{t-1} for (int j 0; j p; j) { X[i][c] x[t - 1 - j] - x[t - 2 - j]; // 增广差分项 } } Ols ols Ols.fit(X, y); int levelIdx trend ? 2 : 1; // 水平项所在列 double stat ols.beta[levelIdx] / ols.se[levelIdx]; return new Result(stat, ols.sigma2, rows); } }关键点有三个。第一被解释变量是 $\Delta x_t$ 而不是 $x_t$这是 ADF 和普通回归最容易写混的地方。第二水平项一定要用 $x_{t-1}$用当期值整个检验就失效了。第三临界值不能用标准 t 分布的表ADF 统计量服从的是非标准分布必须查专门的临界值表实现里直接硬编码了 5% 水平下的近似值样本量含常数项无趋势含常数项 趋势n ≈ 50-2.92-3.50n ≈ 100-2.89-3.45n ≈ 250-2.88-3.43n ≈ 500-2.87-3.42样本量越大临界值收敛越快表格里这几行覆盖了绝大多数业务序列。我的习惯是先跑含趋势的版本如果趋势项系数不显著再退回不含趋势的版本两个版本的stat差得很远时说明序列里确实有确定性趋势此时应该先剥离趋势再建模而不是加差分阶数。2.3 过差分的三种识别信号ADF 告诉你「差分后平不平稳」但没告诉你「是不是差分多了」。这里有几条血泪经验换来的判据第一种信号是方差反弹。正常的差分会让序列波动幅度下降或持平如果在d2之后残差方差明显大于d1说明二阶差分把信号打散成了噪声。做法很简单把每一阶差分后的样本方差打出来横向比一下方差最小的那一阶通常就是合适的d。第二种信号是 ACF 出现大的负值。做过头的序列在一阶滞后上会冒出一个接近 -0.5 的自相关系数这是差分算子过度的典型特征。第三种信号来自还原后的预测形状。拿d1和d2各跑一次预测、各自还原看 12 期之后的走向如果d2的曲线呈明显抛物线外推而业务上没有任何依据支持这种加速就退回d1。季节性序列还要额外注意周期为 12 的月度数据里普通一阶差分往往不够需要叠加季节差分 $(1-B^{12})$但它会一次性丢掉 12 个点样本少于 60 时基本别用。此时更稳的做法是先把季节成分用移动平均剥离出去剩下的部分再走常规 ARIMA 流程。3. ACF 与 PACF 的代码实现让 p、q 从看图变成算数定阶这件事图上看是一回事代码里写是另一回事。看图容易产生错觉尤其是样本量小的时候ACF 上一根稍微高一点的柱子可能只是随机波动。这一章把 ACF、PACF、AIC/BIC 三条线都实现出来目的是让你有三票可以互相校验而不是只靠一张图下结论。3.1 自协方差的分母之争除 n 还是除 n-k自协方差估计有两个流派除以 $n$ 的有偏估计和除以 $n-k$ 的无偏估计。单看数值无偏版本更「正确」但它在滞后阶数接近样本量时会剧烈震荡甚至让整个自协方差矩阵不正定后面的 Durbin-Levinson 递推直接崩掉。主流实现基本都选除 $n$牺牲一点无偏性换数值稳定例程里也这么写。public final class Correlogram { /** 返回长度 maxLag1 的自相关数组r[0] 恒为 1 */ public static double[] acf(double[] x, int maxLag) { int n x.length; double mean 0; for (double v : x) mean v; mean / n; double[] acov new double[maxLag 1]; for (int k 0; k maxLag; k) { double s 0; for (int t k; t n; t) { s (x[t] - mean) * (x[t - k] - mean); } acov[k] s / n; // 统一除 n保证自协方差矩阵正定 } double[] r new double[maxLag 1]; for (int k 0; k maxLag; k) r[k] acov[k] / acov[0]; return r; } }maxLag的取法有讲究。经验值是 $\min(10 \log_{10} n,\ n/4)$100 个点取 20 左右250 个点取 24 左右。取得太短看不出一阶截尾取得太长尾部全是噪声。算出来的r[k]可以直接和 $1.96/\sqrt{n}$ 比较超出这条带子的滞后阶数才值得关注。还有一种情况要提前处理序列均值明显不为零时去均值这一步必须做否则 ACF 会整体偏正所有滞后都显得「显著」。上面代码里先减mean再做乘积就是在处理这个问题。3.2 Durbin-Levinson 递推把 PACF 算出来PACF 的直观含义是「扣掉中间滞后项影响后$x_t$ 与 $x_{t-k}$ 的净相关」。直接算法是每阶都跑一次 k 元回归样本大时慢得离谱。Durbin-Levinson 递推把它变成了 $O(K^2)$ 的迭代而且顺带把 AR 系数也算出来了。/** 输入 p a hrefhttps://download.csdn.net/download/bocai8058/10399921 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p