13-特征工程之金融反欺诈

1.本项目需解决的问题

本项目通过利用信用卡的历史交易数据,进行机器学习,构建信用卡反欺诈预测模型,提前发现客户信用卡被盗刷的事件。

2.建模思路
思路图.png
3.项目背景

数据集包含由欧洲持卡人于2013年9月使用信用卡进行交的数据。此数据集显示两天内发生的交易,其中284,807笔交易中有492笔被盗刷。数据集非常不平衡, 积极的类(被盗刷)占所有交易的0.172%。
它只包含作为PCA转换结果的数字输入变量。不幸的是,由于保密问题,我们无法提供有关数据的原始功能和更多背景信息。特征V1,V2,... V28是使用PCA 获得的主要组件,没有用PCA转换的唯一特征是“时间”和“量”。特征'时间'包含数据集中每个事务和第一个事务之间经过的秒数。特征“金额”是交易金额,此特 征可用于实例依赖的成本认知学习。特征'类'是响应变量,如果发生被盗刷,则取值1,否则为0。 以上取自Kaggle官网对本数据集部分介绍。

4.场景解析(算法选择)

根据历史记录数据学习并对信用卡持卡人是否会发生被盗刷进行预测,二分类监督学习场景,选择逻辑斯蒂回归(Logistic Regression)算法;
分析数据:数据是结构化数据 ,不需要做特征抽象。特征V1至V28是经过PCA处理,而特征Time和Amount的数据规格与其他特征差别较大,需要对其做特征缩放,将特征缩放至同一个规格.

五.导包
#numpy、pandas、matplotlib
import pandas as pd
import numpy as np

#画图#
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
%matplotlib inline
#算法和数据处理、模型评估
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from sklearn.metrics import auc
from sklearn.metrics import roc_curve
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
#样本不均衡处理
#忽略弹出的warnings
import warnings
warnings.filterwarnings('ignore')  
#导入过采样的工具包处理类别不平衡问题
from imblearn.over_sampling import SMOTE
import itertools
六.获取数据
#设置float类型数据保留位数
data_cr=pd.read_csv(r"./creditcard.csv")#读取数据
pd.set_option('display.float_format', lambda x: '%.3f' % x)#设置pandas读入数据保留3位小数点
data_cr.head()#查看数据的前5行,目的是快速查看数据的基本信息
七.查看数据是否缺失
data_cr.info()
八.特征工程
#正负样本均衡问题
#目标变量可视化
fig,ax=plt.subplots(1,2,figsize=(12,8))
from pylab import mpl#用于显示中文
plt.style.use('ggplot')
mpl.rcParams['font.sans-serif'] = ['SimHei']   # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False          # 解决保存图像是负号'-'显示为方块的问题
data_cr["Class"].value_counts().plot(kind="bar",ax=ax[0],fontsize=23)
ax[0].set_title("目标变量中每类的频数分布直方图")
data_cr["Class"].value_counts().plot(kind="pie",ax=ax[1],fontsize=23,autopct='%1.2f%%')#长度为1,保留百分号前面的2个小数点
ax[1].set_title("目标变量中的每类频率分布饼图",fontproperties = 'SimHei')
正负样本均衡.png
#特征转换,将时间从单位每秒化为单位每小时
data_cr["Time"]=data_cr["Time"].apply(lambda x: divmod(x,3600)[0])
#特征选择
v_feature=data_cr.iloc[:,1:29].columns#获取特征名
plt.figure(figsize=(16,28*5))
gs=gridspec.GridSpec(28,1)
# 图形需要绘制10分钟
for i,cn in enumerate(v_feature):
    ax=plt.subplot(gs[i])
    ax.hist(data_cr[cn][data_cr["Class"]==1],bins=50,normed = True)
    ax.hist(data_cr[cn][data_cr["Class"]==0],bins=100,normed = True) 
    data_cr[cn][data_cr["Class"]==1].plot(kind = 'kde',ax = ax)
    data_cr[cn][data_cr["Class"]==0].plot(kind = 'kde',ax = ax)
    ax.set_title("直方图分布"+str(cn),fontproperties = 'SimHei')
