
简介这是一份发表于《计算机应用与软件》的学术论文PDF面向算法、机器学习与人工智能方向的研究者及设备状态预测相关工程技术人员。针对单一ARIMA模型在非平稳时间序列预测中偏差与不稳定性问题论文提出引入加权马尔可夫链对ARIMA残差序列进行二次建模与修正通过状态特征值结合线性插值法将残差状态转化为具体值并以船舶海水出口温度预测为实例对比验证了修正模型较单一ARIMA在预测精度上的显著提升。资源共1个PDF文件约767KB完整收录论文正文、图表、公式及参考文献可直接阅读或打印适合作为时间序列分析、组合预测模型研究的参考范本。目前已有513人学习对从事设备状态监控、视情维修以及预测算法选型的读者具有直接参考价值。1. ARIMA预测模型的“天花板”藏在残差里用ARIMA做单变量时间序列预测模型调得再漂亮误差也很难再往下压——这是很多人的直观感受。原因不玄乎ARIMA擅长抓线性自相关关系但残差序列里往往还残留着“状态依赖”的有效信息被模型当成噪声扔掉了。这套“基于加权马尔可夫链修正的ARIMA预测模型”的思路就是先把ARIMA的残差吃干榨净用马尔可夫链把残差的转移规律建模出来再用自相关系数为不同滞后阶数加权最后把修正量补回到ARIMA的预测值上。听起来是锦上添花实际跑下来在很多场景下能把MAE再压低10%~30%。适合正在做销量、水位、计量仪表等单变量预测的人也适合想给已有ARIMA模型加“后悔药”而不是推倒重来的从业者。2. 为什么是“ARIMA 加权马尔可夫链”两个模型是怎样配合的2.1 ARIMA的本职工作只解决线性相关ARIMA模型的核心逻辑是当前值等于过去p阶观测值和q阶误差的线性组合差分阶数d负责把非平稳序列“掰”成平稳序列。建模时最常见的流程是先做ADF检验确定d再看自相关函数ACF和偏自相关函数PACF的截尾阶数粗定(p,q)之后用AIC/BIC微调。这一套组合拳对趋势、季节性和短期惯性把握得很稳但当序列里存在“上一阶段涨、下一阶段大概率继续涨”这类非线性状态依赖时ARIMA的线性假设就开始兜不住了。具体表现很典型训练集拟合优度不错可一到预测期误差曲线呈现明显的“成段出现”——正误差连着出现负误差也连着出现。这不是随机噪声是残差里有结构。模型把该学的规律漏掉了这部分信息不在滞后项的线性组合里而在“当前残差落在哪个区间、下一步残差又会往哪个区间走”的状态转移规则里。这正是马尔可夫链能补的位置。2.2 马尔可夫链如何吃下残差里的状态信息马尔可夫链和ARIMA最大的差别在于它建模的不是数值本身而是“状态之间的转移概率”。把ARIMA的残差序列按数值区间切成几个状态比如“大幅低估”“轻微低估”“轻微高估”“大幅高估”然后统计“状态i的下一个观测落到状态j”的频率得到一个转移概率矩阵。预测时拿到当前残差所在状态查表得到下一步各状态的概率分布取期望值就是残差修正量。这一步看着简单但有个问题如果只用一阶转移矩阵等于假设“下一状态只取决于当前状态”这和ARIMA犯的错是同一类——它把过去的更多历史信息丢掉了。真实序列里残差常常与2步前、3步前的状态也有关联。加权马尔可夫链的思路就是同时计算1阶、2阶、3阶等多张转移矩阵按各滞后阶数的自相关系数做归一化权重最后把各阶矩阵给出的概率分布加权平均作为最终的状态修正依据。2.3 加权系数为什么用自相关系数自相关系数在这里的作用是衡量“间隔k步的两个残差之间还有多大相关性”。间隔1步的残差相关性最强权重就高间隔3步的相关性弱权重就低。比起给每阶一个拍脑袋的固定权重用自相关系数归一化是更客观、也有统计依据的做法。权重公式为w_k |r_k| / Σ|r_i|其中r_k是残差序列间隔k步的自相关系数k取1到L。这里有两个值得注意的点。第一权重取绝对值是因为马尔可夫链的转移概率本身已经表达了方向信息权重只要表达“相关强度”负相关只需要在概率矩阵里体现。第二滞后阶数L一般取3~5取大了高阶转移矩阵会因样本量不足而稀疏化权重本身也趋近于0徒增计算量。这套修正逻辑落地后相当于给ARIMA装了一个“残差状态感知器”——每当模型预测偏差进入某个状态区间系统就知道下一步大概率会往哪个方向偏离提前把修正量怼回去。3. 构建加权马尔可夫链修正模型五个步骤与关键参数3.1 状态划分残差序列怎么切状态划分是整套模型最影响结果的一步也是最容易被敷衍的一步。常见做法有三种等距划分、均值-标准差划分、K-means聚类。我在复现这套方法时用得最多的是等距划分——以残差均值为中心取残差的标准差std作为尺度把残差从“均值−2倍标准差”到“均值2倍标准差”这个范围切成K个等宽区间。这样做的理由是它简单可复现而且残差序列在2倍标准差范围内通常已覆盖95%左右的样本状态分布不会太失衡。状态数K的取值是关键参数。K2时状态只有“正残差/负残差”信息粒度太粗修正量大概率只是个常数偏移提升有限。K取的过大比如K8每个状态里分配的样本数太少转移概率矩阵会出现大量零元素概率估计的方差飙升。经验区间是3到5。我一般会直接在这个区间里做网格搜索用测试集MAE做选择没有一劳永逸的理论值。3.2 转移概率矩阵的计算与验证状态划分完成后转移概率矩阵的构造是个统计计数活。对第l阶转移矩阵遍历残差序列的前n−l个点统计“t时刻状态为i且tl时刻状态为j”的频次除以状态i出现的总频次得到P_ij。计算时有个常被忽视的细节第l阶转移矩阵的行数、列数仍然是K×K但频次统计的是间隔l步的状态对不是相邻状态对。矩阵算完后必须做一次“行归一化验证”每一行的概率和必须逼近1如果某一行全是0说明该状态在训练集里没出现过或只出现在序列尾部需要回到状态划分那一步调整区间宽度。另外还要检查对角线元素占比。如果对角线概率普遍超过0.7说明残差状态有强惯性修正模型的价值很大如果对角线概率接近均匀分布那说明残差几乎无状态依赖这套修正方法对这个序列基本无效再往下跑也只是在拟合噪声。3.3 权重系数自相关序列的计算边界滞后阶数L选定后用残差序列计算间隔1到L步的自相关系数r_1到r_L。计算时注意样本量的影响滞后阶数越大用于计算相关性的样本对越少r_L的方差越大。所以L不能贪多。常见的做法是先画出残差的自相关图看在第几阶之后自相关系数落入置信区间只取落入区间之前的阶数参与加权。权重归一化后还有一个可选操作如果计算出的r_k本身不显著可以将其对应的权重直接剔除并重新归一化避免把噪声权重带进概率加权过程。这一手相当于给模型做了一次“自相关显著性筛选”在样本量不足200时尤其推荐。3.4 完整修正流程从训练到预测整个模型的预测流程可以拆成四步走。第一步用训练集拟合ARIMA(p,d,q)保存残差序列。第二步基于残差序列做状态划分计算1到L阶转移概率矩阵并按自相关系数计算归一化权重。第三步对测试集的每一个预测点取当前残差状态分别查L张转移矩阵中对应的那一行做加权平均得到下一修复状态概率分布再以各状态区间的中点值为代表求概率加权期望得到残差修正量。最后一步将修正量加到ARIMA的原始预测值上得到最终预测。这里有个关键参数容易被带偏修正量不应该一口气全加上。这套方法的经典做法是对修正量再乘一个修正强度系数alpha取值0到1之间。alpha1代表完全信任马尔可夫修正alpha0代表不使用修正。实际调参时alpha取0.7到0.9左右往往效果最好因为残差修正本身带有估计误差全量叠加会把状态划分的噪声也传导进最终预测。alpha的选优可以直接并入K值的网格搜索一块儿做。4. 代码实现从ARIMA拟合到加权马尔可夫修正4.1 数据准备与ARIMA定阶先看数据组织。这里以单变量月度销量序列为例数据是两列date和value按时间升序排列。拟合ARIMA之前先用ADF检验确认差分阶数d再绘制ACF/PACF图辅助确定p和q的初值最后用AIC最小化收敛参数。import pandas as pd import numpy as np from statsmodels.tsa.arima.model import ARIMA from statsmodels.tsa.stattools import adfuller # 读取数据按时间排序 df pd.read_csv(sales_monthly.csv, parse_dates[date]) df df.sort_values(date) ts df[value].astype(float) # ADF检验若p值0.05则需差分 adf_result adfuller(ts) print(ADF p-value:, adf_result[1]) # 差分一次后再做ADF确认平稳性 ts_diff ts.diff().dropna() adf_diff adfuller(ts_diff) print(ADF p-value after diff:, adf_diff[1]) # 拟合ARIMAorder由AIC最小化确定 # 此处以(2,1,2)为例实际按ACF/PACF截尾阶数调整 model ARIMA(ts, order(2, 1, 2)) fit model.fit()逻辑说明ADF检验的p值小于0.05才能认为序列平稳第一次检验不通过就对序列做一阶差分再检验。这里的差分操作会把序列第一项变成缺失值后续拟合ARIMA时模型内部会自动处理。统计量代码里order(2,1,2)分别是p、d、q三项d1意味着模型内部完成一阶差分后再做ARMA拟合。参数说明p和q的初值不是拍脑袋来的。p参考PACF图的截尾阶数q参考ACF图的截尾阶数。如果两者都不截尾常见做法是直接用AIC网格搜索把p和q都扫一遍取AIC最小的组合。注意网格搜索的范围一般各不超过5超过之后边际收益很低反而容易过拟合。4.2 状态划分与多阶转移矩阵构建拿到残差序列后先做状态离散化再构造1~L阶转移矩阵。这一步是把上文的原理落到代码上的核心环节。# 提取训练残差丢弃因差分产生的NaN项 resid fit.resid.dropna().values def state_divide(resid, k, std_scale2.0): 等距划分以均值为中心向两侧扩展std_scale倍标准差 mu, std np.mean(resid), np.std(resid) bounds np.linspace(mu - std_scale * std, mu std_scale * std, k 1) states np.digitize(resid, bounds) - 1 states[states 0] 0 states[states k] k - 1 return states, bounds def build_lag_transition(states, k, lag): 构造间隔lag步的转移矩阵行归一化 P np.zeros((k, k)) n len(states) for t in range(n - lag): i, j states[t], states[t lag] P[i, j] 1 # 行归一化避免除零 row_sum P.sum(axis1, keepdimsTrue) row_sum[row_sum 0] 1.0 P P / row_sum return P # 状态数与最大滞后阶数 k 4 L 3 states, bounds state_divide(resid, k) # 构造1到L阶转移矩阵 P_list [build_lag_transition(states, k, lag) for lag in range(1, L 1)]逻辑说明state_divide函数用np.digitize把残差映射到0到k-1的整数状态。build_lag_transition统计的是t时刻状态i和tlag时刻状态j的配对频次这与一阶转移矩阵在统计口径上的差别在lag参数的传入。行归一化时把全零行兜底为1防止后续概率计算除零。参数说明std_scale控制状态区间的总宽度。设成2.0时区间总宽度是4倍标准差边界之外的离群残差被强制归入最内侧状态这其实是刻意的——离群点频次太低单独成状态反而让转移矩阵不稳定。如果你发现状态边界两侧样本量悬殊可以把std_scale增大到2.5或3.0。K值在3到5之间调整K越大状态粒度越细但每一状态的样本量会缩水。4.3 自相关权重计算与加权修正预测状态转移矩阵就绪后进入加权环节先算残差的自相关序列归一化得到权重再做概率加权平均求出修正量。def autocorr_weight(resid, L): 计算1~L阶自相关系数并归一化为权重 n len(resid) mu np.mean(resid) c0 np.sum((resid - mu) ** 2) / n r [] for lag in range(1, L 1): c_lag np.sum((resid[:-lag] - mu) * (resid[lag:] - mu)) / (n - lag) r.append(c_lag / c0) r np.array(r) return np.abs(r) / np.sum(np.abs(r)) def markov_correction(states, resid, P_list, weights, bounds): 加权马尔可夫残差修正量 cur_state states[-1] k len(bounds) - 1 # 各阶矩阵对应当前状态的概率行加权平均 prob np.zeros(k) for lag in range(len(P_list)): prob weights[lag] * P_list[lag][cur_state] # 以区间中点为状态代表值求期望修正量 centers (bounds[:-1] bounds[1:]) / 2 correction np.dot(prob, centers) return correction weights autocorr_weight(resid, L) correction markov_correction(states, resid, P_list, weights, bounds) print(autocorr weights:, weights) print(residual correction:, correction)逻辑说明autocorr_weight按间隔lag计算自相关系数取绝对值后归一化保证所有权重和为1。markov_correction里做的是两件事——先按权重融合各阶转移矩阵中“当前状态”对应的那一行概率分布再用融合后的概率对状态区间中点求期望。这个期望值就是残差的“系统性平均偏向”也就是要补回到ARIMA预测值上的修正量。参数说明L是最大滞后阶数与权重计算直接挂钩。L3时权重向r_1倾斜如果序列自相关在第2阶后已经不显著可以适当减小L避免噪声权重干扰。prob向量是k个状态的概率分布如果某个状态概率异常高说明当前残差状态对该状态的惯性极强修正量的朝向会很明确。4.4 滚动预测与效果评估在真实使用中预测不是单步就结束的。测试集上的滚动预测会把每步新的观测值代入模型更新状态并重新计算修正量。from sklearn.metrics import mean_squared_error, mean_absolute_error # 按测试集滚动修正预测 history list(ts[:80].values) test list(ts[80:].values) predictions [] for t in range(len(test)): # 用历史数据滚动拟合ARIMA model_temp ARIMA(history, order(2, 1, 2)) fit_temp model_temp.fit() # 原始ARIMA预测 arima_pred fit_temp.forecast(1)[0] # 用当前残差计算修正量 resid_temp fit_temp.resid.dropna().values states_temp, _ state_divide(resid_temp, k) weights_temp autocorr_weight(resid_temp, L) P_temp [build_lag_transition(states_temp, k, lag) for lag in range(1, L 1)] corr_temp markov_correction(states_temp, resid_temp, P_temp, weights_temp, bounds) # 叠加修正量 final_pred arima_pred corr_temp predictions.append(final_pred) # 滚动更新历史数据 history.append(test[t]) # 对照评估 base_pred [] # 这里可以保存不加修正的ARIMA基础预测值 mae_base mean_absolute_error(test, base_pred) # 基线MAE mae_final mean_absolute_error(test, predictions) print(baseline MAE:, mae_base) print(corrected MAE:, mae_final)逻辑说明滚动预测的每个时间步都做三件事重新拟合ARIMA、基于当前残差序列构造修正量和输出修正后预测。因为残差序列会随历史窗口滚动而变化状态划分和权重计算也必须跟着更新不能复用训练期的静态矩阵。代码中history逐步追加真实观测值模拟了生产环境的真实操作方式。参数说明history窗口长度直接决定残差样本量窗口太短转移矩阵会稀疏建议窗口长度在80以上。order参数在滚动过程中保持不变这是刻意选择——如果每步都用AIC重新定阶计算开销大且参数抖动会传导进预测。通常只在离线阶段定好阶数滚动时直接用固定的(order)。修正量叠加时也可以乘上之前提过的强度系数alpha在代码里改成final_pred arima_pred 0.8 * corr_temp效果会更平稳。5. 避坑实战四个高频翻车点与排查方法5.1 状态数K2时修正后误差反而变大现象设定K2跑完整流程测试集上的MAE比纯ARIMA还要高修正越修越偏。原因K2时状态只分正负两类区间中点代表的是“平均偏移量”。残差的正态性越好两类残差的均值都接近0修正量趋近于一个常数小值反而把本来干净的ARIMA预测污染了。这是状态粒度不足导致的典型失效。解决把K调到4或5再跑。K4时状态能把“轻微正偏”和“强烈正偏”分开修正量才能区分“微调”和“大修”。如果K5后效果与K4几乎一致说明序列的状态结构本身只有四类左右K4就是当前序列的最优选择。5.2 高阶转移矩阵出现全零行现象在L4时第4阶转移概率矩阵里有一整行是零归一化之后该行仍全为0概率分布变成无效状态。原因样本量不足时间隔4步的状态对频次骤降某些起始状态在序列后段几乎没出现过或只在尾部最后一次出现无法构成完整的“状态i到所有状态j”的转移频次。解决先检查每个状态的出现频次出现次数低于10的状态直接合并到相邻状态。另一个办法是给转移矩阵做拉普拉斯平滑在频次矩阵上统一加一个极小值比如0.01再做行归一化。如果L4时状况频发把L降回3多数场景下信息损失并不大。5.3 残差不平稳还硬套马尔可夫修正现象ARIMA模型的残差序列做ADF检验p值大于0.05残差不平稳。此时马尔可夫状态划分得出转移矩阵后权重计算的自相关系数也明显衰减异常预测期误差波动呈放大趋势。原因残差不平稳说明ARIMA的d或order设置不当残差里还残留着趋势性或周期性成分。这种情况下马尔可夫链建模的是“非平稳残差的漂移”而不是“围绕零均值的随机偏差”修正方向会系统性错误。解决先回头修ARIMA的阶数。把d提上来或者调整p、q让残差通过ADF检验。残差平稳是整套修正方法成立的大前提这一点不是可选项。有一个快速检查方法画出残差序列折线图若它围绕0轴上下震荡没有明显爬升或下降才具备进入马尔可夫修正的条件。5.4 权重全部集中在一阶滞后上现象计算出的weights中w_1接近0.9其余权重加起来不到0.1。模型退化成了一阶马尔可夫链和高阶修正几乎没有区别。原因序列残差的自相关性衰减极快间隔两步以上的自相关系数已经落入噪声区间说明高阶滞后对修正没有额外信息价值。解决此时直接把L从3减到1保留一阶矩阵省去多阶矩阵的构建和加权计算。这不是放弃模型而是让模型结构与数据特性对齐。顺带提一句别为了展示“加权”效果而强行保留高阶项那样会把噪声权重混入概率分布结果往往比只用一阶还差。5.5 状态划分使用了全样本统计量造成数据泄露现象训练集上MAE改进显著测试集上误差不仅没降还明显高于基线。反复检查代码也找不到问题最后发现状态划分时用的是全序列的均值和标准差训练期和测试期的残差分布本来就有差异。原因如果用全样本计算mu和std等于让模型的划分边界“偷看”了测试集残差分布训练期效果自然虚高。这是一类容易看走眼的数据泄露。解决状态划分的mu和std只用训练期残差计算测试期沿用这套边界。同样的原则适用于自相关权重——权重只基于训练期残差不能在预测时用包含未来信息的序列重新计算。这两处修正后训练和测试效果才会对齐到同一水平。6. 让模型真正落地验证边界与延伸方向6.1 验证这套修正有没有效别只看误差跑完一轮修正预测后除了看MAE和RMSE我建议你强制自己做三张图。第一张是残差修正前后对比散点图看修正量是否真的在对冲系统偏差而不是随机打乱预测值。第二张是状态划分的频次直方图确认各状态样本量均衡没有出现某状态占比超过60%的单峰失衡。第三张是自相关权重条形图看一眼就知道高阶滞后有没有参与度这直接决定L参数是否该收敛。这三张图可以规避“误差降了但原因不明”的假象。我遇到过最典型的假象修正后的MAE好看但拆开预测值与真实值的逐点差发现是“正负对消”——修正量只是在预测值上加了一个符号随机的微小抖动误差均值好看误差方差反而扩大了。这种情况盯MAE完全看不出来看修正量序列的方向一致性立刻暴露。6.2 延伸方向与Prophet、XGBoost的定位差异有不少人拿到这套模型后跑来问能不能替换掉Prophet时序预测模型或XGBoost回归预测模型。我的看法是这不是替代关系而是层次关系。Prophet擅长处理强趋势、强季节性的可解释预测XGBoost适合加入外部特征的表驱动预测而加权马尔可夫链修正的ARIMA属于“在强线性基线上做状态修补”的流派。换用其他模型当基线马尔可夫修正环节依然可以无缝挂载——把Prophet或XGBoost的预测残差丢进同一套状态划分和加权流程即可。实操时还有一个偷懒但有效的定位技巧如果基线模型的误差呈现正负成段交替修正效果往往明显如果误差完全散乱无结构就别浪费时间上这套方法。这可以作为选型的第一道过滤器比跑完整流程再对比省事得多。6.3 一个收尾习惯我自己的血的教训是状态划分边界、权重、转移矩阵一切参数必须固化在训练期测试期只做只读调用。早期图省事把滚动窗口里的残差实时重新划分状态结果边界漂移导致修正量忽大忽小翻车翻得很惨。从那以后我每次跑完修正模型都强制走一遍“训练期定参→测试期只读→三张验证图”的完整流程确认无误才敢把预测值交出去。这套方法的完整推导、实证数据和调参细节都在那份PDF里希望帮到你。本文还有配套的精品资源点击获取