SHAP:以credit card fraud detection为例做anomaly detection可视化分析解释

   之前做了bitcoin price预测的可视化解释,这次继续做个anomaly detection相关的业务,就用比较常见的信用卡欺诈检测啦,还是用天池上现成的数据集,如下:https://tianchi.aliyun.com/dataset/92665 

  

   这次用application_data来做测试,这是一个典型二分类数据集,target=1表示至少有一笔贷款逾期超过X天,target=0表示还未逾期!废话少说,直接上代码:

import pandas as pd
import numpy as np
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
import shap
import matplotlib.pyplot as plt


# 1. 数据加载与预处理
def load_and_preprocess_data():
    df = pd.read_csv('application_data.csv')
    categorical_cols = df.select_dtypes(include=['object']).columns
    for col in categorical_cols:
        df[col] = df[col].fillna('Unknown').astype(str)
        le = LabelEncoder()
        df[col] = le.fit_transform(df[col])
    df = df.fillna(df.median())
    exclude_cols = ['SK_ID_CURR', 'TARGET']
    features = [c for c in df.columns if c not in exclude_cols]
    X = df[features]
    y = df['TARGET']
    return train_test_split(X, y, test_size=0.2, random_state=42)


# 2. 模型训练
def train_model(X_train, y_train):
    model = xgb.XGBClassifier(n_estimators=200, max_depth=5, random_state=42)
    model.fit(X_train, y_train)
    return model


# 3. SHAP解释器初始化
def initialize_shap_explainer(model, X_test):
    explainer = shap.TreeExplainer(model)
    shap_values = explainer(X_test)
    return explainer, shap_values


# 4. 可视化函数集合
def plot_summary_bar(shap_values, X_test):
    shap.plots.bar(shap_values, show=False)
    plt.title("Feature Importance (Bar Plot)")
    plt.tight_layout()
    plt.savefig("summary_bar.png", dpi=300)
    plt.close()


def plot_summary_detail(shap_values, X_test):
    shap.summary_plot(shap_values.values, X_test, show=False)
    plt.title("Feature Impact Distribution")
    plt.tight_layout()
    plt.savefig("summary_detail.png", dpi=300)
    plt.close()


def plot_force_plot(explainer, shap_values, X_test, idx=0):
    plt.figure(figsize=(20, 4))
    shap.plots.force(shap_values[idx], matplotlib=True, show=False)
    plt.title(f"Force Plot for Sample {idx}")
    plt.tight_layout()
    plt.savefig(f"force_plot_{idx}.png", dpi=300, bbox_inches='tight')
    plt.close()


def plot_waterfall_plot(explainer, shap_values, idx=0):
    plt.figure(figsize=(10, 6))
    shap.plots.waterfall(shap_values[idx], show=False)
    plt.title(f"Waterfall Plot for Sample {idx}")
    plt.tight_layout()
    plt.savefig(f"waterfall_plot_{idx}.png", dpi=300)
    plt.close()


def plot_dependence_plots(shap_values, X_test, features=None):
    if features is None:
        features = X_test.columns[:5]
    for feature in features:
        shap.dependence_plot(feature, shap_values.values, X_test, show=False)
        plt.title(f"Dependence Plot: {feature}")
        plt.tight_layout()
        plt.savefig(f"dependence_{feature}.png", dpi=300)
        plt.close()


def plot_heatmap(shap_values, X_test):
    # 只取前 2000 个样本用于绘图
    subset_size = 2000
    shap_values_subset = shap_values[:subset_size]

    # 特征排序
    feature_order = np.argsort(np.abs(shap_values.values).mean(0))[::-1]

    # 绘图
    shap.plots.heatmap(
        shap_values_subset,
        feature_order=feature_order,
        show=False
    )

    plt.title(f"Heatmap (First {subset_size} Samples)")
    plt.tight_layout()
    plt.savefig("heatmap.png", dpi=300)
    plt.close()


def plot_cohort_analysis(shap_values, X_test):
    """按收入分组的特征重要性对比"""
    # 创建收入分组(三分位数)
    income_groups = pd.qcut(X_test['AMT_INCOME_TOTAL'], q=3, labels=['Low', 'Medium', 'High'])

    # 修复:将布尔掩码转换为 NumPy 数组
    for group in ['Low', 'Medium', 'High']:
        group_mask = (income_groups == group).values  # 使用 .values 转换为 numpy array
        group_shap = shap_values[group_mask]

        plt.figure(figsize=(10, 6))
        shap.plots.bar(group_shap, show=False)
        plt.title(f"Feature Importance for {group} Income Group")
        plt.tight_layout()
        plt.savefig(f"beeswarm_income_{group}.png", dpi=300)
        plt.close()


def plot_interaction_values(explainer, X_test):
    # 直接获取交互作用值(避免使用 .cohorts())
    interaction_values = explainer.shap_interaction_values(X_test[:100])

    # 绘制交互作用图(注意参数顺序)
    shap.summary_plot(
        interaction_values,
        X_test[:100].values,
        feature_names=X_test.columns.tolist(),
        show=False
    )
    plt.title("Feature Interaction Values")
    plt.tight_layout()
    plt.savefig("interaction_values.png", dpi=300)
    plt.close()


def plot_partial_dependence(model, X_test):
    feature_names = X_test.columns.tolist()

    explainer = shap.TreeExplainer(model)
    shap_values = explainer(X_test)

    shap.partial_dependence_plot(
        feature_names[0],
        model.predict,
        X_test,
        feature_expected_value=True,
        show=False
    )
    plt.title(f"Partial Dependence: {feature_names[0]}")
    plt.tight_layout()
    plt.savefig("partial_dependence.png", dpi=300)
    plt.close()


