一个模型在测试集上的准确率约为 97%,逐列打乱输入后,准确率却几乎不变。能据此认定“所有特征都没有用”吗?如果多列特征携带相似的信息,模型可能在一列被打乱后,继续利用其他相关列。单列置换测量的变化就会很小。
本文依据 scikit-learn 官方示例 Permutation Importance with Multicollinear or Correlated Features 全文翻译整理,作者为 The scikit-learn developers。原页在核对时标为 scikit-learn 1.9.1 文档;核对日期为 2026 年 10 月 5 日。原示例使用 Wisconsin 乳腺癌诊断数据集进行算法教学,不构成医疗诊断建议或诊断效能承诺。
结果归属:文中 0.97、0.012 及四张图均来自原站示例输出。本次仅阅读与静态审查,没有训练模型或重新生成图表。本文保留原例,再明确说明其特征筛选方法的验证边界。
置换重要性衡量什么
permutation_importance 固定一个已经训练好的模型,打乱某一特征列,然后观察评分相对原始输入下降多少。在本例中,分类器默认评分是准确率,因此横轴是准确率下降量。下降越大,说明这个模型在这批评估数据上越依赖该列的对应关系。
这衡量的是给定模型、给定数据和给定评分下的依赖程度,不是特征的固有价值,更不是因果作用。特别是在相关特征之间,信息可能互相替代,单列置换就无法直接表达一组特征共同的重要性。
准备一个绘制置换结果的函数
原例先定义绘图函数。每个特征置换 10 次,随机种子为 42,并使用两个并行工作单元。按平均重要性排序后,用箱线图显示每列在多次置换中的分布,再画出零值参考线。
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import matplotlib
from sklearn.inspection import permutation_importance
from sklearn.utils.fixes import parse_version
def plot_permutation_importance(clf, X, y, ax):
result = permutation_importance(
clf, X, y, n_repeats=10, random_state=42, n_jobs=2
)
perm_sorted_idx = result.importances_mean.argsort()
# Matplotlib 3.9 起,labels 参数改名为 tick_labels。
tick_labels_parameter_name = (
"tick_labels"
if parse_version(matplotlib.__version__) >= parse_version("3.9")
else "labels"
)
tick_labels_dict = {
tick_labels_parameter_name: X.columns[perm_sorted_idx]
}
# orientation 从 Matplotlib 3.10 可用;
# vert 在 3.11 被弃用。
orientation_dict = (
{"orientation": "horizontal"}
if parse_version(matplotlib.__version__) >= parse_version("3.10")
else {"vert": False}
)
ax.boxplot(
result.importances[perm_sorted_idx].T,
**orientation_dict,
**tick_labels_dict,
)
ax.axvline(x=0, color="k", linestyle="--")
return ax
代码保留了官方示例为不同 Matplotlib 版本提供的兼容分支。如果项目已固定版本,可以直接使用对应参数,减少分支。这里的 sklearn.utils.fixes 属于 scikit-learn 内部兼容辅助实现;移植到不同版本时,不应把这一导入当作长期稳定的公共 API 承诺。
训练随机森林,建立基线
使用 DataFrame 形式读取数据,保留列名,然后分割训练集与测试集。原例没有显式指定 test_size 或 stratify;以下保留该设置,而不将它改写成另一个实验。
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, random_state=42
)
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
print(f"Baseline accuracy on test data: {clf.score(X_test, y_test):.2}")
原站给出的输出为:
Baseline accuracy on test data: 0.97
这里的 :.2 是原例的数值格式,输出为两位有效数字,不是精确到小数点后两位的未舍入评估结果。这个分数说明该固定划分上的模型具备预测能力,但不能单凭一个准确率判断医疗用途,也不能推广为未来所有数据集上的表现。
对比不纯度重要性和训练集置换重要性
随机森林的 feature_importances_ 给出基于不纯度下降的特征重要性(MDI,这里常称 Gini importance)。它与置换重要性回答的问题不同。下面把二者放在一起,右图在训练集上计算,观察拟合后的模型对各列的依赖。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
mdi_importances = pd.Series(
clf.feature_importances_, index=X_train.columns
)
tree_importance_sorted_idx = np.argsort(clf.feature_importances_)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 8))
mdi_importances.sort_values().plot.barh(ax=ax1)
ax1.set_xlabel("Gini importance")
plot_permutation_importance(clf, X_train, y_train, ax2)
ax2.set_xlabel("Decrease in accuracy score")
fig.suptitle(
"Impurity-based vs. permutation importances on "
"multicollinear features (train set)"
)
_ = fig.tight_layout()

