为了账号安全,请及时绑定邮箱和手机立即绑定

异常检测:使用XGBoost实现全流程的银行信用卡欺诈检测

在这篇文章里,你可以学到如何做:

数据分析:PCA,异常值检测,统计见解(相关性,偏度,峰度等等),预处理过程,数据增强技术(SMOTE),网格搜索,交叉验证,训练,评估…

异常检测的目的在于识别与大部分数据显著不同的数据点。这些异常数据可能表明错误、罕见事件或欺诈行为。本文将探讨一些最流行的异常检测方法,并通过一个在Kaggle上著名的信用卡欺诈数据集提供实用示例。数据集来自Kaggle。

加载数据集吧
df = pd.read_csv("creditcard.csv") # 读取信用卡数据集
一. 数据探索分析
1. 基本的探索性分析
    print("数据摘要如下:")  
    df.describe()  # 以下是对DataFrame的数据摘要统计
    print("数据列如下:")
    print(df.columns)

见解:

  • 数据集的形态:数据集包含284,807行和31列,表明样本量相当大,适合分析。
  • 缺失值:无缺失值,因为所有列均有284,807个非空条目。
  • 特征类型:主要为浮点数特征,类别是整数类型,作为目标变量。
  • 初步观察:根据统计数据,可能存在异常值(例如,金额特征可能存在异常值,因为标准差较大)。

检查班级分布

    # 检查目标变量 "Class" 的分布情况
    class_counts = df["Class"].value_counts()
    for c, count in enumerate(class_counts):
        print("Class " + str(c) + ": " + str(count))
    # 可视化类别分布图
    plt.figure(figsize=(2, 2))  # 设置图形大小为2x2
    sns.countplot(x='Class', data=df, palette='pastel')
    plt.title('类别分布 (0 = 无欺诈, 1 = 欺诈事件)')
    plt.xlabel('类别')
    plt.ylabel('个数')
    plt.show()

见解:

  • 类不平衡:分布显示类别0(非欺诈)的数量远远多于类别1(欺诈),表明存在严重的不平衡。
  • 不平衡比:理解这种不平衡非常重要,因为这可能会导致模型偏向于预测更多的非欺诈情况。
  • 对建模的影响:这种不平衡需要采取处理措施,例如使用SMOTE(合成少数类过采样技术)或采用专门的评估指标,如精确率、召回率和F1分数,而不仅仅是准确性这一单一指标。
3 时间特性分析
    # 直方图
    # 设置样式
    sns.set_style('whitegrid')
    # 创建直方图
    plt.figure(figsize=(12, 3))
    sns.histplot(df['Time'], bins=30, kde=True, color='blue')
    plt.title('交易时间的分布')
    plt.xlabel("时间(秒)")
    plt.ylabel("频率")
    plt.axvline(x=df['Time'].median(), color='red', linestyle='dashed', linewidth=2, label='中位数时间')
    plt.axvline(x=df['Time'].mean(), color='green', linestyle='dashed', linewidth=2, label='平均数时间')
    plt.legend()
    plt.show()
    # 时间变化的折线图
    # 创建折线图以分析随时间变化的欺诈概率
    plt.figure(figsize=(12, 3))
    time_fraud = df.groupby('时间')['Class'].mean().reset_index()
    plt.plot(time_fraud['时间'], time_fraud['Class'], color='blue')
    plt.title('随时间变化的欺诈概率')
    plt.xlabel("时间(秒)")
    plt.ylabel("欺诈概率")
    plt.grid()
    plt.show()

洞察:

  • 时间分布情况: 柱状图让我们看到交易在时间上的分布情况,这有助于识别高或低活动的时期。
  • 欺诈趋势分析: 折线图显示了欺诈概率较高的时段,表明欺诈风险可能在什么时候升高了。
