1 建模目的
信用卡欺诈检测,又叫异常检测。异常检测无非就是正常和异常,这是一个二分类任务,显然正常的占绝大部分,异常的只占很少的比例,我们要检测的就是这些异常的。利用信用卡历史数据进行建模,构建反欺诈模型,预测新的信用卡被盗刷的可能性。
2 数据集介绍
数据集包含由欧洲人于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 matplotlib.gridspec as gridspec
import numpy as np
import seaborn as sns
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
data = pd.read_csv("creditcard.csv")
data.head()
Time | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | ... | V21 | V22 | V23 | V24 | V25 | V26 | V27 | V28 | Amount | Class | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0 | -1.359807 | -0.072781 | 2.536347 | 1.378155 | -0.338321 | 0.462388 | 0.239599 | 0.098698 | 0.363787 | ... | -0.018307 | 0.277838 | -0.110474 | 0.066928 | 0.128539 | -0.189115 | 0.133558 | -0.021053 | 149.62 | 0 |
1 | 0.0 | 1.191857 | 0.266151 | 0.166480 | 0.448154 | 0.060018 | -0.082361 | -0.078803 | 0.085102 | -0.255425 | ... | -0.225775 | -0.638672 | 0.101288 | -0.339846 | 0.167170 | 0.125895 | -0.008983 | 0.014724 | 2.69 | 0 |
2 | 1.0 | -1.358354 | -1.340163 | 1.773209 | 0.379780 | -0.503198 | 1.800499 | 0.791461 | 0.247676 | -1.514654 | ... | 0.247998 | 0.771679 | 0.909412 | -0.689281 | -0.327642 | -0.139097 | -0.055353 | -0.059752 | 378.66 | 0 |
3 | 1.0 | -0.966272 | -0.185226 | 1.792993 | -0.863291 | -0.010309 | 1.247203 | 0.237609 | 0.377436 | -1.387024 | ... | -0.108300 | 0.005274 | -0.190321 | -1.175575 | 0.647376 | -0.221929 | 0.062723 | 0.061458 | 123.50 | 0 |
4 | 2.0 | -1.158233 | 0.877737 | 1.548718 | 0.403034 | -0.407193 | 0.095921 | 0.592941 | -0.270533 | 0.817739 | ... | -0.009431 | 0.798278 | -0.137458 | 0.141267 | -0.206010 | 0.502292 | 0.219422 | 0.215153 | 69.99 | 0 |
5 rows × 31 columns
data.shape
(284807, 31)
data.isnull().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
此数据集无缺失值。
3 探索性分析
3.1 交易时间分布
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(16,4))
bins = 50
ax1.hist(data["Time"][data["Class"]== 1], bins = bins,facecolor='pink',alpha=0.75)
ax1.set_title('Fraud')
ax2.hist(data["Time"][data["Class"] == 0], bins = bins,facecolor= '#ADD8E6',alpha=0.75)
ax2.set_title('Normal')
plt.xlabel('Time')
plt.ylabel('Number of Transactions')
plt.show()
可以看到,欺诈交易的时间离散度高,出现2个峰值迹象;正常交易分布聚集度明显,可以看到在两个时间段里,用户交易频率高。
3.2 交易金额分布
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(16,4))
bins = 50
ax1.hist(data["Amount"][data["Class"]== 1], bins = bins,facecolor='#4682B4',alpha=0.75)
ax1.set_title('Fraud')
ax2.hist(data["Amount"][data["Class"] == 0], bins = bins,facecolor='#FF8C00',alpha=0.75)
ax2.set_title('Normal')
plt.xlabel('Amount')
plt.ylabel('Number of Transactions')
plt.yscale('log')
plt.show()
信用卡被盗刷交易的金额比信用卡正常用户交易的金额小,这说明信用卡盗刷者为了不引起信用卡卡主的注意,更偏向选择小金额消费。
3.3 查看数据标签分布
count_classes = pd.value_counts(data['Class'], sort = True).sort_index()
count_classes.plot(kind = 'bar')
plt.title("Fraud class histogram")
plt.xlabel("Class")
plt.ylabel("Frequency")
data.groupby('Class').size()
Class
0 284315
1 492
dtype: int64
可以看到0样本的个数远远多于1样本的个数,出现样本分布不均衡的情况。
4 数据预处理
4.1特征缩放
在对样本处理之前,需要先处理下数据,对数据规格较大的amount列做归一化处理,使其均值为0,方差为1,使不同维度的特征对模型的重要程度相同。
from sklearn.preprocessing import StandardScaler
normAmount= StandardScaler().fit_transform(data['Amount'].values.reshape(-1, 1))
normTime= StandardScaler().fit_transform(data['Time'].values.reshape(-1, 1))
data.insert(1,'normAmount',normAmount)
data.insert(2,'normTime',normTime)
data = data.drop(['Time','Amount'],axis=1)
data.head()
normAmount | normTime | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | ... | V20 | V21 | V22 | V23 | V24 | V25 | V26 | V27 | V28 | Class | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.244964 | -1.996583 | -1.359807 | -0.072781 | 2.536347 | 1.378155 | -0.338321 | 0.462388 | 0.239599 | 0.098698 | ... | 0.251412 | -0.018307 | 0.277838 | -0.110474 | 0.066928 | 0.128539 | -0.189115 | 0.133558 | -0.021053 | 0 |
1 | -0.342475 | -1.996583 | 1.191857 | 0.266151 | 0.166480 | 0.448154 | 0.060018 | -0.082361 | -0.078803 | 0.085102 | ... | -0.069083 | -0.225775 | -0.638672 | 0.101288 | -0.339846 | 0.167170 | 0.125895 | -0.008983 | 0.014724 | 0 |
2 | 1.160686 | -1.996562 | -1.358354 | -1.340163 | 1.773209 | 0.379780 | -0.503198 | 1.800499 | 0.791461 | 0.247676 | ... | 0.524980 | 0.247998 | 0.771679 | 0.909412 | -0.689281 | -0.327642 | -0.139097 | -0.055353 | -0.059752 | 0 |
3 | 0.140534 | -1.996562 | -0.966272 | -0.185226 | 1.792993 | -0.863291 | -0.010309 | 1.247203 | 0.237609 | 0.377436 | ... | -0.208038 | -0.108300 | 0.005274 | -0.190321 | -1.175575 | 0.647376 | -0.221929 | 0.062723 | 0.061458 | 0 |
4 | -0.073403 | -1.996541 | -1.158233 | 0.877737 | 1.548718 | 0.403034 | -0.407193 | 0.095921 | 0.592941 | -0.270533 | ... | 0.408542 | -0.009431 | 0.798278 | -0.137458 | 0.141267 | -0.206010 | 0.502292 | 0.219422 | 0.215153 | 0 |
5 rows × 31 columns
4.2 特征选择
利用GBDT梯度提升决策树进行特征重要性排序
columns = data.columns
features_columns = columns.delete(len(columns) - 1)
features = data[features_columns]
labels = data['Class']
# The labels are in the last column ('Class'). Simply remove it to obtain features columns
from sklearn.ensemble import GradientBoostingClassifier
gbdt = GradientBoostingClassifier()
gbdt.fit(features, labels)
GradientBoostingClassifier()
# 找到重要特征
feature_importances = gbdt.feature_importances_
# 重要特征下标排序
# argsort返回的排序之后的下标
inds = feature_importances.argsort()[::-1]
# 重要特征降序排列
data.columns[inds]
Index(['V17', 'V14', 'V26', 'V8', 'V10', 'normTime', 'V12', 'V7', 'V20', 'V27',
'V4', 'V16', 'V18', 'V21', 'V23', 'V24', 'V9', 'V6', 'V11', 'V25',
'V15', 'V1', 'V13', 'V3', 'V2', 'V28', 'V5', 'V19', 'V22',
'normAmount'],
dtype='object')
plt.figure(figsize=(15,9))
plt.bar(np.arange(30), feature_importances[inds])
_ = plt.xticks(np.arange(30), data.columns[inds])
根据上图可以发现,v9,v1,v6,v11,v25,v15,v13,v3,v2,v28,v5,v19,v22,normAmount列可以删除
data.drop(columns=['V9', 'V1', 'V6', 'V11', 'V25','V15', 'V13', 'V3', 'V2', 'V28', 'V5', 'V19', 'V22', 'normAmount'], inplace=True)
4.3 样本不均衡处理
- 下采样处理,让正常样本和异常样本一样少。
- 过采样处理,让异常样本和正常样本一样多。
这两种方案的效果如何?下面我们首先采用下采样对数据进行处理。
下采样处理
X = data.iloc[:, data.columns != 'Class']
y = data.iloc[:, data.columns == 'Class']
# 少数异常数据类所对应的索引值
number_records_fraud = len(data[data.Class == 1])
fraud_indices = np.array(data[data.Class == 1].index)
#正常交易数据的索引值
normal_indices = data[data.Class == 0].index
#从正常交易的数据中随机取出和异常数据数目一样多的数据
random_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace = False)
random_normal_indices = np.array(random_normal_indices)
# 将0样本和1样本的索引合并,形成新的数据集
under_sample_indices = np.concatenate([fraud_indices,random_normal_indices])
# 下采样数据集
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']
# 0样本和1样本的个数
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
4.4 数据切分
对下采样数据进行切分是为了训练模型时使用,并且对原始数据进行一定比例的切分,以备测试时使用,不选用下采样数据测试是因为下采样样本数据较少,不具有原始数据的分布特征。
from sklearn.model_selection import train_test_split
# Whole dataset
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
5 逻辑回归建模
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import KFold,cross_val_score
from sklearn.metrics import confusion_matrix,recall_score,classification_report
5.1 模型选择
使用同一个算法,当使用不同的参数时,也会产生不同的模型,这就涉及到模型的选择问题,一般而言,应该选择泛化误差最小的模型,但是我们事先不知道新样本是未知的,通过使用一个测试集来测试对新样本的判别能力,然后以测试集上的测试误差作为泛化误差的近似,常用的有k折交叉验证,
它的基本思路是首先将原始数据按照8:2的比例切分成训练集和测试集,然后再在训练集上进行切分,即训练数据分成K组(K-Fold),将每个子集数据分别做一次验证集,其余的K-1组子集数据作为训练集,这样会得到K个模型。这K个模型分别在验证集中评估结果,将求得的MSE(Mean Squared Error)加和平均就得到交叉验证误差,作为评价当前参数下模型的好坏。再用测试集在最终选择的模型上测试,用来评估模最终模型的泛化能力。
这里仅对正则化惩罚力度进行调参实验,正则化的意义是防止过拟合,所谓过拟合,指的是模型在训练集上表现的很好,但是在交叉验证集合测试集上表现较差,也就是说模型对未知样本的预测表现一般。正则化是指去限制参数的空间,减少特征的数量,控制模型的复杂度。参数选择l1正则化范式,比较适合模型的特征非常多,同时希望将一些不重要的特征系数归零,从而让模型系数更加稀疏,权重值更低。
def printing_Kfold_scores(x_train_data,y_train_data):
fold = KFold(5,shuffle=False)
# 会得到一个可迭代对象(可以用 for 循环遍历取出),可以遍历5次,每次遍历出来的会是一个2值列表,
# 存放每一次的训练集和验证集的索引
# 不同的正则化惩罚力度
c_param_range = [0.01,0.1,1,10,100]
results_table = pd.DataFrame(index = range(len(c_param_range),2), columns = ['C_parameter','Mean recall score'])
results_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 = []
for iteration, indices in enumerate(fold.split(y_train_data),start=1):
# enumerate 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
# iteration 表示第几次循环,indices 是一个列表里面有两个元素,indices[0]表示训练集索引,indices[1] 表示验证集索引
lr = LogisticRegression(C = c_param, penalty='l1', solver='liblinear',max_iter=10000)
#使用训练数据拟合模型,x和y的索引都为0
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_score需要传入真实值和预测值
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.
results_table.loc[j,'Mean recall score'] = np.mean(recall_accs)
j += 1
print('')
print('Mean recall score ', np.mean(recall_accs))
print('')
best_c = results_table.loc[results_table['Mean recall score'].astype('float').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 1 : recall score = 0.958904109589041
Iteration 2 : recall score = 0.9178082191780822
Iteration 3 : recall score = 1.0
Iteration 4 : recall score = 0.972972972972973
Iteration 5 : recall score = 0.9545454545454546
Mean recall score 0.96084615125711
-------------------------------------------
C parameter: 0.1
-------------------------------------------
Iteration 1 : recall score = 0.8356164383561644
Iteration 2 : recall score = 0.863013698630137
Iteration 3 : recall score = 0.9322033898305084
Iteration 4 : recall score = 0.9324324324324325
Iteration 5 : recall score = 0.8787878787878788
Mean recall score 0.8884107676074242
-------------------------------------------
C parameter: 1
-------------------------------------------
Iteration 1 : recall score = 0.8493150684931506
Iteration 2 : recall score = 0.863013698630137
Iteration 3 : recall score = 0.9661016949152542
Iteration 4 : recall score = 0.9459459459459459
Iteration 5 : recall score = 0.8939393939393939
Mean recall score 0.9036631603847762
-------------------------------------------
C parameter: 10
-------------------------------------------
Iteration 1 : recall score = 0.8493150684931506
Iteration 2 : recall score = 0.863013698630137
Iteration 3 : recall score = 0.9661016949152542
Iteration 4 : recall score = 0.9459459459459459
Iteration 5 : recall score = 0.8939393939393939
Mean recall score 0.9036631603847762
-------------------------------------------
C parameter: 100
-------------------------------------------
Iteration 1 : recall score = 0.8493150684931506
Iteration 2 : recall score = 0.863013698630137
Iteration 3 : recall score = 0.9661016949152542
Iteration 4 : recall score = 0.9459459459459459
Iteration 5 : recall score = 0.8939393939393939
Mean recall score 0.9036631603847762
*********************************************************************************
Best model to choose from cross validation is with C parameter = 0.01
*********************************************************************************
通过交叉验证,效果最好的模型参数为0.01
5.2 模型评估
混淆矩阵
混淆矩阵是统计真实值和实际值的分布情况,来计算用来评价模型好坏的指标,比如recall值,准确率等 二分类预测的混淆矩阵中,行向量为观测的实际类别,列向量为预测类别值。其中(TP True Positive)为真正,表示实际值为 1 预测值也为 1,FN (False Negative)为假负,表示实际值为 1 预测值为 0,FP (False Positive)为假正,表示实际值为 0,预测值为 1,TN (True Negative)为真负,表示实际值为 0,预测值也为 0。
评价指标
- 准确率:TP/(FP+TP),表示分类器预测为正例的所有样本中真正为正例的样本的比重。
- 召回率(recall值):TP/(TP+FN) ,反映了被正确预测的正例的样本占总的正例样本的比重。
- 精确率:TP/(TP+FP),反映了真正为正例的样本占预测为正例的样本的比重。
- F1score:2(精确率+召回率)/(精确率+召回率)
ROC曲线 :对于二分类的模型,很多时候并不是给出每个样本预测为哪一类,而是给出其中一类的概率预测,需要选取一个阈值,当这个预测大于这个阈值时,我们将该观测预测为这一类,否则为另一类。而 ROC 曲线通过阈值从 0 到 1 移动,获得多对 FPR=FP/ (TN+FP)和 TPR=TP/ (FN+TP),且当样本出现不均衡的时候,ROC曲线具有不变形,所以选用ROC曲线来比较不同分类器分类效果。
- 纵轴表示TPR=TP/(FN+TP)(真正例率),表示在所有正例中,预测出来的正例样本有多少。
- 横轴表示FPR=FP/(TN+FP)(假正例率)表示在所有负例中,被预测为正例的样本有多少。
显然我们希望TPR越大越好,FPR越小越好。 - AUC表示ROC曲线下的面积,不同的模型对应的ROC曲线中,AUC值大的模型性能自然相对较好。
下面来定义混淆矩阵的画法,代码如下
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)
thresh = cm.max() / 2.
for i, j in 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] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
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()
Recall metric in the testing dataset: 0.9319727891156463
可以看到得到的召回率的值为0.9319727891156463,可以看到分类效果是比较好的,但是这里是用下采样的数据集进行建模,并且测试集采用的也是下采样的数据。
计算指标
from sklearn.metrics import f1_score,precision_score,recall_score,auc,roc_auc_score,accuracy_score,roc_curve
def metrics_score(y_test,y_pred):
Accuracy=accuracy_score(y_test,y_pred)
Recall=recall_score(y_test,y_pred)
Pre=precision_score(y_test,y_pred)
F1_score=f1_score(y_test,y_pred)
AUC=roc_auc_score(y_test,y_pred)
return Accuracy,Recall,Pre,F1_score,AUC
#计算指标得分
y_pred_undersample = lr.predict(X_test_undersample.values)
Accuracy,Recall,Pre,F1_score,AUC=metrics_score(y_test_undersample,y_pred_undersample)
print('准确率:%.2f|召回率:%.2f|精确率:%.2f|F1_score:%.2f|AUC:%.2f'%(Accuracy,Recall,Pre,F1_score,AUC))
准确率:0.89|召回率:0.93|精确率:0.85|F1_score:0.89|AUC:0.89
#ROC曲线图
y_pred_proba=lr.predict_proba(X_test_undersample.values)
fpr, tpr, thresholds = roc_curve(y_test_undersample,y_pred_proba[:,1])
plt.plot(fpr,tpr,label='AUC = %0.2f'% AUC)
plt.legend(loc='lower right')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.plot([0,1],[0,1],'r--')
plt.show()
只在小规模的数据集进行测试,不具有代表性,所以我们在测试时应该采用原始数据集进行测试,代码如下
lr = LogisticRegression(C = best_c, penalty='l1', solver='liblinear')
lr.fit(X_train_undersample,y_train_undersample.values.ravel())
y_pred = lr.predict(X_test.values)
# Compute confusion matrix
cnf_matrix = confusion_matrix(y_test,y_pred)
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()
Recall metric in the testing dataset: 0.9183673469387755
#计算指标得分
Accuracy,Recall,Pre,F1_score,AUC=metrics_score(y_test,y_pred)
print('准确率:%.2f|召回率:%.2f|精确率:%.2f|F1_score:%.2f|AUC:%.2f'%(Accuracy,Recall,Pre,F1_score,AUC))
准确率:0.90|召回率:0.92|精确率:0.01|F1_score:0.03|AUC:0.91
可以看到召回率=135/(135+12)=0.9183673469387755较高,准确率0.90也比较高,但是精确率较低,这是因为误抓的样本10624比较多,并且还给我们增加了工作量,根据实际业务需求,后续肯定要对这些异常样本做一些处理,比如冻结账号、电话询问等。这是用下采样处理数据所存在的问题。
6 模型改进-过采样处理
在采用下采样的方案中,可以看到召回率较高的同时,误抓的样本也非常多。于是采用过采样处理方案看效果是否会好一些。
smote算法
SMOTE(Synthetic Minority Oversampling Technique),合成少数类过采样技术.它是基于随机过采样算法的一种改进方案,由于随机过采样采取简单复制样本的策略来增加少数类样本,这样容易产生模型过拟合的问题,即使得模型学习到的信息过于特别(Specific)而不够泛化(General),SMOTE算法的基本思想是对少数类样本进行分析并根据少数类样本人工合成新样本添加到数据集中,具体如下图所示,算法流程如下。
(1)对于少数类中每一个样本x,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其k近邻。
(2)根据样本不平衡比例设置一个采样比例以确定采样倍率N,对于每一个少数类样本x,从其k近邻中随机选择若干个样本,假设选择的近邻为xn。
(3)对于每一个随机选出的近邻
x
n
x_n
xn,分别与原样本按照如下的公式构建新的样本。
x
n
e
w
=
x
+
r
a
n
d
(
0
,
1
)
∗
(
x
n
−
x
)
x_{new}=x+rand(0,1)*(x_n-x)
xnew=x+rand(0,1)∗(xn−x)
import pandas as pd
from imblearn.over_sampling import SMOTE # pip install imblearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
columns = data.columns
features_columns = columns.delete(len(columns) - 1)
features = data[features_columns]
labels = data['Class']
划分训练集和测试集
features_train, features_test, labels_train, labels_test = train_test_split(
features, labels, test_size=0.2, random_state=0)
smote算法生成过采样数据
oversampler = SMOTE(random_state=0)
os_features,os_labels = oversampler.fit_resample(features_train,labels_train) # OS oversampler
print('过采样后label为1的数据为:',len(os_labels[os_labels==1]))
过采样后label为1的数据为: 227454
k折交叉验证选择最优参数
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
best_c = printing_Kfold_scores(os_features, os_labels)
-------------------------------------------
C parameter: 0.01
-------------------------------------------
Iteration 1 : recall score = 0.9161290322580645
Iteration 2 : recall score = 0.9078947368421053
Iteration 3 : recall score = 0.9026004204935266
Iteration 4 : recall score = 0.8831844011386993
Iteration 5 : recall score = 0.8844923665380684
Mean recall score 0.8988601914540928
-------------------------------------------
C parameter: 0.1
-------------------------------------------
Iteration 1 : recall score = 0.9161290322580645
Iteration 2 : recall score = 0.9078947368421053
Iteration 3 : recall score = 0.9032200951643244
Iteration 4 : recall score = 0.8835251316208879
Iteration 5 : recall score = 0.8848880535496423
Mean recall score 0.8991314098870049
-------------------------------------------
C parameter: 1
-------------------------------------------
Iteration 1 : recall score = 0.9161290322580645
Iteration 2 : recall score = 0.9078947368421053
Iteration 3 : recall score = 0.9033528825937811
Iteration 4 : recall score = 0.8837889229619371
Iteration 5 : recall score = 0.8849320187731504
Mean recall score 0.8992195186858076
-------------------------------------------
C parameter: 10
-------------------------------------------
Iteration 1 : recall score = 0.9161290322580645
Iteration 2 : recall score = 0.9078947368421053
Iteration 3 : recall score = 0.9033528825937811
Iteration 4 : recall score = 0.8837009925149207
Iteration 5 : recall score = 0.8849540013849045
Mean recall score 0.8992063291187552
-------------------------------------------
C parameter: 100
-------------------------------------------
Iteration 1 : recall score = 0.9161290322580645
Iteration 2 : recall score = 0.9078947368421053
Iteration 3 : recall score = 0.9033307513555383
Iteration 4 : recall score = 0.8838658621030765
Iteration 5 : recall score = 0.8849540013849045
Mean recall score 0.8992348767887378
*********************************************************************************
Best model to choose from cross validation is with C parameter = 100.0
*********************************************************************************
使用过采样数据训练模型,并用测试集测试
lr = LogisticRegression(C = best_c, penalty = 'l1', solver='liblinear')
lr.fit(os_features,os_labels.values.ravel())
y_pred = lr.predict(features_test.values)
# Compute confusion matrix
cnf_matrix = confusion_matrix(labels_test,y_pred)
np.set_printoptions(precision=2)
Accuracy,Recall,Pre,F1_score,AUC=metrics_score(labels_test,y_pred)
print('准确率:%.2f|召回率:%.2f|精确率:%.2f|F1_score:%.2f|AUC:%.2f'%(Accuracy,Recall,Pre,F1_score,AUC))
# Plot non-normalized confusion matrix
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Confusion matrix')
plt.show()
#ROC曲线图
y_pred_proba=lr.predict_proba(features_test.values)
fpr, tpr, thresholds = roc_curve(labels_test,y_pred_proba[:,1])
plt.plot(fpr,tpr,label='AUC = %0.2f'% AUC)
plt.legend(loc='lower right')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.plot([0,1],[0,1],'r--')
plt.show()
准确率:0.97|召回率:0.93|精确率:0.06|F1_score:0.11|AUC:0.95
可以看到过采样对比下采样,在准确率,召回率和精确率上都有提高,虽然过采样的准确率和召回率都很高,但是精确率还是非常低,也就是误抓的样本偏多,尽管过采样比欠采样的效果要好,但是此模型的选择方面还有待进一步优化。
7 随机森林建模
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
clf_RF=RandomForestClassifier(n_estimators=10,random_state=123)#构建分类随机森林分类器
clf_RF.fit(os_features,os_labels.values.ravel())
#交叉验证
scores_RF=cross_val_score(clf_RF,os_features,os_labels.values.ravel())
print('RandomForestClassifier交叉验证准确率为:'+str(scores_RF.mean()))
RandomForestClassifier交叉验证准确率为:0.9997933645851017
# 生成混淆矩阵
cnf_matrix_RF= confusion_matrix(labels_test,y_RFpred)
np.set_printoptions(precision=2)
Accuracy,Recall,Pre,F1_score,AUC=metrics_score(labels_test,y_RFpred)
print('准确率:%.3f|召回率:%.2f|精确率:%.2f|F1_score:%.2f|AUC:%.2f'%(Accuracy,Recall,Pre,F1_score,AUC))
#画混淆矩阵
class_names = [0,1]
plt.figure(figsize=(6,6))
plot_confusion_matrix(cnf_matrix_RF,classes=class_names,title='Confusion matrix')
plt.show()
准确率:0.999|召回率:0.83|精确率:0.84|F1_score:0.84|AUC:0.92
在过采样处理下,随机森林虽然召回率(0.83)没有逻辑回归模型的召回率(0.93)高,但是精确率却有很大提升,也就是说针对后续的欺诈行为,可能会去冻结顾客的银行卡帐户,或者打电话向这些怀疑对象要一些额外资料如信用卡银行对账单,用以核对部分卡号等措施,那为了尽可能少的去打扰到“无辜”客户,所以选择随机森林模型对信用卡欺诈预测效果会更好一些。