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

技术

picture.image

背景

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

  • 整体模型解释: 常见的模型解释方法,如全局特征重要性分析、部分依赖图、特征贡献等,通常聚焦于整体目标变量与特征之间的关系, 这些方法展示特征在总体上如何影响模型的输出,而不是细分到具体类别上
  • 全局与局部解释: 全局解释方法关注整个数据集的特征重要性,而局部解释方法则解释单个实例的 预测如何受特征影响, 这些方法虽然能够解释单个预测实例的特征贡献,但大多没有细分到不同类别下的特征影响
  • 多类别模型解释的挑战: 在多类别分类问题中,解释每个类别下各特征的影响更加复杂,因为同一特征可能对不同类别有不同的影响方向和大小, 因此,这种类型的解释需要更细粒度的方法和更复杂的计算
  • 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()
      

picture.image

原始数据包含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)
      

picture.image

使用 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_)
      

picture.image

使用贝叶斯优化方法通过定义惩罚项 、核函数类型和核函数系数 的超参数空间,并利用 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)
      

picture.image

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

模型评价指标输出


          
from sklearn.metrics import classification_report
          
pred = clf.predict(Xtest) # 预测测试集
          
print(classification_report(Ytest, pred)) # 输出模型完整评价指标
      

picture.image


          
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()
      

picture.image

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个样本
      

使用 SHAP 的 KernelExplainer 计算了支持向量机模型对测试数据的预测结果的特征贡献度,从而提供了对模型决策过程的解释,这里为什么只选择部分样本,是因为 KernelExplainer 能处理各种模型并提供全面的解释,但它的计算速度较慢,特别是在大规模数据集或复杂模型的情况下,对于具有更高计算效率的 SHAP 解释器(如 TreeExplainer 或 DeepExplainer),适用于特定类型的模型,能更快地计算 SHAP 值,如果速度是关键考虑因素,选择适合模型类型的 SHAP 解释器将更加高效,这里读者自己抉择

提取各类别SHAP值


          
# 提取每个类别的 SHAP 值
          
shap_values_class_0 = shap_values[:, :, 0]
          
shap_values_class_1 = shap_values[:, :, 1]
          
shap_values_class_2 = shap_values[:, :, 2]
          
shap_values_class_3 = shap_values[:, :, 3]
          
shap_values_class_4 = shap_values[:, :, 4]
      

计算各类别的特征贡献度


          
# 计算每个类别的特征贡献度
          
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
      

picture.image

创建一个数据框 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()
      

picture.image

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

往期推荐

利用XGBoost模型进行多分类任务下的SHAP解释附代码讲解及GUI展示

利用SHAP解释二分类模型的四种机器学习GUI工具

快速选择最佳模型:轻松上手LightGBM、XGBoost、CatBoost和NGBoost!

基于CatBoost回归预测模型的多种可解释性图表绘制

小白轻松上手:一键生成SHAP解释图的GUI应用,支持多种梯度提升模型选择

综合多种梯度提升模型:LightGBM、XGBoost、CatBoost与NGBoost的集成预测

优化XGBoost回归模型:网格搜索与K折交叉验证实现

picture.image

picture.image

picture.image

微信号|deep_ML

欢迎添加作者微信进入Python、ChatGPT群

进群请备注Python或AI进入相关群
无需科学上网、同步官网所有功能、使用无限制

如果你对类似于这样的文章感兴趣。

欢迎关注、点赞、转发~

个人观点,仅供参考

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
云原生机器学习系统落地和实践
机器学习在字节跳动有着丰富业务场景:推广搜、CV/NLP/Speech 等。业务规模的不断增大对机器学习系统从用户体验、训练效率、编排调度、资源利用等方面也提出了新的挑战,而 Kubernetes 云原生理念的提出正是为了应对这些挑战。本次分享将主要介绍字节跳动机器学习系统云原生化的落地和实践。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论