scikit-learn 的随机森林分类器在默认设置下,每次节点分裂只考虑随机抽取的约 √特征数个候选特征。相关特征因而有机会在不同树和分裂位置被使用,一列特征的支配地位会被分散,MDI 也可能分布到多列上。
原文认为,在这个特定数据集与模型条件下,不纯度重要性的数值可以相对参考;这不意味着 MDI 在所有情况下都可靠。它可能偏好取值数量多的特征,也会受过拟合影响。相关比较可继续阅读官方示例 Permutation Importance vs Random Forest Feature Importance (MDI)。
右图中,原文报告单列置换最多只让准确率下降约 0.012,也就是约 1.2 个百分点。如果只看这个结果,很容易误判所有特征都不重要;但基线准确率较高,说明模型显然利用了输入中的某些信息。更合理的问题是:单列被打乱后,其他列是否仍保留了同类信息?
换到测试集,现象仍然存在
fig, ax = plt.subplots(figsize=(7, 6))
plot_permutation_importance(clf, X_test, y_test, ax)
ax.set_title(
"Permutation Importances on multicollinear features\n(test set)"
)
ax.set_xlabel("Decrease in accuracy score")
_ = ax.figure.tight_layout()

原例在测试集上同样观察到,准确率的变化看起来主要落在随机波动的范围内。这并不是在证明特征与目标没有关系。特征相关时,置换其中一列,模型仍可能从另一列获取相似信息,所以性能改变不大。
这种现象也不是对所有预测模型都必然成立。模型具体怎样使用特征,取决于算法与实现;应把解释限定在被评估的模型上。
按 Spearman 相关性分组
原例展示了一种处理相关特征的办法:计算特征间的 Spearman 秩相关系数,进行层次聚类,选择一个切分阈值,然后每个簇只保留一列。它的目的是减少可相互替代的输入,让剩余特征的单列置换结果更容易解释。
验证边界先说明:接下来的原例使用完整的 X 计算相关矩阵,因此特征筛选看到了测试集的输入分布。虽然没有用测试标签,测试集仍然参与了数据驱动的特征选择。原例适合演示现象,不能把之后的测试分数当成严格独立的泛化证据。训练流程的修订方式见后文。
from scipy.cluster import hierarchy
from scipy.spatial.distance import squareform
from scipy.stats import spearmanr
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 8))
corr = spearmanr(X).correlation
# 修正数值误差,确保对称性及单位对角线。
corr = (corr + corr.T) / 2
np.fill_diagonal(corr, 1)
# 先将相关矩阵转换成距离矩阵,再用 Ward linkage 聚类。
distance_matrix = 1 - np.abs(corr)
dist_linkage = hierarchy.ward(squareform(distance_matrix))
dendro = hierarchy.dendrogram(
dist_linkage,
labels=X.columns.to_list(),
ax=ax1,
leaf_rotation=90,
)
dendro_idx = np.arange(0, len(dendro["ivl"]))
ax2.imshow(corr[dendro["leaves"], :][:, dendro["leaves"]])
ax2.set_xticks(dendro_idx)
ax2.set_yticks(dendro_idx)
ax2.set_xticklabels(dendro["ivl"], rotation="vertical")
ax2.set_yticklabels(dendro["ivl"])
_ = fig.tight_layout()

