halo 的技术博客

返回

一句话版本#

MDI 说「这个特征在树里被用了多少」,MDA 说「拿掉这个特征模型坏多少」——两个都会在相关特征面前说谎,方式不同:MDI 给噪声发钱、让冗余稀释排名;MDA 让冗余互相顶班、低估整个特征组。解法不是二选一,是聚类之后整组置换


为什么特征重要性排在回测前面#

López de Prado 在 AFML 里有一句被引用很多的狠话,大意是:回测不是研究工具,特征重要性才是

逻辑链条:回测输出的是一条净值曲线,它只告诉你「这套规则在这段历史上赚了多少」,不告诉你为什么。如果你不知道模型依赖哪些特征、这些特征为什么应该有预测力,你无法区分三种情形——真 alpha、对历史噪声的过拟合、以及碰巧押中了某个未来会消失的 regime。而反复调参数直到回测好看,本质是在第二种情形上做梯度下降。

特征重要性是打开黑箱的第一把钥匙。但这把钥匙本身有两个主流版本,各自有系统性的说谎方式。不搞清楚它们怎么骗你,钥匙会把你带进另一间错误的房间。

受控实验:12 个特征,只有 3 个是真的#

延续这个系列的方法论:不用真实市场数据做演示(无法知道 ground truth),用受控模拟把因果钉死。

构造 4000 个样本、12 个特征的分类数据集:

  • 信息特征 I_1, I_2, I_3:标签直接由它们生成,logit = 1.2·I_1 + 0.9·I_2 − 0.7·I_3 + 噪声
  • 冗余特征 R_1, R_2, R_3:I_1 和 I_2 的高相关复制品(R_1 = I_1 + 小噪声,相关系数 0.989)——模拟量化实践中「同一信号的不同包装」:动量的 20 日版和 22 日版、PE 和 EP、规模因子的对数版和排名版;
  • 纯噪声 N_1 … N_6:与标签零关系。

特征相关矩阵

理想的特征重要性工具应该输出:I_1 > I_2 > I_3 显著非零,R 组与对应 I 特征共享重要性(毕竟携带同样的信息),N 组严格为零。看看两套工具各自交出什么答卷。

MDI:树内记账,噪声也能分到钱#

MDI(Mean Decrease Impurity)是随机森林自带的 feature_importances_:每次分裂带来的不纯度下降,按特征累加,全森林平均。它是训练时的副产品,零额外计算成本——这也是它被滥用最广的原因。

MDI 特征重要性

结果暴露 MDI 的两个结构性缺陷:

缺陷一:噪声特征拿到显著非零的重要性。 六个纯噪声特征每个都分到 0.04 上下——不是 bug,是数学必然。MDI 全森林归一化到和为 1,且每次分裂的不纯度下降非负,所以MDI 永远不会给出零或负值。树足够深时,噪声特征总会在某些节点上「碰巧」切出不纯度下降。你无法从 MDI 数值本身判断一个特征是否真的有用,只能做特征间的相对比较——而且比较基准线是 1/N 而不是 0。

缺陷二:替代效应稀释排名。 I_1 的重要性 0.179,它的两个复制品 R_1、R_2 分别拿到 0.179 和 0.167——三者几乎均分。原因:树在某个节点选了 I_1 之后,下个节点用 R_1 能提供的增量信息就近似为零,但随机森林的列抽样让三者轮流被选中,把本属于一份信息的功劳记在三个账户上。如果你按「重要性 > 0.15」筛选特征,会以为自己找到了三个独立因子——实际上只有一个。

MDA:样本外置换,噪声归零但冗余顶班#

MDA(Mean Decrease Accuracy,即置换重要性)换了一个问法:模型训练好之后,在样本外数据上把某一列打乱重排,看性能退化多少。退化大 = 模型真的依赖它。

注意三个实现细节:性能指标用 log-loss 而不是准确率(金融场景下概率质量比分类对错重要,后续仓位函数直接消费概率);置换发生在测试折上(训练集置换回答的是另一个问题);每折重新训练,输出跨折均值 ± 标准误。

MDA 特征重要性

好消息:六个噪声特征全部干净归零(最大 0.0006,置信区间横跨零)。MDA 有天然的零点——置换一个模型根本不依赖的特征,OOS 性能不动。这让 MDA 具备了 MDI 没有的检验属性:可以回答「这个特征是否显著有用」。

坏消息藏在数值里:I_1 的退化只有 0.048。一个以 1.2 的系数直接生成标签的特征,置换后损失只坏了这么点?原因就是顶班效应:置换 I_1 时,R_1 和 R_2 还原封不动地留在特征矩阵里,模型(尤其 max_features=1 的随机森林,每棵树本来就分散依赖三个副本)转身就用复制品把信息补了回来。单列置换测的不是「这份信息值多少」,是「这一列不可替代的部分值多少」——在冗余普遍存在的金融特征集上,后者系统性低估前者。

推到极端:如果 R_1 与 I_1 相关性是 1.0,两列的 MDA 都会是——尽管它们携带的信息是标签的直接成因。这是 MDA 最危险的失效模式:你可能因为「重要性为零」扔掉一组实际上至关重要的特征

聚类 MDA:把顶班通道堵死#

修复思路直白:既然相关特征互相顶班,就把它们打包,整组一起置换。组内所有列用同一个排列索引重排(保留组内相关结构),组外不动——顶班者和被置换者一起下场,退化才反映整组信息的真实价值。

