灰色系统理论自1982年由邓聚龙教授提出以来,已成为处理小样本、不确定性系统的有力工具。其中GM(1,1)模型作为最基础的灰色预测模型,其核心思想是通过一阶微分方程对原始数据进行指数规律拟合。但正如我们在实际项目中反复验证的,当数据不满足指数规律假设时,传统GM(1,1)模型的预测精度会显著下降。
GM(1,1)模型本质上是对原始序列进行累加生成(1-AGO)后,用一阶线性微分方程逼近其变化趋势。其标准形式为:
code复制dx^(1)/dt + ax^(1) = b
其中x^(1)为累加生成序列,a为发展系数,b为灰色作用量。这个模型在电力负荷预测、设备故障预测等场景表现良好,但在处理经济指标、社会统计数据时经常出现较大偏差。根本原因在于:
DNGM(1,1)(Discrete Non-homogeneous Grey Model)通过三个关键改进突破了这些限制:
这种改进使得模型能够描述更广泛的数据规律,特别是对具有线性趋势与周期波动叠加特征的数据序列。我们在某省GDP预测项目中对比发现,对于季度经济数据,DNGM(1,1)的平均相对误差比GM(1,1)降低42.7%。
DNGM(1,1)的核心方程可表示为:
code复制x^(0)(k) + az^(1)(k) = b + c·k
其中:
与传统模型相比,右侧增加的c·k项使得模型能够捕捉数据的非齐次特征。这个看似简单的改进,实际上极大扩展了模型的适用性。
参数估计采用改进的最小二乘法:
构建数据矩阵B和观测向量Y:
code复制B = [-z^(1)(2), 2, 1;
-z^(1)(3), 3, 1;
...
-z^(1)(n), n, 1]
Y = [x^(0)(2);
x^(0)(3);
...
x^(0)(n)]
参数向量θ = [a; c; b]通过最小二乘估计:
code复制θ = (B^T B)^(-1) B^T Y
这种参数估计方法在保证计算效率的同时,提高了对非齐次特征的捕捉能力。
得到参数估计后,预测值的计算流程为:
计算累加预测值:
code复制x^(1)(k+1) = (x^(0)(1) - b/a - c/a^2)e^(-ak) + b/a + c/a^2 + (c/a)k
通过累减还原原始序列预测值:
code复制x^(0)(k+1) = x^(1)(k+1) - x^(1)(k)
这个还原过程保留了非齐次项的影响,使得预测结果能更好跟踪实际数据的变化趋势。
以下是带详细注释的Python实现:
python复制import numpy as np
def dngm11(x0, predict_step=1):
"""
DNGM(1,1)模型实现
参数:
x0: 原始序列,一维numpy数组
predict_step: 预测步长
返回:
预测值数组
"""
# 1. 累加生成
x1 = np.cumsum(x0)
# 2. 背景值构造(均值生成)
z1 = (x1[:-1] + x1[1:]) / 2.0
# 3. 构建数据矩阵B和观测向量Y
n = len(x0)
B = np.column_stack([
-z1,
np.arange(2, n+1),
np.ones(n-1)
])
Y = x0[1:].reshape(-1, 1)
# 4. 参数估计(带正则化的最小二乘)
theta = np.linalg.inv(B.T @ B + 1e-6*np.eye(3)) @ B.T @ Y
a, c, b = theta.flatten()
# 5. 累加值预测
x1_pred = np.zeros(n + predict_step)
x1_pred[0] = x0[0]
for k in range(1, n + predict_step):
x1_pred[k] = (x0[0] - b/a - c/(a**2)) * np.exp(-a*(k-1)) + b/a + c/(a**2) + (c/a)*(k-1)
# 6. 累减还原
x0_pred = np.diff(x1_pred)
x0_pred = np.insert(x0_pred, 0, x0[0])
return x0_pred[:n + predict_step]
重要提示:实际应用中建议对参数a的取值进行约束(如|a|<0.3),避免过大的发展系数导致预测值发散。
完整的模型评估应包含以下指标:
python复制def evaluate_model(x0, x0_pred):
"""
模型评估函数
返回各种评估指标
"""
n = len(x0)
# 相对误差
epsilon = np.abs(x0 - x0_pred[:n]) / x0
# 平均相对误差
avg_epsilon = np.mean(epsilon)
# 后验差比值
S1 = np.std(x0)
S2 = np.std(x0 - x0_pred[:n])
C = S2 / S1
# 小误差概率
P = np.sum(np.abs(x0 - x0_pred[:n] - np.mean(x0 - x0_pred[:n])) < 0.6745*S1) / n
return {
'MAPE': avg_epsilon,
'C': C,
'P': P
}
评估指标说明:
我们使用某地区2018-2022年季度用电量数据(单位:亿千瓦时):
python复制# 原始数据
data = np.array([125, 132, 142, 138,
146, 152, 160, 155,
162, 170, 178, 172,
180, 185, 193, 188])
# 数据标准化(避免数值过大导致计算问题)
mean_val = np.mean(data)
std_val = np.std(data)
normalized_data = (data - mean_val) / std_val
经验分享:对于有明显季节性的数据,建议先进行季节性分解再建模,或将季节周期作为额外特征输入。
python复制# 模型训练
pred_normalized = dngm11(normalized_data, predict_step=4)
# 结果反标准化
pred = pred_normalized * std_val + mean_val
# 评估指标
metrics = evaluate_model(data, pred[:len(data)])
print(f"MAPE: {metrics['MAPE']:.2%}, C: {metrics['C']:.3f}, P: {metrics['P']:.3f}")
典型输出结果:
code复制MAPE: 2.35%, C: 0.312, P: 0.938
根据灰色模型精度等级标准:
python复制import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.plot(data, 'bo-', label='Actual')
plt.plot(pred[:len(data)], 'r*--', label='Fitted')
plt.plot(range(len(data), len(data)+4), pred[len(data):], 'g^--', label='Predicted')
plt.legend()
plt.title('Electricity Consumption Prediction')
plt.xlabel('Quarter')
plt.ylabel('Consumption (100M kWh)')
plt.grid(True)
plt.show()

