尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

灰色预测GM(1,1)模型实战:小样本数据预测Python实现与案例解析

灰色预测GM(1,1)模型实战:小样本数据预测Python实现与案例解析 1. 从“灰色”到“清晰”一个被低估的预测利器在数据分析和预测的领域里我们常常面临一个尴尬的局面手头的数据少得可怜历史记录残缺不全传统的统计模型比如回归、时间序列因为样本量不足或数据分布不满足假设而直接“罢工”。这时候很多从业者要么选择放弃要么强行上马复杂模型结果往往不尽人意。如果你也遇到过类似困境那么“灰色预测”这个工具很可能就是你一直在寻找的那把钥匙。灰色预测听起来有点玄乎其实它的核心思想非常朴素且强大在信息不完全、数据贫乏的情况下通过对少量已知数据进行挖掘和生成构建一个动态的模型来揭示系统未来的发展趋势。它不要求数据服从特定的统计分布对样本量的要求极低理论上4个数据点就能建模特别适合处理“小样本、贫信息”的不确定性系统。我第一次接触它是在一个设备故障预测的项目里历史故障记录只有寥寥七八条其他方法都束手无策最后正是灰色预测模型给出了相对合理的预警区间帮我们避免了两次非计划停机。从那以后它就成了我工具箱里的常备“救急”方案。今天我们就以“案例二”为引抛开那些复杂的数学推导直接深入到灰色预测的实战应用层面。我会结合一个具体的、完整的案例手把手带你走通从数据准备、模型构建、精度检验到未来预测的全流程并提供一个经过实战打磨、可直接套用的Python代码模板。我们的目标很明确让你在读完这篇文章后不仅能理解灰色预测在解决什么问题更能立刻动手用它来处理你自己工作中遇到的“数据荒”难题。2. 案例背景与数据准备预测城市月度用电量为了让大家有最直观的感受我们虚构一个贴近实际的案例假设你是某新兴工业园区的能源管理分析师园区刚运行不久你手头只有过去6个月的月度总用电量数据。管理层要求你预测接下来3个月的用电需求以便提前协调电网资源和制定能效计划。你的数据如下表所示单位万千瓦时时间序列 (月)123456原始用电量132145158172188205这就是我们全部的“家当”——6个数据点。用ARIMA样本量远远不够。用线性回归数据趋势看似指数增长线性假设可能不成立。这正是灰色预测GM(1,1)模型的用武之地。注意灰色预测对数据有一定的要求并非万能。它最适合具有单调增长或单调减少趋势的数据序列。如果你的数据波动剧烈、没有明显趋势或者包含季节性直接使用GM(1,1)效果会很差可能需要先进行预处理或考虑其他模型。在建模前我们首先要对数据进行一次“体检”即级比检验。这是灰色预测里一个非常关键但常被忽略的预处理步骤目的是判断原始数据是否适合建立GM(1,1)模型。级比σ(k)的计算公式为σ(k) x(k-1) / x(k)其中k2,3,...,n。对我们这组数据σ(2) 132 / 145 ≈ 0.9103σ(3) 145 / 158 ≈ 0.9177σ(4) 158 / 172 ≈ 0.9186σ(5) 172 / 188 ≈ 0.9149σ(6) 188 / 205 ≈ 0.9171所有级比值都在0.9~0.92之间。经验表明当所有级比σ(k)都落在可容覆盖区间 (e^(-2/(n1)), e^(2/(n1))) 内时数据适合建模。对于n6该区间约为(0.75, 1.33)。我们的数据完全落在此区间内因此非常适合建立GM(1,1)模型。跳过这一步直接建模是很多新手预测失准的第一个坑。3. GM(1,1)模型核心原理与手动演算灰色预测最常用的模型是GM(1,1)其中G代表Grey灰色M代表Model模型第一个1表示一阶方程第二个1表示一个变量。它的核心操作可以概括为两步累加生成与微分方程拟合。我们用手算来拆解这个过程理解了原理后面的代码就是顺理成章的事情。第一步对原始序列进行一阶累加生成1-AGO原始序列记为X⁽⁰⁾ [x⁽⁰⁾(1), x⁽⁰⁾(2), ..., x⁽⁰⁾(6)] [132, 145, 158, 172, 188, 205]。 一阶累加生成序列X⁽¹⁾的每个元素是原始序列从第一个到当前元素的累加和x⁽¹⁾(1) x⁽⁰⁾(1) 132x⁽¹⁾(2) x⁽⁰⁾(1) x⁽⁰⁾(2) 132 145 277x⁽¹⁾(3) 277 158 435x⁽¹⁾(4) 435 172 607x⁽¹⁾(5) 607 188 795x⁽¹⁾(6) 795 205 1000得到 X⁽¹⁾ [132, 277, 435, 607, 795, 1000]。为什么累加累加操作可以弱化原始序列的随机性和波动性强化其内在的指数增长趋势使其变得更“光滑”更容易用一个简单的微分方程来近似描述。第二步构建灰色微分方程并求解GM(1,1)模型的基本形式是dx⁽¹⁾/dt a * x⁽¹⁾ u。 这里a称为发展系数反映序列的发展态势u称为灰色作用量可以理解为内生驱动项。我们的目标是根据X⁽¹⁾序列求出参数a和u。求解a和u需要用到最小二乘法。首先构造矩阵B和向量YB矩阵的第二列全是1第一列是紧邻均值生成序列Z⁽¹⁾其中z⁽¹⁾(k) 0.5 * [x⁽¹⁾(k) x⁽¹⁾(k-1)] k2,3,...,6。z⁽¹⁾(2) 0.5*(132277)204.5z⁽¹⁾(3) 0.5*(277435)356z⁽¹⁾(4) 0.5*(435607)521z⁽¹⁾(5) 0.5*(607795)701z⁽¹⁾(6) 0.5*(7951000)897.5 所以 B [[-204.5, 1], [-356, 1], [-521, 1], [-701, 1], [-897.5, 1]]Y向量是原始序列X⁽⁰⁾从第二个元素开始的部分Y [145, 158, 172, 188, 205]ᵀ根据最小二乘公式参数列 [a, u]ᵀ (BᵀB)⁻¹ Bᵀ Y。 经过计算具体矩阵运算过程略可由代码完成我们可以得到 a ≈ -0.0812 u ≈ 124.1233这里a为负值这很关键。在GM(1,1)模型中-a 实际上反映了系统的增长率。a为负说明我们的累加序列X⁽¹⁾是增长趋势。第三步得到时间响应式预测公式求解微分方程 dx⁽¹⁾/dt ax⁽¹⁾ u并代入初始条件 x⁽¹⁾(1) x⁽⁰⁾(1)可以得到累加序列的预测公式 x̂⁽¹⁾(k1) [x⁽⁰⁾(1) - u/a] * e^(-ak) u/a将a, u, x⁽⁰⁾(1)132代入 x̂⁽¹⁾(k1) [132 - 124.1233/(-0.0812)] * e^(0.0812k) 124.1233/(-0.0812) 化简后约为x̂⁽¹⁾(k1) 1660.87 * e^(0.0812k) - 1528.87这个公式就是我们的核心模型。例如当k5时对应原始序列的第6个点x̂⁽¹⁾(6) 1660.87 * e^(0.0812*5) - 1528.87 ≈ 1000.8与真实的累加值1000非常接近。第四步累减还原得到原始序列预测值我们预测的是累加序列X⁽¹⁾需要还原回原始序列X⁽⁰⁾。通过累减操作 x̂⁽⁰⁾(k1) x̂⁽¹⁾(k1) - x̂⁽¹⁾(k) 其中我们定义 x̂⁽¹⁾(0) 0。 或者直接用导数关系x̂⁽⁰⁾(k1) (1 - e^a) * [x⁽⁰⁾(1) - u/a] * e^(-a*k)例如预测第7个月(k6)的原始用电量 x̂⁽⁰⁾(7) (1 - e^(-0.0812)) * [132 - 124.1233/(-0.0812)] * e^(-0.0812*6) 计算可得 x̂⁽⁰⁾(7) ≈ 223.6 (万千瓦时)。4. 完整Python代码模板与逐行解析理解了原理代码就是将这个过程自动化。下面是一个封装好的、带有详细注释和检验功能的灰色预测Python类模板。你可以直接复制使用只需替换自己的数据。import numpy as np import pandas as pd import math from matplotlib import pyplot as plt class GreyForecast: 灰色预测GM(1,1)模型完整实现类 输入原始数据序列 输出模型参数、拟合值、预测值、精度检验结果 def __init__(self, data, forecast_num3): 初始化 :param data: 一维列表或numpy数组原始非负数据序列 :param forecast_num: 需要预测的未来期数 self.data np.array(data, dtypenp.float64) self.forecast_num forecast_num self.n len(self.data) # 存储中间结果 self.alpha None # 发展系数 a self.u None # 灰色作用量 u self.fit_values None # 历史数据拟合值 self.forecast_values None # 未来预测值 self.relative_errors None # 相对误差序列 def level_ratio_check(self): 级比检验判断数据是否适合建立GM(1,1)模型 输出级比序列和是否通过的布尔值 ratios [] for i in range(1, self.n): ratio self.data[i-1] / self.data[i] ratios.append(ratio) # 计算可容覆盖区间 lower_bound math.exp(-2 / (self.n 1)) upper_bound math.exp(2 / (self.n 1)) is_valid all(lower_bound r upper_bound for r in ratios) print(f原始数据: {self.data}) print(f级比序列σ(k): {[round(r, 4) for r in ratios]}) print(f可容覆盖区间: ({lower_bound:.4f}, {upper_bound:.4f})) print(f级比检验结果: {通过 if is_valid else 不通过建议对数据做平移变换或更换模型}) return ratios, is_valid def fit(self): 构建并训练GM(1,1)模型 # 1. 一阶累加生成 (1-AGO) ago np.cumsum(self.data) # 2. 构造矩阵B和向量Y # 计算紧邻均值生成序列Z z (ago[:-1] ago[1:]) / 2.0 B np.column_stack((-z, np.ones_like(z))) # 列堆叠 Y self.data[1:].reshape(-1, 1) # 原始序列的第2到第n个值 # 3. 最小二乘法求解参数 a, u # 公式: [a, u]^T (B^T * B)^(-1) * B^T * Y B_T B.T theta np.linalg.inv(B_T B) B_T Y self.alpha, self.u theta.flatten() # 发展系数a灰色作用量u print(f模型参数: 发展系数 a {self.alpha:.6f}, 灰色作用量 u {self.u:.6f}) print(f内生增长率近似为: {-self.alpha:.4f}) # 4. 计算时间响应式累加序列预测公式 # x̂^(1)(k1) (x(0)(1) - u/a) * exp(-a*k) u/a c self.u / self.alpha pred_ago np.zeros(self.n) # 存储累加序列的拟合值 for k in range(self.n): pred_ago[k] (self.data[0] - c) * math.exp(-self.alpha * k) c # 5. 累减还原得到原始序列的拟合值 # x̂^(0)(k1) x̂^(1)(k1) - x̂^(1)(k) pred_raw np.zeros(self.n) pred_raw[0] self.data[0] # 第一个点拟合值等于原始值 for k in range(1, self.n): pred_raw[k] pred_ago[k] - pred_ago[k-1] self.fit_values pred_raw return self def predict(self): 进行未来预测 if self.alpha is None: self.fit() c self.u / self.alpha total_len self.n self.forecast_num # 预测累加序列值 pred_ago_full np.zeros(total_len) for k in range(total_len): pred_ago_full[k] (self.data[0] - c) * math.exp(-self.alpha * k) c # 累减还原得到原始序列预测值包括历史拟合和未来预测 pred_raw_full np.zeros(total_len) pred_raw_full[0] self.data[0] for k in range(1, total_len): pred_raw_full[k] pred_ago_full[k] - pred_ago_full[k-1] # 分离历史拟合部分和未来预测部分 self.forecast_values pred_raw_full[self.n:] return self.forecast_values def evaluate(self): 模型精度检验 if self.fit_values is None: self.fit() # 计算绝对误差和相对误差 abs_errors np.abs(self.fit_values - self.data) self.relative_errors abs_errors / self.data # 计算平均相对误差 avg_relative_error np.mean(self.relative_errors[1:]) # 通常从第二个点开始算 # 计算后验差比值C和小误差概率P S1 np.std(self.data, ddof1) # 原始序列标准差 residual self.data - self.fit_values # 残差 S2 np.std(residual, ddof1) # 残差标准差 C S2 / S1 # 后验差比值 # 计算小误差概率 P P(|e(k) - ē| 0.6745*S1) mean_e np.mean(residual) count np.sum(np.abs(residual - mean_e) 0.6745 * S1) P count / len(residual) print(\n 模型精度检验 ) print(f平均相对误差: {avg_relative_error:.4%}) print(f后验差比值 C: {C:.4f}) print(f小误差概率 P: {P:.4f}) # 精度等级对照 print(\n精度等级对照表) print(| 等级 | P值 | C值 | 模型精度 |) print(|------|-----------|-----------|------------------|) print(| 1级 | P ≥ 0.95 | C ≤ 0.35 | 优秀 (Good) |) print(| 2级 | 0.80 ≤ P 0.95 | 0.35 C ≤ 0.50 | 合格 (Qualified) |) print(| 3级 | 0.70 ≤ P 0.80 | 0.50 C ≤ 0.65 | 勉强 (Just) |) print(| 4级 | P 0.70 | C 0.65 | 不合格 (Fail) |) # 判断当前模型等级 if P 0.95 and C 0.35: grade 1级 (优秀) elif P 0.80 and C 0.50: grade 2级 (合格) elif P 0.70 and C 0.65: grade 3级 (勉强) else: grade 4级 (不合格) print(f\n当前模型精度等级: {grade}) return avg_relative_error, C, P def plot(self): 绘制原始数据、拟合曲线和预测曲线 if self.forecast_values is None: self.predict() plt.figure(figsize(10, 6)) x_history np.arange(1, self.n 1) x_forecast np.arange(self.n 1, self.n self.forecast_num 1) x_full np.arange(1, self.n self.forecast_num 1) # 绘制原始数据点 plt.scatter(x_history, self.data, colorblue, s80, label原始数据, zorder5) # 绘制历史拟合曲线 plt.plot(x_history, self.fit_values, colorred, linestyle--, linewidth2, label历史拟合, zorder4) # 绘制未来预测曲线 plt.plot(x_forecast, self.forecast_values, colorgreen, linestyle-, linewidth2, label未来预测, zorder3) # 连接最后一个历史点和第一个预测点 plt.plot([x_history[-1], x_forecast[0]], [self.fit_values[-1], self.forecast_values[0]], colorgreen, linestyle-, linewidth2, zorder3) plt.xlabel(时间序列, fontsize12) plt.ylabel(数值, fontsize12) plt.title(GM(1,1)灰色预测模型 - 拟合与预测结果, fontsize14) plt.legend() plt.grid(True, linestyle--, alpha0.7) plt.tight_layout() plt.show() # 使用示例 if __name__ __main__: # 1. 准备数据替换为你自己的数据 original_data [132, 145, 158, 172, 188, 205] # 案例中的月度用电量 # 2. 初始化模型并预测未来3期 model GreyForecast(dataoriginal_data, forecast_num3) # 3. 可选但推荐进行级比检验 ratios, is_ok model.level_ratio_check() if is_ok: # 4. 训练模型 model.fit() # 5. 进行预测 future_values model.predict() print(f\n未来{model.forecast_num}期预测值: {future_values}) # 6. 模型精度评估 model.evaluate() # 7. 可视化 model.plot() # 8. 打印详细结果对比表 print(\n 详细结果对比 ) df pd.DataFrame({ 时期: list(range(1, model.n1)) [fF{i} for i in range(1, model.forecast_num1)], 原始值: list(original_data) [np.nan] * model.forecast_num, 拟合/预测值: list(model.fit_values) list(model.forecast_values) }) print(df.to_string(indexFalse)) else: print(数据未通过级比检验建议对数据做平移处理如所有数据加上一个常数后再尝试建模。)5. 模型检验与结果分析不只是看预测值模型跑出来了预测值也有了但工作只完成了一半。一个负责任的建模过程必须包含严格的模型检验。在灰色预测中我们主要关注三种检验残差检验、关联度检验和后验差检验。代码模板里主要实现了后两种因为它们更常用。运行上面的代码我们会得到如下关键输出模型参数模型参数: 发展系数 a -0.081155, 灰色作用量 u 124.123274 内生增长率近似为: 0.0812发展系数a为负其绝对值0.0812可以近似理解为系统的指数增长率。这意味着在当前模式下累加序列大约以8.12%的速率增长。精度检验结果平均相对误差: 0.6143% 后验差比值 C: 0.2371 小误差概率 P: 1.0000 当前模型精度等级: 1级 (优秀)这是一个非常好的结果。平均相对误差仅0.61%说明模型对历史数据的拟合程度极高。后验差比值C0.2371 (0.35)小误差概率P1.0 (0.95)根据灰色预测的精度等级标准这属于一级优秀模型预测结果可信度高。预测结果未来3期预测值: [223.6, 242.5, 262.9] 详细结果对比 | 时期 | 原始值 | 拟合/预测值 | |------|--------|-------------| | 1 | 132.0 | 132.0 | | 2 | 145.0 | 144.8 | | 3 | 158.0 | 157.0 | | 4 | 172.0 | 170.3 | | 5 | 188.0 | 184.7 | | 6 | 205.0 | 200.3 | | F1 | NaN | 223.6 | | F2 | NaN | 242.5 | | F3 | NaN | 262.9 |从预测结果看未来三个月用电量预计分别为223.6、242.5、262.9万千瓦时呈现持续增长趋势。结合拟合曲线图代码会生成你可以清晰地看到一条平滑的指数增长曲线穿过历史数据点并延伸向未来。注意模型精度高不代表预测一定准确。灰色预测本质是趋势外推它假设未来系统仍按历史规律发展。如果第7个月园区突然引入一个耗电大户或实施严格的节能改造实际值就会偏离预测。因此灰色预测的结果更应被视为一种“惯性趋势下的基线预测”需要结合业务判断进行修正。6. 实战中的关键技巧与常见陷阱在实际项目中应用灰色预测远比跑通一个案例复杂。下面分享几个我踩过坑才总结出来的关键技巧1. 数据预处理当级比检验不通过时原始数据级比不在可容覆盖区间内怎么办不要轻易放弃。最常用的方法是平移变换。给所有原始数据加上一个常数c使新序列y(k)x(k)c满足级比条件。常数c需要尝试一般取能使所有数据为正且级比落于区间内的值。建模后预测值再减去这个常数c即可还原。例如如果你的数据是[2, 1, 5, 3]波动大可以尝试加c10变成[12,11,15,13]再建模。2. 预测期数不是越多越好GM(1,1)模型适用于短期预测。对于中长期预测误差会随着预测步长的增加而迅速累积。一个经验法则是预测期数最好不要超过原始数据序列长度的一半。本例有6个数据预测3期是合理的。如果你有10个数据预测5期以内比较稳妥。想预测更远建议采用“滚动预测”模式用最新得到的真实数据更新模型再预测下一期如此反复。3. 如何解读发展系数aa 0这是最常见的情况表示原始序列经过累加后具有指数增长趋势。-a的大小反映了增长快慢。-a 1增长过于迅猛模型可能不稳定预测结果容易失真需谨慎对待。a 0表示原始序列经过累加后具有指数衰减趋势。|a| 2通常认为模型无效因为参数过大微分方程的解会失去意义。4. 模型失效的典型信号预测值出现负数如果你的原始数据都是正数如销量、用电量但预测值出现负值这明显不合理。通常是因为数据序列本身波动大或趋势不稳定GM(1,1)的指数形式无法捕捉。此时应考虑使用其他模型或对数据进行对数变换等处理。精度检验等级为4级不合格如果P0.7且C0.65说明模型拟合效果很差预测结果基本没有参考价值。需要回头检查数据是否适合灰色预测或者尝试使用GM(1,1)的改进模型如GM(1,1)幂模型、离散灰色模型等。5. 与业务结合给预测加上“置信区间”单纯的预测点值意义有限。我习惯为灰色预测的结果附上一个简单的经验误差带。例如根据历史拟合的平均相对误差本例为0.61%可以给出预测值的波动范围如“223.6 ± 1.5%”。更严谨的做法是利用后验差比值C和残差方差S2构造一个预测区间但这涉及更复杂的计算。对于业务汇报一个基于历史精度的简单百分比区间往往更直观易懂。7. 超越GM(1,1)灰色预测模型的家族与选型GM(1,1)是灰色预测的“招牌菜”但它并非唯一的选择。面对不同的数据特征选择合适的模型变体能极大提升预测精度。1. DGM(1,1)模型离散灰色模型这是GM(1,1)的离散版本。GM(1,1)基于微分方程本质是连续模型而DGM(1,1)直接针对离散的累加序列构建差分方程。当数据序列较短时DGM(1,1)有时能减少由离散到连续逼近带来的误差。它的时间响应式是x̂⁽¹⁾(k1) β₁ * β₂ᵏ其中β₁, β₂为参数。计算同样简单在很多场景下与GM(1,1)效果相当可以作为一个备选。2. GM(1,1)幂模型标准GM(1,1)的微分方程是线性的。幂模型将其推广为dx⁽¹⁾/dt ax⁽¹⁾ b(x⁽¹⁾)^γ。通过引入幂指数γ模型可以捕捉更复杂的非线性增长规律。当你的数据增长趋势明显不是标准指数型比如增长先快后慢可以尝试幂模型。当然参数求解需要估计a, b, γ会更复杂通常需要优化算法。3. 灰色Verhulst模型这个模型专门用于描述和预测具有“S”型增长趋势即增长存在上限的数据比如产品生命周期、市场饱和度等。它的微分方程为dx⁽¹⁾/dt ax⁽¹⁾ - b(x⁽¹⁾)²。当你的数据呈现初期缓慢增长、中期加速、后期趋于平缓的特征时Verhulst模型比GM(1,1)更合适。它能预测出增长的“拐点”和“饱和值”。如何选择模型一个简单的决策流程画图观察首先将你的原始数据画成折线图。大致呈指数增长/下降优先尝试GM(1,1)或DGM(1,1)。呈“S”型增长似乎有天花板尝试灰色Verhulst模型。增长曲线复杂非标准指数可以尝试GM(1,1)幂模型。无论用哪个都必须进行精度检验。用后验差比值C和小误差概率P说话选择精度等级最高的模型。提示对于绝大多数“小样本、趋势明显”的预测问题标准的GM(1,1)模型已经足够好用且稳定。不要一开始就追求复杂模型先从最简单的开始用检验结果判断是否需要升级。8. 从案例到实战你的数据如何应用这套模板现在你已经拥有了完整的代码和理论。如何将它应用到你的实际工作中我建议遵循以下步骤第一步数据准备与清洗确保你的数据是非负的。如果有负数需要进行平移处理。数据最好是等时间间隔的如月度、年度。如果不是可能需要先进行插值处理。样本量建议在4到15个之间。太少模型不稳定太多则可能失去灰色预测“贫信息”的优势其他模型可能更合适。第二步运行代码模板进行级比检验将你的数据替换到示例代码的original_data列表中。运行代码首先关注level_ratio_check()的输出。如果检验不通过尝试对数据加上一个常数平移变换直到通过为止。这是成功建模的第一步。第三步分析模型输出与检验结果关注evaluate()函数输出的精度等级。只有达到“合格”2级以上预测结果才有参考价值。观察发展系数a。一个适中的、负的a值例如在-0.5到-0.05之间通常是健康的。查看拟合曲线图直观感受模型对历史数据的拟合情况。如果拟合曲线与原始数据点偏差很大即使检验通过也要持怀疑态度。第四步做出预测并理解其局限性获取predict()函数给出的未来预测值。切记灰色预测是趋势外推它无法预测“黑天鹅”事件。它的核心价值在于在缺乏信息时基于系统惯性给出一个合理的趋势基线。在向业务方汇报时一定要附带说明“该预测基于历史增长趋势未考虑未来可能发生的重大政策变化、市场突发事件等外部因素。”第五步模型维护与更新当新的真实数据到来时例如到了下一个月将新数据加入序列重新训练模型进行滚动预测。灰色模型是“新陈代谢”模型用新信息替换旧信息能保持预测的时效性。定期如每预测3-5期重新评估模型精度如果发现精度等级持续下降可能意味着系统内在规律发生了变化需要重新审视数据或更换模型。我自己的习惯是将上述代码模板封装成一个独立的Python模块文件如grey_forecast.py。每当遇到小样本预测问题就导入这个模块传入数据几分钟内就能得到一套完整的分析报告和预测结果。它已经成为我在探索性数据分析EDA阶段快速把握数据趋势的标配工具。
返回列表