☰
灰色马尔科夫模型:GM(1,1)结合马尔科夫链的小样本预测方法
2026/10/7 11:16:45 网站建设 项目流程

搞预测的这些年,我有个深有体会的结论:最怕的不是数据太少,而是数据“看起来有趋势,实际上到处是坑”。你用最简单的GM(1,1)去拟合,它能给你一条很丝滑的指数曲线,但碰上真实数据里的突然下挫、季节性回弹,预测值就像在走钢丝,一个波峰就直接击穿你的置信区间。灰色马尔科夫模型就是为这种场景准备的:灰色GM(1,1)负责把趋势骨架搭出来,马尔科夫链负责吃掉残差里的波动规律,两头各管一段,组合起来对付“有趋势、有波动”的小样本数据特别能打。

这篇我来完整拆解这套方法,从模型思路、数学推导、Matlab代码实现到案例实测和踩坑记录都会讲到。适合正在做课设、毕设,或者日常需要做短期指标预测(比如销量、用电量、游客量、故障数)的读者。你不需要懂很深的随机过程,也不需要背公式,跟着代码走一遍就能用起来。

1. 为什么要把灰色模型和马尔科夫链放在一起

1.1 单纯用GM(1,1)会栽在哪

灰色模型的理论基础是灰色系统理论,核心假设是“部分信息已知、部分信息未知”。GM(1,1)本质上是对原始数据做累加生成,把一组参差不齐的数列变成一条近似指数增长的曲线,然后用一阶线性微分方程去拟合。对小样本、单调增长的数据,这个思路非常漂亮,四五个点就能建模。

但问题也出在这个“近似指数”上。一旦数据里出现明显的谷底或峰值,GM(1,1)在拟合阶段就会把极值“抹平”。比如我做过用电量预测,某年因为气温异常导致夏季负荷骤增,GM拟合值在这年附近明显落后于实际值;到了下一年气温回归正常,GM又继续按指数上涨,把实际值远远抛在后面。这不是代码bug,而是模型特性决定的:它只认趋势,不认波动。

更有意思的是,GM的残差往往不是随机噪声,而是成团出现的。连续两三个点偏正,紧接着又连续两三个点偏负。这些规律被丢弃非常可惜,而马尔科夫链恰好是处理“状态转移规律”的经典工具。

1.2 马尔科夫链补的究竟是什么

马尔科夫链的核心性质叫无后效性:未来的状态只与当前状态有关,与更早的历史无关。听起来好像限制很大,但在工程里恰恰是一种合理的简化。我不需要知道前五个月的数据,只要知道残差目前落在“偏高区间”还是“偏低区间”,就能查表知道下一步最可能跑到哪个区间。

套用到GM残差修正上,思路就非常清晰了:

  1. 先用GM算出历史段的拟合值,计算每个点的相对误差;
  2. 把相对误差按大小划分成若干个状态区间,比如“严重低估”“略微低估”“略微高估”“严重高估”;
  3. 统计状态之间的转移概率,得到一个转移矩阵;
  4. 对未来的预测点,从当前残差状态出发,按最大转移概率找到未来最可能落入的状态;
  5. 用该状态的残差均值反过来修正GM的未来预测值。

一句话总结就是:GM给你一个骨架,马尔科夫负责往骨架上补肉。单看GM,预测值是一条单调曲线;组合之后,曲线会根据历史波动特征自动“拱起来”或“压下去”。

1.3 这套组合适合用在哪些地方

不是所有数据都适合灰色马尔科夫,我整理了几个典型场景:

场景数据特征为什么适合
区域用电量月/年度预测整体增长、受季节和气温扰动GM抓趋势,马尔科夫修负荷波动
景区游客量预测每年上升、节假日峰值明显残差状态能反映“旺季高估/低估”规律
产品月度销量预测上升期伴随促销波峰短期1-3步预测非常稳
设备故障频次统计缓慢上升、偶发跳变样本量小,传统ARIMA不适用

最推荐的使用窗口是样本量10到30个、预测步数1到3步。样本再少,GM撑不起趋势;样本再多且周期性强,ARIMA或季节分解效果更好;预测步数一旦超过5步,马尔科夫修正的误差会同步累积,翻车概率明显上升。

2. 灰色GM(1,1)建模全流程,先把参数吃透

2.1 级比检验:数据适不适合GM,先过这关

所有用GM的人第一步都该做级比检验。级比的定义是:

sigma(k) = x0(k-1) / x0(k)

