信用卡欺诈预测——多模型处理不平衡数据的探索

0 理解数据集

本项目的数据来源自Kaggle的开源Credit Card Fraud Detection数据集

拿到数据集后,首先对数据内容进行理解。在现实生活中,用户往往会因丢失信用卡等原因导致自己的信用卡被盗刷,如果信用卡公司能够根据刷卡时的信息识别是否为盗刷行为,那么这样会帮助用户及时止损,保障用户的资金安全。

根据数据集说明可知该数据集包含了2013年9月欧洲持卡人的信用卡交易。这个数据集展示了两天内发生的交易,在284,807次交易中有492次欺诈。数据集高度不平衡,正类(欺诈)占所有交易的0.172%。并且出于保密的考虑,数据集的特征 ( V 1 、 V 2 、 V 3 、 . . . 、 V 28 ) (V1、V2、V3、...、V28) (V1V2V3...V28)都是经过PCA变换的结果,高度抽象不具备实际意义。没有经过PCA变换的特征是 T i m e Time Time A m o u n t Amount Amount T i m e Time Time是刷卡的时间(以秒为单位,时间基准从记录的第一天零时零分零秒算起), A m o u n t Amount Amount是每次刷卡时消费的金额。 C l a s s Class Class为响应变量,欺诈行为时取值1,否则取值0。

因此,可以看到,建立信用卡欺诈预测模型实际上是建立一个二分类模型。整个模型的建立过程下流程图所示。

Created with Raphaël 2.2.0 数据探索及预处理 特征工程 模型训练与调参 模型评估 结论与思考

1 数据探索及预处理

  • 首先通过pandas导入数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib as mpl
from matplotlib.font_manager import FontProperties
%matplotlib inline

mpl.rcParams.update({
    'font.family': 'sans-serif',
    'font.sans-serif': ['Times New Roman'],
    "font.size": 15
    })  # 设置全局字体

# 定义自定义字体,文件名从1.b查看系统中文字体中来,主要是为了显示中文,可忽略
myfont = FontProperties(fname='E:/anaconda3/Lib/site-packages/matplotlib/mpl-data/fonts/ttf/SimSun.ttf')
# 解决负号'-'显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
# plt.rcParams['savefig.dpi'] = 600
# plt.rcParams['figure.dpi'] = 600

data = pd.read_csv("creditcard.csv")
data.tail()
  • 之后对数据进行简单的查看,了解数据量,数据类型,数值大小等等
data.info()
data.describe()

所有特征均为连续性的数值特征,数据量大约是28w多,发现 T i m e Time Time A m o u n t Amount Amount两列数据数值变化特别大,后续需要对其进行标准化操作。

  • 开始数据清洗的工作,先检查数据是否有缺失值
data.isna().sum()

结果如下:

Time      0
V1        0
V2        0
V3        0
V4        0
V5        0
V6        0
V7        0
V8        0
V9        0
V10       0
V11       0
V12       0
V13       0
V14       0
V15       0
V16       0
V17       0
V18       0
V19       0
V20       0
V21       0
V22       0
V23       0
V24       0
V25       0
V26       0
V27       0
V28       0
Amount    0
Class     0
dtype: int64

没有缺失值,很好。

  • 接下来检查重复值
data.duplicated().value_counts()

返回结果为:

False    283726
True       1081
dtype: int64

重复值大约有1000多个,接下来除去重复值。

data.drop_duplicates(inplace=True)
data.duplicated().value_counts()
  • 数据大概清洗好了。接下来分析一下数据,先看一下数据的不均衡程度
count_classes = data['Class'].value_counts()
print(count_classes)
plt.figure(figsize=(8, 6))
count_classes.plot(kind="bar")
plt.title('Fraud class histogram')
plt.xlabel('Class')
plt.ylabel('Frequency')
sns.despine()


可以看到数据的不均衡非常严重,后续需要进行一些操作减少不均衡的影响。

  • 通过箱线图看看正常行为与欺诈行为的特征分布情况