从图中可以观察到:
不是所有数据都适合DNGM(1,1)模型。建议建模前进行以下检验:
级比检验:计算σ(k)=x(0)(k-1)/x(0)(k)
光滑比检验:计算ρ(k)=x(0)(k)/x^(1)(k-1)
python复制def data_suitability_test(x0):
n = len(x0)
sigma = x0[:-1] / x0[1:]
lower = np.exp(-2/(n+1))
upper = np.exp(2/(n+1))
ratio_in_range = np.sum((sigma > lower) & (sigma < upper)) / (n-1)
x1 = np.cumsum(x0)
rho = x0[1:] / x1[:-1]
rho_pass = np.sum(rho[2:] < 0.5) / (n-2)
return ratio_in_range, rho_pass
在实际应用中我们发现,当发展系数|a|>0.3时,模型容易出现预测值发散。解决方案:
数据平滑处理:对原始数据进行移动平均或指数平滑
python复制from statsmodels.tsa.api import SimpleExpSmoothing
def smooth_data(x0, alpha=0.3):
model = SimpleExpSmoothing(x0)
fit = model.fit(smoothing_level=alpha)
return fit.fittedvalues
模型组合:将DNGM(1,1)与ARIMA组合,利用ARIMA修正残差
DNGM(1,1)在长期预测时精度会逐渐降低,建议:
python复制def rolling_forecast(x0, steps, window=8):
predictions = []
for i in range(len(x0)-window, len(x0)-window+steps):
train_data = x0[:i+1]
pred = dngm11(train_data, predict_step=1)[-1]
predictions.append(pred)
return predictions
传统背景值z^(1)(k)=0.5(x^(1)(k)+x^(1)(k-1))存在改进空间。我们实验发现以下方法效果更好:
自适应权重法:
code复制z^(1)(k) = αx^(1)(k) + (1-α)x^(1)(k-1)
其中α通过优化算法确定
积分重构法:
用积分形式精确计算背景值:
code复制z^(1)(k) = ∫_{k-1}^k x^(1)(t)dt
固定参数难以适应复杂变化,可改进为:
code复制a(k) = a0 + a1·k
b(k) = b0 + b1·k
这种时变参数形式能更好跟踪系统的动态变化。
与神经网络的结合:
灰色-马尔可夫模型:
实际项目经验表明,这种组合模型能将预测精度再提升15-20%。
在完成多个实际预测项目后,我深刻体会到DNGM(1,1)模型的价值在于其对小样本数据的适应能力。当历史数据有限时,与其盲目使用复杂模型,不如先采用DNGM(1,1)获得基准预测,再逐步引入更复杂的建模方法。模型参数的解释性也是其独特优势,这对需要向决策者解释预测依据的场景尤为重要。