这里采用 1 - abs(corr):强正相关和强负相关都会对应较小距离,因为两者都可能携带可替代的信息。squareform 把方阵转换成层次聚类所用的压缩距离形式。右侧热图仍显示带符号的相关系数,并按树状图叶子顺序重排,以便观察成组结构。
方法限制:Ward 方法的经典最小方差解释依赖欧氏距离条件。1 - |ρ| 不应未经验证就当作一般欧氏距离;这里保留的是原文的启发式分组方法。实际研究应检查距离与 linkage 的适配性,并根据任务验证分组方案,而不是把树状图视为唯一正确的特征结构。若数据包含常数列或缺失值,还需先处理相关系数可能出现的非有限值。
每簇选一列,再训练新模型
原文通过观察树状图,手工选取距离阈值 1。随后用 fcluster 得到各列所属的簇,每簇保留原始顺序中的第一列。这个选择规则并不表示第一列在业务上最优,也不自动保证稳定性。
from collections import defaultdict
cluster_ids = hierarchy.fcluster(
dist_linkage, 1, criterion="distance"
)
cluster_id_to_feature_ids = defaultdict(list)
for idx, cluster_id in enumerate(cluster_ids):
cluster_id_to_feature_ids[cluster_id].append(idx)
selected_features = [
v[0] for v in cluster_id_to_feature_ids.values()
]
selected_features_names = X.columns[selected_features]
X_train_sel = X_train[selected_features_names]
X_test_sel = X_test[selected_features_names]
clf_sel = RandomForestClassifier(
n_estimators=100, random_state=42
)
clf_sel.fit(X_train_sel, y_train)
print(
"Baseline accuracy on test data with features removed:"
f" {clf_sel.score(X_test_sel, y_test):.2}"
)
原站输出仍为:
Baseline accuracy on test data with features removed: 0.97
在原例展示的划分和舍入精度下,删去冗余特征后的测试准确率变化不大。它并不证明删列永远没有损失,也不能据相同的两位有效数字认定两个模型性能严格相同。
最后,重新检查选定特征子集上的置换重要性:
fig, ax = plt.subplots(figsize=(7, 6))
plot_permutation_importance(clf_sel, X_test_sel, y_test, ax)
ax.set_title(
"Permutation Importances on selected subset of features\n(test set)"
)
ax.set_xlabel("Decrease in accuracy score")
ax.figure.tight_layout()
plt.show()

移除部分相关冗余后,一些特征被打乱时更难由剩余列补偿,其重要性因此更容易体现。重要性图应与分数、数据划分及筛选规则一起阅读,不能单独当作一个“最有价值特征”的绝对排名。
审核补充:把特征选择放回训练流程
如果目标是评估未来数据上的预测能力,最低限度的修订,是让相关性计算仅使用训练数据。以下是相对原例的替换片段,其他聚类步骤仍需按前文衔接:
# 审核修订:相关性只在训练数据上估计。
# 在交叉验证中,这个 X_train 应是当前训练折,
# 不是包含验证折的外层完整训练集。
corr = spearmanr(X_train).correlation
corr = (corr + corr.T) / 2
np.fill_diagonal(corr, 1)
# 后续 selected_features 仍由训练数据的聚类得到。
# 原例中的列名提取也应从训练列结构取值:
# selected_features_names = X_train.columns[selected_features]
阈值的选择、每簇代表特征的选择、模型调参都应在训练折内部完成。最终测试集只应用已确定的特征集合,不再参与规则调整。如果反复查看测试重要性图并据此重新筛列,也会让测试集逐渐变成验证集。可用训练期交叉验证或独立验证集作这些选择,再保留真正未触碰的最终评估集。
这段修订只处理筛选范围,不声称解决了 Ward 距离条件、常数列、样本量、类别比例或所有特征依赖形式的问题。它没有被执行,因此本文没有提供修订版的新分数。对相关特征,还可以按任务考虑组级置换等不同问题设定,但不应把它与本例的逐列置换结果混作同一种量。
代码静态审核未发现硬编码凭据、shell 命令、任意代码求值或破坏性文件操作;数据来自 scikit-learn 自带的教学数据加载器。运行时仍会进行模型拟合和并行计算,需依赖版本兼容并消耗资源。未发现上述问题不等于证明代码没有漏洞。
来源与许可证
原文、原图与示例代码:scikit-learn 官方完整实例。作者:The scikit-learn developers;原代码明确标注 SPDX-License-Identifier: BSD-3-Clause。本稿保留作者与许可,翻译注释,调整排版,新增验证边界与修订片段。
BSD 3-Clause 许可声明(原文保留)
BSD 3-Clause License
Copyright (c) 2007-2026 The scikit-learn developers.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.












暂无评论内容