#我们将选择在不同信用卡状态下的分布有明显区别的变量。因此剔除变量V8、V13 、V15 、V20 、V21 、V22、 V23 、V24 、V25 、V26 、V27 和V28变量
#删除相关变量
droplist=["V8","V13","V15","V20","V21","V22","V23","V24","V25","V26","V27","V28"]#提成相关性较弱的12列,还剩下19列
data_new=data_cr.drop(droplist,axis=1)
#特征缩放
#Amount变量和Time变量的取值范围与其他变量相差较大,所以要对其进行特征缩放
col=["Amount","Time"]
from sklearn.preprocessing import StandardScaler
data_new[col]=StandardScaler().fit_transform(data_new[col])
data_new.head()
#对特征的重要性进行排序,以进一步减少变量
#构造X和Y变量
x_val=data_new.iloc[:,:-1]
y_val=data_new.iloc[:,-1]
#利用GBDT梯度提升决策树进行特征重要性排序
from sklearn.ensemble import GradientBoostingClassifier as GDBT
clf=GDBT()
clf.fit(x_val,y_val)
#排序可视化
# plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (12,6)
importance=clf.feature_importances_
feature_name=data_new.columns[:-1]
indices=np.argsort(importance)[::-1]
fig = plt.figure(figsize=(20,6))
plt.title("Feature importances by GDBTClassifier")
plt.bar(range(len(importance)),importance[indices],color="blue",align="center")
plt.xticks(range(len(importance)),feature_name[indices],rotation='vertical',fontsize=14)
plt.xlim([-1, len(indices)])
排序.png
#删除次要属性
droplist1=['V16','Time','V7','V5','V4','V19','V11','V1','Amount']
data_new1=data_new.drop(droplist1,axis=1)
九.模型训练

逻辑斯蒂回归
SMOTE(Synthetic Minority Oversampling Technique),SMOET的基本原理是:采样最邻近算法,计算出每个少数类样本的K个近邻,从K个近邻中随机挑选N个样本进行随机线性插值,构造新的少数样本,同时将新样本与原数据合成,产生新的训练集。

#SMOTE过采样
#重新构造X变量和Y变量
x_all=data_new1.iloc[:,:-1]
y_all=data_new1.iloc[:,-1]

X_train,X_test,Y_train,Y_test=train_test_split(x_val,y_val,test_size=0.3)
n_samples=len(Y_train)
pos_samples=Y_train[Y_train==1].shape[0]
print("过采样之前被盗刷所占的比例{:.2%}".format(pos_samples/n_samples))
#为了保证预测的数据分布的真实性,所以我们只在训练集上进行过采样处理
X_train_new,Y_train_new=SMOTE(random_state=12).fit_sample(X_train,Y_train)
n_samples_new=len(Y_train_new)
pos_samples_new=Y_train_new[Y_train_new==1].shape[0]
print("过采样之后被盗刷所占的比例{:.2%}".format(pos_samples_new/n_samples_new))
#百分比饼图
fig,ax=plt.subplots(1,2,figsize=(12,8))
plt.style.use('seaborn-darkgrid')
from pylab import mpl#用于显示中文
mpl.rcParams['font.sans-serif'] = ['SimHei']   # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False          # 解决保存图像是负号'-'显示为方块的问题
data_cr["Class"].value_counts().plot(kind="pie",ax=ax[0],fontsize=23,autopct='%1.2f%%')
ax[0].set_title("SMOTE采样之前的频率分布饼图")
pd.Series(Y_train_new).value_counts().plot(kind="pie",ax=ax[1],fontsize=23,autopct='%1.2f%%')#长度为1,保留百分号前面的2个小数点
ax[1].set_title("SMOTE采样之后的频率分布饼图")
ax[1].set_ylabel("Class")
plt.savefig("./smote.jpg")
百分比.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)

    threshold = 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] > threshold else "black")#若对应格子上面的数量不超过阈值则,上面的字体为白色,为了方便查看

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
#单独的逻辑回归求得查全率Recall rate
from sklearn.linear_model import LogisticRegression
lg_clf=LogisticRegression()
lg_clf.fit(X_train_new,Y_train_new)
lg_pred=lg_clf.predict(X_test)
cnf_matrix_lg = confusion_matrix(Y_test,lg_pred)  # 生成混淆矩阵
np.set_printoptions(precision=2)#精确到两位小数点
# Plot non-normalized confusion matrix
class_names = [0,1]
plt.figure(figsize=(6,4))
plt.subplot(111)
plot_confusion_matrix(cnf_matrix_lg
                      , classes=class_names
                      , title='logit_Confusion matrix,recall is {:.4f}'.format(cnf_matrix_lg[1,1]/(cnf_matrix_lg[1,0]+cnf_matrix_lg[1,1])))
