手写实现灰色预测GM(1,1)模型:从小样本数据到趋势预测

📅 发布时间:2026/8/28 11:36:44
手写实现灰色预测GM(1,1)模型:从小样本数据到趋势预测 1. 项目概述从直觉到代码拆解灰色预测的“灰色”魅力刚接触“灰色预测”这个词很多朋友可能会觉得有点玄乎。它不像回归分析那样有明确的数学假设也不像神经网络那样有复杂的结构。我第一次在项目里用上它是因为手头只有寥寥几年的数据传统的时间序列方法根本没法用ARIMA模型看着那点可怜的样本量直摇头。结果用灰色预测模型GM(1,1)跑了一下预测趋势居然和后续的实际发展吻合得不错。这让我意识到这个看似简单的模型在处理“小样本、贫信息”的不确定性问题时有着独特的价值。简单来说灰色预测理论的核心思想就是把看似杂乱无章、信息不全的原始数据序列通过一定的生成变换比如累加变成有较强规律性的新序列然后构建微分方程模型进行预测最后再通过逆变换还原得到预测值。整个过程就像给模糊的“灰色”系统擦亮了一部分让我们能窥见其内在的发展规律。今天我就把手写实现灰色预测GM(1,1)模型的完整过程从理论推导到Python代码毫无保留地拆解给你看。无论你是数据分析的新手想找一个轻量级预测工具入门还是经验丰富的老手需要在数据稀缺场景下寻找补充方案这篇内容都能给你提供可直接“抄作业”的实操指南。2. 灰色预测GM(1,1)模型的核心思想与数学推导2.1 为什么是“灰色”系统认知的哲学基础在控制论和系统科学里我们常根据信息完备程度把系统分为三类“白色系统”指信息完全明确的系统比如一个已知所有参数的电路“黑色系统”指信息一无所知的系统而“灰色系统”则介于两者之间指部分信息已知、部分信息未知的系统。我们现实世界中遇到的大多数问题比如经济走势、设备磨损、疫情传播初期都属于灰色系统。我们有一些观测数据但不足以完全确定其精确的数学模型。灰色预测理论就是专门针对这类“少数据、不确定性”问题而生的。它不强求大样本也不要求数据服从典型的概率分布这种“柔性”特质是它在工程、经济、管理等领域备受欢迎的原因。2.2 GM(1,1)模型一阶单变量的微分方程骨架GM(1,1)是灰色预测中最基础、应用最广的模型。这个名字拆解开来很有意思G代表Grey灰色M代表Model模型第一个1表示一阶微分方程第二个1表示只包含1个变量。它的目标是用一个一阶常微分方程来拟合经过累加生成后的新序列其标准形式是[ \frac{dx^{(1)}}{dt} ax^{(1)} b ]这里(x^{(1)}) 就是我们通过对原始数据做一次累加生成1-AGO得到的新序列。(a) 称为发展系数它反映了序列的发展态势(b) 称为灰色作用量可以理解为系统内的内生驱动或外部等效输入。模型的意义在于它用指数增长或衰减的规律来近似描述累加后序列的变化趋势。为什么是指数形式因为上述微分方程的解本身就是一个指数函数。这其实很符合很多自然和社会现象的规律比如技术的早期扩散、疾病的初始传播、资金的复利增长在初期阶段都近似呈现指数形态。2.3 手撕推导从原始数据到参数估计理论说得再漂亮不如动手推一遍。假设我们有原始非负数据序列 (X^{(0)} (x^{(0)}(1), x^{(0)}(2), ..., x^{(0)}(n)))。第一步进行一次累加生成1-AGO这是灰色预测的“灵魂操作”。累加的目的是弱化原始数据的随机性凸显其内在趋势。 [ x^{(1)}(k) \sum_{i1}^{k} x^{(0)}(i), \quad k1,2,...,n ] 这样我们得到了一个新序列 (X^{(1)} (x^{(1)}(1), x^{(1)}(2), ..., x^{(1)}(n)))。你可以直观地把它想象成“数据累计和”的曲线这条曲线通常比原始数据曲线平滑得多。第二步构建背景值序列 (Z^{(1)})背景值是什么它是微分方程 (dx^{(1)}/dt) 离散化时用于代表 (x^{(1)}) 在区间 ([k-1, k]) 上的一个值。最常用的是紧邻均值生成 [ z^{(1)}(k) 0.5 \times [x^{(1)}(k) x^{(1)}(k-1)], \quad k2,3,...,n ] 为什么取均值从微积分角度看微分表示瞬时变化率但我们的数据是离散的。用区间两端点的平均值来近似代表该区间内函数的值类似于梯形积分的思想是使离散方程最接近原始微分方程的一种合理选择。这里就体现了灰色预测的“灰”色——它不追求精确解而是寻找在信息不足条件下的最优近似。第三步建立灰色微分方程即GM(1,1)模型的离散形式将微分方程 (\frac{dx^{(1)}}{dt} ax^{(1)} b) 离散化。用一阶差分近似微分 (\frac{dx^{(1)}}{dt} \approx x^{(1)}(k) - x^{(1)}(k-1) x^{(0)}(k))并用背景值 (z^{(1)}(k)) 代替 (x^{(1)})得到 [ x^{(0)}(k) a z^{(1)}(k) b, \quad k2,3,...,n ] 这个方程称为GM(1,1)的基本形式。注意这里 (x^{(0)}(k)) 是原始序列值。第四步利用最小二乘法估计参数 (a) 和 (b)将上面的方程写为矩阵形式 (Y B \hat{u})。 其中 [ Y \begin{bmatrix} x^{(0)}(2) \ x^{(0)}(3) \ \vdots \ x^{(0)}(n) \end{bmatrix}, \quad B \begin{bmatrix} -z^{(1)}(2) 1 \ -z^{(1)}(3) 1 \ \vdots \vdots \ -z^{(1)}(n) 1 \end{bmatrix}, \quad \hat{u} \begin{bmatrix} a \ b \end{bmatrix} ] 根据最小二乘法参数向量的估计值为 [ \hat{u} (a, b)^T (B^T B)^{-1} B^T Y ] 这里就完成了模型的核心拟合。计算这个 ((B^T B)^{-1}) 是代码实现中的一个关键点。注意最小二乘法这里暗含了一个假设即误差项是独立同分布的。虽然灰色模型没有严格假设数据分布但这个求解方法本身是一种在平方误差最小意义下的最优线性无偏估计。第五步求解时间响应式即预测公式估计出参数 (a, b) 后代回原始的微分方程 (\frac{dx^{(1)}}{dt} ax^{(1)} b)。这是一个一阶线性常微分方程结合初始条件 (x^{(1)}(1) x^{(0)}(1))可以求出其解也称为时间响应函数为 [ \hat{x}^{(1)}(k1) \left( x^{(0)}(1) - \frac{b}{a} \right) e^{-ak} \frac{b}{a}, \quad k0,1,2,... ] 这个 (\hat{x}^{(1)}(k1)) 就是对累加序列 (X^{(1)}) 的预测值。注意这里 (k) 从0开始(\hat{x}^{(1)}(1)) 对应的是初始值。第六步累减还原IAGO得到原始序列预测值因为我们预测的是累加序列最终需要还原回原始序列的预测值。通过累减生成即做差分 [ \hat{x}^{(0)}(k1) \hat{x}^{(1)}(k1) - \hat{x}^{(1)}(k), \quad k1,2,3,... ] 其中我们定义 (\hat{x}^{(1)}(0) 0)。特别地当 (k1) 时(\hat{x}^{(0)}(2) \hat{x}^{(1)}(2) - \hat{x}^{(1)}(1))。将第五步的时间响应式代入可以得到还原后的直接预测公式 [ \hat{x}^{(0)}(k1) (1-e^{a}) \left( x^{(0)}(1) - \frac{b}{a} \right) e^{-ak}, \quad k1,2,3,... ] 这个公式在编程实现时更为常用因为它一步到位给出了原始序列的预测值。3. Python手写实现GM(1,1)模型全流程理解了数学原理代码实现就是按部就班的“翻译”工作。我们不依赖任何专门的灰色预测库只用NumPy进行基础矩阵运算彻底搞懂每一个环节。3.1 环境准备与数据预处理首先确保你的Python环境安装了NumPy。数据预处理是灰色预测的第一步也是容易踩坑的地方。import numpy as np class GM11: 手写实现灰色预测GM(1,1)模型 def __init__(self): self.a None # 发展系数 self.b None # 灰色作用量 self.x0 None # 原始序列 self.z1 None # 背景值序列 self.fit_success False def fit(self, data): 训练模型估计参数a和b :param data: 一维数组或列表原始非负数据序列 # 1. 数据校验与转换 self.x0 np.array(data, dtypenp.float64) n len(self.x0) if n 4: raise ValueError(灰色预测至少需要4个数据点才能保证基本的拟合效果。) if np.any(self.x0 0): # 灰色预测通常要求非负序列。若含负数可考虑进行平移处理。 print(警告原始序列包含负数可能影响模型精度。) # 2. 一次累加生成 (1-AGO) x1 np.cumsum(self.x0) # 3. 计算背景值z1 (紧邻均值生成) # z1(k) 0.5 * [x1(k) x1(k-1)], k从2开始 self.z1 (x1[1:] x1[:-1]) / 2.0 # 这是一个长度为n-1的数组 # 4. 构造矩阵B和向量Y # Y x0[1:] (因为方程从k2开始对应索引1) Y self.x0[1:].reshape(-1, 1) # 变为列向量 # B [-z1, 1] B np.column_stack((-self.z1, np.ones_like(self.z1))) # 5. 最小二乘法求解参数u [a, b]^T # u_hat (B^T * B)^{-1} * B^T * Y try: # 使用np.linalg.inv求逆更稳定的做法是使用np.linalg.lstsq或np.linalg.pinv # 这里为清晰展示公式使用求逆。实际生产代码建议用pinv防止矩阵奇异。 B_T B.T B_T_B_inv np.linalg.inv(B_T B) u_hat B_T_B_inv B_T Y self.a, self.b u_hat.flatten() # 提取a和b self.fit_success True except np.linalg.LinAlgError: print(矩阵(B^T*B)奇异无法求逆。可能数据序列存在线性相关问题。) self.fit_success False return self实操心得1数据量要求与负数处理理论上GM(1,1)有4个数据就能建模但实际应用中样本量过少如n4会导致模型极不稳定对异常值非常敏感。我个人的经验是至少需要6-7个数据点预测结果才有一定参考价值。如果数据中包含负数直接建模可能会出现问题因为累加序列的指数特性对负数不友好。常见的处理方法是给所有数据加上一个足够大的常数C平移变换使序列变为正数预测后再减去这个常数。但要注意这本质上改变了序列的相对关系需谨慎评估其对预测趋势的影响。3.2 模型预测与结果还原训练好模型后我们就可以进行预测了。这里实现predict方法可以预测未来若干步的值。def predict(self, steps1): 进行预测 :param steps: 预测未来多少步 :return: 预测值数组包含对原始序列的拟合值前n个和未来预测值后steps个 if not self.fit_success: raise RuntimeError(模型尚未训练成功请先调用fit方法。) n len(self.x0) # 计算拟合值对历史数据和预测值 fitted_values np.zeros(n) predicted_values np.zeros(steps) # 使用还原后的直接预测公式: x0_hat(k1) (1-exp(a))*(x0(1)-b/a)*exp(-a*k) # 注意公式中的k从1开始对应预测x0_hat(2) c (1 - np.exp(self.a)) * (self.x0[0] - self.b / self.a) # 1. 计算历史数据的拟合值 (k1,2,...,n-1) for k in range(1, n): # k对应公式中的k预测的是第k1个点 fitted_values[k] c * np.exp(-self.a * (k-1)) # 注意指数项是(k-1) # 第一个数据点的拟合值就是它本身模型定义 fitted_values[0] self.x0[0] # 2. 计算未来steps步的预测值 # 此时k从n开始对应预测第n1, n2, ...个点 for i in range(steps): k n i - 1 # 因为循环中i从0开始对应预测第n1点此时公式中kn predicted_values[i] c * np.exp(-self.a * k) return fitted_values, predicted_values实操心得2预测公式的索引对齐这是手写实现时最容易出错的地方。时间响应式 (\hat{x}^{(1)}(k1)) 和还原公式 (\hat{x}^{(0)}(k1)) 中的索引 (k) 与编程中的数组索引从0开始需要仔细对应。我的经验是先用具体数字如n5在纸上推导一遍明确每个k对应的实际数据点位置再写代码。上面的代码注释中已详细标明了对应关系。一个快速验证方法是用历史数据拟合第一个拟合值fitted_values[0]应该等于原始第一个数据x0[0]第二个拟合值fitted_values[1]应该是对原始第二个数据x0[1]的拟合。3.3 模型检验如何判断预测靠不靠谱模型建好了预测值也出来了但你怎么知道它是不是在“胡说八道”灰色预测有一套常用的后验差检验方法主要看两个指标后验差比C和小误差概率P。def evaluate(self, fitted_values): 模型精度检验后验差检验 :param fitted_values: 模型对历史数据的拟合值 :return: 评价等级字典 n len(self.x0) # 1. 计算残差序列 residuals self.x0 - fitted_values # 2. 计算原始序列的均值、方差 mean_x0 np.mean(self.x0) s1_square np.mean((self.x0 - mean_x0) ** 2) # 原始序列方差 s1 np.sqrt(s1_square) # 原始序列标准差 # 3. 计算残差序列的均值、方差 mean_e np.mean(residuals) s2_square np.mean((residuals - mean_e) ** 2) # 残差序列方差 s2 np.sqrt(s2_square) # 残差序列标准差 # 4. 计算后验差比C C s2 / s1 # 5. 计算小误差概率P # 小误差指 |残差 - 残差均值| 0.6745 * S1 threshold 0.6745 * s1 count_small_error np.sum(np.abs(residuals - mean_e) threshold) P count_small_error / n # 6. 根据C和P评价模型精度 grade 未知 if C 0.35 and P 0.95: grade 优秀 (1级) elif C 0.5 and P 0.8: grade 合格 (2级) elif C 0.65 and P 0.7: grade 勉强合格 (3级) else: grade 不合格 (4级) evaluation_result { 后验差比C: round(C, 4), 小误差概率P: round(P, 4), 精度等级: grade, 残差序列: residuals, 平均相对误差: round(np.mean(np.abs(residuals[1:] / self.x0[1:])) * 100, 2) # 忽略第一个点 } return evaluation_result注意后验差检验是一个重要的参考但不是唯一标准。特别是当数据量很小时C和P的值可能波动很大。我通常会结合平均相对误差和残差图来综合判断。如果残差序列没有明显的趋势或周期性看起来是随机波动那么即使C值稍大模型也可能抓住了主要趋势。反之如果残差呈现明显规律说明模型未能完全提取序列信息需要谨慎使用预测结果。3.4 完整案例演示用GM(1,1)预测某产品季度销量让我们用一个虚构但贴近实际的例子把上面的代码串起来跑一遍。假设我们有某产品过去7个季度的销量数据单位千台[26.7, 31.5, 32.8, 34.1, 35.8, 37.5, 39.2]。我们想预测接下来两个季度的销量。# 示例完整流程 if __name__ __main__: # 1. 准备数据 data [26.7, 31.5, 32.8, 34.1, 35.8, 37.5, 39.2] # 2. 初始化并训练模型 model GM11() model.fit(data) print(f发展系数 a {model.a:.6f}) print(f灰色作用量 b {model.b:.6f}) print(f模型方程: dx/dt ({model.a:.4f})x {model.b:.4f}) # 3. 进行预测预测未来2期 fitted_vals, future_vals model.predict(steps2) print(\n历史数据拟合值:) for i, (true, fitted) in enumerate(zip(data, fitted_vals)): print(f 第{i1}期: 真实值{true}, 拟合值{fitted:.4f}, 残差{true-fitted:.4f}) print(f\n未来2期预测值: {future_vals}) # 4. 模型检验 eval_result model.evaluate(fitted_vals) print(\n 模型精度检验 ) print(f后验差比 C {eval_result[后验差比C]}) print(f小误差概率 P {eval_result[小误差概率P]}) print(f平均相对误差 {eval_result[平均相对误差]}%) print(f综合精度等级: {eval_result[精度等级]}) # 5. 简单可视化可选需安装matplotlib try: import matplotlib.pyplot as plt periods_historical list(range(1, len(data)1)) periods_future list(range(len(data)1, len(data)3)) plt.figure(figsize(10, 6)) plt.plot(periods_historical, data, bo-, label实际销量, markersize8) plt.plot(periods_historical, fitted_vals, rs--, label模型拟合, markersize6) plt.plot(periods_future, future_vals, g^--, label未来预测, markersize10) plt.xlabel(季度) plt.ylabel(销量 (千台)) plt.title(GM(1,1)模型销量预测) plt.legend() plt.grid(True, linestyle--, alpha0.7) plt.show() except ImportError: print(如需可视化请安装matplotlib库)运行这段代码你会得到模型的参数、拟合值、预测值以及精度评价。通过这个完整的流程你不仅得到了预测结果更重要的是理解了每个数字是怎么来的以及如何评判它的可信度。4. 深入探讨GM(1,1)模型的适用边界与优化技巧灰色预测不是万能的用对了场景是“神器”用错了场景可能产生误导。理解它的局限性和优化方法比单纯会用更重要。4.1 什么时候该用什么时候不该用适合使用GM(1,1)的场景数据量极少只有4-15个数据点传统时间序列方法如ARIMA无法有效建模。趋势明显数据呈现单调递增或递减趋势指数型趋势最佳。这是GM(1,1)模型内在的指数特性所决定的。短期预测通常用于未来1-3期的预测。灰色模型是基于现有数据“惯性”的外推预测步长越长不确定性呈指数增长精度下降越快。系统受多重因素影响但无法全部量化这正是“灰色系统”的典型特征。你只知道部分影响因素模型通过灰色作用量b来综合代表所有未知因素的影响。应避免使用或需谨慎处理的场景数据波动剧烈如果原始序列随机波动很大没有明显的指数增长/衰减趋势累加操作可能无法有效提取趋势预测效果会很差。长期预测预测未来5期、10期以上结果往往偏离实际很远。我个人的经验法则是预测步数不要超过原始数据序列长度的一半。数据包含季节性/周期性标准的GM(1,1)无法处理季节性。如果你的销量数据有明显的季度性波动直接使用GM(1,1)只会得到一个平滑的趋势线无法捕捉周期高点或低点。出现异常值或突变点灰色模型对数据突变非常敏感。一个异常的“跳点”会显著影响发展系数a的估计导致整个预测轨迹发生偏移。4.2 模型优化与变体简介当你发现标准GM(1,1)效果不佳时可以考虑以下优化方向或变体模型1. 数据预处理优化平移变换如前所述处理含负数的序列。对数变换如果怀疑原始序列具有乘性趋势或异方差性可以先取对数对变换后的序列建模预测后再指数还原。平滑处理对波动较大的序列在累加前可先进行移动平均等平滑处理但会损失部分信息。2. 背景值生成优化标准模型用紧邻均值 (z^{(1)}(k)0.5[x^{(1)}(k)x^{(1)}(k-1)])。研究表明这并非最优。可以引入背景值优化系数(\alpha)令 (z^{(1)}(k)\alpha x^{(1)}(k) (1-\alpha)x^{(1)}(k-1))并通过智能算法如粒子群、遗传算法寻找使预测误差最小的 (\alpha) 值。这能有效提升模型精度尤其对非齐次指数序列。3. 参数估计方法优化除了普通最小二乘法(OLS)还可以考虑加权最小二乘法(WLS)给不同时期的数据赋予不同的权重通常近期数据权重大符合“近大远小”的预测原则。正则化方法当数据量很少、矩阵 ((B^T B)) 接近奇异时使用岭回归(Ridge Regression)等正则化方法可以增加估计的稳定性。4. 模型变体离散GM(1,1)模型 (DGM)直接针对离散的差分方程建模避免了从微分方程离散化带来的近似误差有时精度更高。GM(1,N)模型适用于一个系统特征变量与多个相关因素变量的情况是多元版本的灰色模型。灰色Verhulst模型适用于原始序列呈“S”型增长有饱和上限的情况比如产品生命周期、市场容量预测等。实操心得3从“能用”到“好用”的关键——残差分析拟合完成后一定要画残差图把残差序列(实际值 - 拟合值)按时间顺序画出来。如果残差随机分布在0轴附近没有明显模式说明模型已较好地提取了趋势信息。如果残差呈现明显的趋势如持续为正或为负或周期性说明还有规律未被模型捕获此时标准GM(1,1)可能不适用需要考虑引入上述优化或换用其他模型。残差分析是诊断模型适用性的最直观工具。5. 常见问题与实战排坑指南在实际应用中我踩过不少坑。这里把最常见的问题和解决方法整理出来希望能帮你省点时间。5.1 报错与异常处理问题1运行时报错LinAlgError: Singular matrix原因矩阵(B^T * B)是奇异矩阵不可逆。这通常发生在数据序列存在完全线性关系或数据点太少、序列变化过于平缓时。解决检查数据量确保n 4。使用更稳定的数值解法将代码中的np.linalg.inv()替换为np.linalg.pinv()求伪逆或直接使用np.linalg.lstsq(B, Y, rcondNone)[0]最小二乘求解。如果数据变化非常缓慢可以尝试对原始数据做一个轻微的放大如乘以一个系数引入细微变化但需注意这改变了数据尺度。问题2预测值出现负数或异常大/小原因发展系数a的估计可能出现问题或者原始数据序列本身不适合用指数模型拟合如震荡序列。解决检查参数a的值。理论上对于增长序列a应为负值因为微分方程解是 (e^{-at})a为负时指数增长对于衰减序列a应为正值。如果符号不符合预期模型可能失效。检查原始数据是否严格递增/递减。如果不是考虑使用其他模型。进行后验差检验如果精度等级为“不合格”则预测结果不可信。问题3对历史数据拟合很好但未来预测明显偏离原因这是灰色预测的固有局限——外推风险。模型捕捉的是历史惯性如果系统在未来发生结构性变化如政策干预、市场突变预测必然失效。解决限定预测期严格遵守短期预测原则。滚动预测获得新的实际数据后立即将其加入训练序列重新建模预测下一步。这能让模型动态适应最新变化。结合定性分析将灰色预测的定量结果与行业专家经验、市场定性判断相结合进行修正。5.2 精度提升实战技巧技巧1引入新陈代谢模型这是提升灰色预测适应性的有效方法。基本思想是每次预测后加入最新的真实数据同时去掉最老的一个数据保持训练序列长度不变重新建立GM(1,1)模型进行下一期预测。这样模型始终基于最新的“信息窗”进行外推更能反映系统的近期变化。代码实现上就是在你的预测循环中不断更新self.x0并重新调用fit方法。技巧2残差修正GM(1,1)如果标准模型的残差序列仍有规律可以对残差序列本身再建立一个GM(1,1)模型或其他模型如AR模型用残差模型的预测值去修正原始模型的预测值。这相当于进行了两次拟合往往能显著提升精度尤其是对中期预测。技巧3模型组合预测不要孤注一掷。将GM(1,1)的预测结果与其他简单方法如移动平均、指数平滑的预测结果进行加权平均。或者使用多个不同背景值系数 (\alpha) 的GM(1,1)模型进行组合。组合预测能在一定程度上降低单一模型的风险。5.3 与其他预测方法的对比选型为了让你更清楚GM(1,1)的定位这里用一个简单的对比表格来说明特性GM(1,1)灰色预测ARIMA时间序列指数平滑法机器学习如LSTM数据需求极少≥4较多通常≥50中等≥20大量≥数百趋势捕捉指数趋势强线性、多项式趋势水平、趋势、季节性复杂非线性趋势周期性处理不能可以通过季节性差分可以Holt-Winters可以通过序列窗口模型复杂度低2个参数中高需定阶低参数少高结构复杂计算速度极快中等快慢需训练可解释性高微分方程中高自回归方程高加权平均低黑盒核心适用场景小样本、趋势明显、短期预测有足够历史数据、存在自相关稳定时间序列、中短期预测大数据量、复杂模式、长期预测选择预测模型本质上是在数据条件、问题复杂度和你对可解释性的要求之间做权衡。GM(1,1)就是你工具箱里那把应对“数据荒”的专用螺丝刀虽然功能单一但在特定场合下无比顺手。最后我想分享一点个人体会灰色预测的魅力在于它用一种极其简约的数学框架去应对现实世界中的不确定性。它不追求完美拟合过去而是致力于从有限的信息中提取最核心的趋势“惯性”。在数据为王、算力至上的今天掌握这种“四两拨千斤”的思维方法有时候比堆砌复杂模型更有价值。当你下次再遇到只有寥寥几个数据点却又必须做出判断的情况时不妨试试手写一个GM(1,1)模型感受一下从“灰”到“白”的推理乐趣。完整的代码我已经在文中分段给出你可以直接复制拼接成一个完整的类导入你的数据开始实验了。