如果序列长度为n,那么级比理论可容区间是:

[ exp(-2/(n+1)), exp(2/(n+1)) ]

只有当全部级比都落在区间内,GM(1,1)才有意义。我用上面的用电量数据算过:n=8时区间约为[0.8007, 1.2488],八年的相邻级比基本都落在里面,比如35.2/38.7≈0.9096,50.6/53.1≈0.9529。这说明数据适合建模。

如果检验不通过,最常用的补救是平移变换,给原始序列整体加一个常数c,让它变得更“平滑”,建模完成后再把c减回去。注意c不能太小,一般取数据绝对值的两倍左右起步。取对数或开方也可以,但还原时麻烦,我很少用。

2.2 累加生成和紧邻均值到底在干什么

GM(1,1)的关键操作是累加生成。假设原始序列是x0,累加后是x1:

x1(k) = sum(x0(1:k))

累加的意义在于,即使原始序列波动剧烈,累加序列通常也会变成一条单调递增的曲线,这样微分方程拟合才有基础。Matlab里一行代码就能实现:

x1 = cumsum(x0);

接下来构造紧邻均值序列z1(z1(k) = 0.5x1(k) + 0.5x1(k-1))。这个序列是为了把微分方程离散化:连续模型里的导数,在离散点上用相邻值的平均去逼近。它起到桥梁作用,没有这一步,微分方程和离散数据没法对接。

z1 = (x1(1:end-1) + x1(2:end)) / 2;

2.3 最小二乘求解,得到时间响应式

GM(1,1)的白化方程是:

dx1/dt + a*x1 = u

离散化后对应:

x0(k) = -a*z1(k) + u

把所有训练点写成矩阵形式就是Y = B * [a, u]^T。其中B的第一列是-z1,第二列全为1,Y是原始序列从第二个点开始的列向量。最小二乘解为:

[a, u]^T = (B'*B) \ (B'*Y)

得到参数后,累加序列的时间响应式为:

x1_hat(k+1) = (x0(1) - u/a) * exp(-a*k) + u/a

最后累减还原得到预测值:

x0_hat(k+1) = x1_hat(k+1) - x1_hat(k)

这里有个经验参数要记住:当-a小于等于0.3时,模型可以做中长期外推;-a在0.3到0.5之间只建议做短期预测;超过0.5说明数据本身不适合用GM,即使级比检验勉强通过,外推几步误差也会失控。我见过有人拿到-a=0.7的模型硬要预测后面八个点,结果预测值直接变成负的,这种属于违背基本使用边界。

2.4 精度检验:除了误差均值,更要看后验差

常规的误差指标是MAPE平均绝对百分比误差和RMSE均方根误差。灰色预测里还常用两个后验指标:后验差比值C和小误差概率P。

S1是原始序列标准差,S2是残差标准差,C=S2/S1。P的定义是残差与其均值的偏差绝对值小于0.6745*S1的概率。等级划分如下:

模型精度等级后验差比值C小误差概率P
一级(好)C < 0.35P > 0.95
二级(合格)C < 0.5P > 0.8
三级(勉强)C < 0.65P > 0.7
四级(不合格)C >= 0.65P <= 0.7

我在实际项目里,MAPE低于10%只是及格线,后验差比值C要小于0.35才敢拿出去给业务方。而更重要的是,这些残差不能直接扔掉,它们正是马尔科夫修正的原料。

3. 马尔科夫修正的四个核心细节

3.1 相对误差比绝对残差靠谱

计算残差时,我强烈建议用相对误差而不是绝对误差。原因很简单,绝对误差在不同量级的数据上没有可比性。比如用电量在100亿和1000亿两个量级时,同样的10亿误差意义完全不同。相对误差公式:

re(k) = (x0(k) - x0_hat(k)) / x0(k) * 100%

这里要特别说明一个细节:GM拟合的第一个点永远是原始第一个点,残差恒等于0,没有统计价值。所以计算残差序列时我会从第二个点开始。

状态划分最省事的方法,是把相对误差的最小值到最大值区间均匀等分成s份。比如最小-8.7%,最大6.3%,s=3,那么三个状态边界就是[-8.7, -3.7, 1.3, 6.3]。每个历史拟合点的相对误差落入哪个区间,就属于哪个状态。状态个数选择上有个朴素原则:8-15个训练样本用3档足够,15-30个可以考虑4到5档,再多容易让转移矩阵稀疏到不可用。

3.2 状态转移矩阵的构造和使用

