所有文章 > AI驱动 > SVM多分类分析:SHAP解释各类别下各特征对模型的影响力

SVM多分类分析:SHAP解释各类别下各特征对模型的影响力

背景

目前大多数模型解释技术通常侧重于分析整体模型的输出如何受到输入特征的影响,而不是针对每个类别来分析特征的贡献。这主要体现在以下几个方面:

  • 整体模型解释:常见的模型解释方法,如全局特征重要性分析、部分依赖图、特征贡献等,通常聚焦于整体目标变量与特征之间的关系,这些方法展示特征在总体上如何影响模型的输出,而不是细分到具体类别上。
  • 全局与局部解释:全局解释方法关注整个数据集的特征重要性,而局部解释方法则解释单个实例的预测如何受特征影响,这些方法虽然能够解释单个预测实例的特征贡献,但大多没有细分到不同类别下的特征影响。
  • 多类别模型解释的挑战:在多类别分类问题中,解释每个类别下各特征的影响更加复杂,因为同一特征可能对不同类别有不同的影响方向和大小,因此,这种类型的解释需要更细粒度的方法和更复杂的计算。
  • SHAP 的优势:SHAP 值在这方面具有优势,因为它提供了一种一致且加性的方法来度量每个特征对预测的贡献,能够很好地扩展到多类别问题中,通过对每个类别分别计算 SHAP 值,来展示特征对不同类别的影响力。

因此,尽管解释特征在不同类别下的影响力不是模型解释的主流方法,但这确实是一个值得关注的方向,尤其是在多类别问题中,通过更精细的分析,可以帮助我们更好地理解模型的决策过程,并在应用中提供更有针对性的优化策略。

项目简介

利用支持向量机 (SVM) 对多类别数据进行分类,通过超参数优化找到最佳模型参数,并使用 SHAP 分析解释每个特征在不同类别下对模型预测的影响力,最终通过可视化展示每个特征的贡献度,使得模型的决策过程更加透明和易于理解,从而帮助改进模型性能和提供对特征重要性的洞察。

代码实现

数据读取

import numpy as np
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei' # 设置中文显示
plt.rcParams['axes.unicode_minus'] = False
import warnings
# 忽略所有警告
warnings.filterwarnings("ignore")

df = pd.read_excel('多类别数据.xlsx')
df.head()

原始数据包含12个特征,5个类别。

数据预处理

类别编码

from sklearn.preprocessing import LabelEncoder

# 创建一个LabelEncoder对象
label_encoder = LabelEncoder()

# 对Type列进行编码
df['Type_encoded'] = label_encoder.fit_transform(df['Type'])

# 查看编码后的结果
encoded_types = df[['Type', 'Type_encoded']].drop_duplicates().reset_index(drop=True)
print(encoded_types)

使用 LabelEncoder 将数据框 df 中的 Type 列的类别标签转换为数值编码,并输出每个类别标签及其对应的编码结果。

数据集分割

from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from hyperopt import fmin, tpe, hp, rand
from sklearn.metrics import accuracy_score
from sklearn import svm
from sklearn import datasets
# 分割数据集
X = df.drop(['Type', 'Type_encoded'], axis = 1)
y = df['Type_encoded']

Xtrain,Xtest,Ytrain,Ytest = train_test_split(X, y, test_size = 0.3,
stratify=df['Type_encoded']) #分离训练集和测试集

超参数搜索

# 定义超参数空间
parameter_space_svc = {
'C': hp.loguniform('C', np.log(100), np.log(1000)), # 惩罚项
'kernel': hp.choice('kernel', ['rbf', 'poly']), # 核函数类型(选择rbf或poly)
'gamma': hp.loguniform('gamma', np.log(100), np.log(1000)), # 核函数的系数
}

# 初始化计数器
count = 0

# 定义优化目标函数
def func(args):
global count
count += 1
print(f"\nIteration {count}: Hyperparameters - {args}")

# 创建SVM分类器,传递超参数
clf = svm.SVC(**args)

# 训练模型
clf.fit(Xtrain, Ytrain)

# 预测测试集
prediction = clf.predict(Xtest)

# 计算准确率
score = accuracy_score(Ytest, prediction)
print(f'Test accuracy: {score}')

# 由于fmin函数默认是最小化目标函数,所以返回负准确率作为目标
return -score

# 使用TPE算法进行超参数优化,最大评估次数为100
best = fmin(func, parameter_space_svc, algo=tpe.suggest, max_evals=100)

# 将最佳的核函数类型从索引值转换为相应的字符串
kernel_list = ['rbf', 'poly']
best['kernel'] = kernel_list[best['kernel']]

# 将最佳超参数保存到 best_params_ 中
best_params_ = {
'C': best['C'],
'kernel': best['kernel'],
'gamma': best['gamma']
}

# 输出最佳超参数
print('\nBest hyperparameters:', best_params_)

使用贝叶斯优化方法通过定义惩罚项 、核函数类型和核函数系数 的超参数空间,并利用 hyperopt 库中的 TPE 算法在 100 次迭代内寻找支持向量机 (SVM) 的最佳超参数组合,以最大化模型在测试集上的预测准确率,最终输出优化后的超参数配置。

模型训练

