天天看点

kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)

项目背景

数据集包含由欧洲持卡人于2013年9月使用信用卡进行交的数据。此数据集显示两天内发生的交易,其中284,807笔交易中有492笔被盗刷。数据集非常不平衡,正类(被盗刷)占所有交易的0.172%。

它只包含作为PCA转换结果的数字输入变量。不幸的是,由于保密问题,我们无法提供有关数据的原始功能和更多背景信息。特征V1,V2,… V28是使用PCA获得的主要组件,没有用PCA转换的唯一特征是“时间”和“量”。特征’时间’包含数据集中每个事务和第一个事务之间经过的秒数。特征“金额”是交易金额,此特征可用于实例依赖的成本认知学习。特征’Class’是响应变量,如果发生被盗刷,则取值1,否则为0。

直接从代码入手,理解数据分布

#引入需要的python库
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import missingno
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
import random
from sklearn.preprocessing import StandardScaler
from imblearn.over_sampling import SMOTE,RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

from lightgbm import LGBMClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split,cross_val_score,GridSearchCV
from sklearn.metrics import roc_auc_score,roc_curve,precision_score,auc,precision_recall_curve, \
                            accuracy_score,recall_score,f1_score,confusion_matrix,classification_report

%matplotlib inline
           
#读取文件,查看数据大小,并初步观察数据
data = pd.read_csv("creditcard.csv")
print(data.shape)
data.head()
           
#查看数据info,可以看到各变量的数据类型
data.info()
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#查看数据中各变量的分布情况
data.describe()
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#可以调用missingno库,快速可视化缺失值情况。
missingno.matrix(data)# 查看缺失值情况
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#查看类别标签情况,可以看到,数据集非常不平衡,欺诈样本在总体样本中只占很小比例。
data.Class.value_counts()
0    284315
1       492
Name: Class, dtype: int64
           
#将Time字段进行小时转换,后续以“小时”进行分析。
data['Hour'] =data["Time"].apply(lambda x : divmod(x, 3600)[0]) #单位转换
           
区分正负样本
Xfraud = data[data["Class"] == 1]
XnonFraud = data[data["Class"] == 0]
           
sns.heatmap(Xfraud.drop('Class',axis=1).corr(),vmax=1,vmin=0,cmap='YlGnBu')
#可视化欺诈样本相关性,结果表明欺诈样本部分变量相关性强。
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
sns.heatmap(XnonFraud.drop('Class',axis=1).corr(),vmax=1,vmin=0,cmap='YlGnBu')
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#分析正负样本的交易金额
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(16,4))

bins = 30

ax1.hist(data[data["Class"]== 1]["Amount"], bins = bins)
ax1.set_title('Fraud')

ax2.hist(data[data["Class"] == 0]["Amount"], bins = bins)
ax2.set_title('Normal')

plt.xlabel('Amount ($)')
plt.ylabel('Number of Transactions')
plt.yscale('log')
plt.show()
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)

结果表明:信用卡盗刷者为了不引起信用卡卡主的注意,更偏向选择小金额消费。

#盗刷交易时间分布
sns.factorplot(x="Hour", data=data[data["Class"]== 1], kind="count",  palette="ocean", size=6, aspect=3)
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#正常交易时间分布
sns.factorplot(x="Hour", data=data[data["Class"]== 0], kind="count",  palette="ocean", size=6, aspect=3)
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#盗刷交易、交易金额和交易时间的关系
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(16,6))

ax1.scatter(data[data["Class"] == 1]["Hour"], data[data["Class"] == 1]["Amount"])
ax1.set_title('Fraud')

ax2.scatter(data[data["Class"] == 0]["Hour"], data[data["Class"] == 0]["Amount"])
ax2.set_title('Normal')

plt.xlabel('Time (in Hours)')
plt.ylabel('Amount')
plt.show()
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)

下面开始可视化各变量在正负样本中的分布

v_feat = data.columns[1:29]
plt.figure(figsize=(16,28*4))
gs = gridspec.GridSpec(28, 1)
for i, cn in enumerate(data[v_feat]):
    ax = plt.subplot(gs[i])
    sns.distplot(data[data["Class"] == 1][cn], bins=50,label='Class_1')
    sns.distplot(data[data["Class"] == 0][cn], bins=100,label='Class_0')
    ax.set_xlabel('')
    ax.set_title('histogram of feature: ' + str(cn))
 #这里使用了sns.distplot
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
删除对正负样本区分度不大的变量,当然,如果设计的模型复杂,样本量很大,可以保留这些变量,避免信息损失。
droplist = ['V8', 'V13', 'V15', 'V20', 'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28','Time']
data_new = data.drop(droplist, axis = 1)
data_new.shape # 查看数据的维度
           
