信用卡欺诈检测 — 逻辑回归

这是一家金融机构的用户信息数据,对于敏感信息已经进行了处理并完成了特征值的提取,本文通过构建逻辑回归模型进行信用卡欺诈的预测

阅读路线

分析思路

  • 1 数据预览及预处理
    1.1数据预览
    1.2数据预处理
  • 2 模型构建
    2.1下采样
    2.2划分训练集和测试集
    2.3模型构建
    2.4参数调节
    2.4.1惩罚力度c
    - 2.4.2混淆矩阵
    - 2.4.3阈值对结果的影响
    2.5用原始数据训练模型
    2.6过采样
    - 2.6.1生成过采样数据
    - 2.6.2用过采样数据进行模型训练
    - 2.6.3过采样数据训练模型的混淆矩阵
  • 3.总结

1 数据预览及预处理

1.1数据预览

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline

data=pd.read_csv('creditcard.csv')
data.head()
数据预览

数据预览

数据量和字段数量

有数据预览可知,该数据共有31个字段,共有284807条数据,其中V1到V28这28个字段是已经处理好的特征,Class字段表示是否有欺诈的分类,其中Amount字段的数值远大于其他字段数值,为了保证特征间重要程度相当,在这里对Amount字段进行标准化,删除无意义字段Time

1.2数据预处理:

# Amount字段标准化
from sklearn.preprocessing import StandardScaler

data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))
# 删除Time字段和元Amount字段
data = data.drop(['Time','Amount'],axis = 1)
data.head()

对Class字段进行计数,统计每个类型的样本数量并做柱状图:

count_classes = pd.value_counts(data['Class'],sort = True).sort_index()
print(count_classes)
count_classes.plot(kind = 'bar')
plt.title('Fraud class histogram')
plt.xlabel('class')
plt.ylabel('Frequency')
label值分布

由上可知,class为0的样本数量远大于class为1的样本,符合异常值数量远小于正常值数量的事实

但是,样本数量不均衡会导致模型的预测效果降低,在这里需要对原始的数据进行处理使得class=1的样本数量和class=0的样本数量一致,可以采用过采样下采样两种方式处理

2 模型构建

2.1下采样

# 对原始数据提取特征值和label值
X = data.ix[:,data.columns != 'Class']
y = data.ix[:,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]

# 提取特征数据和label值
X_undersample = under_sample_data.ix[:,under_sample_data.columns != 'Class']
y_undersample = under_sample_data.ix[:,under_sample_data.columns == 'Class']

# 样本数量比例
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))
下采样后正常和异常的比值

2.2划分训练数据和测试数据

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("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))

# 下采样数据划分
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))
原数据集和下采样数据集划分

由上可知,

原始数据划分后的训练集和测试集数量分别为199364和85443

下采样数据划分后的训练集和测试集数量分别为688和296

2.3模型构建

在异常检测问题中,精度不是衡量模型好坏的指标,因为我们的目的是找出可能的异常值,所以用召回率衡量模型的好坏

# 召回率Recall = TP/(TP+FN) ,异常值检测问题用recall值来评估模型效果
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,c_param):
    _train_data.index.values)
    
    results_table = pd.DataFrame(index = range(5,2),columns = ['C_parameter','Mean recall score'])
    results_table['Mean recall score']=results_table['Mean recall score'].astype('float64')
    
    # kfold 会给出两个列表:train_indices = indices[0], test_indices = indices[1]
    j = 0

    recall_accs = []
    for iteration,indices in enumerate(fold,start=1):# 交叉验证

        # 具体化模型
        lr = LogisticRegression(C = c_param,penalty = 'l1')

        # 训练模型
        lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())

        # 模型预测
        y_pred_undersample = lr.predict(x_train_data.iloc[indices[1],:].values)

        #计算召回分数
        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)

    # 计算每个c值对应的平均召回分数
    results_table.ix[j,'Mean recall score'] = np.mean(recall_accs)
    j +=1
    print('')
    print('Mean recall score:',np.mean(recall_accs))
    print('')

2.4参数调节

2.4.1 惩罚力度c

让c分别取值0.01、0.1、1、10、100
(1)c = 0.01

best_c = printing_KFold_scores(X_train_undersample,y_train_undersample,0.01)
五次迭代的平均召回分数

(2)c = 0.1

best_c = printing_KFold_scores(X_train_undersample,y_train_undersample,0.1)
五次迭代的平均召回分数

(3)c = 1

best_c = printing_KFold_scores(X_train_undersample,y_train_undersample,1)
五次迭代的平均召回分数

(4)c = 10

best_c = printing_KFold_scores(X_train_undersample,y_train_undersample,10)
五次迭代的平均召回分数

(5)c = 100

best_c = printing_KFold_scores(X_train_undersample,y_train_undersample,100)
五次迭代的平均召回分数
  • 由上面模型的召回分数可以知道,当c=10时,模型的召回率最高,即模型的预测效果最好。

  • 同时,我们可以发现,对于相同的c值,用交叉验证每次迭代的召回率相差可达8%,这个现象说明交叉验证是非常必要的,交叉验证可以尽量减少训练数据对结果的影响

2.4.2 混淆矩阵

定义
混淆矩阵(Confusion Matrix),它的本质远没有它的名字听上去那么拉风。矩阵,可以理解为就是一张表格,混淆矩阵其实就是一张表格而已。

