news 2026/10/8 2:21:20

Java实现ARMA与ARIMA时间序列预测:从数学原理到Spring Boot落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java实现ARMA与ARIMA时间序列预测:从数学原理到Spring Boot落地

简介:这份资源是面向时间序列分析初学者与Java开发者的ARMA、ARIMA模型实现例程,帮助读者在项目中快速复用自回归、移动平均及差分整合等核心算法,解决趋势与周期性数据的建模预测问题。压缩包共43个文件,约8.83MB,以java源码与class编译文件为主体,另含jar依赖库、classpath与project工程配置、md说明文档,以及ppt、docx、pdf等辅助学习材料,覆盖从代码到理论的完整链路。资源内含ARMA-Java与ARIMA两套工程,分别提供src源码、data测试数据与lib依赖,便于对照运行main函数观察拟合与预测效果,并借助JFreeChart、Apache Commons Math等库完成统计计算与可视化。目前已有1679人学习下载,适合希望理解模型内部原理、调试参数并迁移到自有数据集的读者参考实践。

1. 时间序列预测落地:ARMA 与 ARIMA 的 Java 实现到底难在哪

很多做 Java 后端的同学第一次接到「预测下个月订单量」「估算未来七天接口调用峰值」这类需求时,第一反应是找现成的 Python 脚本跑一遍 statsmodels,然后把结果硬编码回服务里。短期能交差,长期就是运维噩梦:模型没法在线更新,参数调一次要重启服务,线上出问题连日志都看不懂。ARMA 模型与 ARIMA 模型本身并不复杂,真正卡住 Java 工程师的是三件事——没有趁手的库、差分和定阶这些概念在代码里怎么落地、以及预测结果怎么和现有 Spring Boot 服务对接。这篇笔记就按「先讲清模型在算什么,再用 Java 从零把最小可运行例程跑通,最后把踩过的坑摊开」的顺序走一遍。适合有 Java 基础、懂一点统计但没做过时间序列的工程师,也适合想把 Python 预测逻辑迁到 JVM 上的团队参考。全程不依赖任何商业库,核心计算用 Apache Commons Math 就能撑起来。

2. ARMA 与 ARIMA 的数学骨架:先搞清楚代码里要算什么

2.1 AR、MA、ARMA 三者的关系与选型判断

ARMA 模型是自回归(AR)和移动平均(MA)的组合。AR 部分说的是当前值跟过去若干时刻的值线性相关,MA 部分说的是当前值跟过去若干时刻的预测误差线性相关。写成公式就是:

$$X_t = c + \sum_{i=1}^{p}\varphi_i X_{t-i} + \sum_{j=1}^{q}\theta_j \varepsilon_{t-j} + \varepsilon_t$$

其中 p 是自回归阶数,q 是移动平均阶数,φ 和 θ 是待估参数,ε 是白噪声。落到 Java 代码里,你要做的就是三件事:准备一份平稳序列、确定 p 和 q、用最小二乘或极大似然把 φ 和 θ 估出来。

ARIMA 比 ARMA 多了一个「I」,即 Integrated,差分。当序列不平稳时,先做 d 阶差分把它变平稳,再套 ARMA。所以 ARIMA(p,d,q) 里的 d 就是差分次数,绝大多数业务序列 d 取 1 就够了,少数带明显趋势的取 2。

选型上有个朴素判断:如果你的序列围绕一个固定均值上下波动,用 ARMA;如果有明显上升或下降趋势、或者方差随时间变大,先差分再用 ARIMA。我一般会先画个时序图,肉眼看不平稳就直接上 ARIMA,别在 ARMA 上浪费时间。

2.2 平稳性检验与差分:Java 里怎么算 ADF

平稳性检验常用 ADF(Augmented Dickey-Fuller)。Java 没有现成的 ADF 实现,但它的核心是一个回归,用 Commons Math 的 OLS 就能写。下面是最小实现:

import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression; /** * 简化版 ADF 检验:返回 t 统计量 * series 为原始序列,lag 为滞后阶数 */ public static double adfTStat(double[] series, int lag) { int n = series.length; // 构造差分序列 double[] diff = new double[n - 1]; for (int i = 1; i < n; i++) { diff[i - 1] = series[i] - series[i - 1]; } // 构造回归:diff[t] = rho * series[t-1] + sum(beta_i * diff[t-i]) + const int rows = diff.length - lag; double[][] x = new double[rows][lag + 2]; double[] y = new double[rows]; for (int t = lag; t < diff.length; t++) { int r = t - lag; y[r] = diff[t]; x[r][0] = series[t]; // 滞后水平项 for (int i = 1; i <= lag; i++) { x[r][i] = diff[t - i]; // 滞后差分项 } x[r][lag + 1] = 1.0; // 常数项 } OLSMultipleLinearRegression reg = new OLSMultipleLinearRegression(); reg.newSampleData(y, x); double[] beta = reg.estimateRegressionParameters(); double[] se = reg.estimateRegressionParametersStandardErrors(); return beta[0] / se[0]; // rho 的 t 统计量 }

逻辑说明:ADF 的原假设是「存在单位根,序列不平稳」。算出来的 t 统计量如果小于临界值(1% 约 -3.43,5% 约 -2.86),就拒绝原假设,认为平稳。参数 lag 一般取floor(12*(n/100)^0.25)或者用 AIC 选,业务序列 n 不大时取 1 到 3 都行。注意这里用的是简化版,没有做严格的临界值表,生产环境建议把临界值做成常量表按样本量查。

差分本身很简单,一阶差分就是后一个减前一个:

public static double[] difference(double[] series, int d) { double[] result = series.clone(); for (int k = 0; k < d; k++) { double[] tmp = new double[result.length - 1]; for (int i = 1; i < result.length; i++) { tmp[i - 1] = result[i] - result[i - 1]; } result = tmp; } return result; }

d 每加一阶,序列长度减一。预测完记得把差分逆回去,这是新手最容易漏的一步,后面避坑章节会细说。

2.3 定阶:ACF、PACF 与 AIC 在代码里怎么落地

定阶就是确定 p 和 q。经典做法是看自相关函数(ACF)和偏自相关函数(PACF):AR 模型 PACF 截尾、ACF 拖尾;MA 模型 ACF 截尾、PACF 拖尾;ARMA 两者都拖尾。但肉眼判断在代码里不好自动化,我一般用 AIC 网格搜索,p 和 q 各扫 0 到 5,取 AIC 最小的组合。

/** * 计算给定 p,q 下的 AIC * 残差平方和 RSS 由拟合后的模型算出 */ public static double aic(int p, int q, double rss, int n) { int k = p + q + 1; // 参数个数含常数项 return n * Math.log(rss / n) + 2 * k; } // 网格搜索 public static int[] selectOrder(double[] series, int maxP, int maxQ) { double bestAic = Double.MAX_VALUE; int[] best = new int[]{0, 0}; for (int p = 0; p <= maxP; p++) { for (int q = 0; q <= maxQ; q++) { double rss = fitAndGetRss(series, p, q); // 见 3.2 的拟合方法 double score = aic(p, q, rss, series.length); if (score < bestAic) { bestAic = score; best = new int[]{p, q}; } } } return best; }

参数说明:maxP、maxQ 一般取 5 足够,再大容易过拟合且计算量陡增。AIC 里的 k 要算上常数项,漏掉会让阶数偏大。如果样本量小于 50,建议改用 AICc,公式是AIC + 2k(k+1)/(n-k-1),小样本下更稳。

3. 用 Java 从零实现 ARMA 拟合与预测

3.1 数据准备与平稳化处理

先准备一份序列。真实业务里通常是从数据库按天聚合出来的,比如近 180 天的订单量。我习惯先做两件事:去掉明显的异常点(比如大促当天),再检查平稳性。

// 从数据库读出来的原始序列 double[] raw = loadDailyOrders(); // 长度 180 // 平稳性检验 double tStat = adfTStat(raw, 2); System.out.println("ADF t = " + tStat); // 若 tStat > -2.86,认为不平稳,做一阶差分 double[] stationary = (tStat > -2.86) ? difference(raw, 1) : raw;

逻辑说明:这里把「不平稳就差分」写成了三元表达式,实际项目里建议把 d 记录下来,预测后要逆差分。参数上,ADF 的 lag 取 2 是经验值,序列有明显周期性(比如七天)时可以取 7。注意差分会让序列长度减一,后续所有索引都要跟着调整,别用原始长度去遍历。

3.2 参数估计:最小二乘拟合 ARMA

ARMA 的拟合本质是把公式写成线性回归形式。把X_{t-1}...X_{t-p}和ε_{t-1}...ε_{t-q}当作自变量,X_t当作因变量。难点在于 ε 是未知的,需要迭代:先用 AR 部分估一版残差,再代入 MA 部分。

import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression; /** * 拟合 ARMA(p,q),返回 [常数, phi_1..phi_p, theta_1..theta_q] */ public static double[] fitARMA(double[] series, int p, int q) { int n = series.length; int start = Math.max(p, q); int rows = n - start; double[][] x = new double[rows][p + q + 1]; double[] y = new double[rows]; double[] residuals = new double[n]; // 初始残差全 0 // 迭代两轮,第一轮残差用 0 初始化 double[] beta = null; for (int iter = 0; iter < 2; iter++) { for (int t = start; t < n; t++) { int r = t - start; y[r] = series[t]; for (int i = 1; i <= p; i++) { x[r][i - 1] = series[t - i]; } for (int j = 1; j <= q; j++) { x[r][p + j - 1] = residuals[t - j]; } x[r][p + q] = 1.0; } OLSMultipleLinearRegression reg = new OLSMultipleLinearRegression(); reg.newSampleData(y, x); beta = reg.estimateRegressionParameters(); // 更新残差 for (int t = start; t < n; t++) { double pred = beta[p + q]; for (int i = 1; i <= p; i++) pred += beta[i - 1] * series[t - i]; for (int j = 1; j <= q; j++) pred += beta[p + j - 1] * residuals[t - j]; residuals[t] = series[t] - pred; } } return beta; }

逻辑说明:迭代两轮是工程折中,理论上应该迭代到收敛,但实测两轮后参数变化已经很小,再迭代收益递减。参数 p、q 来自 2.3 的定阶结果。注意残差数组要按时间顺序更新,不能并行,否则 MA 部分会用到未来信息。如果 q 为 0,这段代码退化成纯 AR 拟合,同样适用。

3.3 多步预测与差分还原

拟合完参数,预测就是递推。一步预测直接用公式,多步预测要把上一步的预测值当作已知值代进去。

/** * 预测未来 steps 步 * beta 为 fitARMA 的返回值,residuals 为拟合后的残差 */ public static double[] forecast(double[] series, double[] beta, double[] residuals, int p, int q, int steps) { int n = series.length; double[] result = new double[steps]; double[] history = series.clone(); double[] res = residuals.clone(); for (int s = 0; s < steps; s++) { double pred = beta[p + q]; for (int i = 1; i <= p; i++) { pred += beta[i - 1] * history[n - i]; } for (int j = 1; j <= q; j++) { // 多步预测时,未来残差期望为 0 int idx = n - j; pred += beta[p + j - 1] * (idx >= 0 ? res[idx] : 0.0); } result[s] = pred; // 把预测值追加进历史,供下一步使用 double[] newHistory = new double[n + 1]; System.arraycopy(history, 0, newHistory, 0, n); newHistory[n] = pred; history = newHistory; n++; } return result; }

逻辑说明:MA 部分在多步预测时,超过一步的残差期望为 0,所以只有第一步会用到真实残差,后面都用 0。这是 ARIMA 预测收敛到均值的数学原因,不是 bug。参数 steps 一般不超过序列周期的三分之一,预测太远没有意义。

差分还原:

public static double[] inverseDifference(double[] lastValues, double[] diffForecast, int d) { double[] result = diffForecast.clone(); for (int k = 0; k < d; k++) { double last = lastValues[k]; for (int i = 0; i < result.length; i++) { last = last + result[i]; result[i] = last; } } return result; }

注意 lastValues 要按差分阶数从低到高存原始序列的末尾值,顺序搞反结果会整体偏移,这是血泪经验。

4. 避坑与排查:ARIMA 在 Java 里最容易翻车的 5 个点

4.1 现象:预测结果是一条直线

原因:MA 部分多步预测残差取 0,加上 AR 系数估计偏小,预测很快收敛到均值。这是模型特性,不是代码错。解决:如果业务需要长期趋势,改用带差分的 ARIMA 并保留 d,或者对预测结果做趋势外推。别去改残差逻辑硬凑。

4.2 现象:差分还原后数值量级完全不对

原因:逆差分时初始值取错,或者差分阶数 d 和还原时用的 d 不一致。解决:把差分和还原封装成一对方法,d 作为参数传递,还原时用原始序列最后 d 个值按阶数依次累加。建议写单元测试,用已知序列验证inverseDifference(difference(x,1),1)能还原。

4.3 现象:AIC 选出来的阶数特别大,拟合很好但预测很差

原因:过拟合。样本量小的时候 AIC 偏向复杂模型。解决:限制 maxP、maxQ 不超过 5,样本小于 50 时改用 AICc,或者直接凭业务经验定阶,比如日订单量用 ARIMA(1,1,1) 往往就够。

4.4 现象:OLS 拟合报奇异矩阵异常

原因:自变量之间存在完全共线性,常见于序列几乎不变或者差分后全是 0。解决:拟合前检查序列方差,方差接近 0 直接返回均值预测;或者在 OLS 里加一点岭回归正则。Commons Math 的 OLSMultipleLinearRegression 可以设setDefaultValue兜底。

4.5 现象:线上服务内存持续增长

原因:每次预测都 clone 数组,高频调用下 GC 压力大。解决:把拟合好的 beta 和残差缓存起来,预测时复用;history 数组用环形缓冲替代每次扩容。预测本身是纯计算,不涉及 IO,单次耗时在毫秒级,瓶颈通常在数组分配上。

5. 把模型接进 Spring Boot:在线更新与效果验证的实操技巧

模型跑通只是第一步,真正要落地得解决「参数怎么在线更新」和「预测准不准怎么验证」。我一般会把拟合和预测拆成两个服务方法,拟合结果存进一个带版本号的缓存对象,定时任务每天凌晨重新拟合一次,预测接口直接读缓存。

@Component public class ArimaService { private volatile ArimaModel current; // 含 beta、residuals、p、q、d @Scheduled(cron = "0 0 3 * * ?") public void refit() { double[] raw = loadDailyOrders(); double[] stationary = difference(raw, 1); int[] order = selectOrder(stationary, 3, 3); double[] beta = fitARMA(stationary, order[0], order[1]); double[] residuals = computeResiduals(stationary, beta, order[0], order[1]); ArimaModel model = new ArimaModel(beta, residuals, order[0], order[1], 1, raw); this.current = model; // volatile 保证可见性 } public double[] predict(int steps) { ArimaModel m = this.current; double[] diffForecast = forecast(m.stationary, m.beta, m.residuals, m.p, m.q, steps); return inverseDifference(m.lastValues, diffForecast, m.d); } }

逻辑说明:用 volatile 而不是加锁,是因为拟合结果整体替换,读多写少,volatile 足够。定时任务放在凌晨业务低峰,避免拟合时 CPU 抖动影响接口。参数上 cron 按业务更新频率调整,日频数据每天一次就够,小时频数据可以每小时一次。

效果验证别只看 MAPE,那个指标在序列接近 0 时会爆炸。我习惯用三个指标交叉看:

指标计算方式适用场景经验阈值
MAE绝对误差均值量级稳定的序列小于均值的 10%
RMSE误差平方均值开根关注大偏差小于标准差的 50%
MAPE绝对百分比误差均值量级大且远离 0小于 15%

验证方法用滚动预测:取最近 30 天,每天用之前的数据拟合,预测下一天,累计误差。这样比一次性划分训练测试集更贴近线上真实表现。

最后一个技巧:把每次预测值和实际值都落库,跑一段时间后回看偏差分布。如果发现系统性偏高或偏低,说明常数项估计有偏,可以在预测结果上叠加一个修正量,修正量取最近 7 天偏差的中位数。这个土办法在业务量波动不大的场景下,比重新调参见效快得多。

我自己踩过最深的坑是早期没做差分还原的单元测试,上线后预测值差了整整一个量级,排查了一下午才发现是逆差分时初始值取成了差分后的值。从那以后,凡是涉及序列变换的代码,我一定先写一对正反变换的测试用例,跑通了再接业务逻辑。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 2:20:32

Python NLP实战:诗歌接龙中的分词押韵与语义排序

简介&#xff1a;一份面向自然语言处理初学者与Python开发者的诗歌接龙实战项目&#xff0c;围绕汉字分词、词性标注、拼音转换与韵律匹配展开&#xff0c;结合爬虫、文本清洗和规则/统计混合算法&#xff0c;解决“给出上句、自动接下句”的典型任务。压缩包共17个文件&#x…

作者头像 李华
网站建设 2026/10/8 2:20:32

百万行CSV打不开?流式加载与DuckDB实战指南

简介&#xff1a;这是一款面向数据分析人员、程序员及日常办公用户的CSV文件编辑工具&#xff0c;针对需要频繁查看、修改表格数据却不想依赖Excel的场景&#xff0c;提供类似电子表格的直观操作体验&#xff0c;支持单元格增删改、排序、过滤、查找替换与格式转换等常见需求。…

作者头像 李华
网站建设 2026/10/8 2:20:17

ChatGLM3-6B本地部署实战:从zip包到稳定推理的完整链路

简介&#xff1a;本资源是面向AI开发者与大模型实践者的ChatGLM3-6B中文大语言模型轻量部署包&#xff0c;聚焦知识库问答系统构建场景&#xff0c;适用于具备PyTorch基础和模型微调经验的中高级学习者。压缩包共53个文件&#xff0c;包含7个.bin与7个.safetensors权重文件&…

作者头像 李华
网站建设 2026/10/8 2:19:28

WPF 接入 D3D 实现高性能 3D 动画看板:D3DImage 共享纹理全解析

简介&#xff1a;WPF D3D demo是一份面向WPF开发者的Direct3D视频渲染示例工程&#xff0c;重点演示在WPF界面中高效呈现YUV格式视频。工程整合WPF、YUV颜色空间与D3D硬件加速&#xff0c;通过自定义渲染类将YUV数据转换为D3D纹理并绘制到WPF可视对象&#xff0c;同时利用后台线…

作者头像 李华
网站建设 2026/10/8 2:19:12

Netty物联网网关实战:万级长连接、多协议共存与粘包容错

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 2:17:11

WinForm嵌入谷歌内核:CefGlue实战与踩坑指南

简介&#xff1a;C#/.NET开发者在WinForm应用中集成浏览器功能的实用方案&#xff0c;借助Xilium.CefGlue对CEF的C#封装&#xff0c;可让桌面程序直接获得Chromium内核的渲染能力与兼容性表现。资源共115个文件、7z压缩后约126.63MB&#xff0c;以dll运行库及pak资源文件为主体…

作者头像 李华