# 对Amount和Hour 进行特征缩放
col = ['Amount','Hour']
sc =StandardScaler() # 初始化缩放器
data_new[col] =sc.fit_transform(data_new[col])#对数据进行标准化
data_new.head()
           
#划分数据和标签
X_data = data_new.drop('Class',axis=1)
y = data_new.Class
#获得正负样本的索引
index1 = data_new[data_new.Class==1].index
index0 = data_new[data_new.Class==0].index
           
对数据进行主成分分析,降维到2维空间。
data_pca = PCA(n_components=2).fit_transform(X_data)

f = plt.figure(1)#观察数据分布
plt.scatter(data_pca[index1,0],data_pca[index1,1],marker = '+', color = 'c',s=100)
plt.scatter(data_pca[index0,0],data_pca[index0,1],marker = 'o', color = 'r',s=1)
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)

数据量较大,重合严重,影响视觉效果。

#采样后,再次进行可视化
non_fraud = data_new[data_new['Class'] == 0].sample(1000)
fraud = data_new[data_new['Class'] == 1]

df = non_fraud.append(fraud).sample(frac=1).reset_index(drop=True)
X = df.drop(['Class'], axis = 1).values
index1 = df[df.Class==1].index
index0 = df[df.Class==0].index
data_pca = PCA(n_components=2).fit_transform(X)
f = plt.figure(1)#观察数据分布
plt.scatter(data_pca[index1,0],data_pca[index1,1],marker = '+', color = 'c',s=100)
plt.scatter(data_pca[index0,0],data_pca[index0,1],marker = 'o', color = 'r',s=1)
#可以观察到数据具有很强的线性关系,PCA是线性的
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
#尝试用T-SNE降维,优势:非线性
data_tsne = TSNE(n_components=2).fit_transform(X)
f = plt.figure(1)#观察数据分布
plt.scatter(data_tsne[index1,0],data_tsne[index1,1],marker = '+', color = 'c',s=100)
plt.scatter(data_tsne[index0,0],data_tsne[index0,1],marker = 'o', color = 'r',s=1)
#可以观察到数据具有非线性,TSNE是非线性的。且部分正负样本区分明显。
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)
之后,可以再进行3D可视化
from mpl_toolkits.mplot3d import Axes3D
data_tsne = TSNE(n_components=3).fit_transform(X)
fig = plt.figure(figsize=(16,16))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(data_tsne[index1,0],data_tsne[index1,1],data_tsne[index1,2], c='r')  # 绘制数据点
ax.scatter(data_tsne[index0,0],data_tsne[index0,1],data_tsne[index0,2], c='g')

ax.set_zlabel('Z')  # 坐标轴
ax.set_ylabel('Y')
ax.set_xlabel('X')
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)

通过3d展示,视觉上区分,是不是更加明显。

简单使用LogisticRegression来建模

X_train, X_test, y_train, y_test = train_test_split(X_data,y, test_size = 0.3, random_state = 2019)
print(X_train.shape,X_test.shape)

# 构建参数组合
param_grid = {'C': [0.01,0.1, 1, 10, 100, 1000,],
                            'penalty': [ 'l1', 'l2']}

grid_search = GridSearchCV(LogisticRegression(),  param_grid, cv=10) # 确定模型LogisticRegression,和参数组合param_grid ,cv指定10折
grid_search.fit(X_train, y_train) # 使用训练集学习算法
           
grid_search.best_params_
{'C': 0.1, 'penalty': 'l2'}
           
best_model = grid_search.best_estimator_

print('accuracy_score:',accuracy_score(y_test,best_model.predict(X_test)))
print('roc_auc_score:',roc_auc_score(y_test,best_model.predict(X_test)))
print('recall_score:',recall_score(y_test,best_model.predict(X_test)))
print('precision_score:',precision_score(y_test,best_model.predict(X_test)))

accuracy_score: 0.9991573329588147
roc_auc_score: 0.8038512204037437
recall_score: 0.6078431372549019
precision_score: 0.8857142857142857
           
#可以看到,recall分数低,大量1误判为0,预测往分类占比大的0偏移
print(classification_report(y_test, best_model.predict(X_test)))

              precision    recall  f1-score   support

           0       1.00      1.00      1.00     85290
           1       0.89      0.61      0.72       153

   micro avg       1.00      1.00      1.00     85443
   macro avg       0.94      0.80      0.86     85443
weighted avg       1.00      1.00      1.00     85443
           

接下来利用过采样,并简单使用LogisticRegression来建模