plt.figure(figsize=(20, 20))
for i in range(1, 29):
    plt.subplot(7, 4, i)
    sns.boxplot(y="V{}".format(str(i)), x='Class', data=data)
    
plt.tight_layout()
plt.savefig("001.png", transparent=False, dpi=800)

在这里插入图片描述
可以看到例如 V 11 V11 V11 V 12 V12 V12等特征的分布还是有明显差异的,但例如 V 13 V13 V13, V 24 V24 V24等特征的分布差异也并不怎么明显。同时,欺诈行为特征分布有很多“异常值”,方差很大。在之后特征工程选择特征的时候,特征分布的差异也会是一个重要的考量。

  • 时间和金额是有物理意义的两个特征,接下来看看他们的分布情况
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
data[data.Class==1].Amount.hist(bins=50, alpha=0.5, label='Fraud')
data[data.Class==0].Amount.hist(bins=50, alpha=0.5, label='Normal')
plt.title('Amount')
plt.xlabel('Amount')
plt.ylabel('Counts')
plt.yscale('log')
plt.legend(frameon=False)
plt.grid(False)

plt.subplot(2, 1, 2)
sns.distplot(data[data['Class']==1]['Time'],bins=50, label='Fraud')
sns.distplot(data[data['Class']==0]['Time'],bins=100, label='Normal')
plt.ylabel('Frequency')
plt.legend(frameon=False)
sns.despine()
plt.tight_layout()
plt.savefig("003.png", transparent=False, dpi=800)

在这里插入图片描述
从消费金额上来看,欺诈行为盗刷的金额往往不都大,这可能是因为信用卡大额度会有更多复杂的验证,带来更多的风险,而小额度金额往往没什么风险性。
从时间上来看,正常的刷卡次数在凌晨会比较少,而欺诈行为在一天24小时内刷卡次数都比较均匀一些。从整体上来看,正常行为和欺诈行为频率在时间上分布差不太多。

2 特征工程

  • 接下来看看各个特征与标签 C l a s s Class Class之间的相关性
data.corr().Class.sort_values(ascending = False)

结果如下:

Class     1.000000
V11       0.154876
V4        0.133447
V2        0.091289
V21       0.040413
V19       0.034783
V20       0.020090
V8        0.019875
V27       0.017580
V28       0.009536
Amount    0.005632
V26       0.004455
V25       0.003308
V22       0.000805
V23      -0.002685
V15      -0.004223
V13      -0.004570
V24      -0.007221
Time     -0.012323
V6       -0.043643
V5       -0.094974
V9       -0.097733
V1       -0.101347
V18      -0.111485
V7       -0.187257
V3       -0.192961
V16      -0.196539
V10      -0.216883
V12      -0.260593
V14      -0.302544
V17      -0.326481
Name: Class, dtype: float64

从结果看到大部分特征与标签之间的相关性都比较低,没有高于0.5的。

  • 选取相关性最大的三个特征 V 12 V12 V12, V 14 V14 V14, V 17 V17 V17进行可视化看一下
simple_data = data[['V12', 'V14', 'V17', 'Class']]
simple_data0 = data[data.Class==0]
simple_data1 = data[data.Class==1]
plt.figure(figsize=(16, 9))
ax = plt.subplot(111, projection='3d')  # 创建三维绘图工程
ax.scatter(simple_data0.iloc[:, 0], simple_data0.iloc[:, 1], simple_data0.iloc[:, 2], s=2, c='r', label='0')
ax.scatter(simple_data1.iloc[:, 0], simple_data1.iloc[:, 1], simple_data1.iloc[:, 2], s=2, c='b', label='1')
plt.title('Feature visualization', fontsize=20)
plt.tick_params(labelsize=10)  # 刻度字体大小10
ax.set_ylabel('Y', fontsize=15)
ax.set_xlabel('X', fontsize=15)
ax.set_zlabel('Z', fontsize=15)  # 坐标轴
plt.axis('off')
ax.w_xaxis.set_pane_color((1.0, 0.0, 0.0, 0.0))
ax.legend(loc='best',fontsize=15)

