feature_importance_permutation: 通过特征置换估计特征重要性。

一个基于 置换重要性 估计分类器和回归器特征重要性的函数。

from mlxtend.evaluate import feature_importance_permutation

概述

置换重要性 是一种直观的、模型无关的方法,用于估计分类器和回归模型的特征重要性。该方法相对简单直接:

  1. 使用训练数据集拟合模型
  2. 在独立数据集(例如,验证数据集)上估计模型的预测性能,并将其记录为基线性能
  3. 对于每个特征 i
  4. 随机打乱原始数据集中的特征列 i
  5. 记录模型在打乱列后的数据集上的预测性能
  6. 将基线性能(步骤 2)与在打乱数据集上的性能之间的差异计算为特征重要性

置换重要性通常被认为是一种相对有效的技术,在实践中效果良好 [1],但其缺点是可能高估相关特征的重要性 [2]。

参考文献

  • [1] Terence Parr, Kerem Turgutlu, Christopher Csiszar, and Jeremy Howard. Beware Default Random Forest Importances (https://parrt.cs.usfca.edu/doc/rf-importance/index.html)
  • [2] Strobl, C., Boulesteix, A. L., Kneib, T., Augustin, T., & Zeileis, A. (2008). Conditional variable importance for random forests. BMC bioinformatics, 9(1), 307.

示例 1 -- 分类器的特征重要性

以下示例展示了基于分类模型的置换重要性来估计特征重要性。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from mlxtend.evaluate import feature_importance_permutation

生成一个玩具数据集

from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier

# Build a classification task using 3 informative features
X, y = make_classification(n_samples=10000,
                           n_features=10,
                           n_informative=3,
                           n_redundant=0,
                           n_repeated=0,
                           n_classes=2,
                           random_state=0,
                           shuffle=False)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=1, stratify=y)

通过随机森林计算特征重要性

首先,我们通过 平均不纯度减少 (在代码段后描述) 直接从随机森林计算特征重要性

forest = RandomForestClassifier(n_estimators=250,
                                random_state=0)

forest.fit(X_train, y_train)

print('Training accuracy:', np.mean(forest.predict(X_train) == y_train)*100)
print('Test accuracy:', np.mean(forest.predict(X_test) == y_test)*100)

importance_vals = forest.feature_importances_
print(importance_vals)
Training accuracy: 100.0
Test accuracy: 95.06666666666666
[0.283357   0.30846795 0.24204291 0.02229767 0.02364941 0.02390578
 0.02501543 0.0234225  0.02370816 0.0241332 ]

随机森林中有几种计算特征重要性的策略。scikit-learn (在下一个代码示例中使用) 中实现的基于 Breiman 和 Friedman 的 CART (Breiman, Friedman, "Classification and regression trees", 1984) 方法,即所谓的 平均不纯度减少。这里,特征的重要性值是通过计算该特征在将父节点分裂成两个子节点时,在集成中的所有树中平均不纯度减少来计算的。请注意,不纯度减少值是根据相应节点中的样本数进行加权的。此过程对数据集中的所有特征重复进行,然后对特征重要性值进行归一化,使其总和为 1。在 CART 中,作者还指出这种快速计算特征重要性值的方法与置换重要性相对一致。

接下来,让我们可视化随机森林的特征重要性值,包括 平均不纯度减少 变异性的度量(此处为:标准差)

std = np.std([tree.feature_importances_ for tree in forest.estimators_],
             axis=0)
indices = np.argsort(importance_vals)[::-1]

# Plot the feature importances of the forest
plt.figure()
plt.title("Random Forest feature importance")
plt.bar(range(X.shape[1]), importance_vals[indices],
        yerr=std[indices], align="center")
plt.xticks(range(X.shape[1]), indices)
plt.xlim([-1, X.shape[1]])
plt.ylim([0, 0.5])
plt.show()

png

正如我们所见,特征 1、0 和 2 被估计为随机森林分类器中最具信息量的特征。接下来,让我们通过置换重要性方法计算特征重要性。

置换重要性

