决策曲线分析 (DCA) 可视化:如何判断模型在实际应用中的表现

大模型

picture.image

背景

决策曲线分析(DCA)是一种基于净收益(Net Benefit)的工具,用于评估在不同的决策阈值下使用预测模型是否能够带来实际的利益,通过数学公式,我们可以深入理解 DCA 的核心计算过程,以下是具体解释:

净收益的定义(Net Benefit)

净收益是 DCA 的核心指标,衡量的是在不同阈值下使用预测模型的效益,综合考虑了真正例(TP)和假正例(FP),并对假阳性的代价进行权衡:

其中 真阳性数,即模型正确预测的正类样本数, 假阳性数,即模型错误预测为正类但实际为负类的样本数, 样本总数, 决策阈值,表示模型预测为正类的概率临界点

第一部分: 是在该阈值下的真阳性率,也就是模型预测正确的正类样本占总样本的比例,这体现了模型的收益

第二部分: 是基于阈值加权的假阳性率,反映了错误预测为正类带来的损失,阈值越高,模型对正类预测越保守,此时假阳性造成的损失也越大

Treat all 策略的净收益

在Treat all策略下,假设所有样本都被视为正类(即所有样本都接受某种处理或治疗),其净收益的计算公式为:

在 Treat all 策略下,所有样本都被预测为正类,所以真阳性数等于数据集中实际的正类样本数。 假阳性数则等于数据集中实际的负类样本数。 由于 Treat all 策略中每个样本都被视为正类,假阳性率可能会非常高,因此在高阈值下,净收益会显著下降

Treat none 策略的净收益

在Treat none策略下,假设所有样本都被视为负类(即没有样本被视为正类或接受处理),其净收益为零,因为没有真阳性和假阳性:

Treat none 策略假设没有样本被预测为正类,所以没有任何收益或损失,这也反映了在完全不采取任何措施的情况下,净收益为零

模型的净收益与其他策略的对比

通过绘制净收益随不同阈值变化的曲线,DCA 直观地展示了以下三种策略的净收益表现:

  • 使用预测模型(蓝色曲线):在不同的阈值下,模型的净收益随着阈值的变化而变化
  • Treat all 策略(黑色曲线): 代表所有样本都被视为正类的情景。 随着阈值的增加,净收益通常会下降,因为假阳性带来的损失 增大
  • Treat none 策略(灰色线): 代表不处理任何样本的情景,净收益始终为零

通过比较这三种曲线(这里的颜色曲线针对后文代码可视化背景给出),可以确定在哪些阈值范围内使用预测模型是有意义的,通常,只有当模型的净收益曲线高于 Treat all 和 Treat none 策略时,模型才是最优选择

代码实现

数据读取


          
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
          

          
df = pd.read_csv("Dataset.csv")
          
df.head()
      

picture.image

加载 "Dataset.csv" 数据集并显示其前几行,此数据集为一个二分类数据集

数据分割


          
from sklearn.model_selection import train_test_split
          

          
X = df.drop(['target'], axis=1)
          
y = df['target']
          

          
# 分割数据集
          
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, 
          
                                                    random_state=42, stratify=df['target'])
      

将数据集分为特征变量 X 和目标变量 y,然后使用 train_test_split 函数按 80% 训练集和 20% 测试集的比例将数据集进行分割,同时使用 stratify 参数确保目标变量 target 在训练集和测试集中的分布一致

模型构建


          
from sklearn.ensemble import RandomForestClassifier
          

          
# 构建随机森林模型,并设置多个参数
          
model = RandomForestClassifier(
          
    n_estimators=100,        # 森林中树的数量,更多的树通常能提高模型的性能,但计算开销也会增加
          
    max_depth=10,            # 树的最大深度,防止树过深导致过拟合。较小的深度可能导致欠拟合
          
    min_samples_split=5,     # 每个节点至少需要有5个样本才能继续分裂,增大可以防止过拟合
          
    min_samples_leaf=2,      # 每个叶子节点至少要有2个样本,防止叶子节点过小导致模型过拟合
          
    max_features='sqrt',     # 每次分裂时考虑的特征数量,'sqrt' 表示使用特征数量的平方根,能提高模型的泛化能力
          
    bootstrap=True,          # 是否在构建每棵树时进行有放回的抽样,True 是默认设置,可以减少模型的方差
          
    oob_score=True,          # 是否使用袋外样本来评估模型的泛化能力,开启此项可以进行无偏验证
          
    random_state=42,         # 保证结果可重复,设置随机数种子
          
    class_weight='balanced'  # 类别权重,适用于样本不均衡的情况,'balanced' 自动调整类别权重
          
)
          

          
# 训练模型
          
model.fit(X_train, y_train)
      

构建并训练了一个随机森林分类模型 (RandomForestClassifier),其中设置了多个参数,如树的数量、最大深度、节点分裂的最小样本数、特征选择方式等,以控制模型的复杂度和防止过拟合。使用了袋外评分 (oob_score) 来评估模型的泛化能力,并通过 class_weight='balanced' 来处理类别不平衡问题,最后通过 model.fit(X_train, y_train) 在训练数据上进行模型训练

利用决策曲线分析 (DCA) 评估机器学习模型的净收益


          
from sklearn.metrics import confusion_matrix
          

          
# 矢量化计算模型净收益的函数
          