elev = 75
azim = -70
ax.view_init(elev, azim)
plt.savefig("002.png", transparent=False, dpi=800)

在这里插入图片描述
可以看到,由于相关性都比较低,欺诈行为和正常行为在空间中也并没有分开,没有一簇一簇的聚类效果。

  • 接下来要对特征进行筛选,并对 A m o u n t Amount Amount进行标准化

基于相关性小于0.1和正常行为和欺诈行为特征分布高度相似这两点综合考虑,除去特征 V 13 V13 V13, V 15 V15 V15, V 22 V22 V22, T i m e Time Time四个特征。

data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1, 1))
data = data.drop(['V13', 'V15', 'V22', 'Time', 'Amount'], axis=1)
data.index = range(len(data))

3 模型训练及调参

  • 首先将处理好的数据特征和标签分开
# 将特征和标签分开
X = data.loc[:, data.columns!='Class']
y = data.loc[:, data.columns=='Class']

针对于数据不均衡的问题,可以考虑通过下采样或者过采样来解决,本文会比较下采样和过采样两种处理方式的不同。

下采样就是从数据中的多数类中随机抽出少数类样本数目的样本,这样两类数据都一样少了。
过采样少数类自身生成多数类样本数目的样本, 这样两类数据都一样多了。

3.1 下采样方式

  • 先考虑下采样方式,构造下采样数据集
# 将特征和标签分开
X = data.loc[:, data.columns!='Class']
y = data.loc[:, data.columns=='Class']

# 进行下采样操作
number_records_fraud = len(data[data.Class==1])
fraund_indices = np.array(data[data.Class==1].index)                       
normal_indices = np.array(data[data.Class==0].index)
random_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace=False)
under_sample_indices = np.concatenate([fraund_indices, random_normal_indices])

# 构造下采样的数据集
under_sample_data = data.iloc[under_sample_indices]
X_under_sample_data = under_sample_data.loc[:, under_sample_data.columns!='Class']
y_under_sample_data = under_sample_data.loc[:, under_sample_data.columns=='Class']
y_under_sample_data.Class.value_counts()
1    473
0    473
Name: Class, dtype: int64

可以看到样本数量已经均衡了

  • 接下来对整个数据集、下采样数据集划分训练集和测试集
from sklearn.model_selection import train_test_split

# 划分整个数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=0)

# 划分下采样数据集
X_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split(X_under_sample_data, 
                                                                                                    y_under_sample_data, train_size=0.7, 
                                                                                                    random_state=0)

针对于分类模型,通常用分类准确率来对模型效果进行描述。在本项目中,可能会出现分类准确率很高,但把很多非欺诈的行为预测为欺诈行为的,也有可能出现漏掉很多欺诈行为,即将欺诈行为判定为非欺诈行为。为了综合考虑以上因素的影响,引入召回率 R e c a l l Recall Recall和查准率 P r e c i s i o n Precision Precision。并且在实际中,我们往往在乎有没有漏过欺诈行为,一旦漏过欺诈行为,损失就已经造成,没有办法挽回了。所以模型评估时重点参考召回率。召回率和查准率的计算公式如下:

类别预测正类预测负类
实际正类 T P TP TP F N FN FN
实际负类 F P FP FP T N TN TN

R e c a l l = T P T P + F N Recall = \frac {TP}{TP+FN} Recall=TP+FNTP
P r e c i s i o n = T P T P + F P Precision = \frac {TP}{TP+FP} Precision=TP+FPTP

  • 导入相关的工具包
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import KFold
from sklearn.metrics import confusion_matrix, recall_score, precision_score, accuracy_score

接下来分别建立三种模型,逻辑回归模型,决策树模型、AdaBoost模型进行训练。在训练过程中进行10折交叉验证,选出最优的惩罚项参数C。

  • 逻辑回归模型

逻辑回归虽然被称为回归,但其实际上是非常常用的二分类模型。其尝试找到一条线性(非线性)决策边界将不同类别划分开来。
逻辑回归模型为:
h θ ( x ) = 1 1 + e − θ T X {{h}_{\theta }}\left( x \right)=\frac{1}{1+{{e}^{-{{\theta }^{T}}X}}} hθ(x)=1+eθTX1