有了状态序列之后,统计相邻两个状态之间的转移次数。比如状态序列是[3, 1, 1, 2, 1, 3, 2],那么转移对依次是(3->1)、(1->1)、(1->2)、(2->1)、(1->3)、(3->2)。每行归一化就得到转移概率矩阵P。P(i,j)表示当前处于状态i时,下一步跳转到状态j的概率。

构造过程看似简单,但有两个容易踩的坑:

第一个是状态序列长度太短。训练样本8个时,残差状态序列只有7个点,有效转移次数只有6次。三状态的矩阵9个格子只有6次投票,很多格子概率是0,这很正常,但是解释时要小心。第二个是空行问题,如果某个状态在历史中从未作为“起点”出现,转移矩阵对应行全为0。代码里我会做一个兜底:全零行直接用均匀分布填充,并在注释里提示检查状态划分是否合理。

预测未来状态时,我采用最大概率路径:从最后一个已知训练点的残差状态出发,查转移矩阵取当前行最大概率对应的下一状态,作为第一个未来点的预测状态;然后用这个状态继续查下一行,得到第二个未来点的预测状态。这种极简做法能保证结果可复现,也符合马尔科夫链“一次只依赖当前状态”的基本逻辑。

3.3 修正公式是怎么推导出来的

这一步是整个模型的点睛之笔。相对误差定义为实际值相对GM预测值的偏差率,那么有:

re = (actual - forecast) / actual * 100%

整理之后能得到:

actual = forecast / (1 - re/100)

所以修正系数就是1/(1-re/100)。如果re>0,说明GM预测偏低,修正系数大于1,预测值向上抬;如果re<0,说明GM预测偏高,修正系数小于1,预测值往下压。

举个例子,GM预测2022年为55.6,马尔科夫链判定该预测点最可能落入状态1,而状态1的历史残差均值是-5.2%,说明GM历史上在这个状态下普遍偏高。修正系数=1/(1-(-0.052))≈0.95,修正预测值就是55.6*0.95≈52.8,恰好非常接近真实值52.4。

为了防止某些数据中状态残差均值过大导致修正失控,我会给修正系数加限幅:

corr_factor = max(corr_factor, 0.85); corr_factor = min(corr_factor, 1.15);

这个范围是个经验值,数据波动特别大的场景可以放宽到0.8到1.2,但超过这个幅度基本说明GM和马尔科夫的组合已经不适合当前数据。

3.4 修正幅度和状态数量的经验判断

我自己调参的体会是:状态数量宁少勿多。有人喜欢把残差分成五六档,觉得越细越精确,但小样本下转移矩阵会变得极其稀疏,随机因素被放大。三档往往比五档更稳,因为状态合并后转移概率的统计置信度更高。

另外一个容易被忽视的点是,如果数据本身几乎无波动,马尔科夫修正反而有害。GM拟合残差如果都在±1%以内,状态均值也都很接近0,乘上修正系数属于“画蛇添足”。所以我在代码里加了一个判断:当残差极差小于某个阈值时,直接返回GM的原始预测结果。

4. 手写Matlab代码,灰色马尔科夫预测一次跑通

4.1 代码整体框架

整个实现分成三个文件更清楚:

  • gm11_predict.m:GM(1,1)建模核心函数,输入训练序列和预测步数,输出拟合值、预测值和参数;
  • markov_correct.m:马尔科夫修正函数,输入训练期真实值、GM拟合值和GM未来预测值,输出修正后预测及转移矩阵;
  • demo_gray_markov.m:主脚本,负责数据组织、调用两个函数、打印结果和绘图。

之所以拆成函数,是因为实际项目中数据会换,但建模流程不变。你只需要把主脚本里的x0_all替换成自己的数据,就能一键运行。

4.2 GM(1,1)核心函数实现