print("逻辑回归在测试集上的查全率(Recall rate): ", cnf_matrix_lg[1,1]/(cnf_matrix_lg[1,0]+cnf_matrix_lg[1,1]))
plt.savefig(r"./逻辑回归.jpg",dpi=600)
plt.show()
Recall rate.png
#利用GridSearchCV进行交叉验证和模型参数自动调优
#利用逻辑回归算法分类
from sklearn.linear_model import LogisticRegression
para_logit= {'C': [100,1,10]}#候选参数集
clf=GridSearchCV(LogisticRegression(dual=True),param_grid=para_logit,cv=10,iid=False,n_jobs=-1)#构建分类器,10折交叉验证
clf.fit(X_train_new,Y_train_new)#使用训练集进行训练
print("最佳参数组合: {}".format(clf.best_params_))
print("最佳交叉验证拟合分数{:.5f}".format(clf.best_score_))
#预测
y_pred = clf.predict(X_test)
print("预测集的准确率: {:.5f}".format(accuracy_score(Y_test, y_pred,)))
#结果可视化
# Compute confusion matrix
cnf_matrix_lg = confusion_matrix(Y_test,lg_pred)  # 生成混淆矩阵
cnf_matrix_gd = confusion_matrix(Y_test,y_pred)  # 生成混淆矩阵
# Plot non-normalized confusion matrix
class_names = [0,1]
plt.figure(figsize=(16,8))
plt.subplot(121)
plot_confusion_matrix(cnf_matrix_lg
                      , classes=class_names
                      , title='lg_Confusion matrix,recall is {:.4f}'.format(cnf_matrix_lg[1,1]/(cnf_matrix_lg[1,0]+cnf_matrix_lg[1,1])))
plt.subplot(122)
plot_confusion_matrix(cnf_matrix_gd
                      , classes=class_names
                      , title='GridSearchCV_Confusion matrix,recall is {:.4f}'.format(cnf_matrix_gd[1,1]/(cnf_matrix_gd[1,0]+cnf_matrix_gd[1,1])))
print("逻辑回归在测试集上的查全率(Recall rate): ", cnf_matrix_lg[1,1]/(cnf_matrix_lg[1,0]+cnf_matrix_lg[1,1]))
print("GridSearchCV在测试集上的查全率(Recall rate): ", cnf_matrix_gd[1,1]/(cnf_matrix_gd[1,0]+cnf_matrix_gd[1,1]))
plt.show()
可视化.png
十.模型评估
#考虑设置阈值,来调整预测被盗刷的概率,依次来调整模型的查全率(Recall)
from itertools import cycle
y_pred_proba=clf.predict_proba(X_test)
thresholds=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]#设定不同的阈值,本来原始的阈值是0.5
plt.figure(figsize=(12,8))
j=1
re=[]
pr=[]
a=[]
for i in thresholds:
    y_test_predictions_high_recall=y_pred_proba[:,1]>i#将预测为被盗刷的概率值与阈值作比较,大于阈值则预测为被盗刷,小于阈值则预测为正常
    plt.subplot(3,3,j)
    j+=1
    # Compute confusion matrix
    cnf_matrix1 = confusion_matrix(Y_test, y_test_predictions_high_recall)
    fpr, tpr, _ = roc_curve(Y_test, y_test_predictions_high_recall)
    area = auc(fpr, tpr)
    recall_rate=cnf_matrix1[1,1]/(cnf_matrix1[1,0]+cnf_matrix1[1,1])#查全率
    precision_rate=(cnf_matrix1[1,1]+cnf_matrix1[0,0])/(cnf_matrix1.sum())#准确率
    print("When threshold is {0},  Recall rate is {1:0.5f},  AUC is {2:.5f}".format(i, recall_rate,area))
    # Plot non-normalized confusion matrix
    class_names = [0,1]
    plot_confusion_matrix(cnf_matrix1, classes=class_names,title="threshold>{}".format(i)) 
    re.append(recall_rate)
    pr.append(precision_rate)
    a.append(area)
plt.savefig('./模型评估.jpg')
查全率.png
#趋势图
plt.figure(figsize=(8,6))
plt.plot(thresholds,re,label="recall_rate")
plt.plot(thresholds,pr,label="precision_rate")
plt.plot(thresholds,a,label="Value of AUC")
plt.legend(fontsize=23)
plt.xlabel("threshold",fontsize=19)
plt.ylabel("value",fontsize=19)
plt.title("Recall, Precision rate and thresholds")
plt.show()
趋势图.png

找出模型最优的阈值:
precision和recall是一组矛盾的变量。从上面混淆矩阵和PRC曲线可以看到,阈值越小,recall值越大,模型能找出信用卡被盗刷的数量也就更多,但换来的代价是误判的数量也较大。随着阈值的提高,recall值逐渐降低,precision值也逐渐提高,误判的数量也随之减少。通过调整模型阈值,控制模型反信用卡欺诈的力度,若想找出更多的信用卡被盗刷就设置较小的阈值,反之,则设置较大的阈值。
实际业务中,阈值的选择取决于公司业务边际利润和边际成本的比较;当模型阈值设置较小的值,确实能找出更多的信用卡被盗刷的持卡人,但随着误判数量增加,不仅加大了贷后团队的工作量,也会降低正常情况误判为信用卡被盗刷客户的消费体验,从而导致客户满意度下降,如果某个模型阈值能让业务的边际利润和边际成本达到平衡时,则该模型的阈值为最优值。当然也有例外的情况,发生金融危机,往往伴随着贷款违约或信用卡被盗刷的几率会增大,而金融机构会更愿意设置小阈值,不惜一切代价守住风险的底线。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容