def LogisticRegression_Kfold(x_train_data, y_train_data):
    kf = KFold(10, shuffle=False)
    
    # different C parameters
    C_param_range = [0.01, 0.1, 1, 3, 5]
    
    result_table = pd.DataFrame(columns=['C_parameter', 'Mean recall score', 'Mean precision score', 'Mean accuracy score', 'Method'])
    result_table['C_parameter'] = C_param_range
    j = 0
    for C_param in C_param_range:
        print('----------------------------------------------')
        print('C parameter:', C_param)
        print('----------------------------------------------')
        print()
        
        recall_accs = []
        precision_accs = []
        accracy_accs = []
        # kf为一个可迭代对象,迭代返回一个list, train_indices = indices[0], test_indeces=indices[1]
        for iteration, indices in enumerate(kf.split(x_train_data), start=1):
            
            # 逻辑回归模型
            clf = LogisticRegression(C=C_param)
            clf.fit(x_train_data.iloc[indices[0], :].values, y_train_data.iloc[indices[0], :].values.ravel())
            
            # 预测
            y_pre_undersample = clf.predict(x_train_data.iloc[indices[1], :].values)
            
            # 计算各项评估指标
            recall_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
            recall_accs.append(recall_acc)
            precision_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
            precision_accs.append(precision_acc)
            accracy_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
            accracy_accs.append(accracy_acc)
            print('Iteration', iteration, ': recall score=', recall_acc, '| precision score=', precision_acc,
                  '| accuracy score=', accracy_acc)
            
    #  计算各项指标的均值
        result_table.iloc[j, 1] = np.mean(recall_accs)
        result_table.iloc[j, 2] = np.mean(precision_accs)
        result_table.iloc[j, 3] = np.mean(accracy_accs)
        result_table.iloc[j, 4] = 'LogisticRegression'
        j += 1
        print()
        print('Mean recall score', np.mean(recall_accs), '| Mean precision score=', np.mean(precision_accs), 
              '| Mean accuracy score=', np.mean(accracy_accs))
        print()

    best_C_lr = result_table.iloc[result_table['Mean recall score'].astype("float").idxmax(), 0]
    
    print('*******************************************************************')
    print('best LogisticRegression model to choose from cross validation is with C parameter = ', best_C_lr)
    print('*******************************************************************')
    return result_table, best_C_lr
  • AdaBoost模型(基学习器为SVM)

AdaBoost是英文"Adaptive Boosting"(自适应增强)的缩写,由Yoav Freund和Robert Schapire在1995年提出。它的自适应在于:前一个基本分类器分错的样本会得到加强,加权后的全体样本再次被用来训练下一个基本分类器。

在这里插入图片描述

def AdaBoost_Kfold(x_train_data, y_train_data):
    kf = KFold(10, shuffle=False)
    
    # different C parameters
    C_param_range = [0.01, 0.1, 1, 3, 5]
    
    result_table = pd.DataFrame(columns=['C_parameter', 'Mean recall score', 'Mean precision score', 'Mean accuracy score', 'Method'])
    result_table['C_parameter'] = C_param_range
    j = 0
    for C_param in C_param_range:
        print('----------------------------------------------')
        print('C parameter:', C_param)
        print('----------------------------------------------')
        print()
        
        recall_accs = []
        precision_accs = []
        accracy_accs = []
        # kf为一个可迭代对象,迭代返回一个list, train_indices = indices[0], test_indeces=indices[1]
        for iteration, indices in enumerate(kf.split(x_train_data), start=1):
            
            # AdaBoost模型
            clf = AdaBoostClassifier(base_estimator=SVC(C=C_param), n_estimators=100, algorithm='SAMME')
            clf.fit(x_train_data.iloc[indices[0], :].values, y_train_data.iloc[indices[0], :].values.ravel())
            
            # 预测
            y_pre_undersample = clf.predict(x_train_data.iloc[indices[1], :].values)
            
            # 计算各项评估指标
            recall_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
            recall_accs.append(recall_acc)
            precision_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
            precision_accs.append(precision_acc)
            accracy_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
            accracy_accs.append(accracy_acc)
            print('Iteration', iteration, ': recall score=', recall_acc, '| precision score=', precision_acc,
                  '| accuracy score=', accracy_acc)
            
    #  计算各项指标的均值
        result_table.iloc[j, 1] = np.mean(recall_accs)
        result_table.iloc[j, 2] = np.mean(precision_accs)
        result_table.iloc[j, 3] = np.mean(accracy_accs)
        result_table.iloc[j, 4] = 'LogisticRegression'
        j += 1
        print()
        print('Mean recall score', np.mean(recall_accs), '| Mean precision score=', np.mean(precision_accs), 
              '| Mean accuracy score=', np.mean(accracy_accs))
        print()

    best_C_ab = result_table.iloc[result_table['Mean recall score'].astype("float").idxmax(), 0]
    
    print('*******************************************************************')
    print('best LogisticRegression model to choose from cross validation is with C parameter = ', best_C_lr)
    print('*******************************************************************')
    return result_table, best_C_ab
  • 随机森林

