Spec-Zone.ru › scikit-learn

Примечание

Перейти к концу для загрузки полного примера кода. или запустить этот пример в вашем браузере через JupyterLite или Binder

Важность перестановки против важности признаков случайного леса (MDI)

В этом примере мы сравним основанную на нечистотах важность признаков RandomForestClassifier с важностью перестановки на наборе данных титаника с использованием permutation_importance. Мы покажем, что основанная на нечистотах важность признаков может увеличивать важность числовых признаков.

Кроме того, основанная на нечистотах важность признаков случайных лесов страдает от вычисления на статистике, полученной из набора обучающих данных: важность может быть высокой даже для признаков, которые не предсказывают целевую переменную, поскольку модель обладает возможностью использовать их для переобучения.

Этот пример демонстрирует, как использовать важность перестановки как альтернативу, которая может смягчить эти ограничения.

Ссылки

  • L. Breiman, «Случайные леса», Machine Learning, 45(1), 5-32, 2001.
# 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)
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.
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))])
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)
['pclass', 'sex', 'embarked', 'random_cat']
OrdinalEncoder(encoded_missing_value=-1, handle_unknown='use_encoded_value',
               unknown_value=-1)
['age', 'sibsp', 'parch', 'fare', 'random_num']
SimpleImputer()
RandomForestClassifier(random_state=42)


Точность модели

Перед проверкой важности признаков важно проверить, достаточно ли высока производительность модели. Действительно, мало интереса проверять важные признаки непредсказательной модели.

Здесь можно наблюдать, что точность обучения очень высока (модель леса обладает достаточной мощностью, чтобы полностью запомнить обучающий набор), но она все еще достаточно хорошо обобщается на тестовый набор благодаря встроенному бэггингу случайных лесов.

Возможно, можно пожертвовать некоторой точностью на обучающем наборе для немного лучшей точности на тестовом наборе, ограничив мощность деревьев (например, установив 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()
Random Forest Feature Importances (MDI)

В качестве альтернативы, важности признаков по перестановке для 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()
Permutation Importances (test set)

Также возможно вычислить важности признаков по перестановке на обучающей выборке. Это показывает, что 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()
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.
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))])
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)
RandomForestClassifier(min_samples_leaf=20, random_state=42)


Наблюдая за точностью на обучающей и тестовой выборках, мы видим, что две метрики теперь очень похожи. Поэтому наша модель больше не переобучается. Затем мы можем проверить важность признаков по перестановкам с этой новой моделью.

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_num и random_cat имеют меньшую важность по сравнению с переобученным случайным лесом. Однако выводы относительно важности других признаков остаются справедливыми.

Общее время выполнения скрипта: (0 минут 5,610 секунд)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_permutation_importance.ipynb

Download Python source code: plot_permutation_importance.py

Download zipped: plot_permutation_importance.zip

Связанные примеры

Важность признаков с помощью леса деревьев

Важность перестановки с многоколлинеарными или коррелированными признаками

Основные моменты выпуска scikit-learn 0.22

Регрессия с градиентным бустингом

© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/inspection/plot_permutation_importance.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API