✨ 欢迎关注 ✨
本节介绍: 调整模型预测概率阈值优化性能,是否会影响SHAP值计算 ,作者根据个人对机器学习的理解进行代码实现与图表输出,仅供参考。 完整 数据和代码将在稍后上传至交流群,成员可在交流群中获取下载。需要的朋友可关注公众文末提供的获取方式。 获取 前请咨询,避免不必要的问题。
✨ 前言 ✨
假设我们有一个二分类模型,我们可以将整个过程拆解如下:
- 计算原始分数 :模型内部根据所有规则(如果是树模型就是决策树的结构),为每个样本计算一个原始的、连续的分数。这个分数可以是任何实数(比如-2.5, 0.8, 1.5等)。 这是模型最核心的、可加性的输出。 SHAP值解释的就是每个特征如何贡献于这个原始分数,SHAP_特征A + SHAP_特征B + ... + 模型基准值 = 模型的原始分数
- 转换为概率 :为了让结果更容易理解,模型会使用一个链接函数(通常是Sigmoid函数)将这个无限范围的原始分数映射到 (0, 1) 区间内,形成我们所说的概率,概率 = Sigmoid(原始分数),这个转换是单调的(原始分数越高,概率越高),但不是线性的
- 应用阈值进行分类 :最后,应用一个阈值(默认是0.5)来将这个概率转换成一个最终的、离散的类别(0或1)
SHAP 解释的是哪一步(以树模型为例)?
- shap.TreeExplainer 主要在第1步。计算每个特征对“原始分数”的贡献值。这是最准确、最符合其理论基础的解释
- SHAP库为了方便我们理解,通常会将这些基于“原始分数”的SHAP值和模型的基准值自动转换到“概率”空间(第 2 步)来展示。所以图上显示的可能是对概率的影响,但其底层计算是基于原始分数的
为什么不解释第 3 步?因为第 3 步(应用阈值)是一个人为设定的、不连续的决策规则(默认0.5) ,它不是模型学习到的复杂关系的一部分,所以调整模型预测概率阈值优化性能,不会影响SHAP值计算,但是会影响模型的预测结果类别,接下来从代码角度来看
✨ 代码实现 ✨
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['axes.unicode_minus'] = False
import warnings
# 忽略所有警告
warnings.filterwarnings("ignore")
from sklearn.model_selection import train_test_split
df = pd.read_excel('2025-1-23公众号Python机器学习AI.xlsx')
# 划分特征和目标变量
X = df.drop(['y'], axis=1)
y = df['y']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,
random_state=42, stratify=df['y'])
from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.metrics import accuracy_score
# 定义 XGBoost 二分类模型
model_xgb = XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=8)
# 定义参数网格
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1, 0.2],
'subsample': [0.8, 1.0],
'colsample_bytree': [0.8, 1.0]
}
# 定义 K 折交叉验证 (Stratified K-Fold)
kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=8)
# 使用网格搜索寻找最佳参数
grid_search = GridSearchCV(estimator=model_xgb, param_grid=param_grid, scoring='accuracy',
cv=kfold, verbose=1, n_jobs=-1)
# 拟合模型
grid_search.fit(X_train, y_train)
# 使用最优参数训练模型
xgboost = grid_search.best_estimator_
使用XGBoost的网格搜索(GridSearchCV)和5折Stratified K-Fold交叉验证来调优模型超参数,并在默认的0.5概率阈值下训练一个二分类模型,最终选择最佳超参数进行训练
# 获取模型对测试集的预测概率
probabilities = xgboost.predict_proba(X_test)
# 获取真实标签
true_labels = y_test.values
# 初始化列表以存储TSS值
tss_values = []
# 定义阈值范围
thresholds = np.linspace(0, 1, 101)
# 计算不同阈值下的TSS
for threshold in thresholds:
# 计算预测标签
predicted_labels = (probabilities[:, 1] > threshold).astype(int)
# 计算混淆矩阵
tn, fp, fn, tp = confusion_matrix(true_labels, predicted_labels).ravel()
# 计算灵敏度(Sensitivity)和特异度(Specificity)
sensitivity = tp / (tp + fn)
specificity = tn / (tn + fp)
# 计算TSS
tss = sensitivity + specificity - 1
tss_values.append(tss)
# 转换TSS值为数组
tss_values = np.array(tss_values)
# 找到最大TSS对应的阈值
max_tss = tss_values.max()
optimal_threshold = thresholds[np.argmax(tss_values)]
# 绘制TSS vs 阈值图
plt.figure(figsize=(8, 6), dpi=1200)
plt.plot(thresholds, tss_values, color='blue', label='Smoothed TSS')
plt.axhline(y=max_tss, color='red', linestyle='--', label=f'Max TSS: {max_tss:.4f}')
plt.axvline(x=optimal_threshold, color='green', linestyle='--', label=f'Optimal Threshold: {optimal_threshold:.4f}')
# 在最佳阈值的位置标出交点
plt.scatter(optimal_threshold, max_tss, color='green', zorder=5)
plt.text(optimal_threshold, max_tss, f'({optimal_threshold:.4f}, {max_tss:.4f})',
color='green', horizontalalignment='left', verticalalignment='bottom')
plt.xlabel('Threshold', fontsize=14, fontweight='bold')
plt.ylabel('TSS', fontsize=14, fontweight='bold')
plt.title('TSS vs Threshold', fontsize=16, fontweight='bold')
plt.legend(fontsize=12)
plt.savefig("2.pdf", format='pdf', bbox_inches='tight')
plt.show()
这里计算并绘制TSS (True Skill Statistic)随着不同概率阈值变化的曲线,从而找到最佳的分类阈值。TSS是一个评估二分类模型性能的指标,结合了 灵敏度(Sensitivity)和 特异度(Specificity),它通常用于评估模型的分类能力,尤其是在不均衡数据集中,最终的图形显示不同阈值下的 TSS 值,并标出了最大TSS 对应的阈值和该点的坐标。这可以帮助选择最合适的阈值,以便获得最佳的分类性能,这里最佳的阈值为0.14
# best_model_from_grid_search 是通过网格搜索获得的最佳模型
best_model_from_grid_search = grid_search.best_estimator_
# 定义 CustomXGBClassifier 类
class CustomXGBClassifier(XGBClassifier):
def __init__(self, best_model, optimal_threshold=0.5, *args, **kwargs):
super().__init__(*args, **kwargs)
self.best_model = best_model
self.optimal_threshold = optimal_threshold # 设置最优阈值
def fit(self, X, y, *args, **kwargs):
# 训练模型
self.best_model.fit(X, y, *args, **kwargs)
return self # 返回训练后的模型
def predict(self, X):
# 使用训练好的模型预测概率,并应用最优阈值
probabilities = self.best_model.predict_proba(X)
return (probabilities[:, 1] > self.optimal_threshold).astype(int)
def predict_proba(self, X):
return self.best_model.predict_proba(X)
# 初始化自定义 XGB 模型,并传入最佳模型和最优阈值
custom_xgb = CustomXGBClassifier(best_model=best_model_from_grid_search, optimal_threshold=optimal_threshold)
# 训练模型,并确保训练时模型使用的是 optimal_threshold
custom_xgb.fit(X_train, y_train)
定义一个自定义的CustomXGBClassifier类,通过传入网格搜索获得的最佳模型和最优阈值,使得在使用custom_xgb进行预测时,预测的概率阈值为0.14,而不是默认的0.5,意味着当预测概率大于0.14时,模型预测为类别1;否则预测为类别0,接下来绘制不同阈值下两个模型的混淆矩阵
两个混淆矩阵展示XGBoost模型在不同阈值下的分类表现。使用默认的0.5阈值时,模型在对于少数类样本预测精确度为64%;而使用较低的0.14阈值时,模型倾向于预测更多的少数类样本,提升了少数类样本的灵敏度,但也增加了假正例的数量,导致特异度下降。阈值的选择直接影响模型的分类效果,需根据背景需求做出调整,当然除了这种方法还有其它方法来衡量这个阈值的确定,这里主要是想说明
整模型预测概率阈值优化性能,不会影响SHAP值计算,但是会影响模型的预测结果类别,接下来简单进行一个SHAP可视化
import shap
explainer = shap.TreeExplainer(xgboost)
shap_values = explainer.shap_values(X_test)
shap_values_for_first_sample = shap_values[0] # 从 SHAP 值数组中选择第1个样本的 SHAP 值
feature_names = X_test.columns # 获取 X_test 数据集的列名,即特征名称
original_values = X_test.iloc[1] # 获取 X_test 中第 1 行的数据作为原始特征值
# 获取 SHAP 解释器的基准值(expected_value)
base_value = explainer.expected_value # 这是 SHAP 模型的期望值,通常是背景分布的平均值
# 绘制 SHAP 决策图
plt.figure(figsize=(10, 5), dpi=1200)
shap.decision_plot(base_value, shap_values_for_first_sample, original_values, show=False, link='logit')
plt.savefig("4.pdf", format='pdf', bbox_inches='tight')
plt.tight_layout()
plt.show()
使用 SHAP 库的 TreeExplainer 来解释XGBoost模型对测试集样本的预测,并绘制SHAP 决策图 。通过决策图,可以看到模型输出的预测概率与每个特征的SHAP值的贡献之间的关系。在这个图中,顶部的颜色映射条表示模型预测的概率,遵循 SHAP 的加法模型形式:SHAP_特征A + SHAP_特征B + ... + 基准值 = 模型原始分数 ,其中原始分数是指预测概率,而非最终的分类输出(0 或 1)。因此,调整预测阈值只会影响最终的分类结果(0 或 1),而不会影响预测概率,也不会影响 SHAP 值的计算,当然这里有个参数link='logit',为什么使用这个参数可以参考往期文章——理解 SHAP 值:如何根据模型性质正确解释 XGBoost 与随机森林的结果
当然,公众号中还有更多机器学习期刊实战技巧,您可以通过历史文章进行检索和阅读,关注公众号,点击“发信息”>“历史文章”即可搜索公众号所有文章信息
✨ 该文章案例 ✨
在上传至交流群的文件中,像往期文章一样,将对案例进行逐步分析,确保读者能够达到最佳的学习效果。内容都经过详细解读,帮助读者深入理解模型的实现过程和数据分析步骤,从而最大化学习成果。
同时,结合提供的免费AI聚合网站进行学习,能够让读者在理论与实践之间实现融会贯通,更加全面地掌握核心概念。
✨ 介绍 ✨
本节介绍到此结束,有需要学习数据分析和Python机器学习相关的朋友欢迎到淘宝店铺:Python机器学习AI,或添加作者微信deep_ML联系,获取作者的公众号合集。截至目前为止,合集已包含近300多篇文章,购买合集的同时,还将提供免费稳定的AI大模型使用。
更新的内容包含数据、代码、注释和参考资料。 作者仅分享案例项目,不提供额外的答疑服务。项目中将提供详细的代码注释和丰富的解读,帮助您理解每个步骤 。 获取 前请咨询,避免不必要的问题。
✨ 群友反馈 ✨
✨ 淘宝店铺 ✨
请大家打开淘宝扫描上方的二维码,进入店铺,获取更多Python机器学习和AI相关的内容,或者添加作者微信deep_ML联系 避免淘宝客服漏掉信息 ,希望能为您的学习之路提供帮助!
往期推荐
期刊复现:连续数据与分类数据共存的SHAP可视化散点图与箱形图组合形式
期刊复现:多分类任务如何拆分为二分类任务并进行堆叠预测提高模型预测性能
期刊配图:SHAP值分析模型可解释性在柱状图与蜂窝图中的进阶组合展示
期刊配图:通过堆叠Mean|SHAP|展示不同区间对模型贡献度的可视化分析
期刊复现:利用UMAP降维算法可视化深度学习随着训练次数的增加模型区分能力的变化
期刊配图:PCA、t-SNE与UMAP三种降维方法简化高维数据的展示应用对比
Science期刊复现:分类、回归与Shap分析多角度揭示同一数据集变量对目标的影响
多模型SHAP+PDP解读Stacking集成模型:从基学习器到元学习器的可解释性与模型调参实现
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~
个人观点,仅供参考