随机森林是一个包含多个决策树的集成分类器,不同于boosting的串行形式,随机森林属于并行的bagging集成方法。
在这里插入图片描述

def RandomForest_Kfold(x_train_data, y_train_data):
    kf = KFold(10, shuffle=False)
        
    result_table = pd.DataFrame(index=range(1), columns=['C_parameter', 'Mean recall score', 'Mean precision score', 'Mean accuracy score', 'Method'])
    recall_accs = []
    precision_accs = []
    accracy_accs = []
    # kf为一个可迭代对象,迭代返回一个list, train_indices = indices[0], test_indeces=indices[1]
    for iteration, indices in enumerate(kf.split(x_train_data), start=1):

        # 逻辑回归模型
        clf = RandomForestClassifier()
        clf.fit(x_train_data.iloc[indices[0], :].values, y_train_data.iloc[indices[0], :].values.ravel())

        # 预测
        y_pre_undersample = clf.predict(x_train_data.iloc[indices[1], :].values)

        # 计算各项评估指标
        recall_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
        recall_accs.append(recall_acc)
        precision_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
        precision_accs.append(precision_acc)
        accracy_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pre_undersample)
        accracy_accs.append(accracy_acc)
        print('Iteration', iteration, ': recall score=', recall_acc, '| precision score=', precision_acc,
              '| accuracy score=', accracy_acc)

#  计算各项指标的均值
    result_table.iloc[0, 1] = np.mean(recall_accs)
    result_table.iloc[0, 2] = np.mean(precision_accs)
    result_table.iloc[0, 3] = np.mean(accracy_accs)
    result_table.iloc[0, 4] = 'RandomForest'

    print()
    print('Mean recall score', np.mean(recall_accs), '| Mean precision score=', np.mean(precision_accs), 
          '| Mean accuracy score=', np.mean(accracy_accs))
    print()

    best_C_rf = result_table.iloc[result_table['Mean recall score'].astype("float").idxmax(), 0]

    return result_table, best_C_rf
result_table_lr, best_C_lr = LogisticRegression_Kfold(X_train_undersample, y_train_undersample)
result_table_rf, best_C_rf = RandomForest_Kfold(X_train_undersample, y_train_undersample)
result_table_ab, best_C_rf = AdaBoost_Kfold(X_train_undersample, y_train_undersample)

看一下三个模型的最终的训练效果,如下所示:

C_parameterMean recall scoreMean precision scoreMean accuracy scoreMethod
0.010.8755350.8755350.875535LogisticRegression
0.100.8839390.8839390.883939LogisticRegression
1.000.8894200.8894200.889420LogisticRegression
3.000.8894200.8894200.889420LogisticRegression
5.000.8894200.8894200.889420LogisticRegression
NaN0.9010420.9010420.901042RandomForest
0.010.30.30.3AdaBoost
0.100.3804880.3804880.380488AdaBoost
1.000.30.30.3AdaBoost
3.000.8941950.8941950.894195AdaBoost
5.000.8471270.8471270.847127AdaBoost

