排列重要性 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)

我們定義一個基於隨機森林的預測模型。因此,我們將執行以下預處理步驟

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_num 的重要性與 random_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 生成的圖庫