# 创建SVM分类器,并使用最佳超参数进行配置
clf = SVC(
C=best_params_['C'], # 惩罚项参数
kernel=best_params_['kernel'], # 核函数类型
gamma=best_params_['gamma'], # 核函数系数
decision_function_shape='ovr', # 多分类问题时使用"ovr"(一对多)策略
cache_size=5000, # 缓存大小,单位为MB
probability=True
)
# 使用训练数据进行模型训练
clf.fit(Xtrain, Ytrain)

最优超参数下在训练集上训练模型。

模型评价指标输出

from sklearn.metrics import classification_report
pred = clf.predict(Xtest) # 预测测试集
print(classification_report(Ytest, pred)) # 输出模型完整评价指标
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
# 输出混淆矩阵
conf_matrix = confusion_matrix(Ytest, pred)

# 绘制热力图
plt.figure(figsize=(10, 7), dpi = 1200)
sns.heatmap(conf_matrix, annot=True, annot_kws={'size':15}, fmt='d', cmap='YlGnBu')
plt.xlabel('Predicted Label', fontsize=12)
plt.ylabel('True Label', fontsize=12)
plt.title('Confusion matrix heat map', fontsize=15)
plt.show()

SHAP 分析

SHAP值计算

import shap
# 使用一个小的子集作为背景数据(可以是Xtest的一个子集)
background = shap.sample(Xtest, 100)

# 使用KernelExplainer
explainer = shap.KernelExplainer(clf.predict_proba, background)
# 计算测试集的shap值
shap_values = explainer.shap_values(Xtest.iloc[0:20,:]) # 这里自己定义用多少个样本或者用全部 运行速度相关 我使用了20个样本

计算各类别的特征贡献度

# 计算每个类别的特征贡献度
importance_class_0 = np.abs(shap_values_class_0).mean(axis=0)
importance_class_1 = np.abs(shap_values_class_1).mean(axis=0)
importance_class_2 = np.abs(shap_values_class_2).mean(axis=0)
importance_class_3 = np.abs(shap_values_class_3).mean(axis=0)
importance_class_4 = np.abs(shap_values_class_4).mean(axis=0)

整理为DataFrame

importance_df = pd.DataFrame({
'类别0': importance_class_0,
'类别1': importance_class_1,
'类别2': importance_class_2,
'类别3': importance_class_3,
'类别4': importance_class_4
}, index=Xtrain.columns)
# 根据Type和Type_encoded对照表修改列名
type_mapping = {
0: '类型A',
1: '类型B',
2: '类型C',
3: '类型D',
4: '类型E'
}

importance_df.columns = [type_mapping[int(col.split('类别')[1])] for col in importance_df.columns]
importance_df

创建一个数据框 importance_df,将每个类别的特征重要性值进行汇总,并根据 Type 和 Type_encoded 对照表将列名从类别编号转换为实际的类别名称。

贡献度可视化

# 添加一列用于存储行的和
importance_df['row_sum'] = importance_df.sum(axis=1)

# 按照行和对DataFrame进行排序
sorted_importance_df = importance_df.sort_values(by='row_sum', ascending=True)

# 删除用于排序的行和列
sorted_importance_df = sorted_importance_df.drop(columns=['row_sum'])

elements = sorted_importance_df.index

# 使用 Seaborn 的颜色调色板,设置为 Set2,以获得对比度更高的颜色
colors = sns.color_palette("Set2", n_colors=len(sorted_importance_df.columns))

# 创建图形和坐标轴对象,设置图形大小为12x6英寸,分辨率为1200 DPI
fig, ax = plt.subplots(figsize=(12, 6), dpi=1200)

# 初始化一个数组,用于记录每个条形图的底部位置,初始为0
bottom = np.zeros(len(elements))

# 遍历每个类别并绘制水平条形图
for i, column in enumerate(sorted_importance_df.columns):
ax.barh(
sorted_importance_df.index, # y轴的特征名称
sorted_importance_df[column], # 当前类别的SHAP值
left=bottom, # 设置条形图的起始位置
color=colors[i], # 使用调色板中的颜色
label=column # 为图例添加类别名称
)
# 更新底部位置,以便下一个条形图能够正确堆叠
bottom += sorted_importance_df[column]

# 设置x轴标签和标题
ax.set_xlabel('mean(SHAP value|)(average impact on model output magnitude)', fontsize=12)
ax.set_ylabel('Features (特征)', fontsize=12)
ax.set_title('Feature Importance by Class (各类别下的特征重要性)', fontsize=15)

# 设置y轴刻度和标签
ax.set_yticks(np.arange(len(elements)))
ax.set_yticklabels(elements, fontsize=10)

# 在条形图的末尾添加文本标签
for i, el in enumerate(elements):
ax.text(bottom[i], i, ' ' + str(el), va='center', fontsize=9)

# 添加图例,并设置图例的字体大小和标题
ax.legend(title='Class (类别)', fontsize=10, title_fontsize=12)

# 禁用y轴的刻度和标签
ax.set_yticks([]) # 移除y轴刻度
ax.set_yticklabels([]) # 移除y轴刻度标签
ax.set_ylabel('') # 移除y轴标签

# 移除顶部和右侧的边框,以获得更清晰的图形
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.show()

对特征的 SHAP 重要性值进行排序,以便在水平堆叠条形图中按从最长到最短的顺序展示每个特征在各类别下的贡献度,同时使用 Seaborn 的调色板和 matplotlib 绘制了图形,以清晰地展示每个类别的特征重要性。

文章转自微信公众号@Python机器学习AI