可以看到参数 C C C对AdaBoost算法影响最大,逻辑回归比较稳定,效果最好的是随机森林。接下来按照逻辑回归和随机森林继续分析。

  • 先画一下逻辑回归的混淆矩阵
def cal_cnf_matrix(test_data, pred_data):
    
    cnf_matrix = confusion_matrix(test_data, pred_data, labels=[0, 1])
    print("测试集的召回率为: ", cnf_matrix[1, 1]/(cnf_matrix[1, 1]+cnf_matrix[1, 0]))
    plt.figure()
    sns.heatmap(cnf_matrix,annot=True, fmt='.1f', cmap='YlGnBu')  # 画热力图
    plt.title('Confusion matrix')
    plt.xlabel('predicted labels')  # x轴
    plt.ylabel('true labels')  # y轴
    return cnf_matrix
import seaborn as sns
clf = LogisticRegression(C=best_C_lr)
clf.fit(X_train_undersample.values, y_train_undersample.values.ravel())
y_pred_undersample = clf.predict(X_test_undersample.values)

# 计算混淆矩阵
cal_cnf_matrix(y_test_undersample, y_pred_undersample)
plt.savefig("004.png", dpi=800)

逻辑回归的下采样测试集的召回率为: 0.952054794520548。如下图:
在这里插入图片描述
逻辑回归的测试集的召回率为: 0.9069767441860465。如下图:
在这里插入图片描述

可以看到逻辑回归模型在测试集上的召回率还是很不错的,但是其对太多正常行为进行了“误杀”,查准率太低。

  • 接下来调整一下逻辑回归的阈值,来对混淆矩阵进行观察
clf = LogisticRegression(C=best_C_lr)
clf.fit(X_train_undersample.values, y_train_undersample.values.ravel())
y_pred_undersample_proba = clf.predict_proba(X_test_undersample.values)

thresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

plt.figure(figsize=(10, 10))
j=1
for i in thresholds:
    y_test_predictions_high_recall = y_pred_undersample_proba[:, 1] > i
    
    plt.subplot(3, 3, j)
    j += 1
    
    # 计算混淆矩阵
    cnf_matrix = confusion_matrix(y_test_undersample, y_test_predictions_high_recall, labels=[0, 1])

    print("测试集召回率为:", cnf_matrix[1, 1]/(cnf_matrix[1, 1]+cnf_matrix[1, 0]))

    sns.heatmap(cnf_matrix,annot=True, fmt='.1f', cmap='rainbow')  # 画热力图
    plt.title('Confusion matrix threshold = {}'.format(i))
    plt.xlabel('predicted labels')  # x轴
    plt.ylabel('true labels')  # y轴
plt.tight_layout()
plt.show()
测试集召回率为: 0.9657534246575342
测试集召回率为: 0.958904109589041
测试集召回率为: 0.9452054794520548
测试集召回率为: 0.9383561643835616
测试集召回率为: 0.9315068493150684
测试集召回率为: 0.9178082191780822
测试集召回率为: 0.9041095890410958
测试集召回率为: 0.863013698630137
测试集召回率为: 0.8424657534246576

在这里插入图片描述
随着阈值的增大可以看到逻辑回归模型的“误杀”的少了,但是召回率降下去了,果然鱼与熊掌不可兼得。

  • 画一下随机森林的混淆矩阵
clf = RandomForestClassifier()
clf.fit(X_train_undersample.values, y_train_undersample.values.ravel())
y_pred = clf.predict(X_test.values)

# 计算混淆矩阵
cal_cnf_matrix(y_test, y_pred)

随机森林的测试集的召回率为: 0.9767441860465116。如下图所示:
在这里插入图片描述
随机森林模型在测试集上的表现更加极端,召回率非常高,查准率更低了。

3.2 过采样方法