def clustered_mda(X, y, groups, n_splits=5):
    """groups: {组名: [列名, ...]},由相关矩阵聚类得到"""
    cv = KFold(n_splits=n_splits, shuffle=True, random_state=1)
    res = pd.DataFrame(index=range(n_splits), columns=list(groups), dtype=float)
    for i, (tr, te) in enumerate(cv.split(X)):
        m = RandomForestClassifier(n_estimators=300, max_features=1,
                                   min_samples_leaf=20, n_jobs=-1, random_state=0)
        m.fit(X.iloc[tr], y[tr])
        base = -log_loss(y[te], m.predict_proba(X.iloc[te]))
        for g, cols in groups.items():
            Xp = X.iloc[te].copy()
            perm = np.random.default_rng(i).permutation(len(Xp))
            Xp[cols] = Xp[cols].values[perm]   # 整组同排置换
            res.loc[i, g] = base - (-log_loss(y[te], m.predict_proba(Xp)))
    return res.mean(), res.std() / np.sqrt(n_splits)
python

分组本身可以用相关矩阵的层次聚类自动完成(AFML 第二版把这套流程称为 clustered feature importance)。本实验按已知结构分组:G1 = {I_1, R_1, R_2},G2 = {I_2, R_3},G3 = {I_3},噪声各自成组:

聚类 MDA

结果一举归位:G1 组重要性 0.247,是单列置换 I_1(0.048)的 5 倍;G2 组 0.109;G3 组 0.041(I_3 没有复制品,单列与聚类结果一致,交叉验证了方法本身没有引入偏移);噪声组依然干净归零。三个组的相对量级 0.247 : 0.109 : 0.041 也大致还原了生成系数 1.2 : 0.9 : 0.7 的信息贡献排序(经 logit 非线性和噪声衰减后的合理变形)。

三条铁律与工具选型#

铁律一:MDI 只做组内相对比较,永远不做显著性判断。 它没有零点、不会为负、训练集内计算。用途限于「同一棵森林里谁被用得多」的快速侦察。任何「MDI > 阈值所以特征有效」的推断都是错的。

铁律二:MDA 必须配 Purged K-Fold。 本文演示用普通 KFold 是因为模拟样本 IID。真实金融标签带持有期(上一篇讲的区间重叠),普通 KFold 的测试折与训练折共享收益路径,信息泄漏会伪装成特征重要性——泄漏特征在被污染的测试折上表现优异,置换它退化巨大,MDA 给出漂亮的高分。Purging(清除与测试折标签区间重叠的训练样本)+ embargo 是前置条件,不是可选优化。

铁律三:相关特征先聚类,再谈重要性。 单列 MDA 在冗余面前系统性低估,单列 MDI 在冗余面前系统性稀释。真实因子库里「同一信息的不同包装」是常态而非例外——不聚类的特征重要性报告,参考价值要打对折。

选型速查:

场景工具
训练时免费侦察、特征粗筛MDI(max_features=1,看分布不只看均值)
「这个特征/特征组是否真的有用」聚类 MDA + Purged CV
特征间高度相关(金融常态)先层次聚类,再整组置换
需要单样本归因、非树模型SHAP(本文范围外,注意它同样受冗余干扰)

A 股实操注记#

  • A 股因子天然重共线。规模、流动性、波动率、反转在 A 股横截面上纠缠极深(小市值股票同时低流动性、高波动、强反转),单列置换会把这一整团信息的重要性摊薄到近乎不可见。聚类 MDA 在 A 股因子研究里几乎是必选项而非增强项。
  • 分组不要用全样本相关矩阵。相关结构本身有 regime 依赖(危机期相关性普遍抬升),用训练窗口内的相关矩阵聚类,且检查分组在不同子窗口的稳定性——分组本身不稳定,是特征工程质量的警报。
  • 每折重训的成本。聚类 MDA 的计算量是「折数 × 组数」次预测加「折数」次训练,几百特征的日频因子库在单机上完全可行,别用「太慢」当借口跳过 Purged CV。

你现在拥有的#

一套分层的特征审计流程:MDI 快速侦察 → 相关矩阵聚类 → 聚类 MDA + Purged CV 做显著性判断。以及两个反直觉的教训:重要性为零的特征可能至关重要(冗余顶班),重要性非零的特征可能纯属噪声(MDI 无零点)——工具输出的数字,必须先经过「它会以什么方式说谎」的过滤器。

到这一篇,AFML 的核心管线走完了数据、标注、加权、审计四层。下一个自然的问题是横截面的:这些经过审计的特征,怎么组合成对全市场股票的预测并处理好中性化与拥挤度?留给后面的文章。


参考文献:

  • López de Prado, M. (2018). Advances in Financial Machine Learning, Chapter 8: Feature Importance. Wiley.
  • Breiman, L. (2001). Random Forests. Machine Learning, 45(1).
  • Strobl, C. et al. (2008). Conditional Variable Importance for Random Forests. BMC Bioinformatics, 9(307).
  • 本文实验:4000 样本 × 12 特征受控数据集(3 信息 + 3 冗余 + 6 噪声),全部结果可复现。
MDI 与 MDA 特征重要性:识别真正有用的因子特征
https://blog.halo26812.eu.org/blog/feature-importance-mdi-mda
Author halo
Published at 2026年7月30日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