function gm = gm11_predict(x0, p) % gm11_predict GM(1,1)建模与预测 % 输入: % x0 - 训练原始序列,行向量 % p - 预测步数 % 输出: % gm.x0_hat - 还原序列,前length(x0)个为拟合值,后p个为预测值 % gm.x1_hat - 累加序列预测 % gm.a, gm.u - GM参数 % gm.sigma_ok- 级比检验是否通过 x0 = x0(:)'; % 统一转成行向量,防手滑传列向量 n = length(x0); % 级比检验 sigma = x0(1:end-1) ./ x0(2:end); lb = exp(-2/(n+1)); ub = exp(2/(n+1)); gm.sigma_ok = all(sigma > lb & sigma < ub); if ~gm.sigma_ok warning('级比检验未通过,建议先做平移变换。'); end % 累加生成 x1 = cumsum(x0); % 紧邻均值 z1 = (x1(1:end-1) + x1(2:end)) / 2; % 构造B矩阵和Y向量 B = [-z1(:), ones(n-1, 1)]; Y = x0(2:end)'; % 最小二乘求参数 theta = (B'*B) \ (B'*Y); gm.a = theta(1); gm.u = theta(2); % 时间响应式,k从0开始 k = 0:n+p-1; gm.x1_hat = (x0(1) - gm.u/gm.a) * exp(-gm.a .* k) + gm.u/gm.a; % 累减还原 gm.x0_hat = [gm.x1_hat(1), diff(gm.x1_hat)]; end

代码里有几个细节值得说。第一,x0 = x0(:)'这行很多人不写,一旦别人传进来的是列向量,后面B'*Y维度直接爆炸。第二,时间响应式里的k从0开始,不是从1,这样x1_hat(1)=x0(1),保证还原序列第一个值和原始数据完全一致。第三,diff函数用来做累减还原非常简洁,但注意它返回的序列长度比原序列少1,所以前面必须要补上gm.x1_hat(1)。

4.3 马尔科夫修正函数实现

function mk = markov_correct(x_actual, x_fit, x_gm_fut, n_states) % markov_correct 基于相对误差的马尔科夫修正 % 输入: % x_actual - 训练期真实值 % x_fit - GM训练期拟合值,长度与x_actual相同 % x_gm_fut - GM未来预测值 % n_states - 状态个数 % 输出: % mk.x_final - 修正后的未来预测值 % mk.P - 状态转移概率矩阵 % mk.state_mean- 每个状态的残差均值(%) % mk.init_state- 最后训练点的残差状态 x_actual = x_actual(:)'; x_fit = x_fit(:)'; x_gm_fut = x_gm_fut(:)'; n = length(x_actual); % 从第2个点开始计算相对误差(%),第一个点残差恒为0 re = (x_actual(2:end) - x_fit(2:end)) ./ x_actual(2:end) * 100; % 残差区间几乎没有跨度时,修正没有意义 if max(re) - min(re) < 0.5 warning('历史残差过小,马尔科夫修正意义不大,返回GM原值。'); mk.x_final = x_gm_fut; mk.P = []; mk.state_mean = []; mk.init_state = []; return; end % 状态划分:在min和max之间均分 edges = linspace(min(re), max(re), n_states+1); state_idx = zeros(size(re)); for i = 1:n_states if i == 1 idx = (re >= edges(i)) & (re <= edges(i+1)); else idx = (re > edges(i)) & (re <= edges(i+1)); end state_idx(idx) = i; end % 状态转移频数矩阵 C = zeros(n_states, n_states); for i = 1:n-2 from = state_idx(i); to = state_idx(i+1); C(from, to) = C(from, to) + 1; end % 归一化为转移概率矩阵,全零行做均匀分布兜底 P = zeros(n_states, n_states); for i = 1:n_states if sum(C(i,:)) > 0 P(i,:) = C(i,:) / sum(C(i,:)); else P(i,:) = 1 / n_states; end end mk.P = P; % 各状态残差均值 state_mean = zeros(1, n_states); for i = 1:n_states vals = re(state_idx == i); if ~isempty(vals) state_mean(i) = mean(vals); else state_mean(i) = mean(re); end end mk.state_mean = state_mean; % 初始状态:最后训练点的残差状态 init_state = state_idx(end); mk.init_state = init_state; % 从初始状态出发,逐点预测未来状态(最大概率路径) n_pred = length(x_gm_fut); future_states = zeros(1, n_pred); cur_state = init_state; for j = 1:n_pred [~, nxt] = max(P(cur_state, :)); future_states(j) = nxt; cur_state = nxt; end % 修正公式: actual = forecast / (1 - re/100) corr_factor = 1 ./ (1 - state_mean(future_states) / 100); % 限幅,防止修正过冲 corr_factor = max(corr_factor, 0.85); corr_factor = min(corr_factor, 1.15); mk.x_final = x_gm_fut .* corr_factor; end

这段代码是模型的核心,复杂度不算高,但状态划分和转移矩阵统计的逻辑一定要看仔细。我在里面用等分区间的方式划分状态,好处是简单透明,你可以直接在注释里看到状态边界是怎么来的;缺点是如果残差分布极不均匀,某些状态可能一个样本都没有,所以我在后面做了全局均值兜底。