SMOTE算法的思想是合成新的少数类样本,合成的策略是对每个少数类样本 a a a,从它的最近邻中随机选一个样本 b b b,然后在 a a a b b b之间的连线上随机选一点作为新合成的少数类样本 c c c
在这里插入图片描述
如上图所示,则新样本 c c c为:
c = a + r a n d ( 0 , 1 ) ⋅ ∥ a − b ∥ 2 c=a+rand(0,1) \cdot \left \| a-b \right \|_2 c=a+rand(0,1)ab2

  • 首先通过SMOTE算法进行数据生成
from imblearn.over_sampling import SMOTE 
features = data.copy().drop(['Class'], axis=1)
labels = data.copy()['Class']
feature_train, feature_test, labels_train, labels_test = train_test_split(features, labels, test_size=0.2, random_state=0)
feature_train, feature_test, labels_train, labels_test = train_test_split(features, labels, test_size=0.2, random_state=0)
os_labels.value_counts()
os_labels = pd.DataFrame(os_labels, columns=['Class'])
  • 利用逻辑回归计算混淆矩阵
result_1, best_c = LogisticRegression_Kfold(os_features, os_labels)
clf = LogisticRegression(C=best_c)
clf.fit(os_features.values, os_labels.values.ravel())
y_pred = clf.predict(feature_test.values)

# 计算混淆矩阵
cal_cnf_matrix(labels_test, y_pred)
plt.savefig("007.png", dpi=800)

测试集的召回率为: 0.9310344827586207,如下图所示:
在这里插入图片描述
相较于下采样的逻辑回归模型,查准率稍有改善

  • 利用随机森林计算混淆矩阵
result_2, best_c1 = RandomForest_Kfold(os_features, os_labels)
clf = RandomForestClassifier()
clf.fit(os_features.values, os_labels.values.ravel())
y_pred = clf.predict(feature_test.values)

# 计算混淆矩阵
cal_cnf_matrix(labels_test, y_pred)

测试集的召回率为: 0.8275862068965517
在这里插入图片描述
相较于下采样的随机森林模型,查准率大大改善了,但同时召回率也变低了
为了找到查准率度和召回率的最佳组合,在此引入 F 1   s c o r e F1 \ score F1 score 对精度和召回率的调和平均。
F 1 = 2 R e c a l l ⋅ P r e c i s i o n R e c a l l + P r e c i s i o n F_1 = 2 \frac {Recall \cdot Precision}{Recall + Precision} F1=2Recall+PrecisionRecallPrecision

可以得到结果为:

模型采样方式 F 1 F_1 F1
Logistic Regression下采样0.0811
Random Forest下采样0.0445
Logistic Regression过采样0.0925
Random Forest过采样0.8304

4 结论与思考

  1. 出于保密的原因,源数据集的特征都是经过PCA转换的抽象特征,没有具体的业务意义,特征的提取、筛选、转换受到了一些限制。在现实情况中,消费场所、消费渠道等离散特征往往与标签会有更好的相关性,从中提取特征会帮助模型训练、预测的更好。
  2. 一般来讲,除了分类准确率外,常用到的模型评价指标有召回率和精准率。不同的场景下对召回率和精准率的要求各不相同。比如在本项目场景下,我们往往在乎有没有漏过欺诈行为,因为一旦漏过欺诈行为,损失就已经造成,没有办法挽回了。所以模型评价重点参考召回率。但是召回率和查准率往往不可兼得,非常高的召回率会导致查准率不高,这会导致很多正常用户在刷卡时本来的正常行为识别有可能会预测为欺诈行为,继而需要操作更多的验证、解冻等环节,这会非常影响用户对信用卡公司好感度,所以查准率太低也不好。很多场景都需要将召回率和查准率并重考虑。
  3. 当数据不均匀的时候,应根据具体情况选择下采样或者过采样方法来调整数据比例。从 F 1   s c o r e F1 \ score F1 score上来看,过采样相较于下采样是更好的处理不均衡数据的方式。集成模型在过采样方式下也会发挥更大的作用。但在不同的业务场景中,如果召回率相较于精准率是更重要的指标,对于采样方式以及模型的选择还需做出更多尝试。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值