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

В этом примере мы вычисляем permutation_importance признаков к обученной RandomForestClassifier используя Набор данных о раке молочной железы в Висконсине (диагностический). Модель может легко достичь точности около 97% на тестовом наборе данных. Поскольку этот набор данных содержит мультиколлинеарные признаки, перестановочная важность показывает, что ни один из признаков не является важным, что противоречит высокой тестовой точности.

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

# 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()
Impurity-based vs. permutation importances on multicollinear features (train set)

График слева показывает важность Джини модели. Поскольку реализация scikit-learn RandomForestClassifier использует случайные подмножества \(\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()
Permutation Importances on multicollinear features (test set)

Тем не менее, можно все еще вычислить значимую важность перестановки в присутствии коррелированных признаков, как показано в следующем разделе.

Обработка мультиколлинеарных признаков#

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

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

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()
plot permutation importance multicollinear

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

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()
Permutation Importances on selected subset of features (test set)

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

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

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

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

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

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

Градиентный бустинг для регрессии

Градиентный бустинг для регрессии

Основные нововведения в выпуске scikit-learn 0.22

Основные нововведения в выпуске scikit-learn 0.22

Галерея, созданная Sphinx-Gallery