#通过SMOTE过采样,得到正负样本各50%
X_sample,y_sample = SMOTE(random_state=2019).fit_sample(X_data,y)

X_train, X_test, y_train, y_test = train_test_split(X_sample, y_sample, test_size = 0.3, random_state = 2019)

# 构建参数组合
param_grid = {'C': [0.01,0.1, 1, 10, 100, 1000,],
                            'penalty': [ 'l1', 'l2']}

grid_search = GridSearchCV(LogisticRegression(),  param_grid, cv=10) # 确定模型LogisticRegression,和参数组合param_grid ,cv指定10折
grid_search.fit(X_train, y_train) # 使用训练集学习算法
           
grid_search.best_params_
#得到最佳参数{'C': 0.01, 'penalty': 'l1'}
           
best_model = grid_search.best_estimator_

print('accuracy_score:',accuracy_score(y_test,best_model.predict(X_test)))
print('roc_auc_score:',roc_auc_score(y_test,best_model.predict(X_test)))
print('recall_score:',recall_score(y_test,best_model.predict(X_test)))
print('precision_score:',precision_score(y_test,best_model.predict(X_test)))
accuracy_score: 0.9383899313554801
roc_auc_score: 0.9385167092785598
recall_score: 0.9014207931203702
precision_score: 0.9738330261795966
           
confusion_matrix(y_test, best_model.predict(X_test))  # 生成混淆矩阵
array([[82930,  2073],
       [ 8437, 77149]], dtype=int64)
print(classification_report(y_test, best_model.predict(X_test)))
#得到如下分数:
              precision    recall  f1-score   support

           0       0.91      0.98      0.94     85003
           1       0.97      0.90      0.94     85586

   micro avg       0.94      0.94      0.94    170589
   macro avg       0.94      0.94      0.94    170589
weighted avg       0.94      0.94      0.94    170589
           

模型评估 解决不同的问题,通常需要不同的指标来度量模型的性能。例如我们希望用算法来预测癌症是否是恶性的,假设100个病人中有5个病人的癌症是恶性,对于医生来说,尽可能提高模型的查全率(recall)比提高查准率(precision)更为重要,因为站在病人的角度,发生漏发现癌症为恶性比发生误判为癌症是恶性更为严重。

from itertools import cycle
colors = cycle(['navy', 'turquoise', 'darkorange', 'cornflowerblue', 'teal', 'red', 'yellow', 'green', 'blue','black'])

y_pred_proba = best_model.predict_proba(X_test)  #predict_prob 获得一个概率值

thresholds = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]  # 设定不同阈值
plt.figure(figsize=(12,7))

j = 1
for i,color in zip(thresholds,colors):
    y_test_predictions_prob = y_pred_proba[:,1] > i #预测出来的概率值是否大于阈值  

    precision, recall, thresholds = precision_recall_curve(y_test, y_test_predictions_prob)
    area = auc(recall, precision)
    
    # Plot Precision-Recall curve
    plt.plot(recall, precision, color=color,
                 label='Threshold: %s, AUC=%0.5f' %(i , area))
    plt.xlabel('Recall')
    plt.ylabel('Precision')
    plt.ylim([0.0, 1.05])
    plt.xlim([0.0, 1.0])
    plt.title('Precision-Recall Curve')
    plt.legend(loc="lower left")
           
kaggle 欺诈信用卡预测(由浅入深(一)之数据探索及过采样)

最优阈值 precision和recall是一组矛盾的变量。从上面混淆矩阵和PRC曲线可以看到,阈值越小,recall值越大, 模型能找出信用卡被盗刷的数量也就更多,但换来的代价是误判的数量也较大。随着阈值的提高, recall值逐渐降低,precision值也逐渐提高,误判的数量也随之减少。通过调整模型阈值, 控制模型反信用卡欺诈的力度,若想找出更多的信用卡被盗刷就设置较小的阈值,反之,则设置较大的阈值。

实际业务中,阈值的选择取决于公司业务边际利润和边际成本的比较;当模型阈值设置较小的值, 确实能找出更多的信用卡被盗刷的持卡人,但随着误判数量增加,不仅加大了贷后团队的工作量, 也会降低误判为信用卡被盗刷客户的消费体验,从而导致客户满意度下降,如果某个模型阈值能 让业务的边际利润和边际成本达到平衡时,则该模型的阈值为最优值。当然也有例外的情况, 发生金融危机,往往伴随着贷款违约或信用卡被盗刷的几率会增大,而金融机构会更愿意不惜一切代价守住风险的底线。

在下一篇中,我们将引入半监督学习来进行模型上的优化。

继续阅读