imp_vals, _ = feature_importance_permutation(
    predict_method=forest.predict, 
    X=X_test,
    y=y_test,
    metric='accuracy',
    num_rounds=1,
    seed=1)

imp_vals
array([ 0.26833333,  0.26733333,  0.261     , -0.002     , -0.00033333,
        0.00066667,  0.00233333,  0.00066667,  0.00066667, -0.00233333])

请注意,feature_importance_permutation 返回两个数组。第一个数组(此处为:imp_vals)包含我们实际感兴趣的重要性值。如果 num_rounds > 1,置换会重复多次(使用不同的随机种子),在这种情况下,第一个数组包含从不同运行计算出的重要性的平均值。第二个数组(此处赋值给 _,因为我们未使用它)则包含这些运行中的所有单个值(稍后会详细介绍)。

现在,让我们也用条形图可视化重要性值

indices = np.argsort(imp_vals)[::-1]
plt.figure()
plt.title("Random Forest feature importance via permutation importance")
plt.bar(range(X.shape[1]), imp_vals[indices])
plt.xticks(range(X.shape[1]), indices)
plt.xlim([-1, X.shape[1]])
plt.ylim([0, 0.5])
plt.show()

png

正如我们所见,在这里,特征 1、0 和 2 也被预测为最重要的特征,这与我们之前通过 平均不纯度减少 方法计算的特征重要性值一致。

(请注意,在随机森林的上下文中,通过置换重要性计算的特征重要性通常是使用随机森林的袋外样本进行计算的,而在本实现中,使用的是独立数据集。)

之前提到,如果 num_rounds > 1,置换会重复多次。在这种情况下,feature_importance_permutation 返回的第二个数组包含这些单独运行的重要性值(数组的形状为 [num_features, num_rounds]),我们可以用它来计算这些运行之间的某种变异性。

imp_vals, imp_all = feature_importance_permutation(
    predict_method=forest.predict, 
    X=X_test,
    y=y_test,
    metric='accuracy',
    num_rounds=10,
    seed=1)


std = np.std(imp_all, axis=1)
indices = np.argsort(imp_vals)[::-1]

plt.figure()
plt.title("Random Forest feature importance via permutation importance w. std. dev.")
plt.bar(range(X.shape[1]), imp_vals[indices],
        yerr=std[indices])
plt.xticks(range(X.shape[1]), indices)
plt.xlim([-1, X.shape[1]])
plt.show()

png

应该注意的是,特征重要性值之和不为一,因为它们没有被归一化(如果您愿意,可以通过将它们除以重要性值之和来归一化)。这里,重点是查看相互之间的重要性值的相对关系,而不是过度解释绝对值。

支持向量机

尽管置换重要性方法产生的结果通常与随机森林的 平均不纯度减少 特征重要性值一致,但它是一种模型无关的方法,可以用于任何类型的分类器或回归器。以下示例将 feature_importance_permutation 函数应用于支持向量机。

from sklearn.svm import SVC


svm = SVC(C=1.0, kernel='rbf')
svm.fit(X_train, y_train)

print('Training accuracy', np.mean(svm.predict(X_train) == y_train)*100)
print('Test accuracy', np.mean(svm.predict(X_test) == y_test)*100)
Training accuracy 94.87142857142857
Test accuracy 94.89999999999999
imp_vals, imp_all = feature_importance_permutation(
    predict_method=svm.predict, 
    X=X_test,
    y=y_test,
    metric='accuracy',
    num_rounds=10,
    seed=1)


std = np.std(imp_all, axis=1)
indices = np.argsort(imp_vals)[::-1]

plt.figure()
plt.title("SVM feature importance via permutation importance")
plt.bar(range(X.shape[1]), imp_vals[indices],
        yerr=std[indices])
plt.xticks(range(X.shape[1]), indices)
plt.xlim([-1, X.shape[1]])
plt.show()

png

示例 2 -- 回归器的特征重要性

import numpy as np
import matplotlib.pyplot as plt
from mlxtend.evaluate import feature_importance_permutation
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_regression
from sklearn.svm import SVR