4.4 主脚本与绘图

clear; clc; close all; % 2014-2023年某地区全社会用电量(亿千瓦时) x0_all = [35.2, 38.7, 37.5, 42.1, 45.8, 44.2, 50.6, 53.1, 52.4, 58.7]; year_all = 2014:2023; n_train = 8; n_pred = 2; % 预测未来2年,正好和验证集长度一致 x0 = x0_all(1:n_train); y_true = x0_all(n_train+1:n_train+n_pred); % 1) GM(1,1) gm = gm11_predict(x0, n_pred); x0_hat_fit = gm.x0_hat(1:n_train); x0_hat_fut = gm.x0_hat(n_train+1:end); fprintf('GM参数: a = %.4f, u = %.4f\n', gm.a, gm.u); fprintf('级比检验: %s\n', string(gm.sigma_ok)); % 2) 马尔科夫修正 n_states = 3; mk = markov_correct(x0, x0_hat_fit, x0_hat_fut, n_states); x_mk_fut = mk.x_final; fprintf('\n转移概率矩阵:\n'); disp(mk.P); fprintf('训练期最后状态: %d\n', mk.init_state); % 3) 结果对比 fprintf('\n%-6s %6s %8s %12s\n', '年份', '实际值', 'GM预测', '马尔科夫修正'); for i = 1:n_pred fprintf('%-6d %6.1f %8.2f %12.2f\n', ... year_all(n_train+i), y_true(i), x0_hat_fut(i), x_mk_fut(i)); end % 4) 绘图 figure('Position', [100 100 900 500]); plot(year_all, x0_all, 'ko-', 'LineWidth', 1.5, 'MarkerFaceColor', 'k'); hold on; plot(year_all(1:n_train), x0_hat_fit, 'gs--', 'LineWidth', 1.0); plot(year_all(n_train+1:end), x0_hat_fut, 'r^-', 'LineWidth', 1.5); plot(year_all(n_train+1:end), x_mk_fut, 'bo-', 'LineWidth', 1.5); xlabel('年份'); ylabel('用电量(亿千瓦时)'); legend('实际值', 'GM拟合', 'GM预测', '马尔科夫修正', 'Location', 'northwest'); grid on;

主脚本的逻辑没有悬念,但有两个输出细节我想多说两句。disp(mk.P)直接打印矩阵,适合在调试阶段确认转移矩阵是否出现全零行;绘图时我把GM拟合值单独用绿色虚线画出来,这样可以明显看到GM在训练期的拟合值和实际值之间的偏差形态,帮助判断马尔科夫修正是否合理。

5. 案例实测:某地区十年用电量预测对比

5.1 数据与建模策略

案例数据我用的是某地区2014到2023年的全社会用电量,整体呈上升趋势,但中间有几次回落,比如2016年37.5比2015年38.7低,2019年44.2比2018年45.8低。这种“大趋势向上、局部波动”的数据,正是灰色马尔科夫最典型的应用场景。

建模策略是:前8年数据做训练,后2年做验证。也就是用2014到2021年的数据建模,预测2022和2023,然后拿真实值对照。这样做比把全部十年数据都拿去建模更有说服力,因为能直观看到模型的泛化能力。

5.2 运行结果与效果对比

脚本跑完,输出大概是这样:

GM参数: a = -0.0509, u = 34.7341 级比检验: true 转移概率矩阵: 0.3333 0.3333 0.3333 0.5000 0.0000 0.5000 0.3333 0.0000 0.6667 训练期最后状态: 3 年份 实际值 GM预测 马尔科夫修正 2022 52.4 55.60 53.22 2023 58.7 58.43 58.52

可以看出,GM把2022年预测到了55.6,比实际52.4高出一大截,相对误差约6.1%;经过马尔科夫修正后降到53.2,误差压缩到1.5%左右。2023年GM本身预测得还不错,58.4对58.7,修正后58.5,几乎维持原样。

为什么会这样?因为训练期最后几个点的GM残差处于状态3,查转移矩阵后2022年最大概率转入状态1,而状态1的历史残差均值是负的,说明GM在这个状态下普遍高估,于是修正系数小于1,预测值被压了下来。这个机制不是人为拍脑袋,而是从历史转移规律里自动学出来的。

5.3 参数调整:改变状态个数和预测步数

