註記
前往末尾以下載完整的範例程式碼。或透過 JupyterLite 或 Binder 在您的瀏覽器中執行此範例
排列重要性 vs. 隨機森林特徵重要性 (MDI)#
在此範例中,我們將使用 permutation_importance
,比較 RandomForestClassifier
的基於雜質的特徵重要性與鐵達尼號資料集上的排列重要性。我們將顯示基於雜質的特徵重要性可能會誇大數值特徵的重要性。
此外,隨機森林基於雜質的特徵重要性會受到從訓練資料集衍生的統計資料計算的影響:只要模型有能力使用它們來過擬合,即使對於不是目標變數的預測特徵,重要性也可能很高。
此範例顯示如何使用排列重要性作為可以減輕這些限制的替代方案。
參考文獻
# 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)
我們定義一個基於隨機森林的預測模型。因此,我們將執行以下預處理步驟
使用
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)
模型的準確性#
在檢查特徵重要性之前,務必先檢查模型的預測效能是否足夠高。實際上,檢查非預測模型的重要特徵沒有多大意義。
在這裡可以觀察到訓練準確性非常高(森林模型有足夠的能力完全記住訓練集),但由於隨機森林的內建套袋,它仍然可以充分概括測試集。
可以透過限制樹木的能力(例如,設定 min_samples_leaf=5
或 min_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_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 分鐘 5.773 秒)
相關範例