ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GM(1,1)灰色预测模型:原理、Python实现与数学建模实战

GM(1,1)灰色预测模型:原理、Python实现与数学建模实战 1. 项目概述为什么是灰色预测GM(1,1)如果你正在准备数学建模比赛或者工作中需要处理一些“数据少、信息贫、规律不明显”的预测问题那你大概率听说过或者被推荐过“灰色预测”。而在所有灰色预测模型里GM(1,1)绝对是那个出场率最高的明星选手。我第一次在国赛里用它是因为手头只有短短四年的年度数据传统的时间序列模型比如ARIMA要求数据量足够大、平稳看着那可怜巴巴的四个点感觉用啥都像在“硬套”。导师当时就提了一句“试试灰色预测它专治‘小样本、贫信息’。”GM(1,1)这个名字听起来有点玄乎其实拆开看很简单。“G”是Grey灰色“M”是Model模型“(1,1)”里的第一个“1”表示模型只用一个变量即一元第二个“1”表示模型是一阶微分方程。它的核心思想非常巧妙它不直接去啃那堆看起来杂乱无章、没明显趋势的原始数据而是先给数据做一次“累加”操作生成一串新的、有明显指数增长趋势的数据。然后对这个光滑的新序列建立微分方程进行拟合和预测最后再通过“累减”还原得到原始序列的预测值。简单说就是“曲线救国”——把难拟合的变成好拟合的。这个方法特别适合数学建模竞赛中的一些场景比如预测未来几年的人口数量但只有过去5-7年的数据、预测某种疾病的发病率、预测城市的用电负荷、或者像一些竞赛题里预测某种新型传染病的传播趋势初期数据。它的优势就在于“四两拨千斤”用很少的数据就能做出一个看起来不错的预测为你的论文提供一个量化的、有模型支撑的结果。当然它也有它的局限比如对原始数据的光滑度有要求长期预测误差可能会放大这些我们后面都会详细拆解。但无论如何作为一个快速上手的预测工具GM(1,1)绝对是数学建模武器库里必备的一把“瑞士军刀”。2. 核心原理拆解GM(1,1)是如何“无中生有”的很多教程一上来就扔公式让人看得云里雾里。我们换个方式用一个最简单的例子把每一步的“为什么”讲清楚。假设我们有一个地区过去5年的能源消费量单位万吨标准煤X⁽⁰⁾ (x⁽⁰⁾(1), x⁽⁰⁾(2), x⁽⁰⁾(3), x⁽⁰⁾(4), x⁽⁰⁾(5)) (2.874, 3.278, 3.337, 3.390, 3.679)这个X⁽⁰⁾就是我们的原始序列上标(0)表示0次累加生成即原始数据。2.1 第一步累加生成AGO—— 让数据变得“光滑”直接看X⁽⁰⁾它上下波动增长趋势并不稳定。GM(1,1)的第一招就是做一次累加Accumulated Generating Operation, AGO得到新序列X⁽¹⁾。x⁽¹⁾(1) x⁽⁰⁾(1) 2.874x⁽¹⁾(2) x⁽⁰⁾(1) x⁽⁰⁾(2) 2.874 3.278 6.152x⁽¹⁾(3) x⁽¹⁾(2) x⁽⁰⁾(3) 6.152 3.337 9.489x⁽¹⁾(4) x⁽¹⁾(3) x⁽⁰⁾(4) 9.489 3.390 12.879x⁽¹⁾(5) x⁽¹⁾(4) x⁽⁰⁾(5) 12.879 3.679 16.558所以X⁽¹⁾ (2.874, 6.152, 9.489, 12.879, 16.558)。把它画出来你会发现它是一条明显向上弯曲、近似指数增长的平滑曲线这就是累加的魅力它弱化了原始数据的随机波动强化了内在的趋势。微分方程擅长描述这种光滑的、有规律的趋势所以我们接下来针对X⁽¹⁾建立模型。注意为什么是一阶累加理论上可以做更高阶但GM(1,1)默认一阶就足够了。累加次数越多序列越光滑但信息损失也可能越大计算也越复杂。实践中95%以上的情况一阶累加1-AGO是首选。2.2 第二步构建灰微分方程—— 找到趋势的“骨架”我们对光滑序列X⁽¹⁾建立如下形式的白化微分方程这是个连续函数模型dx⁽¹⁾(t) / dt a * x⁽¹⁾(t) u这个方程就是GM(1,1)模型的核心。其中x⁽¹⁾(t)是累加序列的连续时间函数。a称为发展系数它反映了x⁽¹⁾的增长势头。a为负时表示序列呈指数增长趋势a为正时表示呈指数衰减趋势。在预测领域我们通常希望a为负表示未来是增长的。u称为灰色作用量可以理解为系统内的“内生驱动力量”。但是我们的数据是离散的不是连续的。所以需要用离散形式来近似这个微分方程这就得到了灰微分方程x⁽⁰⁾(k) a * z⁽¹⁾(k) u 其中k 2, 3, ..., n(n是数据个数)这里出现了两个新东西x⁽⁰⁾(k) 就是原始序列的第k个值。它为什么在这里因为在微积分里导数近似等于差分dx⁽¹⁾/dt近似等于x⁽¹⁾(k) - x⁽¹⁾(k-1)而这正好等于x⁽⁰⁾(k)。z⁽¹⁾(k) 这是X⁽¹⁾在区间[k-1, k]上的背景值通常取相邻两项的均值即z⁽¹⁾(k) 0.5 * [x⁽¹⁾(k) x⁽¹⁾(k-1)]。用均值是为了更好地代表这个区间的整体水平减少端点波动的影响。以我们的数据k2时z⁽¹⁾(2) 0.5 * (x⁽¹⁾(1) x⁽¹⁾(2)) 0.5 * (2.874 6.152) 4.513方程即为3.278 a * 4.513 u2.3 第三步参数估计a, u—— 用最小二乘法“定参”我们对每个k2,3,4,5都能写出一个方程这样就得到了一个方程组。但方程组方程数4个多于未知数2个a和u通常无精确解。这时就用上最小二乘法来求最优解。将方程组写成矩阵形式Y B * [a, u]ᵀY [ x⁽⁰⁾(2), x⁽⁰⁾(3), x⁽⁰⁾(4), x⁽⁰⁾(5) ]ᵀ [3.278, 3.337, 3.390, 3.679]ᵀ B [ -z⁽¹⁾(2) 1 ] [ -4.513 1 ] [ -z⁽¹⁾(3) 1 ] [ -7.820 1 ] [ -z⁽¹⁾(4) 1 ] [-11.184 1 ] [ -z⁽¹⁾(5) 1 ] [-14.718 1 ]参数列向量Â [a, u]ᵀ的最小二乘估计为Â (BᵀB)⁻¹ Bᵀ Y通过计算具体计算过程可用Python或MATLAB后面会讲我们可以得到a ≈ -0.0372,u ≈ 3.0653参数解读a为负值-0.0372印证了我们的累加序列X⁽¹⁾具有指数增长趋势。u是一个正的常数项。2.4 第四步时间响应式与预测—— 从模型到结果求出a和u后我们回到那个白化微分方程dx⁽¹⁾/dt a x⁽¹⁾ u。这是一个一阶线性常微分方程它的解即时间响应函数为x̂⁽¹⁾(t) (x⁽⁰⁾(1) - u/a) * e^{-a(t-1)} u/a注意这里t是连续时间。对于离散时间点kk1,2,3,...我们将tk代入得到累加序列的拟合值x̂⁽¹⁾(k) (x⁽⁰⁾(1) - u/a) * e^{-a(k-1)} u/a以我们的数据x⁽⁰⁾(1)2.874,u/a ≈ 3.0653 / (-0.0372) ≈ -82.40代入公式x̂⁽¹⁾(1) (2.874 - (-82.40)) * e^{0.0372*0} (-82.40) 85.274 * 1 - 82.40 2.874(与原始值一致这是初始条件)x̂⁽¹⁾(2) 85.274 * e^{-0.0372*1} - 82.40 ≈ 85.274 * 0.9635 - 82.40 ≈ 6.136...但这还是累加序列X⁽¹⁾的拟合值。我们需要通过累减还原IAGO得到原始序列X⁽⁰⁾的拟合和预测值x̂⁽⁰⁾(k) x̂⁽¹⁾(k) - x̂⁽¹⁾(k-1) 其中定义x̂⁽¹⁾(0) 0。对于k2x̂⁽⁰⁾(2) x̂⁽¹⁾(2) - x̂⁽¹⁾(1) 6.136 - 2.874 3.262这个3.262就是我们模型对原始序列第二个数据点3.278的拟合值。进行预测比如要预测第6年k6的值。 先算x̂⁽¹⁾(6) 85.274 * e^{-0.0372*5} - 82.40 ≈ 85.274 * 0.8300 - 82.40 ≈ -11.63(这里出现负值别急看累减) 再算x̂⁽⁰⁾(6) x̂⁽¹⁾(6) - x̂⁽¹⁾(5)。我们需要先算出x̂⁽¹⁾(5) ≈ 16.443。 则x̂⁽⁰⁾(6) ≈ -11.63 - 16.443 ≈ -28.07。等等出现了负值这显然不符合能源消费量的实际意义。这说明什么说明我们的模型在此时可能已经失效或者原始数据不适合直接用GM(1,1)。这引出了模型适用性检验和修正的问题我们会在第4部分详细讨论。这里先记住这个重要的陷阱。3. 手把手实操用Python从零实现GM(1,1)理解了原理我们动手实现一遍。我会用最基础的NumPy和SciPy库让你看清每一步计算而不是直接调包。调包虽然快但比赛时如果让你阐述原理或进行修改自己实现过心里才有底。3.1 环境准备与数据导入首先确保你的Python环境安装了numpy和scipy。如果没有在命令行里pip install numpy scipy。import numpy as np from scipy.optimize import curve_fit import matplotlib.pyplot as plt # 原始数据就是我们例子中的能源消费量 original_data np.array([2.874, 3.278, 3.337, 3.390, 3.679], dtypenp.float64) n len(original_data) print(f原始数据序列 X(0): {original_data})3.2 核心算法分步实现我们按照原理部分的四个步骤来编码。# 1. 进行一次累加生成 (1-AGO) def ago(data): return np.cumsum(data) x1 ago(original_data) print(f一次累加序列 X(1): {x1}) # 2. 计算背景值序列 Z(1) z1 (x1[:-1] x1[1:]) / 2.0 print(f背景值序列 Z(1): {z1}) # 3. 构造矩阵B和向量Y利用最小二乘法估计参数a, u # Y original_data[1:] # 即x0(2), x0(3), ... Y original_data[1:].reshape(-1, 1) # 转为列向量 # B [-z1, 1] B np.column_stack((-z1, np.ones_like(z1))) print(矩阵B:) print(B) # 最小二乘法求解 (B^T * B) * [a, u]^T B^T * Y # 使用numpy.linalg.lstsq更稳定 params, residuals, rank, s np.linalg.lstsq(B, Y, rcondNone) a, u params.flatten() # a是发展系数u是灰色作用量 print(f\n估计参数: 发展系数 a {a:.6f}, 灰色作用量 u {u:.6f})运行这段代码你会得到类似a -0.037204, u 3.065316的结果与我们之前手算近似。3.3 模型拟合与预测函数# 4. 定义时间响应函数累加序列的预测函数 def time_response(k, x0_1, a, u): 计算第k个点的累加序列预测值 x1_hat(k) k: 时间序号 (从1开始) x0_1: 原始序列的第一个值 x0(1) a, u: 估计参数 return (x0_1 - u/a) * np.exp(-a * (k-1)) u/a # 计算累加序列的拟合值 k_values np.arange(1, n1) # [1,2,3,4,5] x1_fitted np.array([time_response(k, original_data[0], a, u) for k in k_values]) print(f\n累加序列拟合值 X1_fitted: {x1_fitted}) # 5. 累减还原得到原始序列的拟合值 x0_fitted np.zeros_like(original_data) x0_fitted[0] original_data[0] # 第一个值不变 for i in range(1, n): x0_fitted[i] x1_fitted[i] - x1_fitted[i-1] print(f原始序列拟合值 X0_fitted: {x0_fitted}) # 6. 进行未来预测 (例如预测未来3期) m 3 # 预测期数 future_k np.arange(n1, nm1) # [6,7,8] x1_forecast np.array([time_response(k, original_data[0], a, u) for k in future_k]) x0_forecast np.zeros(m) # 计算预测值时需要用到前一期累加拟合值作为基准 x0_forecast[0] x1_forecast[0] - x1_fitted[-1] # 第6期 x1(6)-x1(5) for i in range(1, m): x0_forecast[i] x1_forecast[i] - x1_forecast[i-1] print(f\n未来{m}期原始序列预测值: {x0_forecast})3.4 可视化与结果分析# 可视化 plt.figure(figsize(10, 6)) # 绘制原始数据与拟合数据 plt.subplot(2, 1, 1) plt.plot(k_values, original_data, bo-, label原始数据 X(0), markersize8) plt.plot(k_values, x0_fitted, rs--, label模型拟合值, markersize6) plt.axvline(xn, colorgray, linestyle:, alpha0.5) # 分隔线 # 绘制预测数据 forecast_x np.concatenate([k_values, future_k]) forecast_y np.concatenate([x0_fitted, x0_forecast]) plt.plot(future_k, x0_forecast, g^--, label模型预测值, markersize8) plt.xlabel(时间序列 k) plt.ylabel(数值) plt.title(GM(1,1)模型拟合与预测效果) plt.legend() plt.grid(True, alpha0.3) # 绘制残差图 residuals original_data - x0_fitted plt.subplot(2, 1, 2) plt.bar(k_values, residuals, colororange, alpha0.7) plt.axhline(y0, colorblack, linestyle-, linewidth0.5) plt.xlabel(时间序列 k) plt.ylabel(残差 (原始值-拟合值)) plt.title(拟合残差图) plt.grid(True, alpha0.3, axisy) plt.tight_layout() plt.show() # 计算一些评价指标 from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(original_data, x0_fitted) mse mean_squared_error(original_data, x0_fitted) rmse np.sqrt(mse) # 平均相对误差 relative_errors np.abs(residuals / original_data) mapre np.mean(relative_errors) * 100 # 平均相对百分比误差 print(f\n 模型拟合评价指标 ) print(f平均绝对误差 (MAE): {mae:.4f}) print(f均方误差 (MSE): {mse:.4f}) print(f均方根误差 (RMSE): {rmse:.4f}) print(f平均相对百分比误差 (MAPE): {mapre:.2f}%)运行完整的代码你会得到预测值可能包含不合理的负值和一系列评估图表。这个完整的脚本就是一个可复现的GM(1,1)模型基础实现。实操心得自己实现一遍的最大好处是你能完全控制中间过程。比如当你发现预测值出现负数时你可以立刻检查是参数a估计有问题还是原始数据本身就不满足建模条件比如有剧烈波动或负值。调包函数如greyforecasting库的GM11虽然方便但出了问题往往更难定位。4. 模型检验、优化与避坑指南模型建好了预测值也出来了但工作只完成了一半。更重要的是你要能判断这个模型好不好、能不能用。直接拿一个没经过检验的模型结果放到数学建模论文里是会被评委扣分的。4.1 必须进行的模型检验“三部曲”1. 残差检验这是最直观的检验。计算每个点的相对误差ε(k) |x⁽⁰⁾(k) - x̂⁽⁰⁾(k)| / x⁽⁰⁾(k)。经验标准通常要求所有点的相对误差ε(k) 0.2即20%最好 0.110%。如果有个别点超限但大多数点很好可以接受。如果普遍超限模型需要修正。在我们的例子中计算出的MAPE平均相对百分比误差就是一个整体衡量指标。2. 关联度检验关联度分析是灰色系统理论的特色用于衡量模型曲线与原始数据曲线在几何形状上的相似程度。计算关联系数ξ(k) (min|Δ| ρ * max|Δ|) / (|Δ(k)| ρ * max|Δ|)其中Δ(k) |x⁽⁰⁾(k) - x̂⁽⁰⁾(k)|ρ是分辨系数通常取0.5。然后求平均关联度r (1/(n-1)) * Σ ξ(k)(k从2到n)。经验标准通常认为r 0.6时关联度是满意的。ρ的取值会影响r的大小但一般不影响定性判断。3. 后验差检验这是一种基于统计的检验方法比较可靠。计算原始序列的均值X̄和标准差S1。计算残差序列的均值ε̄理想应为0和标准差S2。计算后验差比值C S2 / S1。计算小误差概率P P(|ε(k) - ε̄| 0.6745 * S1)。评价标准表| 模型精度等级 | 后验差比值 C | 小误差概率 P | | :--- | :--- | :--- | | 优秀 (1级) | C ≤ 0.35 | P ≥ 0.95 | | 合格 (2级) | 0.35 C ≤ 0.5 | 0.80 ≤ P 0.95 | | 勉强 (3级) | 0.5 C ≤ 0.65 | 0.70 ≤ P 0.80 | | 不合格 (4级) | C 0.65 | P 0.70 |只有通过了这些检验至少是合格级你的GM(1,1)模型才算站得住脚。我们的能源消费量例子计算出的C值很可能偏大P值偏小预测还出现了负值这直接提示模型不合格。4.2 当模型不合格时五大优化与修正策略遇到模型检验不通过或者预测结果荒谬如负值时别急着放弃GM模型可以尝试以下修正方法这也是论文里体现你思考深度的地方。策略一数据变换处理这是最常用也最有效的预处理方法。如果原始数据有负数或零或者波动剧烈直接建模会出问题。平移变换如果数据全为负或含有零/负数令Y⁽⁰⁾(k) X⁽⁰⁾(k) c其中c是一个常数使得新序列全部为正且无明显零点。对Y⁽⁰⁾建模预测结果再减去c即可。我们的能源数据都是正数但若预测负值可尝试对原始数据加一个足够大的常数c再做建模。对数变换/方根变换如果数据增长过快方差过大可以先取对数Y⁽⁰⁾ ln(X⁽⁰⁾)或开方弱化波动后再建模预测结果再通过指数或平方还原。策略二背景值优化传统背景值z⁽¹⁾(k)0.5*(x⁽¹⁾(k)x⁽¹⁾(k-1))假设序列是直线变化但累加序列X⁽¹⁾是指数趋势。可以用积分思想优化z⁽¹⁾(k) (1/λ) * ∫_{k-1}^{k} x⁽¹⁾(t) dt通过求解得到更复杂的背景值公式如引入调节参数λ。很多改进的GM(1,1)模型如DGM离散灰色模型本质上就是优化了背景值的构造方式。策略三初始条件优化传统模型固定使用x⁽⁰⁾(1)作为初始条件。但有时用x⁽¹⁾(1)或序列中其他更有代表性的点如加权平均作为微分方程解的初始条件可能提高精度。可以尝试将初始条件设为待定参数与a, u一起用最小二乘法估计。策略四建立残差GM(1,1)模型如果原始序列的拟合残差序列ε⁽⁰⁾有明显的趋势而不是随机波动可以对残差序列再建立一个GM(1,1)模型用这个残差模型去修正原始模型的预测值。这相当于对误差进行了二次建模往往能显著提升精度。策略五新陈代谢模型这是处理增长型序列的强有力工具。传统GM(1,1)用全部历史数据建模认为所有数据权重相同。但实际情况往往是“近期的数据更有价值”。新陈代谢模型每次预测后加入一个新信息实际值同时去掉一个最老的信息始终保持固定长度的数据序列进行滚动建模和预测。这更符合动态预测的思想能有效跟踪趋势变化。避坑指南在数学建模论文中不要只展示一个“裸”的GM(1,1)模型。标准的写法是先建立基础GM(1,1)模型 - 进行三项检验 - 发现精度不足/预测不合理 - 分析原因如数据波动大、存在异常点 - 采用一种或多种优化策略如数据平移、建立新陈代谢模型 - 重新建模并检验 - 展示优化后精度显著提升。这个过程本身就体现了你的建模思想和解决问题的能力。5. 在数学建模竞赛中的应用实战与技巧知道了原理会了编程也懂了检验和优化最后我们聊聊怎么在比赛中把它用好。5.1 适用场景快速判断GM(1,1)不是万能的在以下场景中它的表现往往比较好数据量极少只有4-10个数据点传统统计方法无能为力时。趋势单调数据呈现明显的增长或下降趋势累加后近似指数型。中短期预测预测步长不宜过长一般推荐预测期不超过数据期。例如有5年数据预测未来1-2年相对可靠预测5年后风险很大。宏观指标预测如人口、GDP总量、能源消费总量、年降水量等这些数据通常比较平滑。慎用或需要预处理的情况数据包含零、负值或绝对值很小的数需平移。数据波动非常剧烈没有明显趋势可能不适合或需先平滑。数据存在明显的季节性纯GM(1,1)无法处理需结合季节模型。长期预测误差会指数级放大。5.2 论文中的书写要点与表达在论文的“模型建立与求解”部分写GM(1,1)模型时建议按以下结构问题分析与模型选择理由“鉴于题目所给数据仅为连续n年的观测值样本量小信息有限符合灰色系统理论的应用条件。故采用适用于小样本预测的GM(1,1)灰色预测模型。”模型原理简述用公式简要说明累加生成(AGO)、灰微分方程、参数估计最小二乘法、时间响应函数、累减还原(IAGO)的过程。不必完全照抄教科书提炼核心步骤即可。建模过程给出关键计算结果。可以列表展示原始序列、累加序列、背景值、参数a和u的估计值。表1 GM(1,1)模型建模数据表 年份 | 原始值X(0) | 累加值X(1) | 背景值Z(1) ----|-----------|-----------|----------- 1 | 2.874 | 2.874 | - 2 | 3.278 | 6.152 | 4.513 ... | ... | ... | ...模型检验必须包含列出残差、相对误差、后验差比值C和小误差概率P的计算结果并对照精度等级表给出结论如“经检验本模型后验差比值C0.240.35小误差概率P0.960.95模型精度为一级可用于预测”。预测结果以表格形式清晰给出未来若干期的预测值。模型优化如果基础模型效果不佳作为亮点单独一节。说明优化动机如“基础模型预测出现负值不符合实际”介绍所采用的优化策略如“采用平移变换法令Y(0)X(0)10”展示优化后的模型参数、检验结果和最终预测值并对比说明优化效果如“优化后MAPE从15%降低至5%”。5.3 与其他模型的对比与结合在比赛中单独使用GM(1,1)有时会显得单薄。可以考虑对比展示如果数据量允许可以同时用GM(1,1)和一种传统时间序列模型如指数平滑进行预测对比两者的结果和误差分析各自优缺点能让你的论文内容更丰满。组合预测将GM(1,1)的预测结果与其它模型如线性回归、ARIMA的预测结果进行加权平均构成组合模型。权重可以根据各模型在历史数据上的拟合误差倒数来确定。这常常能获得比单一模型更稳定、更精确的预测效果。作为子系统在一些复杂的综合评价或决策模型中GM(1,1)可以作为预测某个关键指标的子模块。例如在预测城市未来综合发展水平的模型中你需要先预测未来的人口、GDP、能耗等单项指标这时就可以用GM(1,1)来预测这些单项指标。最后再分享一个我自己的小技巧在编程实现时务必把数据检验、模型建立、精度评估、预测输出这几个功能封装成独立的函数。比如check_data_suitability(data),build_gm11(data),evaluate_model(data, fitted_data),forecast_gm11(model, steps)。这样在比赛时你可以快速地对不同的数据列进行测试和调整效率极高。而且代码结构清晰也方便在论文附录中展示。灰色预测GM(1,1)是一个入门友好、见效快的工具但它背后的思想——面对“贫信息”不确定性系统通过数据生成和挖掘内在规律——才是更值得玩味的地方。把它用对、用巧它就能成为你在数学建模赛场上解决预测类问题的利器。
返回列表