这个案例里n_states=3效果很好,但我用同一组数据试过n_states=5,发现转移矩阵里有多行全是0,部分状态只出现一次,转移概率估计基本靠“孤证”,2022年修正结果反而偏差更大。这说明状态划分必须和样本量匹配,8个训练点对应5个状态,平均每个状态分不到两个样本,统计规律无从谈起。

预测步数方面,我把n_pred改成5试过一次,前两步还勉强能看,到第4步预测值就出现明显漂移,因为马尔科夫链沿着最大概率路径越走越偏。我的结论很明确:灰色马尔科夫适合短期预测,1到3步是舒适区,超出这个范围就要考虑滚动预测,也就是每预测一步就把真实值加入训练集重新建模。

另外一个值得试的参数是训练集长度。我把n_train从8改成7时,GM的趋势项变化不大,但马尔科夫的转移矩阵因为少了一个转移样本,部分概率值发生了明显跳变。所以训练样本如果少于8个,我对马尔科夫修正的结果会保守一点,宁愿只用GM原值加个简单经验修正,也不用状态转移机制。

6. 常见问题与排查技巧实录

6.1 级比检验不通过该怎么办

如果gm.sigma_ok输出false,先别急着继续跑。最稳妥的补救是平移变换:

shift = max(x0) * 2; x0_shift = x0 + shift; % 用x0_shift建模,得到预测值后再减shift

平移量到底取多少没有绝对标准,原则是能通过级比检验,同时不要改变序列的相对形态。取对数变换也能用,但预测还原时要先exp再减平移量,多一步逆向运算更容易出错,我通常优先用平移。

6.2 马尔科夫修正后反而更差

这个问题几乎每个用这套模型的人都会遇到一次。最常见的原因是状态划分不合理,残差的正负分布严重不对称,导致某个状态的均值绝对值过大,修正系数触碰到限幅边界。解决办法是先画出训练期残差条形图,观察正负残差的分布形态,再决定状态个数。

第二个原因是状态转移矩阵的概率结构太“锋利”。比如矩阵里存在0.8和0.2这种悬殊概率,最大概率路径基本固定死在某个状态上,修正方向也一路走到黑。遇到这种情况,建议放弃最大概率路径,改用概率加权的修正方式:把当前行的所有状态按概率加权平均出一个修正系数,而不是只取最大概率的单一状态。

6.3 转移概率矩阵出现全零行

全零行意味着某个状态从未作为起点出现过,这和状态划分太细或者样本量不足有关。我的代码里已经用均匀分布做了兜底,但兜底只是“不报错”,不代表结果可靠。真正要做的还是回到数据层面,调低n_states,或者把空状态合并到相邻状态。

调试小技巧:在主脚本里用disp(mk.P)把矩阵打出来看,如果有一整行都是0,就调整状态个数;如果只有个别零值,不影响主路径可以忽略。

6.4 两个容易忽略的Matlab细节

第一,Matlab里数组除法和矩阵除法要分清。我见过有人把 ./ 写成 / 导致结果完全错误,比如re计算的相对误差序列变成矩阵求逆的报错。第二,函数的输入向量方向要统一。gm11_predict内部我已经用x0 = x0(:)'强制转成行向量,但你自己扩展代码时也要注意,否则B'*Y的维度会让你排查半天。

另外,这套代码不需要额外工具箱,纯基础Matlab就能跑,版本2016之后都能兼容。我经常遇到有人在知乎或贴吧问“为什么我的Matlab报错无法识别函数”,十有八九是函数没保存在当前路径,或者文件名和函数名不一致。记住:函数文件名必须与函数名完全同名,否则Matlab找不到入口。

最后想说的几句

我用灰色马尔科夫这套组合做过不少小样本预测任务,从用电量到设备故障率都有。最大的体会是:模型组合的思路比单个模型本身更重要。GM(1,1)单独用,遇到波动数据就会显得“笨”;马尔科夫链单独用,没有趋势骨架又不知道从哪里预测起。两者一结合,刚好互相补短板。

在实操中,我建议新手先把代码跑通,再用自己的数据替换,最后才去纠结状态个数和转移矩阵的细节。参数调优必须有数据反馈,不要凭感觉预设状态数。如果在你的数据上修正效果不明显,先检查GM是否已经拟合得很好——如果残差本身就在正负2%以内,马尔科夫修正属于可选项,而不是必选项。

这套方法还可以继续扩展,比如用滑窗方式每次加入新数据重新估计GM参数,或者用二阶马尔科夫链捕捉更长时间跨度的状态依赖。但在那些进阶玩法之前,先把今天这套基础代码跑明白,比什么都强。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询