ARTICLE DETAIL

资讯详情

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

DM检验详解:科学比较预测模型差异的原理与实操

DM检验详解:科学比较预测模型差异的原理与实操 预测对比是个老话题但真正能一次说清楚的没几个人。做量化也好、做能源负荷预测也好最常被问的话就是“你这个模型比基线好多少”如果你回答“测试集MSE低了百分之八”对方下一句基本就是这个差异是真实的还是撞运气Diebold-Mariano检验DM检验就是用来回答这个问题的标准工具。它把两个模型在同一段样本外预测上的损失差当成研究对象检验这段差异在统计上是否显著异于零。这篇文章我会从头拆解DM检验的原理、统计量的推导逻辑再给出可以直接运行的Python和R实现最后聊聊我实际使用中踩过的坑。无论你是刚接触预测对比的新手还是已经用RMSE、MAE对比过一堆模型的老手这套方法都能让你的结论更站得住脚。1. DM检验要回答的问题两个模型谁更准差多少才算数1.1 为什么不能只看测试集误差我先说个真实场景。有次我拿一个新模型去和线上基线做样本外对比100个测试点MSE从0.032降到了0.029看起来是接近10%的提升。但把误差序列逐点画出来一看新模型只是在某几个波动大的时段表现得特别好其余时候和基线几乎一样。如果只看整体MSE很容易得出“新模型明显更好”的结论但这类结论有一个致命漏洞你观察到的那点差异可能只是随机波动。这个“随机波动”不是小事。预测误差本质上是一个随机序列即使两个模型能力完全相同在有限的测试集上算出来的MSE也几乎不可能完全相等。问题就变成了0.003的MSE差距到底是模型能力的真实差异还是这段样本外数据的运气普通做法是看一眼数字就下结论严谨做法是做假设检验。DM检验的思路非常直接把两个模型在时间点上的预测误差带入损失函数构造一个逐期的损失差序列然后检验这个序列的均值是否显著不为零。如果显著不为零说明两个模型在预测精度上的差异不太可能只是偶然如果不显著说明现有证据不足以支持“模型A比模型B好”这个结论。1.2 损失差序列的构造逻辑假设两个模型在样本外同一时间窗口上分别得到预测误差记为e1_t和e2_t其中t1,...,T。选择一个损失函数L通常就是平方损失或绝对损失。第t期的损失差定义为d_t L(e1_t) - L(e2_t)如果d_t的均值显著为正说明模型1的损失更大模型2整体上更准如果均值显著为负则结论反过来。零假设和备择假设分别是H0: E(d_t) 0两个模型预测精度相同 H1: E(d_t) ! 0两个模型预测精度存在差异用损失差而不是直接比较两列误差好处在于它天然地“配对了”每个时间点。宏观数据、金融数据里普遍存在的共同波动、共同冲击会在做差时被消掉一大半。比如预测某个宏观指标到特定月份可能所有模型都出现较大误差这是数据本身在那一期的异常用配对结构构造损失差后这个共同成分不会对检验统计量产生额外负担检验更干净。1.3 DM检验到底适用哪些场景不是所有“两个模型对比”的问题都适合用DM检验。它最舒服的场景是两个模型都不嵌套在对方里面或者嵌套结构不明显时。比如比较随机游走模型和AR模型或者比较XGBoost和LSTM这种横向对比通常没问题。有两个场景要特别小心。第一样本外预测期数太少时DM检验的功效很低容易出现不显著但实际有差异的情况第二当两个模型是嵌套关系比如AR(1)和AR(2)直接在损失差上跑DM检验会有严重问题原因我在2.4和4.2里细讲。另外DM检验隐含要求损失差序列是一个短记忆、弱平稳的序列。如果d_t随时间明显漂移或者呈现出极强的持续性那这个检验的前提就已经动摇了。2. 原理解析从零假设到统计量背后的统计逻辑2.1 原版DM统计量的推导原版DM检验是Diebold和Mariano在1995年提出的。它构造了损失差序列d_t的样本均值d_bar然后用这个均值除以它的标准差估计量得到形如t统计量的检验量DM d_bar / sqrt(V_hat_d_bar)这里的关键在于V_hat_d_bar的估计。如果d_t是独立同分布的那V(d_bar)就是序列方差除以T这很简单。但预测误差序列通常不满足独立同分布尤其是多步预测时d_t会呈现出非常强的序列相关性这就把问题复杂化了。DM检验的核心贡献之一正是用异方差和自相关一致估计量HAC来估计均值估计量的方差。具体来说先把d_t中心化计算它的样本自协方差gamma_j Cov(d_t, d_{t-j})然后用类似Newey-West的形式构造长期方差V_hat_d_bar (1/T) * [ gamma_0 2 * sum_{j1}^{h-1} (1 - j/h) * gamma_j ]这里的h是预测步长。当h1时这个公式退化为gamma_0 / T也就是普通方差当h1时它会把序列相关部分纳入进去。DM统计量在零假设下渐近服从标准正态分布所以大样本下可以直接用正态分布临界值判断显著性。2.2 h步预测时的方差修正到底在修正什么很多人跑DM检验时最容易忽略的就是这个h。如果你预测的是h步后的值比如用t期信息预测th期的值那么样本外误差序列本身会有一个MA(h-1)结构。为什么因为t1期和t2期的预测使用了大量重叠信息两个误差都不独立。宏观预测里动辄预测12个月、24个月误差重叠的情况非常严重。如果不修正方差统计量的分母会被严重低估DM统计量会被放大结果就是你很容易得到一个虚假的“显著”。我见过不少报告用h12的预测误差序列做比较却按h1的方式算方差这基本等于抬高了检验的显著性。反过来如果老老实实用HAC估计长期方差变大统计量变小结论可能就从“显著”变成“不显著”。用一个生活类比来理解假设你在测两个销售员的平均日销售额差异但这两个销售员每天都会共享前一天的客户线索日销售额天然相关。如果你天真地假设每天独立样本均值的方差会算得太小最后得出的“A比B高”的结论就很不可靠。DM检验里的HAC方差修正相当于承认这些重叠和相关性再做判断。2.3 小样本修正HLN与其它变体原版DM检验依赖渐近正态近似但实证中样本外预测的期数往往只有几十个。Harvey、Leybourne和Newbold在1997年提出修正给DM统计量乘一个调整因子然后更推荐用t分布尾部而不是标准正态来算p值。修正后的统计量大约是DM_HLN DM * sqrt( (T 1 - 2h h(h-1)/T) / T )再用自由度T-1的t分布做推断。这个修正有两个直观效果小样本下让检验更保守一点减少虚假拒绝同时用t分布的厚尾替代标准正态的薄尾更贴合有限样本的分布形态。现在多数教科书和软件里的DM检验默认就是这种经过HLN修正的版本。另外还有固定带宽HAC、预白化HAC等不同方差估计方案思路都是更稳健地估计长期方差。别把DM检验理解成一个“铁板一块”的公式它更像一套思想比较损失差均值是否为零方差估计要尽量稳健。2.4 DM检验与其它比较方法的边界先放一张对比表把常见的几种预测比较检验放一起看检验适用场景核心思想注意事项DM检验非嵌套模型的样本外预测对比逐期损失差均值是否为零h1时必须修正方差Clark-West检验嵌套模型的样本外预测对比调整损失差剔除参数估计噪声专门解决嵌套模型问题Mincer-Zarnowitz回归单个模型的校准检验预测值对实际值回归看斜率是否为1不适合两个模型的直接对比West检验考虑参数估计不确定性的比较在DM基础上叠加参数估计方差项实现复杂应用较少很多人不理解为什么嵌套模型时DM会失效。简单说当模型2是模型1加上额外变量时在零假设真实模型是模型1下模型2本质上是在估计一堆零系数。样本外预测时这些额外系数的估计噪声会让模型2的样本外损失偏高损失差序列的均值不再围绕零附近而且受到参数估计不确定性的影响DM统计量会偏离正态近似。Clark和West的思路是构造一个调整后的损失差把这种“过拟合损失”剥离出来再检验。3. 手把手实操Python和R里的DM检验怎么落地3.1 用Python手写DM检验函数比想象中更简单虽然Python里没有特别官方的DM检验内置函数但手写一个并不复杂。下面这段代码支持MSE和MAE两种损失函数默认使用Newey-West方差估计并自动应用HLN小样本修正import numpy as np from scipy import stats def dm_test(e1, e2, h1, lossmse, hlnTrue): 参数说明 e1 : array模型1的样本外预测误差序列 e2 : array模型2的样本外预测误差序列 h : int预测步长比如用t期预测th期就传h loss : strmse或mae也可以用自定义损失 hln : bool是否应用Harvey-Leybourne-Newbold修正 e1 np.asarray(e1, dtypefloat).ravel() e2 np.asarray(e2, dtypefloat).ravel() if len(e1) ! len(e2): raise ValueError(两个误差序列长度不一致) T len(e1) if loss mse: d e1**2 - e2**2 elif loss mae: d np.abs(e1) - np.abs(e2) else: raise ValueError(目前只内置了mse和mae请自定义损失差序列) dbar d.mean() def autocov(x, j): xc x - x.mean() n len(x) return np.dot(xc[:n-j], xc[j:]) / n # Newey-West长期方差估计带宽取h-1 gamma0 autocov(d, 0) if h 1: lr_var gamma0 2 * sum( (1 - j / h) * autocov(d, j) for j in range(1, h) ) else: lr_var gamma0 se np.sqrt(lr_var / T) stat dbar / se pval_norm 2 * (1 - stats.norm.cdf(np.abs(stat))) if hln: adj np.sqrt((T 1 - 2*h h*(h-1)/T) / T) stat stat * adj pval 2 * (1 - stats.t.cdf(np.abs(stat), dfT-1)) else: pval pval_norm return {DM_statistic: stat, p_value: pval}这段代码有几个关键点。第一autocov函数用的是除以n的有偏自协方差估计这和Newey-West估计量的默认设定一致。第二当h1时长期方差就是gamma0相当于普通配对t检验的形式。第三HLN修正默认开启在实际样本外期数不够多时我建议保持开启状态。如果你已经自定义了损失函数只需要提前把损失差序列d算出来然后把上面的d替换掉其余部分完全不用动。这也说明DM检验本质上不依赖具体损失函数任何能反映你的业务目标的损失都可以套进这个框架。3.2 一个模拟案例的完整流程下面用一个简单的模拟来演示DM检验的调用方式。假设真实数据由AR(2)过程生成我们分别用AR(1)和AR(2)做样本外预测得到两列误差序列然后跑DM检验。rng np.random.default_rng(42) T_total 250 y np.zeros(T_total) eps rng.normal(0, 1, T_total) for t in range(2, T_total): y[t] 0.6 * y[t-1] - 0.2 * y[t-2] eps[t]由于这里只是演示DM检验本身我不展开完整的滚动预测代码假设你已经用前200个样本训练模型在后50个样本上得到两列预测误差e_ar1和e_ar2。典型的调用结果如下e_ar1 np.random.normal(0, 1.3, 50) # 示意数据实际应该来自预测 e_ar2 np.random.normal(0, 1.0, 50) res dm_test(e_ar1, e_ar2, h1, lossmse) print(res) # 可能输出{DM_statistic: 2.34, p_value: 0.022}这里DM统计量为2.34p值为0.022在5%显著性水平下拒绝零假设说明模型2显著优于模型1。如果p值大于0.1那就说明现有数据不足以下“模型A更好”的结论。真实使用中我还会把损失差序列单独画出来。视觉检查非常重要如果损失差序列多数时间都在零轴上下随机波动只有个别点很大那么即使DM检验显著也要小心结论是不是被少数离群值驱动的。3.3 R语言中的现成实现如果你用R做时间序列分析DM检验有现成的包可以用。ForecastCombinations包提供了DM.test函数library(ForecastCombinations) # e1和e2是两个模型的样本外预测误差 # H是预测步长power2表示MSE损失power1表示MAE DM.test(e1, e2, H 1, power 2)输出会给出DM统计量和p值。multDM包还支持多个模型之间的两两比较适合做模型池筛选。不过这些包有时因为维护原因进入存档状态如果装不上参考上面Python版的逻辑写一个R版也就二十来行反而更可控。4. 踩坑经验从样本量到嵌套模型的常见问题清单4.1 样本量太小检验的说服力不足我踩过的第一个坑就是样本外期数太少。那时手上只有40个季度样本外点两个模型的MSE差距看起来很大DM检验p值却到了0.2。原因是样本太少长期方差估计不稳检验功效非常低。后来把预测窗口换成滚动式的月度预测样本外点增加到120个同样的模型对比p值就明显下降了。经验上样本外预测点少于50个时DM检验基本只能看个趋势很难给出强结论50到80个处于“可以用但别太自信”的状态100个以上相对舒服。如果你只能用少量样本外点建议配合block bootstrap给统计量做经验分布而不是只依赖渐近近似。4.2 嵌套模型误用DM检验结论容易失真嵌套模型是DM检验的高发雷区。你想验证加入某个因子是否提升预测力基准模型是包含3个变量的线性模型新模型是包含全部4个变量的线性模型。这种结构下原版DM检验并不合适因为新模型多估计的那个系数在零假设下没有真实作用但估计本身会引入样本外噪声损失差序列的分布会被扭曲。至少有没有一种直观的补救办法有就是Clark-West检验。它的核心思路是构造一个调整后的损失差把嵌套模型下额外参数造成的过度拟合效应剥离掉再判断剩余的真实差异是否显著。我用CW检验处理过好几组嵌套模型的对比结论比DM检验稳定得多。如果你的模型池里存在明显的嵌套关系别硬套DM。4.3 损失函数选择和厚尾问题DM检验的结果高度依赖损失函数。用MSE可能显著换MAE可能就不显著。这不是检验本身的问题而是业务问题的定义问题你在乎大误差还是普通误差如果预测误差有厚尾或明显离群值平方损失会把少数极端点放到主导地位整个检验实际上是在判断“谁在极端时段更准”。建议在项目开始前就确定损失函数把MSE、MAE各跑一遍作为敏感性分析并且在报告里写清楚。如果损失差序列厚尾非常明显统计量的大样本近似会更脆弱。一种实用做法是同时报告DM检验结果和基于bootstrap的p值bootstrap对厚尾更稳健。自助抽样时记得用block bootstrap保留序列相关结构不能直接普通重采样。4.4 预测窗口、数据重叠与多重检验最后一个容易栽跟头的地方是预测方案本身。滚动窗口预测和递推窗口预测得到的误差序列相关性结构完全不同。前者每次把窗口往前移一格误差重叠少后者把训练集越滚越大重叠相对多。做DM检验时带宽h的设定要和实际预测方案对应起来别机械地只看预测对象是几步。即使h1如果两个模型的预测都依赖同一段历史数据损失差也可能存在相关性需要留意。还有一个更隐蔽的问题一个数据集上反复跑不同模型的DM检验本质上是多重比较。你比较了10个模型两两之间跑了45次DM检验按5%的显著性水平平均就会出现两三次假阳性。这时要么用Bonferroni之类的多重检验校正要么把DM检验当成探索性工具不要过度解读单次显著的p值。我自己现在拿到两个模型的样本外误差第一件事不是急着算DM统计量而是先把损失差序列画出来看看它是否在零轴附近稳定波动。如果d_t有明显的上升趋势或者方差急剧变化那后续所有检验可能都没有意义如果序列表现稳定再跑DM检验才放心。另外当预测步长比较大比如季度数据的8步以上预测我会把Newey-West带宽适当放大不只是严格取h-1有时会取到1.5倍h附近配合HLN修正多做几次敏感性分析。这不是标准流程但能帮你判断结论到底是稳固的还是恰好卡在显著性边界上。做预测对比方法只是手段真正的价值在于对方看到你的p值、检验过程和你对潜在风险的说明后愿意相信这个结论。
返回列表