排列重要性与随机森林特征重要性 (MDI)#

在本例中,我们将使用 RandomForestClassifier 的基于杂质的特征重要性与泰坦尼克号数据集上的排列重要性进行比较,使用 permutation_importance。我们将展示基于杂质的特征重要性可能会夸大数值特征的重要性。

此外,随机森林的基于杂质的特征重要性受限于从训练数据集派生的统计数据计算:即使对于与目标变量不相关的特征,只要模型有能力使用它们进行过拟合,重要性也可能很高。

本例展示了如何使用排列重要性作为一种替代方法,可以减轻这些限制。

参考文献

数据加载和特征工程#

让我们使用 pandas 加载泰坦尼克号数据集的副本。以下展示了如何对数值特征和类别特征应用单独的预处理。

我们还包括两个与目标变量 (survived) 无关的随机变量。

  • random_num 是一个高基数数值变量(与记录一样多的唯一值)。

  • random_cat 是一个低基数类别变量(3 个可能的值)。

import numpy as np

from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split

X, y = fetch_openml("titanic", version=1, as_frame=True, return_X_y=True)
rng = np.random.RandomState(seed=42)
X["random_cat"] = rng.randint(3, size=X.shape[0])
X["random_num"] = rng.randn(X.shape[0])

categorical_columns = ["pclass", "sex", "embarked", "random_cat"]
numerical_columns = ["age", "sibsp", "parch", "fare", "random_num"]

X = X[categorical_columns + numerical_columns]
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)

我们定义了一个基于随机森林的预测模型。因此,我们将执行以下预处理步骤

from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OrdinalEncoder

categorical_encoder = OrdinalEncoder(
    handle_unknown="use_encoded_value", unknown_value=-1, encoded_missing_value=-1
)
numerical_pipe = SimpleImputer(strategy="mean")

preprocessing = ColumnTransformer(
    [
        ("cat", categorical_encoder, categorical_columns),
        ("num", numerical_pipe, numerical_columns),
    ],
    verbose_feature_names_out=False,
)

rf = Pipeline(
    [
        ("preprocess", preprocessing),
        ("classifier", RandomForestClassifier(random_state=42)),
    ]
)
rf.fit(X_train, y_train)
Pipeline(steps=[('preprocess',
                 ColumnTransformer(transformers=[('cat',
                                                  OrdinalEncoder(encoded_missing_value=-1,
                                                                 handle_unknown='use_encoded_value',
                                                                 unknown_value=-1),
                                                  ['pclass', 'sex', 'embarked',
                                                   'random_cat']),
                                                 ('num', SimpleImputer(),
                                                  ['age', 'sibsp', 'parch',
                                                   'fare', 'random_num'])],
                                   verbose_feature_names_out=False)),
                ('classifier', RandomForestClassifier(random_state=42))])
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任笔记本。
在 GitHub 上,HTML 表示形式无法呈现,请尝试使用 nbviewer.org 加载此页面。


模型的准确率#

在检查特征重要性之前,重要的是要检查模型的预测性能是否足够高。实际上,检查非预测模型的重要特征几乎没有意义。

这里可以观察到,训练准确率非常高(森林模型有足够的容量完全记住训练集),但由于随机森林的内置装袋,它仍然可以很好地泛化到测试集。

可以通过限制树的容量来换取训练集上的某些准确率,以换取测试集上略微更好的准确率(例如,通过设置 min_samples_leaf=5min_samples_leaf=10),以限制过拟合,同时不引入太多欠拟合。

但是,让我们暂时保留我们的高容量随机森林模型,以说明特征重要性在具有许多唯一值的变量上的一些陷阱。

print(f"RF train accuracy: {rf.score(X_train, y_train):.3f}")
print(f"RF test accuracy: {rf.score(X_test, y_test):.3f}")
RF train accuracy: 1.000
RF test accuracy: 0.814

树的基于平均杂质减少 (MDI) 的特征重要性#

基于杂质的特征重要性将数值特征排名为最重要的特征。因此,非预测性 random_num 变量被排名为最重要的特征之一!

这个问题源于基于杂质的特征重要性的两个局限性

  • 基于杂质的重要性对高基数特征有偏差;

  • 基于杂质的重要性是在训练集统计数据上计算的,因此不能反映特征对做出泛化到测试集的预测的能力(当模型有足够的容量时)。

对高基数特征的偏差解释了为什么 random_numrandom_cat 相比具有非常大的重要性,而我们预计这两个随机特征都应该具有零重要性。

使用训练集统计数据的事实解释了为什么 random_numrandom_cat 特征都具有非零重要性。

import pandas as pd

feature_names = rf[:-1].get_feature_names_out()

mdi_importances = pd.Series(
    rf[-1].feature_importances_, index=feature_names
).sort_values(ascending=True)
ax = mdi_importances.plot.barh()
ax.set_title("Random Forest Feature Importances (MDI)")
ax.figure.tight_layout()
Random Forest Feature Importances (MDI)

