注意
转到末尾以下载完整示例代码或通过 JupyterLite 或 Binder 在浏览器中运行此示例。
排列重要性与随机森林特征重要性(MDI)的比较#
在此示例中,我们将使用 permutation_importance 在 Titanic 数据集上比较 RandomForestClassifier 基于杂质的特征重要性与排列重要性。我们将展示基于杂质的特征重要性可能会夸大数值特征的重要性。
此外,随机森林基于杂质的特征重要性存在一个问题,即它是在训练数据集派生的统计数据上计算的:即使对于那些对目标变量没有预测能力的特征,只要模型有能力使用它们来过拟合,其重要性也可能很高。
此示例展示了如何使用排列重要性作为替代方案来减轻这些限制。
References
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
数据加载和特征工程#
让我们使用 pandas 加载 Titanic 数据集的副本。以下展示了如何对数值和类别特征应用单独的预处理。
我们进一步包含两个与目标变量(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)
我们定义了一个基于随机森林的预测模型。因此,我们将执行以下预处理步骤:
使用
OrdinalEncoder对类别特征进行编码;使用
SimpleImputer使用均值策略填充数值特征的缺失值。
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)
模型的准确性#
在检查特征重要性之前,重要的是检查模型的预测性能是否足够高。确实,检查一个没有预测能力的模型的重要特征意义不大。
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
在这里,可以看到训练准确性非常高(森林模型有足够的能力完全记住训练集),但由于随机森林内置的 bagging,它仍然可以很好地泛化到测试集。
通过限制树的能力(例如设置 min_samples_leaf=5 或 min_samples_leaf=10)来限制过拟合,同时又不引入过多的欠拟合,有可能在训练集上牺牲一些准确性以换取测试集上稍好的准确性。
然而,现在让我们保留高容量的随机森林模型,以便说明具有许多唯一值的变量在特征重要性方面的一些陷阱。
基于杂质减少量(MDI)的树特征重要性#
基于杂质的特征重要性将数值特征排名为最重要的特征。结果,非预测性的 random_num 变量被排名为最重要的特征之一!
这个问题源于基于杂质的特征重要性的两个限制:
基于杂质的重要性偏向于高基数特征;
基于杂质的重要性是在训练集统计数据上计算的,因此不能反映特征对于泛化到测试集的预测(当模型有足够的能力时)的有用性。
对高基数特征的偏见解释了为什么 random_num 具有比 random_cat 大得多的重要性,而我们预期这两个随机特征的重要性都为零。
我们使用训练集统计数据的事实解释了为什么 random_num 和 random_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()

作为替代方案,rf 的排列重要性是在保留的测试集上计算的。这表明低基数类别特征 sex 和 pclass 是最重要的特征。确实,对这些特征的值进行排列将导致模型在测试集上的准确性得分下降最多。
另外,请注意,正如预期的那样,两个随机特征的重要性都非常低(接近于 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()

也可以在训练集上计算排列重要性。这表明 random_num 和 random_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()

我们可以通过将树的过拟合能力限制为设置 min_samples_leaf 为 20 个数据点来进一步重试实验。
rf.set_params(classifier__min_samples_leaf=20).fit(X_train, y_train)
观察训练集和测试集上的准确性得分,我们发现这两个指标现在非常相似。因此,我们的模型不再过拟合。然后,我们可以使用这个新模型检查排列重要性。
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()
现在,我们可以观察到,在两个集合上,random_num 和 random_cat 特征的重要性都低于过拟合的随机森林。然而,关于其他特征重要性的结论仍然有效。
脚本总运行时间: (0 分钟 6.265 秒)
相关示例