以分类模型中最简单的二分类为例,对于这种问题,我们的模型最终需要判断样本的结果是0还是1,或者说是positive还是negative。

我们通过样本的采集,能够直接知道真实情况下,哪些数据结果是positive,哪些结果是negative。同时,我们通过用样本数据跑出分类型模型的结果,也可以知道模型认为这些数据哪些是positive,哪些是negative。

因此,我们就能得到这样四个基础指标,我称他们是一级指标(最底层的):

真实值是positive,模型认为是positive的数量(True Positive=TP)
真实值是positive,模型认为是negative的数量(False Negative=FN):这就是统计学上的第一类错误(Type I Error)
真实值是negative,模型认为是positive的数量(False Positive=FP):这就是统计学上的第二类错误(Type II Error)
真实值是negative,模型认为是negative的数量(True Negative=TN)
将这四个指标一起呈现在表格中,就能得到如下这样一个矩阵,我们称它为混淆矩阵(Confusion Matrix):


混淆矩阵

定义绘制混淆矩阵的函数:

# 定义一个混淆矩阵作图的函数
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')

(1)用下采样数据进行模型训练,同时用下采样测试集数据进行测试,并作出相应的混淆矩阵:

# 测试数据集上
import itertools
lr = LogisticRegression(C = 0.01, penalty = 'l1')
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()
下采样测试集混淆矩阵

由上面混淆矩阵可知,该模型召回率为93%,精度为95%,效果好像很好,但这是对下采样数据的测试集的预测效果,我们应该用原始数据的测试集来衡量这个模型的优劣
(2)用原始数据中的测试集进行评估

# 对原测试数据集进行预测,发现误杀率太高,这是由于下采样的原因
lr = LogisticRegression(C = 0.01, penalty = 'l1')
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()
原始数据测试集混淆矩阵

由原始测试数据的混淆矩阵可以知道,该模型召回率为93%,精度为96%,但是出现了错将8846个正常值误判为异常值的情况,这说明该模型具有较大的误杀率,这就是下采样方法的缺点

2.4.3 阈值对结果的影响

以下采样数据来训练模型并作出不同阈值条件下下采样数据的测试集的混淆矩阵,以便观察阈值对模型的影响,从而选择合适的阈值获得最优模型:

lr = LogisticRegression(C =0.01 , penalty = 'l1')
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
    
    # Compute confusion matrix
    cnf_matrix = confusion_matrix(y_test_undersample,y_test_predictions_high_recall)
    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]
    plot_confusion_matrix(cnf_matrix
                          , classes=class_names
                          , title='Threshold >= %s'%i) 
不同阈值对应的混淆矩阵

由上可知,阈值的设置极大的影响了模型的泛化效果,随着阈值的改变,模型的召回率和精度都会随之变化,我们根据精度和召回率的要求设置最合适的阈值为0.5

2.5用原始数据训练模型

求出c分别取0.01,0.1,1,10,100时,用原始数据训练得到的模型召回分数可得:


c=0.01

c=0.1

c=1

c=10

c=100

可以看出,当用原始数据进行模型训练时,模型的效果远没有下采样的效果好

2.6 过采样

用SMOTE算法生成与class为0数量一样的样本,用生成后的数据来做模型训练和测试

# 导入相关库
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

2.6.1 生成过采样数据

划分特征和label:

credit_cards = pd.read_csv('creditcard.csv')

columns = credit_cards.columns
# 特征列名
features_columns = columns.delete(len(columns)-1)
# 特征值
features = credit_cards[features_columns]
# label值
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)
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
len(os_labels[os_labels==1])
过采集后class为1的样本数量

2.6.2用过采样数据进行模型训练

分别取c为0.01,0.1,1,10,100,用过采样数据训练模型,输出模型召回分数:


c=0.01

c=0.1

c=1

c=10

c=100
  • 之前用下采样数据训练模型时当c=10时效果最好,召回分数为91.8%

  • 由上可知,用过采样数据训练模型时当c=0.01时效果最好,召回分数为92.6%,说明过采样数据训练的模型更好

2.6.3过采样数据训练模型的混淆矩阵

由上面分析可知,使用过采样有一个问题就是对原数据进行测试时,对于正常样本的误杀率比较高,共有8856条正常数据错误的预测为了异常数据,现在用过采样数据模型来对原数据进行预测:

lr = LogisticRegression(C = 0.01, penalty = 'l1')
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)

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()
原始数据测试集混淆矩阵
  • 可以发现,模型召回率为90%,精度为99%,同时误杀的样本数量由下采样的8856个减少为533个,这说明过采样数据训练出的模型效果比下采样数据训练出来的模型更好

3.总结

  • 做异常检测项目时,召回率比精度更能反映模型的好坏,因为模型的目的是找出异常值

  • 样本不均衡会极大地影响模型的预测效果,需要对原始数据进行处理,一般采用过采样和下采样,过采样的效果相较于下采样更好,下采样会出现预测的误杀率较高,所以过采样方法更好

  • 不同的惩罚力度c和不同的阈值会对模型的泛化能力有很大影响,所以需要反复调试以得到最合适的参数,从而获得最优模型

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

推荐阅读更多精彩内容