欺诈检测

一、任务基础

数据集包含由欧洲人于2013年9月使用信用卡进行交易的数据。此数据集显示两天内发生的交易,其中284807笔交易中有492笔被盗刷。数据集非常不平衡,正例(被盗刷)占所有交易的0.172%。,这是因为由于保密问题,我们无法提供有关数据的原始功能和更多背景信息。特征V1,V2,... V28是使用PCA获得的主要组件,没有用PCA转换的唯一特征是“Class”和“Amount”。特征'Time'包含数据集中每个刷卡时间和第一次刷卡时间之间经过的秒数。特征'Class'是响应变量,如果发生被盗刷,则取值1,否则为0。

任务目的是完成数据集中正常交易数据和异常交易数据的分类,并对测试数据进行预测。

首先导入需要使用的库

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

读取数据集文件,查看数据集前5行数据

data= pd.read_csv("creditcard.csv")
data.head()

1126989-20190717105010734-704898472.png

在上图中Class标签代表数据分类,0代表正常数据,1代表欺诈数据。

这里是做信用卡数据的欺诈检测。在整个数据里面,有正常的数据,也有问题的数据。对于一般情况来说,有问题的数据肯定只占了极少部分。

下面绘出柱状图可以直观显示正常数据与异常数据的数量差异。

count_classes= pd.value_counts(data['Class'], sort=True).sort_index()
count_classes.plot(kind='bar')# 使用pandas可以绘制一些简单的图
# 欺诈类别柱状图
plt.title("Fraud class histogram")
plt.xlabel("Class")
# 频率
plt.ylabel("Frequency")

1126989-20190717110306597-509405628.png

从输出的结果可以看出正常的样本0大概有28万个,异常的样本1非常少,从图中不太容易看出来,但是实际上是存在的,大概只有那么几百个。

因为Amount这列的数据浮动太大,在做机器学习的过程中,需要保证特征值差异不能过大,于是需要对Amount进行预处理,标准化数据。

Time这一列本身没有多大用处,Amount这一列被标准化后的数据代替。所有删除这两列的数据。

# 预处理  标准化数据
from sklearn.preprocessingimport StandardScaler
# norm 标准  -1表示自动判断X维度  对比源码 这里要加上.values<br># 加上新的特征列
data['normAmount']= StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))
data= data.drop(['Time','Amount'], axis=1)
data.head()

1126989-20190717111452398-207630871.png

二、样本数据分布不均衡解决方案

上面说到数据集里面正常数据和异常数据数量差异极大,对于这种样本数据不均衡问题,一般有以下两种策略:

(1)下采样策略:之前统计的结果可以看出0的样本有28万个,而1的样本只有几百个。现在将0的数据也变成几百个就可以了。下采样,是使样本的数据同样少 (2)过采样策略:之前统计的结果可以看出0的样本有28万个,而1的样本只有几百个。0比较多1比较少,对1的样本数据进行生成数列,让生成的数据与0的样本数据一样多。

下面首先采用下采样策略

# loc 基于标签索引  iloc 基于行号索引
# ix 基于行号和标签索引都行  但是已被放弃
 
# X = data.ix[:, data.columns != 'Class']
# # print(X)
# y = data.ix[:, data.columns == 'Class']
 
X= data.iloc[:, data.columns != 'Class']# 特征数据
# print(X)
y= data.iloc[:, data.columns== 'Class']#
 
# Number of data points in the minority class 选取少部分异常数据集
number_records_fraud= len(data[data.Class== 1])
fraud_indices= np.array(data[data.Class== 1].index)
 
# Picking the indices of the normal classes 选取正常类的索引
normal_indices= data[data.Class== 0].index
 
# Out of the indices we picked, randomly select "x" number (number_records_fraud)
# 从正常类的索引中随机选取 X 个数据  replace 代替的意思
random_normal_indices= np.random.choice(normal_indices,
                                         number_records_fraud,
                                         replace=False)
random_normal_indices= np.array(random_normal_indices)
 
# Appending the 2 indices
under_sample_indices= np.concatenate([fraud_indices, random_normal_indices])
 
# Under sample dataset
under_sample_data= data.iloc[under_sample_indices, :]
 
X_undersample= under_sample_data.iloc[:, under_sample_data.columns != 'Class']
y_undersample= under_sample_data.iloc[:, under_sample_data.columns== 'Class']
 