def plot_beeswarm_plot(shap_values):
    # 新版蜂群图直接传入 Explanation 对象
    shap.plots.beeswarm(
        shap_values,
        max_display=10,  # 显示最重要的前10个特征
        show=False
    )
    plt.title("Beeswarm Plot")
    plt.tight_layout()
    plt.savefig("beeswarm_plot.png", dpi=300)
    plt.close()


# 5. 执行所有可视化
def run_all_visualizations():
    X_train, X_test, y_train, y_test = load_and_preprocess_data()
    model = train_model(X_train, y_train)
    explainer, shap_values = initialize_shap_explainer(model, X_test)

    plot_summary_bar(shap_values, X_test)
    plot_summary_detail(shap_values, X_test)
    plot_force_plot(explainer, shap_values, X_test, idx=0)
    plot_waterfall_plot(explainer, shap_values, idx=0)
    plot_dependence_plots(shap_values, X_test)
    
    plot_cohort_analysis(shap_values, X_test)
    plot_interaction_values(explainer, X_test)
    plot_partial_dependence(model, X_test)
    plot_beeswarm_plot(shap_values)

    plot_heatmap(shap_values, X_test)


# 主程序入口
if __name__ == "__main__":
    run_all_visualizations()

  这里一口气展示10张图,挨个详细分析credit fraude的可视化分析解释!

  1、先看最重要的:feather importance,如下:

  

   影响力最大的两个特征是外部评分,这个分数具体是怎么得到的原作者并未说明,只能顺数看后续的feather:

  • AMT_GOODS_PRICE:消费贷中商品价格;价格越高,需要贷款的金额越多,逾期还不上的风险越大?比如房贷这类大宗贷款;
  • AMT_CREDIT:贷款金额,同上:金额越大,还款压力越大,逾期还不上的压力也就越大!
  • DAYS_EMPLOYED:客户当前工作的开始时间;越长说明收入越高,还款压力越小,逾期的风险越小

  与之对应的还有另外一个heatmap:这里抽样了2000个样本,每个样本的每个feather的SHAP值大小展示如下:

  

   越红说明该feather的值越容易导致target=1,也就是违约;越蓝说明越安全,不容易违约;从上图可以看到:source3、source2、GOODS_PRICE、CREDIT、source1这几个特征蓝色和红色的块明显比其他特征多,说明这几个特征的区分度很大,所以明显更重要!

  2、 上面只展示了特征的重要性,这些特征的影响到底是正面的还是负面的?注意:因为target=1代表逾期违约,所以SHAP值越大,说明逾期违约概率越大;SHAR值越小(越负),违约的概率越低,方向别搞反了

  

  • 前面两个特征:外部的评分,特征值越高(红色),SHAP 负影响力越大,说明违约的概率越低!
  • AMT_GOODS_PRICE:该特征可能是一个关键的转折点特征,其值的变化会对模型预测结果产生较大波动;
    • 低贷款金额(蓝色) :小额度商品贷款可能被视为高风险(如短期周转困难),或则被贷款客户滥用,导致模型预测违约概率升高(负向影响)
    • 高贷款金额(红色) :大额度商品贷款可能对应更严格的审核流程或优质客户群体,导致模型预测违约概率降低(正向影响),典型的比如房贷、车贷
  • AMT_CREDIT:特征值越高(红色),SHAP 值越低,表明该特征对模型预测结果的负向贡献越大。贷款金额越大,越容易逾期!反之,越低越不容易逾期!
  • DAYS_EMPLOYED:相对于申请日的天数,负数表示过去的时间;工作时间越长(负数越小),违约概率越低
  • AMT_ANNUITY:每期偿还的金额越大,违约的概率越低,可能是因为贷款年限低?或者说大金额代码审核严格,都是优质客户?

  3、WaterFall Plot:单条数据是怎么影响最终结果的了?如下:展示了每个特征如何累加影响最终预测值(f(x) = -1.114

   

   每个值对最终结果的影响力度:

  

   4、依赖图:

  (1)CHILDREN和住宅面积的依赖:

  • 横轴:CNT_CHILDREN家庭小孩数量,最多有12个小孩的!
  • 纵轴:
    • SHAP Value:小孩数量对结果的影响;越红违约概率越高!
    • toal area amound:家庭住宅面积大小

  

  • CNT_CHILDREN=0或1时,住宅面积越大,违约概率越低(下面红色多,上面蓝色多),可能是因为有房产抵押,所以不敢随意违约?
  • CNT_CHILDREN=2时,住宅面积越大,违约概率越大(上面红色点明显比下面多),是因为养小孩太过于烧钱,经济压力太大导致的?
  • CNT_CHILDREN=3或4时,住宅面积大或小对于违约的概率接近,要结合其他feather派判断了!

  (2)GENDER和age的依赖关系:

  

  • gender = 0的时候,age大的违约概率小;age小的大约有一半概率违约
  • gender=1的时候,age大的违约概率大,age小的大约一半概率违约

  (3)NAME_CONTRACT_TYPE贷款类型和贷款年金之间的依赖关系:

  

  •  贷款类型=0的用户,不论ANNUITY大小,SHAP值集中在0附近,说明这部分用户是否违约是不受ANNUITY影响的!
  •  贷款类型=1的用户,ANNUITY>35000(鲜红)的SHAP全是负的,违约概率很低!ANNUITY<15000(湛蓝)的,SHAP值正负都有,几乎对称,所以违约概率大概一半!

 

参考:

1、https://www.bilibili.com/video/BV1vcJVzZEz3/?spm_id_from=333.1007.tianma.45-2-176.click&vd_source=241a5bcb1c13e6828e519dd1f78f35b2

posted @ 2025-05-31 23:39  第七子007  阅读(107)  评论(0)    收藏  举报