欺诈检测

背景介绍

数据集包含欧洲持卡人于2013年9月通过信用卡进行的交易。这个数据集显示了两天内发生的交易,在284,807笔交易中我们有492笔诈骗。
数据集非常不平衡,正面类(欺诈)占所有交易的0.172%。
数据集只包含数值输入变量,这是PCA变换的结果。不幸的是,由于保密问题,我们无法提供有关数据的原始特征和更多背景信息。特征V1,V2,... V28是使用PCA获得的主要组件,没有用PCA转换的特征是“时间”和“金额”。“时间”包含数据集中每个事务和第一个事务之间经过的秒数。“金额”是交易额,此特征可用于基于样本的成本灵敏度学习。特征'类'是响应变量,如果发生欺诈,则取值1,否则为0。

初步分析

我们的目的是通过训练得到一个模型,这个模型通过特征变量能识别出该笔交易是否发生欺诈。
由背景介绍可知:

  1. 正反样本分布极度不平衡,可能对预测存在影响,需要衡量采用过采样还是下采样来解决这个问题。
  2. 数据集已经过PCA变换,相对干净,可以将重点放在建模分析上。
  3. 一般评价模型我们用的准确度,但是结合实际业务,在准确度很高的情况下可能FN很高但是TP很低,翻译一下就是欺诈识别能力不怎么样,这不符合我们的预期。我们希望考察模型的欺诈识别能力,同时也兼顾模型的准确度,所以我们考虑用Recall指标:TP/(TP+FN)

数据预处理

由于数据已相对干净,这里我们着重考虑样本平衡问题。
首先还是处理一下“amount”变量,做一个变换让变量值落在[-1,1]的区间内。

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler

data = pd.read_csv("creditcard.csv")

data['normAmount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1))
data = data.drop(['Time','Amount'],axis=1)

现在我们考虑样本平衡问题,要让样本平衡很容易想到的一个方法就是反面样本集中抽取和正面样本集数量一致的样本形成新的反面样本集,也就是所谓的下采样方法。

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,:]
print(u"正常样本比例: ", len(under_sample_data[under_sample_data.Class == 0])/len(under_sample_data))
print(u"欺诈样本比例: ", len(under_sample_data[under_sample_data.Class == 1])/len(under_sample_data))
print(u"下采样总样本数: ", len(under_sample_data))

#正常样本比例:  0.5
#欺诈样本比例:  0.5
#总样本数:  984

下采样已经完成,可以切分数据集准备建模了。

X_undersample = under_sample_data.loc[:, under_sample_data.columns != 'Class']
y_undersample = under_sample_data.loc[:, under_sample_data.columns == 'Class']
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)

建模分析

分类算法我们先考虑业界的流行算法——逻辑回归。
确定特征、确定模型之后,我们还需要考虑的就是模型的参数。利用交叉验证法我们来选一下逻辑回归的正则化惩罚力度参数。

#逻辑回归的参数选择
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 = pd.DataFrame(index = range(len(c_param_range),1), columns = ['C_parameter','Mean recall score'])
    results['C_parameter'] = c_param_range

    # k-fold 后, indices[0]作为训练集, indices[1]作为测试集
    j = 0
    for c_param in c_param_range:
        print('-------------------------------------------')
        print('C parameter: ', c_param)
        print('-------------------------------------------')
        print('')

        recall_accs = []
        for iteration, indices in enumerate(fold.split(x_train_data,y_train_data),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值
            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)

        # The mean value of those recall scores is the metric we want to save and get hold of.
        results_table.ix[j,'Mean recall score'] = np.mean(recall_accs)
        j += 1
        print('')
        print('Mean recall score ', np.mean(recall_accs))
        print('')

    #选出分数最高的参数C
    best_c = results_table.loc[results_table['Mean recall score'].idxmax()]['C_parameter']
    
    # Finally, we can check which C parameter is the best amongst the chosen.
    print('*********************************************************************************')
    print('Best model to choose from cross validation is with C parameter = ', best_c)
    print('*********************************************************************************')
    
    return best_c

这组参数中c=0.01时表现最好,下采样测试集上的recall为 0.938775510204,暂取c=0.01。(PS:0.01不是最佳参数,只是这一组中表现最好的)
看看模型在整个测试集上的表现。

import itertools
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size = 0.3, random_state = 0)
#建模预测
lr = LogisticRegression(C = best_c, penalty = 'l1')
lr.fit(X_train,y_train.values.ravel())
y_pred= lr.predict(X_test.values)

#混肴矩阵
cnf_matrix = confusion_matrix(y_test,y_pred)
np.set_printoptions(precision=2)

print("基于测试集的Recall: ", 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()

recall为0.925170068027,表现良好。
所谓没有对比就没有伤害,我们来看看没有经过下采样处理的情况。
将输入改成整个数据集,再做一次参数选择。

best_c = printing_Kfold_scores(X_train,y_train)

这回选的是10,且recall为0.61847902217。
结果说明下采样处理能够显著提高欺诈识别能力。

过采样

上文提到除了下采样我们还可以采用过采样,也就是我们构造数据使欺诈样本和正常样本保持平衡。
这里我们采用过采样中的经典算法SMOTE。

oversampler=SMOTE(random_state=0)
X_oversample,y_oversample=oversampler.fit_sample(X_train,y_train)
X_oversample = pd.DataFrame(X_oversample)
y_oversample = pd.DataFrame(y_oversample)
best_c = printing_Kfold_scores(X_oversample,y_oversample)
lr = LogisticRegression(C = best_c, penalty = 'l1')
lr.fit(X_oversample,y_oversample.values.ravel())
y_pred = lr.predict(X_test.values)

cnf_matrix = confusion_matrix(y_test,y_pred)
np.set_printoptions(precision=2)

print("基于测试集的Recall: ", 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()

c选择100,recall为0.918367346939。

小结

对比下采样和过采样,两者的recall指标相差不远,但是下采样的误杀率明显高于过采样,因此在处理样本不平衡问题时,SMOTE是被广泛采用用的手段之一。


下采样

过采样

综上,在解决欺诈检测类问题时,样本不平衡问题可能是我们无法避免的问题,一方面欺诈本就属于不常见样本,缺乏历史数据,和安全类软件的病毒检测处于类似的境地;另一方面,参考安全问题,我们目前解决的还是根据历史经验解决欺诈问题,面对越来越复杂的环境,我们可能需要更多的预防手段,仅仅依赖历史数据可能还不够。



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

推荐阅读更多精彩内容