作为替代方案,rf 的排列重要性是在保留的测试集上计算的。这表明低基数类别特征 sexpclass 是最重要的特征。实际上,排列这些特征的值会导致模型在测试集上的准确率得分下降最多。

还要注意,正如预期的那样,这两个随机特征的重要性都非常低(接近 0)。

from sklearn.inspection import permutation_importance

result = permutation_importance(
    rf, X_test, y_test, n_repeats=10, random_state=42, n_jobs=2
)

sorted_importances_idx = result.importances_mean.argsort()
importances = pd.DataFrame(
    result.importances[sorted_importances_idx].T,
    columns=X.columns[sorted_importances_idx],
)
ax = importances.plot.box(vert=False, whis=10)
ax.set_title("Permutation Importances (test set)")
ax.axvline(x=0, color="k", linestyle="--")
ax.set_xlabel("Decrease in accuracy score")
ax.figure.tight_layout()
Permutation Importances (test set)

也可以在训练集上计算排列重要性。这表明 random_numrandom_cat 的重要性排名明显高于在测试集上计算时的排名。这两个图之间的差异证实了 RF 模型有足够的容量来使用这些随机的数值和分类特征进行过拟合。

result = permutation_importance(
    rf, X_train, y_train, n_repeats=10, random_state=42, n_jobs=2
)

sorted_importances_idx = result.importances_mean.argsort()
importances = pd.DataFrame(
    result.importances[sorted_importances_idx].T,
    columns=X.columns[sorted_importances_idx],
)
ax = importances.plot.box(vert=False, whis=10)
ax.set_title("Permutation Importances (train set)")
ax.axvline(x=0, color="k", linestyle="--")
ax.set_xlabel("Decrease in accuracy score")
ax.figure.tight_layout()
Permutation Importances (train set)

我们可以通过将树的容量限制在过拟合,将 min_samples_leaf 设置为 20 个数据点来进一步重试实验。

rf.set_params(classifier__min_samples_leaf=20).fit(X_train, y_train)
Pipeline(steps=[('preprocess',
                 ColumnTransformer(transformers=[('cat',
                                                  OrdinalEncoder(encoded_missing_value=-1,
                                                                 handle_unknown='use_encoded_value',
                                                                 unknown_value=-1),
                                                  ['pclass', 'sex', 'embarked',
                                                   'random_cat']),
                                                 ('num', SimpleImputer(),
                                                  ['age', 'sibsp', 'parch',
                                                   'fare', 'random_num'])],
                                   verbose_feature_names_out=False)),
                ('classifier',
                 RandomForestClassifier(min_samples_leaf=20, random_state=42))])
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任笔记本。
在 GitHub 上,HTML 表示形式无法呈现,请尝试使用 nbviewer.org 加载此页面。


观察训练集和测试集上的准确率得分,我们发现这两个指标现在非常相似。因此,我们的模型不再过拟合。然后我们可以检查这个新模型的排列重要性。

print(f"RF train accuracy: {rf.score(X_train, y_train):.3f}")
print(f"RF test accuracy: {rf.score(X_test, y_test):.3f}")
RF train accuracy: 0.810
RF test accuracy: 0.832
train_result = permutation_importance(
    rf, X_train, y_train, n_repeats=10, random_state=42, n_jobs=2
)
test_results = permutation_importance(
    rf, X_test, y_test, n_repeats=10, random_state=42, n_jobs=2
)
sorted_importances_idx = train_result.importances_mean.argsort()
train_importances = pd.DataFrame(
    train_result.importances[sorted_importances_idx].T,
    columns=X.columns[sorted_importances_idx],
)
test_importances = pd.DataFrame(
    test_results.importances[sorted_importances_idx].T,
    columns=X.columns[sorted_importances_idx],
)
for name, importances in zip(["train", "test"], [train_importances, test_importances]):
    ax = importances.plot.box(vert=False, whis=10)
    ax.set_title(f"Permutation Importances ({name} set)")
    ax.set_xlabel("Decrease in accuracy score")
    ax.axvline(x=0, color="k", linestyle="--")
    ax.figure.tight_layout()
  • Permutation Importances (train set)
  • Permutation Importances (test set)

现在,我们可以观察到,在两个集合上,random_numrandom_cat 特征的重要性比过拟合的随机森林要低。但是,关于其他特征重要性的结论仍然有效。

脚本总运行时间:(0 分钟 4.529 秒)

相关示例

使用树林的特征重要性

使用树林的特征重要性

使用并行树林的像素重要性

使用并行树林的像素重要性

具有多重共线性或相关特征的排列重要性

具有多重共线性或相关特征的排列重要性

scikit-learn 0.22 版本亮点

scikit-learn 0.22 版本亮点

由 Sphinx-Gallery 生成的图库