检查特定时间段是否更容易发生欺诈
    # 将 'Time' 从秒转换为小时,以便更好地分析时间
    df['Hour'] = (df['Time'] // 3600) % 24

    # 计算每小时的平均欺诈率
    hour_fraud = df.groupby('Hour')['Class'].mean().reset_index()

    # 绘制按小时的平均欺诈概率条形图
    plt.figure(figsize=(12, 4))
    sns.barplot(data=hour_fraud, x='Hour', y='Class', palette='viridis')
    plt.title('每天按小时的平均欺诈概率')
    plt.xlabel("每天的小时")
    plt.ylabel("平均欺诈概率")
    plt.xticks(range(0, 24))
    plt.grid()
    plt.show()

分析欺诈中的异常激增
    # 创建一个滚动平均值来识别欺诈的激增  
    df['Fraud_Spike'] = df['Class'].rolling(window=1200).mean()  # 调整滚动窗口的大小  
    # 绘制出欺诈滚动平均随时间变化  
    plt.figure(figsize=(12, 2))  
    plt.plot(df['Time'], df['Fraud_Spike'], color='#d6604d', label='欺诈滚动平均')  
    plt.title('欺诈滚动平均随时间变化')  
    plt.xlabel('自首次交易以来的时间(秒)')  
    plt.ylabel('欺诈滚动平均')  
    plt.axhline(y=df['Class'].mean(), color='blue', linestyle='--', label='整体欺诈平均')  
    plt.legend()  
    plt.grid()  
    plt.show()

见解:

  • 一天中什么时间段更易发生欺诈行为,这有助于识别风险高峰时段。柱状图显示了哪些小时更可能有欺诈行为。
  • 异常峰值:滚动平均图可以指示欺诈活动显著上升的时期,从而促使更深入调查。
4. 金额分配情况
    # 创建箱线图以根据欺诈分类可视化金额分布  
    plt.figure(figsize=(2, 2))  
    sns.boxplot(x='Class', y='Amount', data=df, palette=['blue', 'orange'])  
    plt.title('基于欺诈分类的金额分布')  
    plt.xlabel('类别 (0 = 非欺诈, 1 = 欺诈)')  
    plt.ylabel('交易金额')  
    plt.yscale('log')  # 使用对数比例尺更好地显示异常值  
    plt.grid()  
    plt.show()  

    # 计算不同交易金额区间中的欺诈交易百分比  
    bins = [0, 50, 100, 200, 500, 1000, 5000, 10000, 50000]   
    labels = ['0-50', '51-100', '101-200', '201-500', '501-1000', '1001-5000', '5001-10000', '10001+']  
    df['Amount Range'] = pd.cut(df['Amount'], bins=bins, labels=labels, right=False)  

    # 计算每个交易金额区间中的欺诈交易数量  
    fraud_counts = df.groupby('Amount Range')['Class'].value_counts(normalize=True).unstack().fillna(0)  
    fraud_counts.columns = ['非欺诈', '欺诈']  
    fraud_counts = fraud_counts * 100  

    # 绘制每个交易金额区间中的欺诈交易百分比  
    plt.figure(figsize=(6, 2))  
    fraud_counts['欺诈'].plot(kind='bar', color='#d6604d', alpha=0.7, label='欺诈百分比')  
    plt.title('按交易金额区间划分的欺诈交易百分比')  
    plt.xlabel('交易金额区间')  
    plt.ylabel('欺诈百分比 (%)')  
    plt.xticks(rotation=45)  
    plt.grid()  
    plt.axhline(y=fraud_counts['欺诈'].mean(), color='blue', linestyle='--', label='平均欺诈百分比')  
    plt.legend()  
    plt.show()  
    # 计算欺诈和非欺诈交易的平均和中位金额  
    mean_amounts = df.groupby('Class')['Amount'].agg(['mean', 'median']).reset_index()  
    print("如下所示是各类别交易金额的平均和中位数值:")  
    print(mean_amounts)

见解:

  • 交易金额比较:箱线图使我们能够直观地比较欺诈和正常交易的中位数和四分位间距。
  • 交易金额频率:直方图显示不同交易金额出现频率,显示某些金额是否更频繁地与欺诈行为相关联。
  • 平均和中位数交易金额可以立即表明欺诈交易是否通常高于或低于正常交易。
  • 柱状图将显示不同交易金额区间内的欺诈百分比分布,有助于识别欺诈风险较高的特定金额区间。
5. 相关性分析
    # 计算相关矩阵  
    corr_matrix = df.drop(columns=['Amount Range']).corr()  
    # 绘制相关矩阵  
    plt.figure(figsize=(5, 5))  
    sns.heatmap(corr_matrix, annot=False, fmt=".2f", cmap='coolwarm', square=True, cbar_kws={"shrink": .8})  
    plt.title('特征相关热力图(排除\'金额范围\')')  
    plt.show()

6. PCA(主成分分析)
    # 主成分分析(PCA)的主成分列表(例如 V1 到 V28)
    pca_components = [f'V{i}' for i in range(1, 29)]

    # 绘制主成分的分布图
    设置图形大小为 (16, 8)
    for i, component in enumerate(pca_components, 1):
        在 7 行 4 列的网格中绘制第 i 个子图
        绘制 df[component] 的直方图,配置参数包括核密度估计为 True,区间数为 30,颜色为 #2f7ed8
        将 {component} 的分布设为标题
        不显示 X 轴标签
        不显示 Y 轴标签

    调整图像布局,避免重叠
    设置主标题为 'PCA 主成分分布',调整主标题的位置和大小
    plt.show()

PCA成分分析见解

  • 组件分布情况:每个PCA组件(V1到V28)显示出不同的分布模式,可能表明它们如何编码不同的交易方面。
  • 识别易受欺诈影响的组件:某些组件在欺诈交易与非欺诈交易的分布上可能存在显著差异。
  • 特征的重要性:这些组件在分析与欺诈的相关性时可以提供关于特征重要性的线索。
7. 异常值检测
    # 异常值检测:'Amount' 特征
    plt.figure(figsize=(4, 2))  
    sns.boxplot(x='Class', y='Amount', data=df, palette=['blue', 'orange'])  
    plt.title("交易金额(Amount)的异常值检测")  
    plt.xlabel("类别(0 = 非欺诈, 1 = 欺诈)")  
    plt.ylabel("交易金额")  
    plt.show()  

    # 异常值检测:主成分(V1 至 V28)
    plt.figure(figsize=(16, 10))  
    for i, component in enumerate(pca_components, 1):    
        plt.subplot(7, 4, i)  
        sns.boxplot(x='Class', y=component, data=df, palette=['blue', 'orange'])  
        plt.title(f"{component} 的类别异常值检测图")  
        plt.xlabel("类别(非欺诈,欺诈)")  
        plt.ylabel(component)  

    plt.tight_layout()  
    plt.suptitle('主成分(V1 至 V28)的类别异常值检测', y=1.02, fontsize=16)  
    plt.show()

离群值检测见解

  • 欺诈类(类别1,即Class 1):欺诈交易的PCA成分在大多数图中通常具有更窄的范围,并且较少的极端异常值,表明变化性较小。这可能表明欺诈交易在这类变换特征中具有更具体且一致的模式。
  • 非欺诈类(类别0,即Class 0):非欺诈交易的大多数成分表现出大量的异常值、更宽的范围和更高的变化性。这可能表明非欺诈交易在这些转换特征上更为多样。这些观察可以帮助我们通过关注某些PCA成分在欺诈和非欺诈交易中的不同行为来检测欺诈。

第八 偏度(偏斜程度)和峰度(尖峰程度)及分布

    # 计算所有数值特征的偏度和峰度
    数值特征偏度 = df.drop(columns=['Class', 'Amount Range']).apply(skew).sort_values(ascending=False)
    数值特征峰度 = df.drop(columns=['Class', 'Amount Range']).apply(kurtosis).sort_values(ascending=False)

    # 可视化偏度或峰度过高的特征的分布情况及其偏度和峰度
    高偏度或高峰度特征 = 数值特征偏度[数值特征偏度 > 0.5].index.union(数值特征峰度[数值特征峰度 > 3].index)

    plt.figure(figsize=(15, 12))
    for i, 特征 in enumerate(高偏度或高峰度特征, 1):
        plt.subplot(8, 4, i)
        sns.histplot(df[特征], kde=True, bins=30)
        plt.title(f"{特征}\n偏度 = {数值特征偏度[特征]:.2f}, 峰度 = {数值特征峰度[特征]:.2f}")
        plt.xlabel(特征)

    plt.tight_layout()  # 确保图像布局紧凑
    plt.suptitle('高偏度或高峰度特征的分布情况', y=1.02, fontsize=16)
    plt.show()

    # 对高度偏斜或峰度特征应用对数变换(对数不适用于负值)
    df[highly_skewed_or_kurt] = df[highly_skewed_or_kurt].apply(lambda x: np.log1p(x) if x.min() >= 0 else x)
    # 如果特征值中存在负数,则不执行对数变换,否则应用np.log1p(x)进行变换。

    # 绘制变换后的分布图
    plt.figure(figsize=(15, 12))
    for i, feature in enumerate(highly_skewed_or_kurt, 1):
        plt.subplot(8, 4, i)
        sns.histplot(df[feature], kde=True, bins=30)
        plt.title(f"特征 {feature} 的变换分布")
        plt.xlabel(feature)

    plt.tight_layout()
    plt.suptitle('高度偏斜或尖峰特征的变换分布图', y=1.02, fontsize=16)
    plt.show()

偏度和峰度的见解

  • 偏斜和峰度的特征:几个主成分显示出高偏斜或峰度,表明这些特征有不对称性和厚尾分布。
  • 极端离群值:具有高峰度的特征可能包含极端值,这些值可能会对模型性能产生影响。
  • 转换:对偏斜或峰度过高的特征进行对数变换可以帮助正态化其分布,减少模型对极端值的敏感性。
  • 增强模型的稳定性:通过解决偏斜和峰度问题,数据变得更加适合机器学习,从而提高模型的准确性和泛化能力。
9. 特征与类别的关联性
    # 选择与目标 'Class' 变量进行相关性分析的特征  
    features_to_analyze = ['Amount', 'Time'] + [f'V{i}' for i in range(1, 29)]  # V1 到 V28  

    # 设置按类别进行特征分析的图表  
    plt.figure(figsize=(15, 30))  
    for i, feature in enumerate(features_to_analyze, 1):  
        plt.subplot(10, 4, i)  
        sns.violinplot(x='Class', y=feature, data=df, palette=['#2f7ed8', '#d6604d'])  
        plt.title(f'{feature} 按类别的分布')  
        plt.xlabel('类别 (0 = 非欺诈, 1 = 欺诈)')  
        plt.ylabel(feature)  

    plt.tight_layout()  
    plt.suptitle("各类特征分布对比", y=1.02, fontsize=16)  
    plt.show()

关于特征与分类之间相关性的见解

  • 显著模式:几个主成分(例如 V4V10 等)显示出欺诈交易和非欺诈交易分布上的显著差异,表明可能具有预测能力。
  • 金额特征也表现出明显的模式,某些金额可能在欺诈案例中更为常见。
  • 高度相关性:具有可明显区分的类别特征可以作为欺诈检测模型中的强指示器。
  • 下一步:考虑将高度区分化的特征包含在最终模型中,因为可能对识别欺诈有显著贡献。
II. 数据预处理及清理

因为我们大部分的数据已经进行过标准化处理了,我们需要对尚未标准化的列进行标准化,比如Amount、Time、Hour、Fraud_Spike和Amount Range这些列。

    # 使用 Z-score 检测 'Amount' 异常值  
    z_scores = np.abs((df['Amount'] - df['Amount'].mean()) / df['Amount'].std())  
    异常值 = np.where(z_scores > 3)[0]  # 设定异常值阈值  
    print(f"'Amount' 中的异常值数量: {len(异常值)}")

外部层数:218

数据标准化

     从 sklearn.preprocessing 导入 RobustScaler  

    # 创建 RobustScaler 的一个实例  
    rob_scaler = RobustScaler()  

    # 对 'Time' 特征应用 RobustScaler 缩放  
    df['Time'] = rob_scaler.fit_transform(df['Time'].values.reshape(-1, 1))  
    df['Amount'] = rob_scaler.fit_transform(df['Amount'].values.reshape(-1, 1))  
    df['Hour'] = rob_scaler.fit_transform(df['Hour'].values.reshape(-1, 1))  

    # 删除用于分析部分添加的 'Amount Range' 和 'Fraud_Spike' 两列  
    df.drop(['Amount Range'], axis=1, inplace=True)  
    df.drop(['Fraud_Spike'], axis=1, inplace=True)

分割数据集

我们将采用分层K折交叉验证法来确保每折中的目标类(y)分布相似,这在处理不平衡数据集时尤为关键。

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

    sss = StratifiedKFold(n_splits=5, random_state=None, shuffle=False)  

    for train_index, test_index in sss.split(X, y):  
        原始训练集X, 原始测试集X = X.iloc[train_index], X.iloc[test_index]  
        原始训练集y, 原始测试集y = y.iloc[train_index], y.iloc[test_index]  

    # 检查标签类别的分布情况  

    # 转换为数组  
    原始训练集X = 原始训练集X.values  
    原始测试集X = 原始测试集X.values  
    原始训练集y = 原始训练集y.values  
    原始测试集y = 原始测试集y.values  

    # 比较训练集和测试集的类别分布  
    train_unique_label, train_counts_label = np.unique(原始训练集y, return_counts=True)  
    test_unique_label, test_counts_label = np.unique(原始测试集y, return_counts=True)  

    print('数据集中的标签分布情况:')  
    print('正常交易', round(df['Class'].value_counts()[0]/len(df) * 100,2), '百分比')  
    print('欺诈交易', round(df['Class'].value_counts()[1]/len(df) * 100,2), '百分比')  

    print('训练集中的标签分布情况:')  
    print("正常交易 " + str(round(train_counts_label[0]/ len(原始训练集y)*100, 2)) + " 百分比")  
    print("欺诈交易 " + str(round(train_counts_label[1]/ len(原始训练集y)*100, 2)) + " 百分比")  

    print('测试集中的标签分布情况:')  
    print("正常交易 " + str(round(test_counts_label[0]/ len(原始测试集y)*100, 2)) + " 百分比")  
    print("欺诈交易 " + str(round(test_counts_label[1]/ len(原始测试集y)*100, 2)) + " 百分比")

III.分类 (Classification)

为了做好分类并训练出高效的模型,我们正在使用一些关键的机器学习方法,如下:

SMOTE 方法

SMOTE(合成少数过采样技术)是一种用于解决机器学习中不平衡的样本分布问题的方法。不平衡的样本分布意味着一个类别(通常是“正”或“少数”类别)的样本数量显著少于另一个类别(“负”或“多数”类别)。这种不平衡会导致机器学习模型在训练过程中更倾向于多数类别,从而在少数类别上的表现较差。

SMOTE 通过生成少数类的合成样本以填补不足来平衡数据,而不是简单地复制现有的样本。简单来说,以下是 SMOTE 生成这些合成样本的概述:

  1. 识别少数类样本:SMOTE 首先识别少数类的样本。
  2. 选择邻居:对于每个少数类样本,它在同类中找到几个最近邻的样本。
  3. 生成合成样本:利用这些最近邻样本,SMOTE 通过插值原始样本与其一个邻居之间的距离来创建新样本。这意味着它生成了一个位于原始样本与邻居之间的线段上的新点,从而增加了少数类的样本多样性。
随机搜索CV

RandomizedSearchCV 是一种在机器学习中使用的超参数优化技术。与 GridSearchCV 不同,后者会评估所有可能的超参数组合,RandomizedSearchCV 从超参数空间中随机采样固定数量的组合。在处理较大的超参数空间时,这种方法可以更高效,因为它探索了广泛的可能值,而无需对每个选项进行详尽的测试。这有助于在减少计算时间的情况下找到最佳模型参数,特别是当搜索空间很大时。

交叉验证(Cross Validation)

交叉验证 是一种用于评估机器学习模型性能和泛化性的技术。它通过将数据集分割成多个子集,或称为“折”(即分组),来确保模型在不同的数据上进行训练和验证。

  1. 将数据集分成kk份(通常为5或10份)。
  2. 对于每一份数据:使用k−1份来训练模型。使用剩余的一份来验证或测试
  3. 重复以上步骤,确保每个数据点都被用于训练和验证。
  4. 模型的整体性能是所有折的平均,这提供了模型在未见过的数据上的更可靠性能估计。
from xgboost import XGBClassifier  
from sklearn.model_selection import RandomizedSearchCV  
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score  
from imblearn.pipeline import make_pipeline as imbalanced_make_pipeline  
from imblearn.over_sampling import SMOTE  

# 存储分数的列表,以便计算平均值  
accuracy_lst = []  
precision_lst = []  
recall_lst = []  
f1_lst = []  
auc_lst = []  

# 定义XGBoost的超参数,提供更多选项  
xgb_params = {  
    'n_estimators': [50, 100, 200, 300, 500],               # 模型中的树的数量  
    'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.2, 0.3],   # 学习率,影响学习速度  
    'max_depth': [3, 5, 7, 10, 15],                         # 树的最大深度,更大的深度意味着更高的复杂度  
    'scale_pos_weight': [1, len(original_ytrain[original_ytrain == 0]) / len(original_ytrain[original_ytrain == 1])],  # 少数类别的权重,原始标签中类别0的数量/原始标签中类别1的数量  
    'colsample_bytree': [0.3, 0.5, 0.7, 1],                 # 每棵树采样的列的比例  
    'subsample': [0.5, 0.6, 0.8, 1],                        # 每棵树的子采样比例  
    'min_child_weight': [1, 3, 5, 10],                      # 终端节点上观察值的最小权重  
    'gamma': [0, 0.1, 0.3, 0.5, 1],                         # 节点的正则化复杂度,用于避免过拟合  
    'reg_alpha': [0, 0.1, 0.5, 1, 5, 10],                   # L1正则化(Lasso),鼓励特征选择  
    'reg_lambda': [1, 1.5, 2, 5, 10],                       # L2正则化(Ridge),控制过拟合  
    'max_delta_step': [0, 1, 3, 5, 10]                      # 用于处理类别不平衡时的稳定性  
}  

# 创建RandomizedSearchCV实例  
rand_xgb = RandomizedSearchCV(  
    XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42),   
    xgb_params,   
    n_iter=20,  # 增加迭代次数以进行更彻底的搜索  
    scoring='f1',  # 可选,用于最大化F1得分,特别适用于不平衡类别  
    cv=5,         # 交叉验证的折叠数  
    random_state=42  
)  