X, y = make_regression(n_samples=1000,
                       n_features=5,
                       n_informative=2,
                       n_targets=1,
                       random_state=123,
                       shuffle=False)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=123)    

svm = SVR(kernel='rbf')
svm.fit(X_train, y_train)

imp_vals, _ = feature_importance_permutation(
    predict_method=svm.predict, 
    X=X_test,
    y=y_test,
    metric='r2',
    num_rounds=1,
    seed=1)

imp_vals
array([ 0.43309137,  0.22058866,  0.00148447,  0.01613953, -0.00529505])
plt.figure()
plt.bar(range(X.shape[1]), imp_vals)
plt.xticks(range(X.shape[1]))
plt.xlim([-1, X.shape[1]])
plt.ylim([0, 0.5])
plt.show()

png

示例 3 -- 使用独热编码特征的特征重要性

进行独热编码后,具有 10 个不同类别的特征变量将分成 10 个新的特征列(如果您删除一个冗余列,则为 9 个)。如果我们要将每个新的特征列视为一个单独的特征变量,我们可以像往常一样使用 feature_importance_permutation

这在下面的示例中进行了说明。

准备数据集

这里,我们看一个数据集,它由一个分类特征('categorical')和 3 个数值特征('measurement1''measurement2''measurement3')组成。

import pandas as pd


df_data = pd.read_csv('https://gist.githubusercontent.com/rasbt/b99bf69079bc0d601eeae8a49248d358/raw/a114be9801647ec5460089f3a9576713dabf5f1f/onehot-numeric-mixed-data.csv')
df_data.head()
分类 测量1 测量2 标签 测量3
0 F 1.428571 2.721313 0 2.089
1 R 0.685939 0.982976 0 0.637
2 P 1.055817 0.624210 0 0.226
3 S 0.995956 0.321101 0 0.138
4 R 1.376773 1.578309 0 0.478
from sklearn.model_selection import train_test_split


df_X = df_data[['measurement1', 'measurement2', 'measurement3', 'categorical']]
df_y = df_data['label']


df_X_train, df_X_test, df_y_train, df_y_test = train_test_split(
     df_X, df_y, test_size=0.33, random_state=42, stratify=df_y)

这里,我们对分类特征进行独热编码,并将其与数值列合并回去

from sklearn.preprocessing import OneHotEncoder
import numpy as np


ohe = OneHotEncoder(drop='first')
ohe.fit(df_X_train[['categorical']])

df_X_train_ohe = df_X_train.drop(columns=['categorical'])
df_X_test_ohe = df_X_test.drop(columns=['categorical'])

ohe_train = np.asarray(ohe.transform(df_X_train[['categorical']]).todense())
ohe_test = np.asarray(ohe.transform(df_X_test[['categorical']]).todense())

X_train_ohe = np.hstack((df_X_train_ohe.values, ohe_train))
X_test_ohe = np.hstack((df_X_test_ohe.values, ohe_test))
# look at first 3 rows
print(X_train_ohe[:3])
[[0.65747208 0.95105388 0.36       0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         1.         0.         0.
  0.         0.         0.        ]
 [1.17503636 1.01094494 0.653      0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         1.         0.
  0.         0.         0.        ]
 [1.25516647 0.67575824 0.176      0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         1.         0.         0.         0.
  0.         0.         0.        ]]

拟合基线模型进行分析

from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV


pipe = make_pipeline(StandardScaler(),
                     MLPClassifier(max_iter=10000, random_state=123))

params = {
    'mlpclassifier__hidden_layer_sizes': [(30, 20, 10), 
                                          (20, 10), 
                                          (20,),
                                          (10,)],
    'mlpclassifier__activation': ['tanh', 'relu'],
    'mlpclassifier__solver': ['sgd'],
    'mlpclassifier__alpha': [0.0001],
    'mlpclassifier__learning_rate': ['adaptive'],
}

gs = GridSearchCV(estimator=pipe, 
                  param_grid=params, 
                  scoring='accuracy', 
                  refit=True,
                  n_jobs=-1,
                  cv=10)

