项目目的
通过信用卡的历史交易数据,利用机器学习算法构建信用卡反欺诈预测模型,提前发现客户信用卡被盗刷的事件。
项目介绍
该数据集包含由欧洲持卡人于2013年9月使用信用卡进行交易的数据。此数据集显示两天内发生的交易记录,其中284,807笔交易中有492笔被盗刷。数据集非常不平衡,积极的类(被盗刷)占所有交易的0.172%。因为其中涉及到了隐私的内容,数据集已经进行了PCA的处理,将特征数据提取出来了,特征V1,V2,... V28是使用PCA获得的主要特征变量。
模型选择:该项目要解决的问题是预测持卡人是否会发生信用卡被盗刷。信用卡持卡人是否会发生被盗刷只有两种可能,所以这是一个二分类问题,意味着可以通过二分类相关的算法来找到具体的解决办法,本项目选用的算法模型是逻辑回归模型(Logistic Regression)。
-
数据处理:
- 数据是结构化数据 ,不需要做特征抽象。特征V1至V28是经过PCA处理,而特征Time和Amount的数据规格与其他特征差别较大,需要对其做特征缩放,将特征缩放至同一个规格。在数据质量方面 ,没有出现乱码或空字符的数据,可以确定字段Class为目标列,其他列为特征列。
- 该数据集非常不平衡,被盗刷占所有交易的0.172%,需要进行平衡样本处理。
分析思路:
- 一:数据预处理
1:导入数据
2:标准化处理
3:样本不均衡处理- 二:模型训练
1:欠采样平衡样本
1.1 数据拆分:训练集、验证集与测试集
1.2 交叉验证
1.3 模型评估方法
1.4 正则化惩罚项
1.5 构建模型
1.5.1K折交叉验证-寻找较优参数*
1.5.2使用下采样数据进行训练,下采样数据进行测试*
1.5.3不同阈值对结果的影响*
1.5.4使用下采样数据进行训练,原始数据进行测试*
1.5.5使用原始数据进行训练与测试*
2:过采样平衡样本
2.1 SMOTE算法
2.2 生成过采样数据
2.3 K折交叉验证获取较优参数
2.5 使用过采样数据训练和测试模型- 三:总结:过采样和欠采样模型对比
项目实现步骤
一:数据预处理
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:标准化处理
Amount这一列的数据,没有进行特征缩放,所以接下来对其进行标准化处理
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()
3:样本不平衡
count_classes = pd.value_counts(data['Class'], sort = True).sort_index() # 查看Class字段值得类别
count_classes.plot(kind = 'bar',rot=0)
plt.xlabel("Class")
plt.ylabel("频数")
for a, b in zip(count_classes.index, count_classes.values):
plt.text(a, b,b,ha='center', va='bottom', fontsize=10)
响应变量Class的取值中,0是指正常交易,1是指被盗刷。通过上面的图和数据可知,存在492例盗刷,这是一个明显样本不平衡问题。
如何平衡样本?
在进行不平衡样本的数据预处理以前,先来谈谈非平衡样本的影响以及常用的一些平衡样本的方法,及适用场景。
-
不平衡样本
不平衡的样本会影响模型的评估效果,严重的会带来过拟合或欠拟合的结果。所以我们需要让正负样本在训练过程中拥有相同话语权或权重。在这里,称数据集中样本较多的一类称为“大众类”,样本较少的一类称为“小众类”。 -
处理方法
1:下采样(Undersampling,欠采样):以小众类的样本数为标准,在大众类的样本中取得样本数和小众类的样本数一样(如让0和1两个样本同样少)。
2:上采样(Oversampling,过采样):以大众类的样本数为标准,生成一些样本使得小众类的样本数和大众类的样本数一样多(对1号样本进行生成,让 0 和 1 这两个样本一样多)。
但是这样简单处理也会有相应的弊端出现。因为上采样是复制多份小众类,所以上采样中小众类会反复出现一些样本,这会导致过拟合;下采样是选取部分大众类,下采样会由于丢失信息而导致欠拟合。
-
改进方法
1:下采样:下采样信息丢失的问题,有EasyEnsemble,与BalanceCascade两种改进方法。
(1)EasyEnsemble:多次下采样选择不同的数据集,训练不同的分类器。
(2)BalanceCascade:先通过一次下采样产生数据集,训练一个分类器,然后将那些分类正确的大众样本不放回,然后对剩余的大众样本采样产生数据集,训练多个分类器。
2:上采样:
(1)数据合成:SMOTE是最为常见过数据合成方法来基于已有的数据生成更多的样本
(2)数据加权:其难点在于如何合理设置权重。
3:异常值检测:
可以换一种角度,对于正负样本极不平衡的情况下,我们也可以视其为异常值检测或一分类问题。 -
方法选择
所以解决不平衡样本的问题有很多种方法,那如何选择?
1:在正负样本都非常之少的情况下, 采用数据合成的方式:
2:在负样本足够多,正样本非常之少且比例及其悬殊的情况下, 考虑一分类方法
3:在正负样本都足够多且比例不是特别悬殊的情况下, 应该考虑采样或者加权的方法
4:同时也有适用于不平衡样本的模型比如XGBoost 。
二:模型训练
针对样本不平衡本项目将依次采用欠采样和过采样两种方法进行数据处理,并进行模型训练。
1:欠采样平衡样本
X = data.loc[:, data.columns != 'Class'] # 特征变量数据集
y = data.loc[:, 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
(1)数据拆分:训练集、验证集与测试集
a)训练集:直接参与了模型调参的过程,显然不能用来反映模型真实的能力(防止课本死记硬背的学生拥有最好的成绩,即防止过拟合)
b)验证集:参与了人工调参(超参数)的过程,也不能用来最终评判一个模型(刷题库的学生不能算是学习好的学生)
c)测试集:所以要通过最终的测试来考察一个模型真正的效果
这里仅仅将数据分成了训练集和测试集,之后介绍交叉验证,会把训练集再次分成训练集与测试集。
from sklearn.model_selection import train_test_split
# 将整个样本数据集划分:训练数据和测试数据
## test_size=0.3,表示测试集占总样本数据集30%
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(X_test_undersample))
print("整个下采样数据集长度: ", len(X_train_undersample)+len(X_test_undersample))
> 训练数据集长度: 199364
测试数据集长度: 85443
整个数据集长度: 284807
>下采样训练数据集长度: 688
下采样测试数据集长度: 296
整个下采样数据集长度: 984
说明:为什么进行了下采样,还要把原始数据进行切分呢?对数据集的训练是通过下采样的训练集,对数据的测试的是通过原始的数据集的测试集,下采样的测试集可能没有原始部分当中的一些特征,不能充分进行测试。
(2)交叉验证
交叉验证是在机器学习建立模型和验证模型参数时常用的办法,就是重复的使用数据,把得到的样本数据进行切分,组合为不同的训练集和测试集,用训练集来训练模型,用测试集来评估模型预测的好坏。在此基础上可以得到多组不同的训练集和测试集,某次训练集中的某样本在下次可能成为测试集中的样本,即所谓“交叉”。
交叉验证方法
1:简单交叉验证:首先随机将已给数据分成两份,一份作为训练集,另一份作为测试集(比如: 70%的训练集,30%的测试集)。然后用训练集来训练模型,在测试集上验证模型及参数。接着,我们再把样本打乱,重新选择训练集和测试集,继续训练数据和检验模型。最后我们选择损失函数评估最优的模型和参数。
2:K折交叉验证:会把样本数据随机的分成S份,每次随机的选择S-1份作为训练集,剩下的1份做测试集。当这一轮完成后,重新随机选择S-1份来训练数据。若干轮(小于S)之后,选择损失函数评估最优的模型和参数。
3:留一交叉验证:它是第二种情况的特例,此时S等于样本数N,这样对于N个样本,每次选择N-1个样本来训练数据,留一个样本来验证模型预测的好坏。此方法主要用于样本量非常少的情况,比如对于普通适中问题,N小于50时,一般采用留一交叉验证。
对于普通适中问题,如果数据样本量小于一万条,我们就会采用交叉验证来训练优化选择模型。 如果样本大于一万条的话,我们一般随机的把数据分成三份,一份为训练集,一份为验证集,最后一份为测试集。用训练集来训练模型,用验证集来评估模型预测的好坏和选择模型及其对应的参数。把最终得到的模型再用于测试集,最终决定使用哪个模型以及对应参数。
(3)模型评估方法
混淆矩阵:以分类模型中最简单的二分类为例,对于这种问题,我们的模型最终需要判断样本的结果是0还是1,或者说是negative还是positive。
准确率: 所有预测结果中,预测正确的样本数量占比。
1)公式:Acc=(TN+TF) / S。
2)由于样本不平衡的问题,导致了得到的高准确率结果含有很大的水分。即如果样本不平衡,准确率就会失效。正因为如此,也就衍生出了其它两种指标:精准率和召回率
精确率: 预测为正中,实际为正占比。
1)公式:PRE=TP / Mt
2)精准率代表对正样本结果中的预测准确程度,而准确率则代表整体的预测准确程度,既包括正样本,也包括负样本。
召回率: 实际为正中,预测为正的占比。
1)公式:Recall=TP / Nt
2)召回率越高,代表实际坏用户被预测出来的概率越高,它的含义类似:宁可错杀一千,绝不放过一个。
漏检率: 实际为正中,预测为反的占比,漏检率与召回率相反。
1)公式:MR=1-Recall
误检率: 预测为真中实际为负,占所有负样本比例。
1)公式:FPR=FP / Nf
F1-score
1)公式:F1-score=2 * 精准率*召回率/ (精准率+召回率)
2)我们希望精准率和召回率同时都非常高,但是精准率和召回率是两个向矛盾的指标,如果其中一个非常高,另一个肯定会非常低,如当召回率很高时,精准率往往很低。这需要综合考虑他们情况,F1-score同时考虑了两个指标,让二者同时达到最高,取一个平衡,当F1-score较高时说明模型有效。
ROC曲线
1)纵坐标:召回率TPR/Recall
2)横坐标:误检率FPR
3)ROC曲线也是通过遍历所有阈值来绘制整条曲线的。如果不断的遍历所有阈值,预测的正样本和负样本是在不断变化的,相应的精准率和召回率在ROC曲线图中也会沿着曲线滑动,但是曲线本身是不会变的。判断一个模型的ROC曲线是好的呢?我们当然是希望精准率越高,误检率越低(即ROC曲线越陡峭)那么模型的性能越好。
AUC:ROC曲线下的面积
1:ROC曲线越陡越好,所以理想值就是1,一个正方形,而最差的随机判断都有0.5,所以一般AUC的值是介于0.5到1之间的。
2:AUC的一般判断标准
0.5 - 0.7:效果较低
0.7 - 0.85:效果一般
0.85 - 0.95:效果很好
0.95 - 1:效果非常好,但一般不太可能
(4)正则化惩罚项
正则化是结构风险最小化策略的实现,是在经验风险上加上一个正则化项或惩罚项。正则化项一般是负责模型复杂度的单调递增函数,模型越复杂,正则化值就越大。正则化的作用是选择经验风险与模型复杂度同时较小的模型。正则化符合奥卡姆剃刀原理,在所有可能选择的模型中,能够很好地解释数据并且十分简单的才是最好的模型。
-
下面损失函数中,第一项是经验风险,第二项为正则化项。正则化项可以取不同的形式,可以是参数向量的L2范数,也可以是L1范数。
L1正则化和L2正则化对比
- L1正则化中可以对|w|求累加和,但是只直接计算绝对值求累加和的话,[1,0,0,0,0]和[0.25,0.25,0.25,0.25,0.25]的结果是相同的,无法做出区分。
- L2正则化惩罚力度更大,对权重参数求平方和,目的是让大的更大,相对惩罚更多。
- λ系数,表示正则化惩罚力度。λ值越大,表示惩罚力度越大,对模型影响越大。如果λ值较小,意味着惩罚力度较小,不会对结果产生太大影响。
选择L1正则化的模型还是L2正则化?
- 如果数据集中有很多特征,而这些特征中并不是每一个都对结果有重要影响,那么就应该选择L1正则化的模型。
- 但如果数据集中特征本来就不多,而且每一个都有很重要的作用,那么就应该选择L2正则化的模型。
(5)构建模型
K折交叉验证——使用下采样训练数据集,训练和测试模型,寻找较优模型参数
- 模型正则化L1参数分别设置为[0.01,0.1,1,10,100];使用K折交叉验证将下采样训练数据分拆为5份,逐一遍历数据训练模型,寻找较优的L1参数。
- 在异常检测问题中,精准率不是衡量模型好坏的指标,因为我们的目的是找出可能的异常值,所以用召回率衡量模型的好坏。
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
def printing_Kfold_scores(x_train_data,y_train_data):
#k折交叉验证
# 会得到一个可迭代对象(可以用 for 循环遍历取出),可以遍历5次,每次遍历出来的会是一个2值列表,
# 存放每一次的训练集和验证集的索引
fold = KFold(n_splits=5,shuffle=False)
#不同的C参数
c_param_range = [0.01,0.1,1,10,100]
results_table = pd.DataFrame(index = range(len(c_param_range)), columns = ['C_parameter','Mean recall score'])
results_table['C_parameter'] = c_param_range
#k折操作将会给出两个列表:train_indices = indices[0], test_indices = indices[1]
j = 0
for c_param in c_param_range:
print('-------------------------------------------')
print('C parameter: ', c_param)
print('-------------------------------------------')
print('')
recall_accs = [] # 存放召回率
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
for iteration,indices in enumerate(fold.split(x_train_data)):
#把c_param_range代入到逻辑回归模型中,并使用了l1正则化
lr = LogisticRegression(C = c_param,penalty = 'l1',solver='liblinear')
#使用indices[0]的数据进行拟合曲线,
lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel()) #ravel()将多维数组转换为一维数组
#在indices[1]数据上预测值
y_pred_undersample = lr.predict(x_train_data.iloc[indices[1],:].values)
#根据不同的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)
#求出我们想要的召回平均值
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'].values.argmax()]['C_parameter']
#最后选择最好的 C parameter
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.9315068493150684
Iteration 1 : recall score = 0.9178082191780822
Iteration 2 : recall score = 1.0
Iteration 3 : recall score = 0.9864864864864865
Iteration 4 : recall score = 0.9696969696969697
Mean recall score 0.9610997049353213
-------------------------------------------
C parameter: 0.1
-------------------------------------------
Iteration 0 : recall score = 0.8493150684931506
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.8968834993678272
-------------------------------------------
C parameter: 1
-------------------------------------------
Iteration 0 : recall score = 0.863013698630137
Iteration 1 : recall score = 0.8904109589041096
Iteration 2 : recall score = 0.9830508474576272
Iteration 3 : recall score = 0.9459459459459459
Iteration 4 : recall score = 0.9090909090909091
Mean recall score 0.9183024720057457
-------------------------------------------
C parameter: 10
-------------------------------------------
Iteration 0 : recall score = 0.863013698630137
Iteration 1 : recall score = 0.8904109589041096
Iteration 2 : recall score = 0.9830508474576272
Iteration 3 : recall score = 0.9459459459459459
Iteration 4 : recall score = 0.9242424242424242
Mean recall score 0.9213327750360488
-------------------------------------------
C parameter: 100
-------------------------------------------
Iteration 0 : recall score = 0.863013698630137
Iteration 1 : recall score = 0.8904109589041096
Iteration 2 : recall score = 0.9830508474576272
Iteration 3 : recall score = 0.9459459459459459
Iteration 4 : recall score = 0.9242424242424242
Mean recall score 0.9213327750360488
*********************************************************************************
Best model to choose from cross validation is with C parameter = 0.01
*********************************************************************************
- 上述结果可以得知:当参数C为0.01时,模型的平均召回率最高,即模型的预测效果最好。同时,我们可以发现,对于相同的参数值,用交叉验证每次迭代的召回率相差较大,这个现象说明交叉验证是非常必要的,交叉验证可以尽量减少训练数据对结果的影响。
绘制混淆矩阵
import itertools
def plot_confusion_matrix(cm,classes,title='Confusion matrix',cmap=plt.cm.Blues):
'''这个方法用来输出和画出混淆矩阵的'''
#cm为混淆矩阵数据,interpolation='nearest'使用最近邻插值,cmap颜色图谱(colormap), 默认绘制为RGB(A)颜色空间
plt.imshow(cm,interpolation='nearest',cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
#xticks(刻度下标,刻度标签)
plt.xticks(tick_marks, classes, rotation=0)
plt.yticks(classes)
plt.ylim([1.5,-0.5])
#text()命令可以在任意的位置添加文字
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')
计算模型指标
# 计算模型指标
from sklearn.metrics import f1_score,precision_score,recall_score,roc_auc_score,accuracy_score,roc_curve
def metrics_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) # F1_score
AUC=roc_auc_score(y_test,y_pred) # roc面积
return Recall,Pre,F1_score,AUC
使用下采样数据进行训练,下采样数据进行测试
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)
# 计算混淆矩阵:使用下采样测试数据
cnf_matrix = confusion_matrix(y_test_undersample,y_pred_undersample)
np.set_printoptions(precision=2) #输出精度为小数点后两位
#画出非标准化的混淆矩阵
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,classes=class_names,title='Confusion matrix')
plt.show()
# 计算指标得分
Recall,Pre,F1_score,AUC=metrics_score(y_test_undersample,y_pred_undersample)
print(' 召回率:{:.2%} | 精准率:{:.2%} | F1_score:{:.2f} | AUC:{:.2f}'.format(Recall,Pre,F1_score,AUC))
# ROC曲线图
y_pred_proba=lr.predict_proba(X_test_undersample.values)
lr_fpr0,lr_tpr0,lr_threasholds0=roc_curve(y_test_undersample,y_pred_proba[:,1]) # 计算ROC的值,lr_threasholds为阈值
plt.title("ROC曲线(AUC={:.2f})".format(AUC))
plt.xlabel('误检率')
plt.ylabel('召回率')
plt.plot(lr_fpr,lr_tpr)
plt.show()
召回率:93.20% | 精准率:92.57% | F1_score:0.93 | AUC:0.93
- 使用下采样数据训练和测试模型,得到的模型召回率为93.19%,精确率为91.33%,看起来效果好像非常好;但这只是对下采样数据的测试集的预测效果,我们应该用原始数据的测试集来衡量这个模型的优劣。
不同阈值对结果的影响
- 使用下采样数据来训练模型并作出不同阈值条件下下采样数据的测试集的混淆矩阵,以便观察阈值对模型的影响,从而选择合适的阈值获得最优模型.
lr = LogisticRegression(C = best_c, 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 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)
#画出非标准化的混淆矩阵
class_names = [0,1]
plot_confusion_matrix(cnf_matrix,classes=class_names,title='Threshold >= %s'%i)
# 计算指标得分
Recall,Pre,F1_score,AUC=metrics_score(y_test_undersample,y_test_predictions_high_recall)
print('阈值:{} | 召回率:{:.2%} | 精准率:{:.2%} | F1_score:{:.2f} AUC{:.2f}'.format(i,Recall,Pre,F1_score,AUC))
阈值:0.1 | 召回率:100.00% | 精准率:49.66% | F1_score:0.66 | AUC:0.50
阈值:0.2 | 召回率:100.00% | 精准率:49.66% | F1_score:0.66 | AUC:0.50
阈值:0.3 | 召回率:100.00% | 精准率:49.66% | F1_score:0.66 | AUC:0.50
阈值:0.4 | 召回率:96.60% | 精准率:61.74% | F1_score:0.75 | AUC:0.69
阈值:0.5 | 召回率:93.20% | 精准率:92.57% | F1_score:0.93 | AUC:0.93
阈值:0.6 | 召回率:87.07% | 精准率:97.71% | F1_score:0.92 | AUC:0.93
阈值:0.7 | 召回率:81.63% | 精准率:99.17% | F1_score:0.90 | AUC:0.90
阈值:0.8 | 召回率:76.19% | 精准率:100.00% | F1_score:0.86 | AUC:0.88
阈值:0.9 | 召回率:57.82% | 精准率:100.00% | F1_score:0.73 | AUC:0.79
- 由上可知,阈值的设置极大的影响了模型的泛化效果,随着阈值的改变,模型的召回率和精确率都会随之变化;当阈值设置为0.5和0.6时,得到的AUC相同,但综合考虑召回率和F1_score指标,应该选择阈值为0.5。
使用下采样数据进行训练,原始数据进行测试
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)
np.set_printoptions(precision=2)
Recall=cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]) # 召回率
Pre=cnf_matrix[1,1]/(cnf_matrix[0,1]+cnf_matrix[1,1]) #精准率
F1_score=2*Recall*Pre/(Recall+Pre) # F1_score
print('召回率:{:.2%}'.format(cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1])), '|',
'精确率:{:.2%}'.format(cnf_matrix[1,1]/(cnf_matrix[0,1]+cnf_matrix[1,1])),'|',
'F1_score:{:.2%}'.format(F1_score))
#画出非标准化的混淆矩阵
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix,classes=class_names,title='Confusion matrix')
plt.show()
召回率:91.16% | 精准率:1.41% | F1_score:0.03 | AUC:0.90
- 使用下采样数据进行训练,原始数据进行测试,得到的召回率:92.52% ,精确率:1.40%。虽然召回率很高,但模型出现了错将9573个正常值误判为异常值的情况,这说明该模型具有较大的误杀率,导致精确率很低,这是下采样方法的缺陷。
使用原始数据进行训练与测试
- 对原始数据进行K折交叉验证
best_c2 = printing_Kfold_scores(X_train,y_train)
-------------------------------------------
C parameter: 0.01
-------------------------------------------
Iteration 0 : recall score = 0.4925373134328358
Iteration 1 : recall score = 0.6027397260273972
Iteration 2 : recall score = 0.6833333333333333
Iteration 3 : recall score = 0.5692307692307692
Iteration 4 : recall score = 0.45
Mean recall score 0.5595682284048672
-------------------------------------------
C parameter: 0.1
-------------------------------------------
Iteration 0 : recall score = 0.5671641791044776
Iteration 1 : recall score = 0.6164383561643836
Iteration 2 : recall score = 0.6833333333333333
Iteration 3 : recall score = 0.5846153846153846
Iteration 4 : recall score = 0.525
Mean recall score 0.5953102506435158
-------------------------------------------
C parameter: 1
-------------------------------------------
Iteration 0 : recall score = 0.5522388059701493
Iteration 1 : recall score = 0.6164383561643836
Iteration 2 : recall score = 0.7166666666666667
Iteration 3 : recall score = 0.6153846153846154
Iteration 4 : recall score = 0.5625
Mean recall score 0.612645688837163
-------------------------------------------
C parameter: 10
-------------------------------------------
Iteration 0 : recall score = 0.5522388059701493
Iteration 1 : recall score = 0.6164383561643836
Iteration 2 : recall score = 0.7333333333333333
Iteration 3 : recall score = 0.6153846153846154
Iteration 4 : recall score = 0.575
Mean recall score 0.6184790221704963
-------------------------------------------
C parameter: 100
-------------------------------------------
Iteration 0 : recall score = 0.5522388059701493
Iteration 1 : recall score = 0.6164383561643836
Iteration 2 : recall score = 0.7333333333333333
Iteration 3 : recall score = 0.6153846153846154
Iteration 4 : recall score = 0.575
Mean recall score 0.6184790221704963
*********************************************************************************
Best model to choose from cross validation is with C parameter = 10.0
*********************************************************************************
- 构建模型
lr = LogisticRegression(C = best_c2, penalty = 'l1',solver='liblinear')
lr.fit(X_train,y_train.values.ravel())
y_pred_undersample = lr.predict(X_test.values)
- 画混淆矩阵图
#计算混淆矩阵
cnf_matrix = confusion_matrix(y_test,y_pred_undersample)
np.set_printoptions(precision=2) #输出精度为小数点后两位
# 画混淆矩阵图
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Confusion matrix')
plt.show()
#计算指标得分
Recall,Pre,F1_score,AUC=metrics_score(y_test,y_pred_undersample)
print(' 召回率:{:.2%} | 精准率:{:.2%} | F1_score:{:.2f} | AUC:{:.2f}'.format(Recall,Pre,F1_score,AUC))
# 画ROC曲线
y_pred_proba=lr.predict_proba(X_test.values)
lr_fpr,lr_tpr,lr_threasholds=roc_curve(y_test,y_pred_proba[:,1]) # 计算ROC的值,lr_threasholds为阈值
plt.title("ROC曲线(AUC={:.2f})".format(AUC))
plt.xlabel('误检率')
plt.ylabel('召回率')
plt.plot(lr_fpr,lr_tpr)
plt.show()
召回率:61.90% | 精准率:88.35% | F1_score:0.73 | AUC:0.81
- 单纯使用未处理过的原始数据训练和测试模型,得到的模型召回率仅为61.90%,精确率为88.35%,并没有下采样模型效果好。
2:过采样平衡样本
SMOTE算法
- 由于随机采样采取简单素质样本的策略来增加少数类样本,这样容易产生模型过拟合的问题。
- SMOTE算法它是基于采样算法的一种改进方案。用SMOTE算法生成小众类样本,使得大众类样本和小众类样本数量相同,用生成后的数据来做模型训练和测试。
算法流程:
-
设一个少数类样本数为T,那么SMOTE算法将为这个少数类合成NT个新样本。这里要求N必须是正整数,如果给定的N<1,那么算法认为少数类的样本数T=NT,并将强制N=1。考虑该少数类的一个样本i,其特征向量为Xi,i∈{1,…,T}
- Step1:首先从该少数类的全部T个样本中找到样本Xi的k个近邻(例如欧式距离),记为:xi(near),near∈{1,…,k}。
- Step2:然后从这k个近邻中随机选择一个样本Xi(nn),再生成一个0到1之间的随机数random,从而合成一个新样本Xi1:Xi1=Xi+random*(Xi(nn)-Xi);
- Step3:将步骤2重复进行N次,从而可以合成N个新样本: Xinew,new∈{1,…,k}。
-
那么,对全部的T个少数类样本进行上述操作,便可为该少数类合成NT个新样本。如果样本的特征维数是2维,那么每个样本都可以用二维平面的一个点来表示。SMOTE算法所合成出的一个新样本xi1相当于是表示样本xi的点和表示样本xi(nn)的点之间所连线段上的一个点,所以说该算法是基于“差值”来合成新样本。
# 导入相关库
import pandas as pd
from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
SMOTE算法生成过采样数据
#导入数据
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.2, random_state=0)
# 过采样
oversampler = SMOTE(random_state=0)
os_features,os_labels = oversampler.fit_sample(features_train,labels_train)
print('过采样后 1 的样本的个数为:',len(os_labels[os_labels==1]))
过采样后 1 的样本的个数为: 227454
K折交叉验证获取较优参数L1
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 0 : recall score = 0.8903225806451613
Iteration 1 : recall score = 0.8947368421052632
Iteration 2 : recall score = 0.9687506916011951
Iteration 3 : recall score = 0.9555181851155736
Iteration 4 : recall score = 0.9584308811729921
Mean recall score 0.9335518361280369
-------------------------------------------
C parameter: 0.1
-------------------------------------------
Iteration 0 : recall score = 0.8903225806451613
Iteration 1 : recall score = 0.8947368421052632
Iteration 2 : recall score = 0.9704105344694036
Iteration 3 : recall score = 0.9559688286565327
Iteration 4 : recall score = 0.9528912630109583
Mean recall score 0.9328660097774637
-------------------------------------------
C parameter: 1
-------------------------------------------
Iteration 0 : recall score = 0.8903225806451613
Iteration 1 : recall score = 0.8947368421052632
Iteration 2 : recall score = 0.9698351222750913
Iteration 3 : recall score = 0.9598707422428859
Iteration 4 : recall score = 0.9605082379837548
Mean recall score 0.9350547050504312
-------------------------------------------
C parameter: 10
-------------------------------------------
Iteration 0 : recall score = 0.8903225806451613
Iteration 1 : recall score = 0.8947368421052632
Iteration 2 : recall score = 0.9705433218988603
Iteration 3 : recall score = 0.9597718204899924
Iteration 4 : recall score = 0.9608819423835746
Mean recall score 0.9352513015045704
-------------------------------------------
C parameter: 100
-------------------------------------------
Iteration 0 : recall score = 0.8903225806451613
Iteration 1 : recall score = 0.8947368421052632
Iteration 2 : recall score = 0.9705433218988603
Iteration 3 : recall score = 0.9603543597014761
Iteration 4 : recall score = 0.9602884118662138
Mean recall score 0.9352491032433949
*********************************************************************************
Best model to choose from cross validation is with C parameter = 10.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)
# 计算混淆矩阵
cnf_matrix = confusion_matrix(labels_test,y_pred)
np.set_printoptions(precision=2)
# 画出非规范化的混淆矩阵
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Confusion matrix')
plt.show()
#计算指标得分
Recall,Pre,F1_score,AUC=metrics_score(labels_test,y_pred)
print('召回率:{:.2%} | 精准率:{:.2%} | F1_score:{:.2f} | AUC:{:.2f}'.format(Recall,Pre,F1_score,AUC))
# 画出ROC曲线图
y_pred_proba=lr.predict_proba(features_test.values)
lr_fpr,lr_tpr,lr_threasholds=roc_curve(labels_test,y_pred_proba[:,1]) # 计算ROC的值,lr_threasholds为阈值
plt.title("ROC曲线(AUC={:.2f})".format(AUC))
plt.xlabel('误检率')
plt.ylabel('召回率')
plt.plot(lr_fpr,lr_tpr)
plt.show()
召回率:91.09% | 精准率:14.63% | F1_score:0.25 | AUC:0.95
总结:过采样和欠采样模型对比
- 使用欠采样数据训练出的模型,召回率:91.16% ,精准率:1.41% ,误杀数:9369,F1_score:0.03 , AUC:0.90
使用过采样数据训练出的模型,召回率:91.09% ,精准率:14.63% ,误杀数:537,F1_score:0.25 ,AUC:0.95 - 通过对比两个模型得分,在召回率相差不大的情况下,使用过采样数据训练出的模型,误杀数量明显更少,模型精准率更高,所以使用过采样处理数据的方法更好。
- 另外不同的惩罚力度c和不同的阈值会对模型的泛化能力有很大影响,所以需要反复调试以得到最合适的参数,从而获得最优模型。