Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.
Перестановочная важность при мультиколлинеарных или коррелирующих признаках
В этом примере мы вычисляем permutation_importance признаков для обученного RandomForestClassifier с использованием набора данных о диагностике рака молочной железы в Висконсине. Модель легко достигает точности около 97% на тестовой выборке. Поскольку этот набор данных содержит мультиколлинеарные признаки, перестановочная важность показывает, что ни один из признаков не важен, что противоречит высокой точности на тестовой выборке.
Мы демонстрируем возможный подход к обработке мультиколлинеарности: иерархическую кластеризацию признаков по ранговым корреляциям Спирмена, выбор порогового значения и сохранение одного признака из каждого кластера.
Примечание
См. также Перестановочная важность и важность признаков в случайном лесе (MDI)
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Важность признаков в случайном лесе на данных о раке молочной железы
Сначала определим функцию для упрощения построения графиков:
import matplotlib
from sklearn.inspection import permutation_importance
from sklearn.utils.fixes import parse_version
def plot_permutation_importance(clf, X, y, ax):
result = permutation_importance(clf, X, y, n_repeats=10, random_state=42, n_jobs=2)
perm_sorted_idx = result.importances_mean.argsort()
# `labels` argument in boxplot is deprecated in matplotlib 3.9 and has been
# renamed to `tick_labels`. The following code handles this, but as a
# scikit-learn user you probably can write simpler code by using `labels=...`
# (matplotlib < 3.9) or `tick_labels=...` (matplotlib >= 3.9).
tick_labels_parameter_name = (
"tick_labels"
if parse_version(matplotlib.__version__) >= parse_version("3.9")
else "labels"
)
tick_labels_dict = {tick_labels_parameter_name: X.columns[perm_sorted_idx]}
ax.boxplot(result.importances[perm_sorted_idx].T, vert=False, **tick_labels_dict)
ax.axvline(x=0, color="k", linestyle="--")
return ax
Затем обучим RandomForestClassifier на наборе данных о диагностике рака молочной железы в Висконсине и оценим точность модели на тестовой выборке:
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
print(f"Baseline accuracy on test data: {clf.score(X_test, y_test):.2}")
Baseline accuracy on test data: 0.97
Далее построим графики важности признаков на основе деревьев и перестановочной важности. Перестановочная важность вычисляется на обучающей выборке, чтобы показать, насколько модель полагается на каждый признак во время обучения.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
mdi_importances = pd.Series(clf.feature_importances_, index=X_train.columns)
tree_importance_sorted_idx = np.argsort(clf.feature_importances_)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 8))
mdi_importances.sort_values().plot.barh(ax=ax1)
ax1.set_xlabel("Gini importance")
plot_permutation_importance(clf, X_train, y_train, ax2)
ax2.set_xlabel("Decrease in accuracy score")
fig.suptitle(
"Impurity-based vs. permutation importances on multicollinear features (train set)"
)
_ = fig.tight_layout()

На графике слева показана важность Джини для модели. Поскольку реализация RandomForestClassifier в scikit-learn использует случайные подмножества из \(\sqrt{n_\text{features}}\) признаков при каждом разбиении, она позволяет уменьшить доминирование любого отдельного коррелирующего признака. В результате важность отдельных признаков может распределяться более равномерно между коррелирующими признаками. Поскольку признаки имеют большое число уникальных значений, а классификатор не переобучен, этим значениям можно в достаточной мере доверять.
Перестановочная важность на графике справа показывает, что перестановка признака снижает точность не более чем на 0.012, что говорит о неважности всех признаков. Это противоречит высокой точности, вычисленной на тестовой выборке в качестве базового уровня: какой-то признак должен быть важен.
Аналогично, изменение показателя точности, вычисленное на тестовой выборке, по-видимому, обусловлено случайностью:
fig, ax = plt.subplots(figsize=(7, 6))
plot_permutation_importance(clf, X_test, y_test, ax)
ax.set_title("Permutation Importances on multicollinear features\n(test set)")
ax.set_xlabel("Decrease in accuracy score")
_ = ax.figure.tight_layout()

Тем не менее при наличии коррелирующих признаков всё ещё можно вычислить содержательную перестановочную важность, как показано в следующем разделе.
Обработка мультиколлинеарных признаков
Когда признаки коллинеарны, перестановка одного признака мало влияет на производительность модели, поскольку она может получить ту же информацию из коррелирующего признака. Обратите внимание, что это справедливо не для всех моделей прогнозирования и зависит от особенностей их реализации.
Один из способов обработки мультиколлинеарных признаков — выполнить иерархическую кластеризацию на основе ранговых корреляций Спирмена, выбрать пороговое значение и оставить по одному признаку из каждого кластера. Сначала построим тепловую карту коррелирующих признаков:
from scipy.cluster import hierarchy
from scipy.spatial.distance import squareform
from scipy.stats import spearmanr
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 8))
corr = spearmanr(X).correlation
# Ensure the correlation matrix is symmetric
corr = (corr + corr.T) / 2
np.fill_diagonal(corr, 1)
# We convert the correlation matrix to a distance matrix before performing
# hierarchical clustering using Ward's linkage.
distance_matrix = 1 - np.abs(corr)
dist_linkage = hierarchy.ward(squareform(distance_matrix))
dendro = hierarchy.dendrogram(
dist_linkage, labels=X.columns.to_list(), ax=ax1, leaf_rotation=90
)
dendro_idx = np.arange(0, len(dendro["ivl"]))
ax2.imshow(corr[dendro["leaves"], :][:, dendro["leaves"]])
ax2.set_xticks(dendro_idx)
ax2.set_yticks(dendro_idx)
ax2.set_xticklabels(dendro["ivl"], rotation="vertical")
ax2.set_yticklabels(dendro["ivl"])
_ = fig.tight_layout()

Затем визуально оценим дендрограмму и вручную выберем пороговое значение, чтобы распределить признаки по кластерам, выберем по одному признаку из каждого кластера, оставим эти признаки в наборе данных и обучим новый случайный лес. Точность нового случайного леса на тестовой выборке почти не изменилась по сравнению со случайным лесом, обученным на полном наборе данных.
from collections import defaultdict
cluster_ids = hierarchy.fcluster(dist_linkage, 1, criterion="distance")
cluster_id_to_feature_ids = defaultdict(list)
for idx, cluster_id in enumerate(cluster_ids):
cluster_id_to_feature_ids[cluster_id].append(idx)
selected_features = [v[0] for v in cluster_id_to_feature_ids.values()]
selected_features_names = X.columns[selected_features]
X_train_sel = X_train[selected_features_names]
X_test_sel = X_test[selected_features_names]
clf_sel = RandomForestClassifier(n_estimators=100, random_state=42)
clf_sel.fit(X_train_sel, y_train)
print(
"Baseline accuracy on test data with features removed:"
f" {clf_sel.score(X_test_sel, y_test):.2}"
)
Baseline accuracy on test data with features removed: 0.97
Наконец, изучим перестановочную важность выбранного подмножества признаков:
fig, ax = plt.subplots(figsize=(7, 6))
plot_permutation_importance(clf_sel, X_test_sel, y_test, ax)
ax.set_title("Permutation Importances on selected subset of features\n(test set)")
ax.set_xlabel("Decrease in accuracy score")
ax.figure.tight_layout()
plt.show()

Общее время выполнения скрипта: (0 минут 8.782 секунды)
Связанные примеры
© 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_multicollinear.html