# 使用SMOTE和XGBoost进行交叉验证  
for train, test in sss.split(original_Xtrain, original_ytrain):  
    # SMOTE + XGBoost管道  
    pipeline = imbalanced_make_pipeline(SMOTE(sampling_strategy='minority'), rand_xgb)  
    model = pipeline.fit(original_Xtrain[train], original_ytrain[train])  
    best_est = rand_xgb.best_estimator_  # 找到RandomizedSearchCV后的最佳估计器  
    prediction = best_est.predict(original_Xtrain[test])  # 对测试集进行预测  

    # 添加得分  
    accuracy_lst.append(pipeline.score(original_Xtrain[test], original_ytrain[test]))  
    precision_lst.append(precision_score(original_ytrain[test], prediction))  
    recall_lst.append(recall_score(original_ytrain[test], prediction))  
    f1_lst.append(f1_score(original_ytrain[test], prediction))  
    auc_lst.append(roc_auc_score(original_ytrain[test], prediction))  

# 显示平均得分  
print('---' * 45)  
print("Accuracy: {}".format(np.mean(accuracy_lst)))  
print("Precision: {}".format(np.mean(precision_lst)))  
print("Recall: {}".format(np.mean(recall_lst)))  
print("F1: {}".format(np.mean(f1_lst)))  
print("AUC: {}".format(np.mean(auc_lst)))  
print('---' * 45)

在原始测试数据集上的应用表现:

    首先,从 sklearn.metrics 导入 classification_report  

    labels = ['正常', '欺诈']  
    # 接下来,我们使用最佳模型对测试数据进行预测。
    smote_prediction = best_est.predict(original_Xtest)  
    # 最后,打印分类报告,使用原始测试标签和预测标签,目标名称是 labels。
    print(classification_report(original_ytest, smote_prediction, target_names=labels))

从通俗易懂说起 🚀

感谢你加入In Plain English社区!在你离开之前,还有:

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消