搞预测的人估计都有同感数据量不够大的时候神经网络不敢用回归模型又经常被异常点带偏。这时候灰色模型往往是大家第一个想到的救兵。而如果你真正拿GM(1,1)去比赛或者做小样本预测很快就会发现一个值得玩味的问题累加生成、白化微分方程、最小二乘求解这套“老三样”写在PPT里特别漂亮可面对真实数据时经常一波三折——级比检验出界、还原误差莫名其妙偏大、预测结果总觉得少了一条趋势线。今天要聊的DNGM(1,1)模型正是冲着这几个老毛病去的。可以把它理解成“离散化的非齐次灰色预测模型”。它用差分递推替代微分方程在驱动项里加入随时间变化的线性项专门用来补传统GM(1,1)在非齐次增长、线性趋势这类小样本序列上的短板。如果你正在做销量预测、用电量预测、客流预测或者论文里需要一个小样本建模方法这篇东西值得花十分钟读完。1. 传统GM(1,1)成名绝技与固有瓶颈1.1 经典流程并不复杂但背后有一整套“妥协”先快速过一遍GM(1,1)的标准流程。它总共五步第一步准备非负的原始序列x(0)(k)k1,2,…,n第二步对原始序列做一次累加生成AGO得到累加序列x(1)(k)第三步假设累加序列满足一阶线性白化微分方程dx(1)/dt a·x(1) b第四步用背景值z(1)(k)0.5·[x(1)(k)x(1)(k-1)]做离散化再用最小二乘法估计参数a和b第五步求出时间响应式累减还原成原始序列的拟合值和预测值。为什么GM(1,1)长期受欢迎因为它抓住了小样本预测的核心矛盾样本越少越不能指望统计方法估计出复杂分布。累加就是那招“化随机为平滑”的关键——把一页一页看起来乱糟糟的进货台账变成月底累计数趋势一下就露出来了。灰色模型正是因为利用了这种平滑性才敢在只有三五个数据点的时候出手。但注意这一步已经开始“妥协”了。GM(1,1)假设系统发展符合一阶微分方程而这个方程的解天然是指数函数。它天生适合“每期变化量按固定比例增长”的数据比如每年销售额增长10%。可现实中的小样本数据经常不是这种规律而是“增长量本身在变化”某产品一季度多卖5台二季度多卖7台三季度多卖9台。这不是齐次指数增长而是线性趋势驱动的非齐次增长。把这种序列硬塞进齐次指数模型误差自然会冒出来。1.2 三大先天局限越用越明显我把这些年用GM(1,1)踩过的坑做了个总结它们不是调参能解决的属于模型设计层面的大问题。第一微分方程离散化的信息损耗。GM(1,1)在离散化时使用背景值z(1)(k)本质是用梯形面积近似积分。梯形公式默认累加曲线在相邻两点之间是直线可实际累加序列往往更像指数曲线。步长越大这个近似误差就越大最后全部传导到发展系数a上。你检查参数时可能会发现a明明估得很准但还原出来的原始序列却对不上十有八九就是这一步损耗掉了。第二模型、估计、预测三层不一致。白化方程是连续形式参数却由离散数据估计预测时又回到离散还原。相当于你准备用一把欧式尺子量零件却用英式刻度标了读数到最后再换算回去误差在两头都要承担一遍。这种不一致对于理论推导可能无所谓但工程落地时很痛。第三驱动项过于朴素。经典GM(1,1)的方程右侧只有一个常数b相当于假设系统外部作用力恒定不动。可许多实际系统的外部作用本身随时间变化促销力度逐月加大、市场渗透率逐步提升、政策每年加码。恒定驱动项拟合不出这种动态。这也是为什么很多老手对GM(1,1)的结论又爱又恨小样本场景下它确实能算出结果但结果经常是“看着有趋势细看差口气”。2. DNGM(1,1)的核心原理差分递推非齐次驱动项2.1 命名拆解D、N、G、M各自代表了什么DNGM(1,1)这个缩写在灰色系统文献里最常被解释成Discrete Non-homogeneous Grey Model也就是离散非齐次灰色模型。数字(1,1)和GM(1,1)的含义一样一阶差分、一个系统变量。D代表“离散化”N代表“非齐次”这两个词几乎就是它存在的全部理由。市面上关于这类模型的叫法并不完全统一。有人把它写成NGM(1,1,k)的离散改进版有人直接叫DGM(1,1)的扩展不同的论文里细节略有出入。但这不影响理解核心思想把传统GM(1,1)的连续白化方程换成一族线性差分递推关系并在递推项中加入时间变量k。只要抓住这个骨架文献里再换什么马甲你都能认出来。2.2 模型式子与计算逻辑设原始序列为x(0)(1), x(0)(2), …, x(0)(n)一次累加后得到x(1)(1), x(1)(2), …, x(1)(n)。DNGM(1,1)采用的递推式是x(1)(k) β1 · x(1)(k-1) β2 · k β3k2,3,…,n右边第一项是累加序列的“惯性项”第二项是随时间变化的线性驱动项第三项是常数驱动项。三个参数共同决定下一个累加值。这个式子看起来像一元回归但它和普通回归有明显区别回归预测的是原始输出这里预测的是累加序列。原始序列的随机扰动经过累加后被压制了递推式的输入噪声自然更小。这和深度学习里“对信号先做移动平均再预测”的思路一个道理。参数β1、β2、β3用最小二乘估计。把k2到n的式子写成矩阵形式Y B·θ其中Y是x(1)(2)到x(1)(n)组成的列向量B的第一列是x(1)(1)到x(1)(n-1)第二列是时间点2,3,…,n第三列全是1。解为θ (BᵀB)⁻¹BᵀY。矩阵维数只有3×3计算负担极小这也是灰色模型家族适合工程落地的优势之一。2.3 为什么离散递推能突破传统局限用一句话概括DNGM(1,1)让“模型假设”和“数据生成过程”更一致了。把x(1)(k)想象成月底累计销量。GM(1,1)的做法是先假设每天销量按固定比例增长拟合出一条连续指数曲线再反过来逐日还原。DNGM(1,1)的做法则更直接用上月底累计值加上本月的增长规律算出本月底累计值。每一步都在模仿真实世界里数据的递推过程中间少了“连续曲线→离散取值”这一步转换误差自然更小。再看非齐次项β2·k带来的能力扩展。GM(1,1)的指数解相当于给每个相邻点的变化量绑定了同一个增长因子而β2·k允许这个变化量随着时间线性增减。数据整体呈线性上升时β2会主导数据呈指数上升时β1会主导两者叠加时模型还能自动分配权重。一个小式子覆盖了更多数据形态这叫结构性增强不是单纯调参可以媲美的。3. 手把手建模实操从级比检验到参数估计3.1 建模前检查级比检验和预处理建模前先看数据配不配得上灰色模型。拿一个我常用的示意数据某产品近五期销量为15、20、27、36、49。原始序列必须非负这点不用多说随后要算级比λ(k)x(0)(k-1)/x(0)(k)看是否落在可行区间内。当样本量n5时可行区间是e^(-2/6)到e^(2/6)也就是约0.7169到1.3956。这段序列的级比分别是20/151.3333、27/201.35、36/271.3333、49/361.3611全部落在区间内说明数据符合灰色建模的基本要求。如果级比出界怎么办常见做法是对整个序列做平移变换也就是每个数加上同一个常数c。平移量太小不起作用太大又可能把趋势变化抹平我的习惯是写个循环从c1开始逐次增加直到所有级比都落在可行区间。也可以用开方变换或对数变换但要注意变换会影响还原后的误差指标后续解释起来会麻烦很多。这一步请务必不要跳过级比检验是灰色模型的“安全网”我见过太多人图省事直接建模型结果后验差比值惨不忍睹。3.2 一次累加与矩阵装配原始序列做一次累加得到x(1) {15, 35, 62, 98, 147}把k2,3,4,5代入递推式得到四个方程。矩阵形式如下Y {35, 62, 98, 147}ᵀB的第一列是x(1)(1)到x(1)(4)即15、35、62、98第二列是2、3、4、5第三列是1、1、1、1。完整的矩阵B是| 15 | 2 | 1 | | 35 | 3 | 1 | | 62 | 4 | 1 | | 98 | 5 | 1 |计算BᵀB和BᵀY后解最小二乘方程得到三个参数β1 ≈ 1.3769累加序列惯性系数 β2 ≈ -0.8037线性驱动项系数 β3 ≈ 16.0235常数驱动项β1在1附近说明累加序列保留了很强的惯性β2为负说明只看时间项的话增长速度在边际上有所放缓。这两个系数放在一起正好模拟了“基数变大以后增长放缓”的真实场景。不少第一次接触这个模型的人看到β2是负值会慌其实完全没必要它是模型表达非线性趋势的方式。3.3 预测还原与误差对照外推第6期时以实际累加终值147作为递推起点x(1)_hat(6) 1.3769 × 147 (-0.8037) × 6 16.0235 ≈ 213.63第6期销量预测值 213.63 - 147 ≈ 66.63。如果这期真实销量是64相对误差约4.1%如果真实值是67相对误差只有0.6%。样本内的拟合值也很有说服力期数真实值DNGM拟合值绝对误差11515.00022020.070.0732726.850.1543636.160.1654948.990.01平均相对误差MAPE约0.35%在我经手的小样本案例里属于相当能打的表现。实际项目中我还会补算后验差比值C和小误差概率p一级精度基本是常态。3.4 十行Python搞定整个流程把上面的流程落到代码其实就是个10多行的小函数。我这里用numpy的lstsq做最小二乘求解会比直接求逆矩阵更稳。import numpy as np def dngm11(x0, steps1): x0 np.asarray(x0, dtypefloat) x1 np.cumsum(x0) n len(x0) y x1[1:] B np.column_stack([x1[:-1], np.arange(2, n 1), np.ones(n - 1)]) theta, *_ np.linalg.lstsq(B, y, rcondNone) b1, b2, b3 theta # 样本内拟合值重建用于误差检验 fitted_x1 np.empty(n) fitted_x1[0] x1[0] for k in range(1, n): fitted_x1[k] b1 * fitted_x1[k-1] b2 * (k 1) b3 fitted np.r_[fitted_x1[0], np.diff(fitted_x1)] # 滚动外推以真实累加终值为起点 outs [] cur x1[-1] idx n 1 for _ in range(steps): nxt b1 * cur b2 * idx b3 outs.append(nxt - cur) cur nxt idx 1 return fitted, np.array(outs), [b1, b2, b3] x0 [15, 20, 27, 36, 49] fitted, forecast, beta dngm11(x0, steps1) print(拟合值:, fitted) print(预测值:, forecast) print(参数:, beta)运行结果大致是拟合值[15.0, 20.07, 26.85, 36.16, 48.99]预测值[66.63]参数[1.3769, -0.8037, 16.0235]代码里有个细节值得说明滚外推时我用的是真实累加值x1[-1]作为起点而不是拟合序列的最后一个值。原因是灰色模型外推时以当前最新真实状态为起点能省掉前面所有误差的累积这是很多论文里默认的处理方式。如果你希望完全依赖模型递推也可以用拟合序列继续滚但要意识到误差会随步数增加而逐渐放大。4. 模型选型对比与检验指标DNGM(1,1)值不值得换4.1 同一份数据GM和DNGM的对照我把同一份示意数据分别塞进经典GM(1,1)和DNGM(1,1)看结果差异有多大。GM(1,1)先算出背景值序列25、48.5、80、122.5再做最小二乘得到发展系数a约-0.2967、灰作用量b约12.53。用时间响应式还原后拟合结果如下期数真实值GM(1,1)拟合值DNGM(1,1)拟合值11515.0015.0022019.7720.0732726.6026.8543635.7736.1654948.1748.99平均相对误差-约1.24%约0.35%第1期作为初值给定不参与误差统计这个对比不是全面的实验但趋势非常直观DNGM的每一期拟合点都更贴近真实值尤其最后一期GM已经出现“增速跟不上”的迹象DNGM仍然跟得比较紧。原因就是那个非齐次项β2·k补偿了线性趋势。我把这视为一个实用经验只要原始数据不是教科书里那种完美指数序列DNGM至少在样本内拟合上会压制GM(1,1)。而现实中又有多少数据是完美指数呢所以我的默认选型已经悄悄变成了DNGM。4.2 误差检验的两个常用指标模型建完不能只看吻合度够不够漂亮还要检验可靠性。两个最通用的指标是后验差比值C和小误差概率p。C S2 / S1其中S1是原始序列的标准差S2是残差序列的标准差。C小于0.35时模型精度为一级0.35到0.5之间为二级0.5到0.65为三级大于0.65基本要返工。在刚才的例子里原始序列均值是29.4标准差约12.08残差序列的标准差约0.13C算下来约0.01属于很优秀的一级精度。p是残差绝对值小于0.6745倍S1的概率这个例子里p接近1。需要提醒的是C指标对残差标准差非常敏感而这恰恰暴露了灰色模型的软肋如果原始序列里混进一个离谱的异常点残差标准差会被瞬间拉大后验差比值直接爆表。建模前做一次数据清洗把明显的促销高峰、临时断货这类异常点剔除或修正比后边用什么高级模型都重要。4.3 什么场景别盲目切换DNGM(1,1)不是万能药它的家族边界仍然清晰。如果你的数据带明显周期性跳跃比如电商平台大促前后的销量尖峰灰色模型整个家族都会失准此时应先用更精细的数据清洗处理周期分量或者干脆换用能处理季节性模型。如果数据已经超过30期又是相对平稳的序列ARIMA、指数平滑等经典时序模型通常更容易驾驭。灰色模型的看家本领始终是小样本、短趋势、中等噪声。DNGM(1,1)把传统GM(1,1)的本领范围扩大了一圈但没有改变家族边界。你手上的数据如果少于4期或者中间有大量负值和突变换什么灰色变体都救不回来不如直接改用简单的均值回归。5. 实战进阶把DNGM(1,1)用出花的三种姿势5.1 滚动窗口更新参数用DNGM(1,1)做周期性预测时我强烈建议不要一次建完模型就一劳永逸。每拿到一个新的实际值就把最早一个样本剔除用最近n期重新估计β1、β2、β3。模型参数就不会老化。举个例子刚才的五期序列预测出第6期66.63后如果第6期实测值回来了是64那么下一轮建模就改用20、27、36、49、64这五个数重新估计参数再预测第7期。这一步成本几乎为零但效果立竿见影。我在实际项目里踩过很多次坑最后发现灰色模型长期预测翻车绝大多数不是模型的问题而是“参数保鲜”没做好。5.2 残差二次建模模型的拟合值和真实值之间总会留下一段残差这段残差里有时还藏着可挖掘的小趋势。我的标准操作是先训练DNGM主模型算出残差序列再对残差序列单独建一个DNGM(1,1)把它的预测结果叠加到主模型预测上。思路类似机器学习里的残差学习。注意两个前提一是残差序列要满足非负和级比可接受如果含有负数要做整体平移或绝对值处理二是叠加后要检查最终残差是否真的降低了如果没降低说明残差已经接近白噪声强行建模只是在拟合噪音没有任何泛化意义。5.3 多序列并行建模与组合预测做电商库存或者多门店销售预测时单个SKU或单个门店往往只有七八个数据点。单独建回归模型很不靠谱但把每个序列分别用DNGM(1,1)滚动建模就能保留每条序列自身独有的增长形态。所有子序列预测完再汇总常常比“先把所有数据汇成一条大序列、再预测总量”更稳。原因在于每条子序列的异质性被单独建模吸收了而不是在汇总阶段被平均掉。如果某条子序列的级比检验始终不通过优先做平移变换实在不行才考虑剔除该序列因为它可能已经不满足灰色建模的前置条件。最后再分享一个很多人不知道的小技巧DNGM(1,1)对初值x(0)(1)非常敏感。有些变体会把初值也当作待估参数参与最小二乘优化而不是直接用原始首项。如果你发现外推结果对首期数据依赖过重可以把初值纳入参数估计算完后再比较两版预测精度选择误差更小的方案。这招在我处理几组回归趋势很强的数据时确实把预测误差再压低了10%以上。
阅读完成 · 觉得有帮助?