def compute_net_benefit_model_vectorized(thresholds, y_pred_scores, y_labels):
          
    # 先将 y_labels 转换为 numpy 数组以避免多维索引问题
          
    y_labels = np.array(y_labels)
          
    y_pred_scores = np.array(y_pred_scores)
          
    # 计算总样本数
          
    n = len(y_labels)
          
    # 预分配数组
          
    net_benefit_model = np.zeros_like(thresholds)
          
    # 将预测得分和阈值进行广播计算
          
    y_pred_matrix = (y_pred_scores[:, None] > thresholds).astype(int)
          
    # 矢量化计算混淆矩阵的元素:TP 和 FP
          
    tp = (y_pred_matrix & y_labels[:, None]).sum(axis=0)
          
    fp = ((y_pred_matrix == 1) & (y_labels[:, None] == 0)).sum(axis=0)
          
    # 计算净收益
          
    net_benefit_model = (tp / n) - (fp / n) * (thresholds / (1 - thresholds))
          
    return net_benefit_model
          

          
# 矢量化计算 Treat all 策略净收益的函数
          
def compute_net_benefit_all_vectorized(thresholds, y_labels):
          
    # 将 y_labels 转换为 numpy 数组以避免多维索引问题
          
    y_labels = np.array(y_labels)
          
    # 计算混淆矩阵的元素(基于 Treat all 策略,所有样本被视为正类)
          
    tn, fp, fn, tp = confusion_matrix(y_labels, y_labels).ravel()  
          
    total = tp + tn
          
    # 预分配数组
          
    net_benefit_all = np.zeros_like(thresholds)
          
    # 矢量化计算净收益
          
    net_benefit_all = (tp / total) - (tn / total) * (thresholds / (1 - thresholds))
          
    return net_benefit_all
          

          
# 绘制 DCA 的函数
          
def plot_dca_custom(thresholds, net_benefit_model, net_benefit_all):
          
    fig, ax = plt.subplots(figsize=(8, 6),dpi=1200)
          
    # 绘制净收益曲线
          
    ax.plot(thresholds, net_benefit_model, color='deepskyblue', label='Model') 
          
    ax.plot(thresholds, net_benefit_all, color='black', label='Treat all')  
          
    ax.plot((0, 1), (0, 0), color='#808080', label='Treat none')  
          

          
    # 填充模型比 Treat all 和 Treat none 优势部分
          
    y2 = np.maximum(net_benefit_all, 0)
          
    y1 = np.maximum(net_benefit_model, y2)
          
    ax.fill_between(thresholds, y1, y2, color='deepskyblue', alpha=0.3)  # 保持原来的填充颜色
          
    # 美化图表
          
    ax.set_xlim(0, 1)
          
    ax.set_ylim(net_benefit_model.min() - 0.15, net_benefit_model.max() + 0.15)
          
    ax.set_xlabel('Threshold Probability', fontdict={'family': 'Times New Roman', 'fontsize': 15})
          
    ax.set_ylabel('Net Benefit', fontdict={'family': 'Times New Roman', 'fontsize': 15})
          
    ax.grid(True)
          
    ax.legend(loc='upper right')
          
    plt.savefig("DCA.pdf", bbox_inches='tight')
          
    plt.show()
          
# 运行 DCA 分析函数
          
def run_dca_analysis(model, X_test, y_test):
          
    # 使用模型预测概率 
          
    y_pred_scores = model.predict_proba(X_test)[:, 1]  # 获得正类的预测概率
          
    y_labels = y_test  # 使用测试集的真实标签
          
    # 定义阈值范围
          
    thresholds = np.arange(0, 1, 0.01)
          
    # 计算不同阈值下的净收益
          
    net_benefit_model = compute_net_benefit_model_vectorized(thresholds, y_pred_scores, y_labels)
          
    net_benefit_all = compute_net_benefit_all_vectorized(thresholds, y_labels)
          
    # 调用绘图函数
          
    plot_dca_custom(thresholds, net_benefit_model, net_benefit_all)
      

通过决策曲线分析 (DCA) 评估机器学习模型在不同阈值下的净收益,首先通过 compute_net_benefit_model_vectorized 和 compute_net_benefit_all_vectorized 函数分别计算模型和 Treat all 策略的净收益,依据阈值来衡量真正例 (TP) 和假正例 (FP) 对模型效益的影响,然后通过 plot_dca_custom 函数将模型与 Treat all 和 Treat none 策略的净收益曲线进行可视化对比,最后 run_dca_analysis 函数执行整个分析过程,帮助决策者判断在不同阈值下,使用模型是否比其他策略带来更高的效益

函数调用


        
            

          run\_dca\_analysis(model, X\_test, y\_test)
        
      

picture.image

往期推荐

SCI图表复现:整合数据分布与相关系数的高级可视化策略

复现顶刊Streamlit部署预测模型APP

树模型系列:如何通过XGBoost提取特征贡献度

SHAP进阶解析:机器学习、深度学习模型解释保姆级教程

特征选择:Lasso和Boruta算法的结合应用

从基础到进阶:优化SHAP力图,让样本解读更直观

SCI图表复现:优化SHAP特征贡献图展示更多模型细节

多模型中的特征贡献度比较与可视化图解

基于SHAP值的 BorutaShap 算法在特征选择中的应用与优化

基于相关性与标准差的多模型评价指标可视化比较 —— 泰勒图应用解析

picture.image

picture.image

picture.image

微信号|deep_ML

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

进群请备注Python或AI进入相关群

无需科学上网、同步官网所有功能、使用无限制

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

欢迎关注、点赞、转发~

个人观点,仅供参考

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
火山引擎大规模机器学习平台架构设计与应用实践
围绕数据加速、模型分布式训练框架建设、大规模异构集群调度、模型开发过程标准化等AI工程化实践,全面分享如何以开发者的极致体验为核心,进行机器学习平台的设计与实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论