# Showing ratio   transactions:交易
print(
    "Percentage of normal transactions:",
    len(under_sample_data[under_sample_data.Class== 0])/
    len(under_sample_data))
print(
    "Percentage of fraud transactions:",
    len(under_sample_data[under_sample_data.Class== 1])/
    len(under_sample_data))
print("Total number of transactions in resampled data:",
      len(under_sample_data))
Percentage of normal transactions: 0.5
Percentage of fraud transactions: 0.5
Total number of transactions in resampled data: 984

可以看出经过下采样策略过后,正常数据与异常数据各占50%,并且总样本数也只有少部分。

下面对原始数据集和下采样后的数据集分别进行切分操作。

# sklearn更新后在执行以下代码时可能会出现这样的问题:
# from sklearn.cross_validation import train_test_split
# ModuleNotFoundError: No module named 'sklearn.cross_validation'
# 原因新版本已经不支持 改为以下代码
from sklearn.model_selectionimport train_test_split
 
# Whole dataset  test_size 表示训练集测试集的比例 
X_train, X_test, y_train, y_test= train_test_split(X,
                                                    y,
                                                    test_size=0.3,
                                                    random_state=0)
 
print("Number transactions train dataset:",len(X_train))
print("Number transactions test dataset:",len(X_test))
print("Total number of transactions:",len(X_train)+ len(X_test))
 
# Undersampled dataset
X_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample= train_test_split(
    X_undersample, y_undersample, test_size=0.3, random_state=0)
 
print("")
print("Number transactions train dataset:",len(X_train_undersample))
print("Number transactions test dataset:",len(X_test_undersample))
print("Total number of transactions:",len(X_train_undersample)+ len(X_test_undersample))
Number transactions train dataset: 199364
Number transactions test dataset: 85443
Total number of transactions: 284807

Number transactions train dataset: 688
Number transactions test dataset: 296
Total number of transactions: 984

三、交叉验证

比如有个集合叫data,通常建立机器模型的时候,先对数据进行切分或者选择,取前面80%的数据当成训练集,取20%的数据当成测试集。80%的数据是来建立一个模型,剩下的20%的数据是用来测试模型。因此第一步是将数据进行切分,切分成训练集以及测试集。这部分操作是必须要做的。第二步还要在训练集进行平均切分,比如平均切分成3份,分别是数据集1,2,3。

在建立模型的时候,不管建立什么样的模型,这个模型伴随着很多参数,有不同的参数进行选择,这个参数选择大比较好,还是选择小比较好一些?从经验值角度来说,肯定没办法很准的,怎么样去确定这个参数呢?只能通过交叉验证的方式。

那什么又叫交叉验证呢?

第一次:将数据集1,2分别建立模型,用数据集3在当前权重下去验证当前模型的效果。数据集3是个验证集,验证集是训练集的一部分。用验证集去验证模型是好还是坏。 第二次:将数据集1,3分别建立模型,用数据集2在当前权重下去验证当前模型的效果。 第三次:将数据集2,3分别建立模型,用数据集1在当前权重下去验证当前模型的效果。

如果只是求一次的交叉验证,这样的操作会存在风险。比如只做第一次交叉验证,会使3验证集偏简单一些。会使模型效果偏高,此外模型有些数据是错误值以及离群值,如果把这些不太好的数据当成验证集,会使模型的效果偏低的。模型当然是不希望偏高也不希望偏低,那就需要多做几次交叉验证模型,求平均值。这里有1,2,3分别作验证集,每个验证集都有评估的标准。最终模型的效果将1,2,3的评估效果加在一起,再除以3,就可以得到模型一个大致的效果。

