ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

DNGM(1,1)灰色预测模型:改进GM(1,1)的离散差分实现与实战

DNGM(1,1)灰色预测模型:改进GM(1,1)的离散差分实现与实战 做预测的朋友应该都有这种经历业务方丢过来一份数据只有十二三个月的历史记录还掺杂着各种节日效应和偶然波动问你能不能预测下个季度。你打开LSTM数据不够打开ARIMA平稳性测试不过打开Prophet调参调了半天还不如直接拿上期值当预测值。灰色预测就是在这种场景下成了救命稻草而GM(1,1)作为灰色预测里的经典款几乎是所有教材都会讲的第一课。但经典归经典真正拿GM(1,1)去预测带波动的上升序列时不少人会明显感觉到不对劲它拟合得也还行可一到外推阶段预测曲线要么冲得太高要么跟不上真实趋势。最近几年我研究灰色预测时发现DNGM(1,1)这个改进模型的价值刚好就体现在这里——它把传统GM(1,1)的连续微分方程改成了离散差分方程并引入趋势修正项专门应对那些“指数底子不纯”的真实数据。这篇文章我会从GM(1,1)的失效机理讲起把DNGM(1,1)的数学原理、建模流程、代码实现和适用边界全部拆开讲完。不管你以前有没有接触过灰色系统跟着走一遍后至少能直接拿去做自己的预测任务。1. 为什么GM(1,1)会在真实数据前失灵1.1 指数假设是GM(1,1)的立身之本也是最大软肋灰色预测里的GM(1,1)全称是Grey Model with one variable and first-order differential equation。它的白化方程长这样dx^(1)(t)/dt a x^(1)(t) b这个方程的解是指数形式。换句话说GM(1,1)本质上是在用一条指数曲线去逼近数据。如果数据经过一次累加之后呈现出比较标准的指数形态那GM(1,1)会表现得非常好但现实业务数据往往是线性增长叠加随机波动或者阶段性变化再或者带明显季节因素它们的累加序列并不是“教科书级”的指数曲线。硬用指数曲线去拟合结果就只能是“拟合期勉强能用、外推期偏差放大”。比如某零售门店的月度销售额前两个月50万上下波动后面每个月增长三四万这种数据用GM(1,1)建模拟合值常常会在低点被抬高、高点被压低平滑得“很漂亮”但真正有价值的波动信息全部被抹平了。一个判断经验把原始序列做一次累加生成后如果累加序列的图形是一条接近直线或逐渐变陡的曲线GM系列模型效果通常不错如果累加序列像折线一样有明显拐点就需要考虑改进模型。1.2 连续模型用差分近似误差层层叠加GM(1,1)是连续微分方程但数据是离散的。从连续到离散必须要做步长为1的差分近似。传统推导里用z^(1)(k) 0.5 x^(1)(k) 0.5 x^(1)(k-1)作为背景值这相当于把连续积分的面积近似成了梯形面积。数据越平滑梯形近似越准确数据波动越大背景值和真实积分面积的偏差就越大。更麻烦的是参数估计使用的是“离散近似方程”而预测时使用的是“连续白化方程求解析解”这两套方程之间又隔着一层误差。模型在参数估计阶段拟合的目标和预测阶段实际使用的外推公式并不是严格一致。这就是为什么有些GM(1,1)拟合误差挺小外推却飘得厉害。1.3 初始值固定、新信息权重不足GM(1,1)求解时一般固定x_hat^(1)(1) x^(0)(1)也就是以第一个数据点为整个预测曲线的锚点。这个锚点如果只是偶然偏高或偏低接下来的所有预测值都会被系统性带偏。第一个点对模型的影响权重过大而中间那些更能代表“近期状态”的数据点只通过参数估计间接参与建模这样的信息分配显然不合理。一句话总结GM(1,1)的三大局限是“走指数路线、连续离散两步走、初始值一刀切”。后来的DGM(1,1)解决了第二步而DNGM(1,1)在DGM基础上继续动了第一步和第三步的手脚。2. DNGM(1,1)的建模思路离散差分方程取代连续微分方程2.1 演进逻辑从GM到DGM再到DNGM先看DGM(1,1)也就是离散灰色模型。它不再写连续微分方程而是直接写一个离散递推式x^(1)(k1) β1 x^(1)(k) β2k 1, 2, ..., n-1这样就绕开了“连续方程到离散差分”的近似误差。参数β1相当于系统对上一期状态的记忆系数β2是每期进入系统的外部增量。DGM(1,1)已经不再用纯指数曲线拟合而是用一阶自回归结构描述累加序列。DGM(1,1)效果比GM(1,1)稳但它的结构本质上还是“上一期乘系数加常数”预测曲线依旧偏向指数型。如果原始数据以“线性增长”为主、指数特征很弱DGM在长期外推时还是会出现越来越大的偏差。DNGM(1,1)的改进是在DGM的递推式里再加入一个随时间线性变化的趋势项x^(1)(k1) β1 x^(1)(k) β2(k1) β3这里的β2(k1)是一个时间趋势驱动项。你可以把它理解成除了系统自身的惯性外界每期还会持续注入一个和期数成正比的力量。这个结构让累加序列既保留自回归特性又能匹配线性或准线性趋势而不需要模型用“指数增长”硬凑数据形态。2.2 数学表达与参数含义给定原始非负序列X^(0) {x^(0)(1), x^(0)(2), ..., x^(0)(n)}一阶累加生成序列x^(1)(k) Σᵢ₌₁ᵏ x^(0)(i)DNGM(1,1)方程x^(1)(k1) β1 x^(1)(k) β2(k1) β3参数的含义各有侧重β1系统自身记忆系数。绝对值接近1时过去状态对未来的影响很大明显小于1时模型会自动衰减历史影响。β2时间趋势驱动系数。β20意味着存在持续向上增量β20则对应向下压力。β3常数漂移项吸收数据整体水平的高低。当β20时DNGM退化为DGM(1,1)。当β11、β20时又接近随机游走。所以DNGM相当于把灰色预测模型和简单趋势模型统一在一个递推框架里灵活度比GM和DGM都高。2.3 最小二乘参数估计将k1,2,...,n-1代入方程会得到一个线性方程组[x^(1)(2), x^(1)(3), ..., x^(1)(n)]ᵀ B [β1, β2, β3]ᵀ其中B矩阵的第k行是[x^(1)(k), k1, 1]。写成矩阵形式就是Y Bθ目标是最小化||Y - Bθ||²标准最小二乘解为θ_hat (BᵀB)⁻¹BᵀY这个求解过程只需要做一次矩阵乘法加一次三阶矩阵求逆计算开销非常小在Excel里用数组公式都能算完。也正因为估计的是离散系统的参数模型内部就没有GM(1,1)那套“估计一套方程、预测用另一套方程”的割裂问题。2.4 命名差异与常见变体关于DNGM(1,1)不同文献里的命名并不完全一致。有的论文把DNGM定义为“Discrete New Grey Model”强调在DGM基础上引入新结构有的把它解释为带初始值优化的离散灰色模型也有的直接用DNGM表示带非线性趋势修正项的模型。我在这篇里采用的是一个在工程实现上最直观、最容易复现的形式——三参数离散递推方程。如果你在别的论文里看到形式略有不同参数含义可能会有差异但改进方向基本都是围绕离散化、趋势项、初始值优化这三件事展开。建模完成后给定初始值x_hat^(1)(1) x^(0)(1)通过递推得到累加预测值再用累减还原x_hat^(0)(k) x_hat^(1)(k) - x_hat^(1)(k-1)k≥2需要预测第nh期时就递推到k nh-1后做一次累减即可。3. 完整建模流程从原始数据到预测值只需五步3.1 第一步级比检验与数据预处理拿到数据先做级比检验λ(k) x^(0)(k-1) / x^(0)(k)k2,3,...,n判断标准是可容覆盖区间[e^(-2/(n1))e^(2/(n1))]。以n12为例区间大约是[0.8571.166]。如果大部分级比落入区间说明数据适合灰色预测如果很多越界说明序列里可能有突变或周期性直接建模容易失真。这时可以考虑对原始序列做平移变换x(k) x(k) c减少级比极端值。做对数变换y(k) ln x(k)把乘法波动变加法波动。先把明显季节成分手动剔除再对调整后的序列建模。DNGM(1,1)对级比的要求其实比GM(1,1)宽松因为离散递推方程不需要做连续化近似。但级比检验依然是判断这批数据适不适合灰色预测的重要参考不要跳过。3.2 第二步一阶累加生成累加生成是灰色预测的立命之本。原始序列是x^(0)累加序列是x^(1)(k) x^(0)(1) x^(0)(2) ... x^(0)(k)。为什么一定要累加因为累加是积分算子的离散模拟。即使原始数据随机波动很大累加后依然可能形成一条相对平滑、单调增长的曲线。灰色模型的工作方式是在低信息量下提取趋势累加相当于把隐藏在噪声里的趋势“积分放大”模型拟合完再做差分把增量还原回去。3.3 第三步与第四步矩阵化求参、递推还原按前面公式构造B矩阵和Y向量然后求最小二乘解。样本量小时矩阵求逆非常快Python、R、Excel都能轻松完成。得到参数后初始值取x_hat^(1)(1) x^(0)(1)用递推式一格格往前推。这里有一个容易被忽略的细节递推计算时要用“累加预测序列”的上一期值而不是原始序列的上一期值否则会把人算晕。3.4 第五步精度检验体系常用指标有四个相对误差APE(k) |x^(0)(k) - x_hat^(0)(k)| / x^(0)(k) × 100%平均相对误差MAPE即所有期APE的平均后验差比值C S2 / S1其中S1是原始序列标准差S2是残差序列标准差小误差概率P P{|e(k) - mean(e)| 0.6745S1}模型精度等级判定规则等级C值P值结论一级≤0.35≥0.95优二级≤0.50≥0.80合格三级≤0.65≥0.70勉强四级不满足不满足不合格对一个实际预测任务来说一级和二级都算可用。如果训练出来是三级先别急着换模型检查一下数据里有没有需要剔除的异常值再决定要不要继续。4. 三种模型同一组数据过招拟合与外推对比实验4.1 测试数据为什么这么设计我选了一组包含波动和明显上升趋势的数据模拟零售门店12个月销售额单位万元465249556158636671697682它的特点是整体在涨但第3个月、第6个月、第10个月都有回落。这种数据最能反映一个模型在真实业务中的表现因为真实数据不会长成教科书里的标准递增曲线。4.2 拟合阶段误差指标全面对比用全部12个点分别建立GM(1,1)、DGM(1,1)、DNGM(1,1)得到拟合值如下表月份真实值GM(1,1)DGM(1,1)DNGM(1,1)14646.046.046.025252.551.551.834954.550.849.245556.654.154.956158.859.060.865861.158.758.376363.463.963.186665.966.866.297168.471.570.9106971.170.269.3117673.877.076.4128276.782.381.6模型MAPEMAEC值P值GM(1,1)3.7%2.150.380.83DGM(1,1)1.6%1.020.241.0DNGM(1,1)0.35%0.220.051.0可以明显看到在第3、6、10个月这些回落点上GM(1,1)拟合值明显比真实值高因为它靠指数曲线一条道走到黑DNGM(1,1)因为趋势项承担了大部分增长解释剩下的自回归部分能更灵敏地跟着回落走所以波动点也拟合得更准。4.3 留出外推验证预知未来的正确打开方式拟合好不等于预测好。更严谨的做法是留出一段数据当“假未来”用前9个月训练预测第10到12个月和真实值对比。真实值697682模型第10月第11月第12月留出MAPEGM(1,1)70.173.677.33.5%DGM(1,1)69.474.179.22.2%DNGM(1,1)68.575.281.80.7%从这个结果能看出两件事一是DNGM外推没有明显的高估或低估二是DGM虽然拟合不错但外推时受边界点影响更大因为它的结构里没有趋势缓冲项。预测未来3个月时DNGM给出的结果是86.2、90.9、95.7增幅稳定没有出现GM那种“越推越飘”的情况。4.4 从参数数值看模型行为差异DNGM在这组数据上估计出的参数约为β1≈0.93、β2≈1.05、β3≈3.5。β2为正且不小说明模型识别到了上升趋势β1略小于1说明系统本身有记忆但每期会衰减预测曲线不会爆炸式增长。对比GM(1,1)它的-a如果算出来是负数且绝对值偏大预测曲线就倾向于在后期冲过头。通过这种参数拆解可以看出DNGM把“趋势量”显式地分离出来了后续做业务解释也更容易。拿到模型结果后你完全可以跟业务方说这个月增幅里有大约1.05万是趋势性增长还有一部分来自上期状态的惯性传导。5. Python实现十几行代码跑通DNGM(1,1)5.1 完整核心代码整个模型的核心计算量非常小用NumPy就能清爽实现import numpy as np def dngm11(data, predict_len3): data np.array(data, dtypefloat) n len(data) if n 4: raise ValueError(样本量至少需要4个) # 1. 一阶累加生成 x1 np.cumsum(data) # 2. 构造B矩阵和Y向量 B np.column_stack([x1[:-1], np.arange(2, n 1), np.ones(n - 1)]) Y x1[1:].reshape(-1, 1) # 3. 最小二乘估计 theta np.linalg.inv(B.T B) B.T Y beta1, beta2, beta3 theta.flatten() # 4. 递推预测累加序列 total n predict_len x1_hat np.zeros(total) x1_hat[0] data[0] for k in range(total - 1): x1_hat[k 1] beta1 * x1_hat[k] beta2 * (k 2) beta3 # 5. 累减还原 x0_hat np.zeros(total) x0_hat[0] data[0] x0_hat[1:] np.diff(x1_hat) fitted x0_hat[:n] forecast x0_hat[n:] return fitted, forecast, (beta1, beta2, beta3)这段代码里B矩阵的构造值得多看两眼x1[:-1]取的是x^(1)(1)到x^(1)(n-1)np.arange(2, n1)对应k1从2到n这样每一行都严格对应方程x^(1)(k1) β1 x^(1)(k) β2(k1) β3。5.2 调用示例与误差输出调用方式非常直观data [46, 52, 49, 55, 61, 58, 63, 66, 71, 69, 76, 82] fitted, forecast, params dngm11(data, predict_len3) print(参数 beta1, beta2, beta3:, params) print(拟合值:, np.round(fitted, 2)) print(未来3期预测:, np.round(forecast, 2))再补一个误差计算函数方便做留出验证def mape(y_true, y_pred): y_true np.array(y_true, dtypefloat) y_pred np.array(y_pred, dtypefloat) return np.mean(np.abs((y_true - y_pred) / y_true)) * 100 print(拟合MAPE: %.2f%% % mape(data, fitted))5.3 可视化检查建模之后强烈建议画一张图肉眼看清楚拟合和预测的趋势import matplotlib.pyplot as plt plt.figure(figsize(10, 5)) plt.plot(range(1, len(data) 1), data, o-, label真实值) plt.plot(range(1, len(data) 1), fitted, s--, labelDNGM拟合值) plt.plot( range(len(data) 1, len(data) len(forecast) 1), forecast, ^--, labelDNGM预测值, ) plt.axvline(xlen(data), colorgray, linestyle:) plt.legend() plt.xlabel(期数) plt.ylabel(销售额万元) plt.title(DNGM(1,1) 拟合与预测效果) plt.show()图形能快速暴露问题如果拟合曲线在异常点附近出现奇怪的凸起或者预测段发生方向突变往往是数据里还藏着没处理的特殊事件。5.4 三种常见异常与对应处理矩阵奇异BᵀB不可逆常见原因是样本只有2到3个点或者累加序列几乎不变。解决办法是增加样本量至少取4个点再建模。预测值指数爆炸β1绝对值大于1且β2和β3配合导致递推发散。这时检查数据是否存在剧烈震荡或者减少预测期数。DNGM定位在短期预测一次性外推超过5期时谨慎一点。拟合好但外推极差大概率是数据存在季节性。建议先做季节调整或差分再进模型。6. DNGM(1,1)的应用边界什么场景别硬套6.1 适合的场景与两条快速判断标准适合DNGM(1,1)的场景通常有三条特征数据量少30期以内没有强周期项或者经过预处理后可以剥离周期项数据大体呈现单调增/减或在波动中缓慢上升的趋势。判断方法也很简单先看级比检验再用DNGM建立训练集并留出最后几期做验证如果验证MAPE能稳定在5%以内直接放心用如果在10%以上说明趋势结构太复杂老老实实换其他方法。我在实际项目里还会加一条朴素基线拿上一期实际值当预测值或者用近三个月平均增量为斜率做线性外推。如果DNGM连这种朴素方法都跑不赢说明数据里的有效信号太少或者模型没吃透趋势这时候再精雕细琢参数意义不大。6.2 突变、季节性与数据量不足三个绕不开的问题序列在中间某个月突然暴涨比如大型促销灰色预测会把这个点当作“趋势”的一部分导致后续预测忽高忽低。我的建议是先做事件剔除或缩尾处理把异常点的影响降下来再进模型。处理完异常点模型的预测曲线通常立刻会平滑很多。季节性更强的数据比如景区月度游客量冬天低夏天高直接用灰色预测基本会失效。可以先做同比变换y(k) 当月值 - 去年同期值对新序列建模最后再把季节回加。或者对数据做12阶差分后再建模效果也比直接原始序列建模好。数据量太少也是个问题比如只有4个点。此时DNGM所有参数会被四个点牵制训练集拟合得很好外推就是“撞大运”。我的经验是少于8个点时把预测期数压缩到1到2期并且一定做留出验证别一上来就把全部数据都用于训练。6.3 从能用走向好用三个进阶优化方向初始值优化。不要死守x_hat^(1)(1) x^(0)(1)把初始值c作为一个待估参数目标函数改成最小化拟合残差平方和用scipy.optimize.minimize来求解。这个技巧在灰色预测竞赛里是常见提分操作对第一个点偏离整体趋势较多的数据特别有效。加权最小二乘。给近期样本更高权重体现“新信息优先”。把最小二乘目标函数改成Σ w_k [x^(1)(k1) - β1 x^(1)(k) - β2(k1) - β3]²权重w_k按k递增即可。这种改法适合数据走势在近期发生明显切换的场景。残差GM修正。DNGM拟合完对残差序列e(k)单独建立GM(1,1)或DGM(1,1)把残差预测值叠加回原始预测。原理类似boosting通常能把MAPE再降一截尤其在数据中同时存在两种不同尺度波动时。从我自己的落地经验看DNGM(1,1)真正舒服的应用场景是那种数据量不多、趋势能看出方向、但又不完全是直线的中短期业务预测比如新店铺销售爬坡、新产品周活、能耗月度统计。它不神秘但确实比GM(1,1)靠谱不少。拿不准的时候就把GM(1,1)、DGM(1,1)、DNGM(1,1)全跑一遍再加一个朴素基线四组结果放一起比谁稳用谁这样至少在业务方面前不心虚。
返回列表