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

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

此外,随机森林的基于杂质的特征重要性存在一个问题:它是基于训练数据集的统计数据计算的;即使特征本身并不能预测目标变量,只要模型能够利用这些特征进行过拟合,其重要性也可能很高。

本例展示了如何使用置换重要性作为替代方法来减轻这些限制。

参考文献

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

数据加载和特征工程#

让我们使用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加载此页面。


模型的准确性#

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

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

可以通过限制树的容量(例如设置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分钟5.773秒)

相关示例

使用树林的特征重要性

使用树林的特征重要性

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

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

scikit-learn 0.22 发行亮点

scikit-learn 0.22 发行亮点

梯度提升回归

梯度提升回归

由Sphinx-Gallery生成的图库