信用卡欺诈检测
基于信用卡交易记录数据建立分类模型来预测哪些交易记录是异常的哪些是正常的。
任务流程:
- 加载数据,观察问题
- 针对问题给出解决方案
- 数据集切分
- 评估方法对比
- 逻辑回归模型
- 建模结果分析
- 方案效果对比
主要解决问题:
(1)在此项目中,我们首选对数据进行了观察,发现了其中样本不均衡的问题,其实我们做任务工作之前都一定要先进行数据检查,看看数据有什么问题,针对这些问题来选择解决方案。
(2)这里我们提出了两种方法,下采样和过采样,两条路线来进行对比实验,任何实际问题来了之后,我们都不会一条路走到黑的,没有对比就没有伤害,通常都会得到一个基础模型,然后对各种方法进行对比,找到最合适的,所以在任务开始之前,一定得多动脑筋多一手准备,得到的结果才有可选择的余地。
(3)在建模之前,需要对数据进行各种预处理的操作,比如数据标准化,缺失值填充等,这些都是必要操作,由于数据本身已经给定了特征,此处我们还没有提到特征工程这个概念,后续实战中我们会逐步引入,其实数据预处理的工作是整个任务中最为最重也是最苦的一个阶段,数据处理的好不好对结果的影响是最大的。
(4)先选好评估方法,再进行建模。建模的目的就是为了得到结果,但是我们不可能一次就得到最好的结果,肯定要尝试很多次,所以一定得有一个合适的评估方法,可以用这些通用的,比如Recall,准确率等,也可以根据实际问题自己指定评估指标。
(5)选择合适的算法,这里我们使用的是逻辑回归,也详细分析了其中的细节,这是因为我们刚刚讲解完逻辑回归的原理就拿它来练手了,之后我们还会讲解其他算法,并不一定非要用逻辑回归来完成这个任务,其他算法可能效果会更好。但是有一点我希望大家能够理解就是在机器学习中并不是越复杂的算法越实用,恰恰相反,越简单的算法反而应用的越广泛。逻辑回归就是其中一个典型的代表了,简单实用,所以任何分类问题都可以把逻辑回归当做一个待比较的基础模型了。
(6)模型的调参也是很重要的,之前我们通过实验也发现了不同的参数可能会对结果产生较大的影响,这一步也是必须的,后续实战内容我们还会来强调调参的细节,这里就简单概述一下了。对于参数我建立大家在使用工具包的时候先看看其API文档,知道每一个参数的意义,再来实验选择合适的参数值。
(7)得到的结果一定要和实际任务结合在一起,有时候虽然得到的结果指标还不错,但是实际应用却成了问题,所以测试环节也是必不可少的。到此,这个项目就给大家介绍到这里了,在实践中学习才能成长的更快,建议大家一定使用提供的Notebook代码文件来自己完成一遍上述操作。
1.导入工具包并读取数据
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
data = pd.read_csv("creditcard.csv")
data.head()
2.数据标签分布
count_classes = pd.value_counts(data['Class'],sort=True)
count_classes.plot(kind='bar')
plt.title('Fraud class histogram')
plt.xlabel('Class')
plt.ylabel('Frequency')
3.数据标准化处理
from sklearn.preprocessing import StandardScaler
data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))
data = data.drop(['Time','Amount'],axis=1)
data.head()
4.下采样方案
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)
#有了正常和一场样本后把他们的索引都拿到手
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']
下采样 样本比例
print("正常样本所占整体比例: ", len(under_sample_data[under_sample_data.Class == 0])/len(under_sample_data))
print("异常样本所占整体比例: ", len(under_sample_data[under_sample_data.Class == 1])/len(under_sample_data))
print("下采样策略总体样本数量: ", len(under_sample_data))
正常样本所占整体比例: 0.5
异常样本所占整体比例: 0.5
下采样策略总体样本数量: 984
5.数据集切分
from sklearn.model_selection import train_test_split
#整个数据集的划分
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.3,random_state=0)
print('原始训练集包括样本数量:',len(X_train))
print('原始测试集包含样本数量:',len(X_test))
print('原始样本数量:',len(X_train)+len(X_test))
#下采样样本进行划分
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('下采样训练集包括样本数量:',len(X_train_undersample))
print('下采样测试集包括样本数量:',len(y_test_undersample))
原始训练集包括样本数量: 199364
原始测试集包含样本数量: 85443
原始样本数量: 284807
下采样训练集包括样本数量: 688
下采样测试集包括样本数量: 296
6.逻辑回归建模
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
from sklearn.model_selection import cross_val_predict
def printing_Kfold_scores(x_train_data,y_train_data):
fold = KFold(5,shuffle=False)
# 定义不同力度的正则化惩罚力度
c_param_range = [0.01,0.1,1,10,100]
# 展示结果用的表格
results_table = pd.DataFrame(columns = ['C_parameter','Mean recall score'])
results_table['C_parameter'] = c_param_range
# K-fold表示K折的交叉验证,这里会得到两个索引集合,训练集 = indices[0],验证集 = indices[1]
j = 0
#循环遍历所有参数
for c_param in c_param_range:
print('---------------------------------------------')
print('正则化力度:',c_param)
print('---------------------------------------------')
print('')
recall_accs = []
#一步步分解来执行交叉验证
for iteration,indices in enumerate(fold.split(x_train_data)):
# 指定算法模型,并且给定参数
lr = LogisticRegression(C = c_param,penalty = 'l1',solver = 'liblinear')
# 训练模型,注意索引不要给错了
lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())
# 建立好模型后,预测模型结果,这里用的是验证集,索引为1
y_pred_undersample = lr.predict(x_train_data.iloc[indices[1],:])
# 有了预测结果之后就可以来评估了,这里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_acc)
#当执行所有交叉验证后,计算平均结果
results_table.loc[j,'Mean recall score'] = np.mean(recall_accs)
j += 1
print('')
print('平均召回率 ',np.mean(recall_accs))
#找到最好的参数,哪一个recall高自然就是最好的
best_c = results_table.loc[np.argmax(results_table['Mean recall score'].astype('float32'))]['C_parameter']
# 打印最好的结果
print('*********************************************************************************')
print('效果最好的模型所选参数 = ', best_c)
print('*********************************************************************************')
return best_c
best_c = printing_Kfold_scores(X_train_undersample,y_train_undersample)
正则化力度: 0.01
Iteration 0 召回率 = 0.9315068493150684
Iteration 1 召回率 = 0.9178082191780822
Iteration 2 召回率 = 0.9830508474576272
Iteration 3 召回率 = 0.9594594594594594
Iteration 4 召回率 = 0.9545454545454546
平均召回率 0.9492741659911385
正则化力度: 0.1
Iteration 0 召回率 = 0.8493150684931506
Iteration 1 召回率 = 0.863013698630137
Iteration 2 召回率 = 0.9491525423728814
Iteration 3 召回率 = 0.9459459459459459
Iteration 4 召回率 = 0.9090909090909091
平均召回率 0.9033036329066049
正则化力度: 1
Iteration 0 召回率 = 0.863013698630137
Iteration 1 召回率 = 0.9041095890410958
Iteration 2 召回率 = 0.9661016949152542
Iteration 3 召回率 = 0.9459459459459459
Iteration 4 召回率 = 0.9090909090909091
平均召回率 0.9176523675246685
正则化力度: 10
Iteration 0 召回率 = 0.863013698630137
Iteration 1 召回率 = 0.9041095890410958
Iteration 2 召回率 = 0.9661016949152542
Iteration 3 召回率 = 0.9459459459459459
Iteration 4 召回率 = 0.9090909090909091
平均召回率 0.9176523675246685
正则化力度: 100
Iteration 0 召回率 = 0.8767123287671232
Iteration 1 召回率 = 0.9178082191780822
Iteration 2 召回率 = 0.9661016949152542
Iteration 3 召回率 = 0.9459459459459459
Iteration 4 召回率 = 0.9090909090909091
平均召回率 0.923131819579463
效果最好的模型所选参数 = 0.01
7.混淆矩阵绘制
def plot_confusion_matrix(cm,classes,title='Confusion matrix',cmap = plt.cm.Blues):
'''
绘制混淆矩阵
'''
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)
chresh = 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] > chresh else 'black')
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
8.下采样方案在下采样数据集中结果
import itertools
lr = LogisticRegression(C = best_c,penalty = 'l1',solver='liblinear')
lr.fit(X_train_undersample.values,y_train_undersample.values.ravel())
y_pred_undersample = lr.predict(X_test_undersample.values)
# 计算所需值
cnf_matrix = confusion_matrix(y_test_undersample,y_pred_undersample)
np.set_printoptions(precision=2)
print('召回率:',cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]))
# 绘制
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,classes = class_names,title = 'Confusion matrix')
plt.show()
9.下采样方案在原始数据集中结果
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)
#计算所需值
cnf_matrix = confusion_matrix(y_test,y_pred)
np.set_printoptions(precision=2)
print('召回率:',cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]))
# 绘制
class_name = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,classes = class_name,title = 'Confusion matrix')
10.原始数据直接建模
best_c = printing_Kfold_scores(X_train,y_train)
正则化惩罚力度: 0.01
Iteration 0 : 召回率 = 0.4925373134328358
Iteration 1 : 召回率 = 0.6027397260273972
Iteration 2 : 召回率 = 0.6833333333333333
Iteration 3 : 召回率 = 0.5692307692307692
Iteration 4 : 召回率 = 0.45
平均召回率 0.5595682284048672
正则化惩罚力度: 0.1
Iteration 0 : 召回率 = 0.5671641791044776
Iteration 1 : 召回率 = 0.6164383561643836
Iteration 2 : 召回率 = 0.6833333333333333
Iteration 3 : 召回率 = 0.5846153846153846
Iteration 4 : 召回率 = 0.525
平均召回率 0.5953102506435158
正则化惩罚力度: 1
Iteration 0 : 召回率 = 0.5522388059701493
Iteration 1 : 召回率 = 0.6164383561643836
Iteration 2 : 召回率 = 0.7166666666666667
Iteration 3 : 召回率 = 0.6153846153846154
Iteration 4 : 召回率 = 0.5625
平均召回率 0.612645688837163
正则化惩罚力度: 10
Iteration 0 : 召回率 = 0.5522388059701493
Iteration 1 : 召回率 = 0.6164383561643836
Iteration 2 : 召回率 = 0.7333333333333333
Iteration 3 : 召回率 = 0.6153846153846154
Iteration 4 : 召回率 = 0.575
平均召回率 0.6184790221704963
正则化惩罚力度: 100
Iteration 0 : 召回率 = 0.5522388059701493
Iteration 1 : 召回率 = 0.6164383561643836
Iteration 2 : 召回率 = 0.7333333333333333
Iteration 3 : 召回率 = 0.6153846153846154
Iteration 4 : 召回率 = 0.575
平均召回率 0.6184790221704963
效果最好的模型所选参数 = 10.0
11.原始数据建模结果
lr = LogisticRegression(C = best_c,penalty = 'l1',solver = 'liblinear')
lr.fit(X_train,y_train.values.ravel())
y_pred_undersample = lr.predict(X_test)
cnf_matrix = confusion_matrix(y_test,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]))
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,classes = class_name,title = 'Confusion matrix')
plt.show()
12.阈值对结果的影响
# 用之前最好的参数来进行建模
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
# 用混淆矩阵展示
y_pred_undersample_proba
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)
np.set_printoptions(precision=2)
print('给定阈值:',i,'时召回率:',cnf_matrix[1][1]/(cnf_matrix[1][1]+cnf_matrix[1][0]))
class_names = [0,1]
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Threshold >= %s'%i)
给定阈值: 0.1 时召回率: 1.0
给定阈值: 0.2 时召回率: 1.0
给定阈值: 0.3 时召回率: 1.0
给定阈值: 0.4 时召回率: 0.9727891156462585
给定阈值: 0.5 时召回率: 0.9183673469387755
给定阈值: 0.6 时召回率: 0.891156462585034
给定阈值: 0.7 时召回率: 0.8299319727891157
给定阈值: 0.8 时召回率: 0.782312925170068
给定阈值: 0.9 时召回率: 0.5782312925170068
13.SMOTE过采样方案
import pandas as pd
from imblearn.over_sampling import SMOTE
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
credit_cards = pd.read_csv('creditcard.csv')
columns = credit_cards.columns
# 在特征中删掉标签
features_columns = columns.delete(len(columns)-1)
features = credit_cards[features_columns]
labels = credit_cards['Class']
features_train,features_test,labels_train,labels_test = train_test_split(features,labels,test_size=0.3,random_state=0)
oversampler = SMOTE(random_state=0)
os_features,os_labels = oversampler.fit_sample(features_train,labels_train)
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
best_c = printing_Kfold_scores(os_features,os_labels)
正则化力度: 0.01
Iteration 0 召回率 = 0.9142857142857143
Iteration 1 召回率 = 0.88
Iteration 2 召回率 = 0.9716742539200809
Iteration 3 召回率 = 0.9622520632607685
Iteration 4 召回率 = 0.9619380205258331
平均召回率 0.9380300103984794
正则化力度: 0.1
Iteration 0 召回率 = 0.9142857142857143
Iteration 1 召回率 = 0.88
Iteration 2 召回率 = 0.9729135053110773
Iteration 3 召回率 = 0.9640986345421885
Iteration 4 召回率 = 0.9640483877045989
平均召回率 0.9390692483687157
正则化力度: 1
Iteration 0 召回率 = 0.9142857142857143
Iteration 1 召回率 = 0.88
Iteration 2 召回率 = 0.9730146686899342
Iteration 3 召回率 = 0.9643749921489316
Iteration 4 召回率 = 0.9643121836019446
平均召回率 0.9391975117453051
正则化力度: 10
Iteration 0 召回率 = 0.9142857142857143
Iteration 1 召回率 = 0.88
Iteration 2 召回率 = 0.9730652503793626
Iteration 3 召回率 = 0.9641111962515859
Iteration 4 召回率 = 0.9642116899267652
平均召回率 0.9391347701686857
正则化力度: 100
Iteration 0 召回率 = 0.9142857142857143
Iteration 1 召回率 = 0.88
Iteration 2 召回率 = 0.97298937784522
Iteration 3 召回率 = 0.9644252389865213
Iteration 4 召回率 = 0.9608828369364503
平均召回率 0.9385166336107812
效果最好的模型所选参数 = 1.0
14.过采样结果
lr = LogisticRegression(C = best_c,penalty = 'l1',solver='liblinear')
lr.fit(os_features,os_labels.values.ravel())
y_pred = lr.predict(features_test.values)
# 计算混淆矩阵
cnf_matrix = confusion_matrix(labels_test,y_pred)
np.set_printoptions(precision=2)
print('召回率: ',cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]))
# 绘制
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,classes = class_names,title = 'Confusion matrix')
召回率: 0.8843537414965986
15.项目总结
项目总结
(1)在此项目中,我们首选对数据进行了观察,发现了其中样本不均衡的问题,其实我们做任务工作之前都一定要先进行数据检查,看看数据有什么问题,针对这些问题来选择解决方案。
(2)这里我们提出了两种方法,下采样和过采样,两条路线来进行对比实验,任何实际问题来了之后,我们都不会一条路走到黑的,没有对比就没有伤害,通常都会得到一个基础模型,然后对各种方法进行对比,找到最合适的,所以在任务开始之前,一定得多动脑筋多一手准备,得到的结果才有可选择的余地。
(3)在建模之前,需要对数据进行各种预处理的操作,比如数据标准化,缺失值填充等,这些都是必要操作,由于数据本身已经给定了特征,此处我们还没有提到特征工程这个概念,后续实战中我们会逐步引入,其实数据预处理的工作是整个任务中最为最重也是最苦的一个阶段,数据处理的好不好对结果的影响是最大的。
(4)先选好评估方法,再进行建模。建模的目的就是为了得到结果,但是我们不可能一次就得到最好的结果,肯定要尝试很多次,所以一定得有一个合适的评估方法,可以用这些通用的,比如Recall,准确率等,也可以根据实际问题自己指定评估指标。
(5)选择合适的算法,这里我们使用的是逻辑回归,也详细分析了其中的细节,这是因为我们刚刚讲解完逻辑回归的原理就拿它来练手了,之后我们还会讲解其他算法,并不一定非要用逻辑回归来完成这个任务,其他算法可能效果会更好。但是有一点我希望大家能够理解就是在机器学习中并不是越复杂的算法越实用,恰恰相反,越简单的算法反而应用的越广泛。逻辑回归就是其中一个典型的代表了,简单实用,所以任何分类问题都可以把逻辑回归当做一个待比较的基础模型了。
(6)模型的调参也是很重要的,之前我们通过实验也发现了不同的参数可能会对结果产生较大的影响,这一步也是必须的,后续实战内容我们还会来强调调参的细节,这里就简单概述一下了。对于参数我建立大家在使用工具包的时候先看看其API文档,知道每一个参数的意义,再来实验选择合适的参数值。
(7)得到的结果一定要和实际任务结合在一起,有时候虽然得到的结果指标还不错,但是实际应用却成了问题,所以测试环节也是必不可少的。到此,这个项目就给大家介绍到这里了,在实践中学习才能成长的更快,建议大家一定使用提供的Notebook代码文件来自己完成一遍上述操作。
声明:代码notebook来自唐宇迪机器学习课程,由于sklearn版本问题,可能有些不一致。