def printing_Kfold_scores(x_train_data,y_train_data):
    fold= KFold(5,shuffle=False)
     
    # Different C parameters
    c_param_range= [0.01,0.1,1,10,100]
     
    result_table= pd.DataFrame(index=range(len(c_param_range),2),columns=['C_parameter','Mean recall score'])
    result_table['C_parameter']= c_param_range
     
    # the k-fold will give 2 lists:train_indices=indices[0],test_indices = indices[1]
    j=0  # 循环找到最好的惩罚力度
    for c_paramin c_param_range:
        print('-------------------------------------------')
        print('C parameter:',c_param)
        print('-------------------------------------------')
        print('')
         
        recall_accs= []
        for iteration,indicesin enumerate(fold.split(x_train_data)):
             
            # 使用特定的C参数调用逻辑回归模型
            # Call the logistic regression model with a certain C parameter
            # 参数 solver=’liblinear’ 消除警告
            # 出现警告:模型未能收敛 ,请增加收敛次数
            #  ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
            #  "the number of iterations.", ConvergenceWarning)
            #  增加参数 max_iter 默认1000
            lr= LogisticRegression(C= c_param, penalty='l1', solver='liblinear',max_iter=10000)
            # Use the training data to fit the model. In this case, we use the portion
            # of the fold to train the model with indices[0], We then predict on the
            # portion assigned as the 'test cross validation' with indices[1]
            lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())
             
            # Predict values using the test indices in the training data
            y_pred_undersample= lr.predict(x_train_data.iloc[indices[1],:].values)
             
            # Calculate the recall score and append it to a list for recall scores
            # representing the current c_parameter
            recall_acc= recall_score(y_train_data.iloc[indices[1],:].values,y_pred_undersample)
            recall_accs.append(recall_acc)
            print('Iteration ',iteration,': recall score = ',recall_acc)
             
        # the mean value of those recall scores is the metric we want to save and get
        # hold of.
        result_table.loc[j,'Mean recall score']= np.mean(recall_accs)
        j+= 1
        print('')
        print('Mean recall score ',np.mean(recall_accs))
        print('')
         
    # 注意此处报错  源代码没有astype('float64')
    best_c= result_table.loc[result_table['Mean recall score'].astype('float64').idxmax()]['C_parameter']
    # Finally, we can check which C parameter is the best amongst the chosen.
    print('*********************************************************************************')
    print('Best model to choose from cross validation is with C parameter',best_c)
    print('*********************************************************************************')
     
    return best_c

使用下采样数据集调用上面这个函数 

best_c= printing_Kfold_scores(X_train_undersample,y_train_undersample)

输出结果:

-------------------------------------------
C parameter: 0.01
-------------------------------------------

Iteration  0 : recall score =  0.958904109589041
Iteration  1 : recall score =  0.9178082191780822
Iteration  2 : recall score =  1.0
Iteration  3 : recall score =  0.9864864864864865
Iteration  4 : recall score =  0.9545454545454546

Mean recall score  0.9635488539598128

-------------------------------------------
C parameter: 0.1
-------------------------------------------

Iteration  0 : recall score =  0.8356164383561644
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9322033898305084
Iteration  3 : recall score =  0.9459459459459459
Iteration  4 : recall score =  0.8939393939393939

Mean recall score  0.8941437733404299

-------------------------------------------
C parameter: 1
-------------------------------------------

Iteration  0 : recall score =  0.8493150684931506
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9830508474576272
Iteration  3 : recall score =  0.9459459459459459
Iteration  4 : recall score =  0.9090909090909091

Mean recall score  0.9100832939235539

-------------------------------------------
C parameter: 10
-------------------------------------------

Iteration  0 : recall score =  0.863013698630137
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9830508474576272
Iteration  3 : recall score =  0.9324324324324325
Iteration  4 : recall score =  0.9242424242424242

Mean recall score  0.9131506202785514

-------------------------------------------
C parameter: 100
-------------------------------------------

Iteration  0 : recall score =  0.863013698630137
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9830508474576272
Iteration  3 : recall score =  0.9459459459459459
Iteration  4 : recall score =  0.9242424242424242

Mean recall score  0.9158533229812542

*********************************************************************************
Best model to choose from cross validation is with C parameter 0.01
*********************************************************************************

根据上面结果可以看出,当正则化参数为0.01时,recall的值最高。

四、混淆矩阵

混淆矩阵是由一个坐标系组成的,有x轴以及y轴,在x轴里面有0和1,在y轴里面有0和1。x轴表达的是预测的值,y轴表达的是真实的值。可以对比真实值与预测值之间的差异,可以计算当前模型衡量的指标值。

这里精度的表示:(136+138)/(136+13+9+138)。之前有提到recall=TP/(TP+FN),在这里的表示具体如下: 1126989-20190718105730426-1056949807.png 下面定义绘制混淆矩阵的函数:

def plot_confusion_matrix(cm,
                          classes,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    # This function prints and plots the confusion matrix
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks= np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=0)
    plt.yticks(tick_marks, classes)
 
    # cneter 改为 center
    thresh= cm.max()/ 2
    for i, jin itertools.product(range(cm.shape[0]),range(cm.shape[1])):
        plt.text(j,
                 i,
                 cm[i, j],
                 horizontalalignment="center",
                 color="white" if cm[i, j] > threshelse "black")
 
    plt.tight_layout()
    plt.ylabel("True label")
    plt.xlabel("Predicted label")

下面根据上面得出的最好的那个C值,根据下采样数据集绘制出混淆矩阵。

import itertools
 
lr= LogisticRegression(C=best_c, penalty='l1', solver='liblinear')
lr.fit(X_train_undersample, y_train_undersample.values.ravel())
y_pred_undersample= lr.predict(X_test_undersample.values)
 
# Compute confusion matrix
cnf_matrix= confusion_matrix(y_test_undersample, y_pred_undersample)
np.set_printoptions(precision=2)
 
print("Recall metric in the testing dataset:",
      cnf_matrix[1,1]/ (cnf_matrix[1,0]+ cnf_matrix[1,1]))
 
# Plot non-normalized confusion.matrix
class_names= [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,
                      classes=class_names,
                      title='Confusion matrix')
plt.show()

1126989-20190718110214013-1546035996.png

可以看出,在样本数据分布不均衡的情况下,直接进行建立模型,结果并不太好。

在以前学习的逻辑回归模型中,默认是根据0.5来对结果进行分类。那我们可以作出猜想,可不可以通过改变这个阈值来确定到底哪个阈值对模型的最终结果更好呢?

lr= LogisticRegression(C=0.01, penalty='l1', solver='liblinear')
lr.fit(X_train_undersample, y_train_undersample.values.ravel())
y_pred_undersample_proba= lr.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 iin thresholds:
    y_test_predictions_high_recall= y_pred_undersample_proba[:,1] > i
    plt.subplot(3,3, j)
    j+= 1
 
    # Compute confusion matrix
    cnf_matrix= confusion_matrix(y_test_undersample,
                                  y_test_predictions_high_recall)
    np.set_printoptions(precision=2)
 
    print("Recall metric in the testing dataset:",
          cnf_matrix[1,1]/ (cnf_matrix[1,0]+ cnf_matrix[1,1]))
 
    # Plot non-normalized confusion matrix
    class_names= [0,1]
    plot_confusion_matrix(cnf_matrix,
                          classes=class_names,
                          title='Threshold >= %s' % i)
Recall metric in the testing dataset: 1.0
Recall metric in the testing dataset: 1.0
Recall metric in the testing dataset: 1.0
Recall metric in the testing dataset: 0.9795918367346939
Recall metric in the testing dataset: 0.9387755102040817
Recall metric in the testing dataset: 0.891156462585034
Recall metric in the testing dataset: 0.8367346938775511
Recall metric in the testing dataset: 0.7687074829931972
Recall metric in the testing dataset: 0.5850340136054422

1126989-20190718112229605-134175495.png 图上可以看出,不同的阈值,混淆矩阵是长什么样子的。根据精度、recall值和误预测的值来综合考虑,可以看出阈值在0.5和0.6模型的效果不错。

五、总结

总结: 对于样本不均衡数据,要利用越多的数据越好。下采样误预测值很高,这是模型本身自带的一个问题,因为0和1一样少,模型会认为原始数据0和1的数据一样少,导致误预测值偏高。在这次的案例中,过采样的结果偏好一些,虽然recall偏低了一点,但是整体的效果还是不错的。

流程:

(1)首先要观察数据,当前数据是否分布均衡,不均衡的情况下就要想一些方法。(这次的数据是比较纯净的,就不需要做其他一些预处理的操作,直接原封不动的拿出来就可以了。很多情况下,不见得可以直接拿到特征数据。) (2)让数据进行标准化,让数据的浮动比较小一些,然后再进行数据的选择。 (3)混淆矩阵以及模型的评估标准,然后通过交叉验证的方式来进行参数的选择。 (4)通过阈值与预测值进行比较,然后得到最终的一个预测结果。不同的阈值会使结果发生很大的变化。 (5)SMOTE算法。

通过对信用卡欺诈检测这个案例了解了机器学习中样本数据分布不均衡的解决方案、交叉验证、正则化惩罚、混淆矩阵和模型的评估方法等等。