Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере через JupyterLite или Binder.
Важность перестановок и важность признаков в случайном лесе (MDI)
В этом примере мы сравним важность признаков на основе impurity для RandomForestClassifier с важностью перестановок на наборе данных Titanic с помощью permutation_importance. Мы покажем, что важность признаков на основе impurity может завышать важность числовых признаков.
Кроме того, важность признаков на основе impurity в случайных лесах вычисляется на основе статистик, полученных из обучающего набора данных: важность признаков может быть высокой даже для признаков, не позволяющих предсказывать целевую переменную, если модель способна использовать их для переобучения.
В этом примере показано, как использовать важность перестановок в качестве альтернативы, позволяющей смягчить эти ограничения.
Литература
# 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)
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.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
['pclass', 'sex', 'embarked', 'random_cat']
Параметры
Обученные атрибуты
| Имя | Тип | Значение |
|---|---|---|
| categories_ categories_: список массивов Категории каждого признака, определённые во время ``fit`` (в порядке признаков в X и соответствующие выходным данным ``transform``). Не включает категории, которые не встречались во время ``fit``. | list | [array([1, 2, 3]), array(['femal... dtype=object), array(['C', '... dtype=object), array([0, 1, 2])] |
| feature_names_in_ feature_names_in_: ndarray формы (`n_features_in_`,) Имена признаков, обнаруженных во время :term:`fit`. Определяется только если `X` содержит имена признаков, представленные строками. .. versionadded:: 1.0 | ndarray[object](4,) | ['pclass','sex','embarked','random_cat'] |
| n_features_in_ n_features_in_: int Количество признаков, обнаруженных во время :term:`fit`. .. versionadded:: 1.0 | int | 4 |
4 признака
| pclass |
| sex |
| embarked |
| random_cat |
['age', 'sibsp', 'parch', 'fare', 'random_num']
Параметры
Обученные атрибуты
5 признаков
| age |
| sibsp |
| parch |
| fare |
| random_num |
9 признаков
| pclass |
| sex |
| embarked |
| random_cat |
| age |
| sibsp |
| parch |
| fare |
| random_num |
Параметры
Атрибуты обученной модели
Точность модели
Прежде чем изучать важности признаков, важно убедиться, что качество предсказаний модели достаточно высокое. В самом деле, изучать важные признаки модели, которая ничего не предсказывает, было бы нецелесообразно.
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
Здесь видно, что точность на обучающей выборке очень высока (модель случайного леса обладает достаточной ёмкостью, чтобы полностью запомнить обучающую выборку), но при этом она достаточно хорошо обобщает данные тестовой выборки благодаря встроенному бэггингу случайных лесов.
Можно пожертвовать некоторой точностью на обучающей выборке ради немного более высокой точности на тестовой выборке, ограничив ёмкость деревьев (например, задав 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)
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.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
['pclass', 'sex', 'embarked', 'random_cat']
Параметры
Обученные атрибуты
| Имя | Тип | Значение |
|---|---|---|
| categories_ categories_: список массивов Категории каждого признака, определенные во время ``fit`` (в порядке признаков в X и соответствующие результату ``transform``). Сюда не входят категории, которые не встречались во время ``fit``. | list | [array([1, 2, 3]), array(['femal... dtype=object), array(['C', '... dtype=object), array([0, 1, 2])] |
| feature_names_in_ feature_names_in_: ndarray формы (`n_features_in_`,) Имена признаков, встречавшихся во время :term:`fit`. Определяется только если `X` имеет имена признаков, представленные строками. .. versionadded:: 1.0 | ndarray[object](4,) | ['pclass','sex','embarked','random_cat'] |
| n_features_in_ n_features_in_: int Количество признаков, встречавшихся во время :term:`fit`. .. versionadded:: 1.0 | int | 4 |
4 признака
| pclass |
| sex |
| embarked |
| random_cat |
['age', 'sibsp', 'parch', 'fare', 'random_num']
Параметры
Обученные атрибуты
5 признаков
| age |
| sibsp |
| parch |
| fare |
| random_num |
9 признаков
| pclass |
| sex |
| embarked |
| random_cat |
| age |
| sibsp |
| parch |
| fare |
| random_num |
Параметры
Атрибуты после обучения
Если посмотреть на точность на обучающей и тестовой выборках, видно, что теперь эти две метрики очень похожи. Следовательно, наша модель больше не переобучается. Теперь можно проверить важность перестановок для этой новой модели.
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 минут 8.147 секунды)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/inspection/plot_permutation_importance.html