gs = gs.fit(X_train_ohe, df_y_train.values)
model = gs.best_estimator_

常规置换重要性

这里,我们使用常规方法计算特征重要性,其中每个独热编码特征被视为一个单独的变量。

from mlxtend.evaluate import feature_importance_permutation
imp_vals, imp_all = feature_importance_permutation(
    predict_method=model.predict, 
    X=X_test_ohe,
    y=df_y_test.values,
    metric='accuracy',
    num_rounds=50,
    seed=1)
feat_names_with_ohe = ['measurement1', 'measurement2', 'measurement3'] \
    + [f'categorical_ohe_{i}' for i in range(2, 20)]
%matplotlib inline
import matplotlib.pyplot as plt


std = np.std(imp_all, axis=1)
indices = np.argsort(imp_vals)[::-1]

plt.figure()
#plt.title("Feature importance via permutation importance w. std. dev.")
plt.bar(range(len(feat_names_with_ohe)), imp_vals[indices],
        yerr=std[indices])
plt.xticks(range(len(feat_names_with_ohe)),
           np.array(feat_names_with_ohe)[indices], rotation=90)
plt.xlim([-1, len(feat_names_with_ohe)])
plt.show()

png

然而,请注意,如果分类值很多,则单个二元特征(独热编码后)的特征重要性变得难以解释。在某些情况下,希望在特征置换重要性评估中将独热编码的二元特征视为单个变量。我们可以通过使用特征组来实现这一点。

使用特征组

在下面的示例中,所有独热编码变量都被视为一个特征组。这意味着,它们在特征置换重要性分析中都被打乱并作为一个单一特征进行分析。

feature_groups = [0, 1, 2, range(3, 21)]

imp_vals, imp_all = feature_importance_permutation(
    predict_method=model.predict, 
    X=X_test_ohe,
    y=df_y_test.values,
    metric='accuracy',
    num_rounds=50,
    feature_groups=feature_groups,
    seed=1)
feature_names = ['measurement1', 'measurement2', 'measurement3', 'categorical']

std = np.std(imp_all, axis=1)
indices = np.argsort(imp_vals)[::-1]

plt.figure()
plt.bar(range(len(feature_names)), imp_vals[indices],
        yerr=std[indices])
plt.xticks(range(len(feature_names)),
           np.array(feature_names)[indices], rotation=90)
plt.xlim([-1, len(feature_names)])
plt.show()

png

API

feature_importance_permutation(X, y, predict_method, metric, num_rounds=1, feature_groups=None, seed=None)

通过置换重要性填充特征重要性

参数

  • X : NumPy 数组,形状 = [n_samples, n_features]

    数据集,其中 n_samples 是样本数量,n_features 是特征数量。

  • y : NumPy 数组,形状 = [n_samples]

    目标值。

  • predict_method : 预测函数

    一个可调用函数,用于从 X 预测目标值。

  • metric : str, callable

    通过置换评估特征重要性的指标。默认情况下,建议分类器使用字符串 'accuracy',回归器使用字符串 'r2'。可选地,可以使用自定义评分函数(例如,metric=scoring_func),该函数接受两个参数 y_true 和 y_pred,其形状类似于 y 数组。

  • num_rounds : int (默认值=1)

    置换特征列以计算置换重要性的轮数。

  • feature_groups : list 或 None (默认值=None)

    可选参数,用于将某些特征视为一组。例如 [1, 2, [3, 4, 5]],这对于可解释性很有用,例如,如果特征 3、4、5 是独热编码特征。

  • seed : int 或 None (默认值=None)

    用于打乱特征列的随机种子。

返回值

  • mean_importance_vals, all_importance_vals : NumPy 数组。

    第一个数组 mean_importance_vals 的形状为 [n_features, ],包含所有特征的重要性值。第二个数组的形状为 [n_features, num_rounds],包含每次重复的特征重要性。如果 num_rounds=1,则它包含与第一个数组 mean_importance_vals 相同的值。

示例

有关用法示例,请参阅 https://mlxtend.cn/mlxtend/user_guide/evaluate/feature_importance_permutation/

ython ython