構建信用卡反欺詐預測模型——機器學習

本項目需解決的問題

本項目通過利用信用卡的歷史交易數據,進行機器學習,構建信用卡反欺詐預測模型,提前發現客戶信用卡被盜刷的事件。

建模思路

項目背景

數據集包含由歐洲持卡人於2013年9月使用信用卡進行交的數據。此數據集顯示兩天內發生的交易,其中284,807筆交易中有492筆被盜刷。數據集非常不平衡,積極的類(被盜刷)佔所有交易的0.172%。

它只包含作爲PCA轉換結果的數字輸入變量。不幸的是,由於保密問題,我們無法提供有關數據的原始功能和更多背景信息。特徵V1,V2,... V28是使用PCA獲得的主要組件,沒有用PCA轉換的唯一特徵是“時間”和“量”。特徵'時間'包含數據集中每個事務和第一個事務之間經過的秒數。特徵“金額”是交易金額,此特徵可用於實例依賴的成本認知學習。特徵'類'是響應變量,如果發生被盜刷,則取值1,否則爲0。

1 場景解析(算法選擇)

1)首先,我們拿到的數據是持卡人兩天內的信用卡交易數據,這份數據包含很多維度,要解決的問題是預測持卡人是否會發生信用卡被盜刷。信用卡持卡人是否會發生被盜刷只有兩種可能,發生被盜刷或不發生被盜刷。又因爲這份數據是打標好的(字段Class是目標列),也就是說它是一個監督學習的場景。於是,我們判定信用卡持卡人是否會發生被盜刷是一個二分類問題,意味着可以通過二分類相關的算法來找到具體的解決辦法,本項目選用的算法是邏輯迴歸(Logistic Regression)。

2)分析數據:數據是結構化數據 ,不需要做特徵抽象。特徵V1至V28是經過PCA處理,而特徵Time和Amount的數據規格與其他特徵差別較大,需要對其做特徵縮放,將特徵縮放至同一個規格。在數據質量方面 ,沒有出現亂碼或空字符的數據,可以確定字段Class爲目標列,其他列爲特徵列。

3)這份數據是全部打標好的數據,可以通過交叉驗證的方法對訓練集生成的模型進行評估。70%的數據進行訓練,30%的數據進行預測和評估。

現對該業務場景進行總結如下

1)根據歷史記錄數據學習並對信用卡持卡人是否會發生被盜刷進行預測,二分類監督學習場景,選擇邏輯斯蒂迴歸(Logistic Regression)算法

2)數據爲結構化數據,不需要做特徵抽象,是否需要做特徵縮放有待後續觀察

2 數據預處理(Pre-processing Data)

前期準備
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cross_validation import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.cross_validation import KFold, cross_val_score
from sklearn.metrics import confusion_matrix,precision_recall_curve,auc,roc_auc_score,roc_curve,recall_score,classification_report 
import itertools
import seaborn as sns
讀取數據以及數據初步探查
data = pd.read_csv('C:/Users/Administrator/Desktop/CredictCard/creditcard.csv')
data.shape
data.head()

該數據共有284807行,31列,其中V1-V28爲結構化數據;特徵Time和Amount的數據規格和其他特徵不一樣,數量級較大。

msno.matrix(data)

數據初步探索

對目標特徵進行初步探索

count_classes.plot(kind='bar')
plt.title('Fraud class histogram')
plt.xlabel('Class')
plt.ylabel('Frequency')

可以看出數據很不均衡:數據不均衡很可能導致我們模型預測結果‘0’時很準確,而預測‘1’時並不準確。

數據不均衡是機器學習中很常見的情況,解決方案有如下幾種:

1)擴大數據樣本

2)改變評價標準,以下標準可以更加深入地洞察模型的準確率

  • 混淆矩陣:將要預測的數據分到表裏來顯示正確的預測(對角線),並瞭解其不正確的預測的類型(哪些類被分配了不正確的預測);
  • 精度:一種分類準確性的處理方法;
  • 召回率:一種分類完整性的處理方法;
  • F1分數(或F-分):精度和召回率的加權平均。

3)對數據重新採樣

過抽樣:可以從代表性不足的類添加實例的副本

抽樣不足:您可以從過度代表類裏刪除實例

4)生成人工樣本(SMOTE)

5)使用不同的算法

6)嘗試名義變量

3 特工程(Feature Engineering)

1)查看盜刷與正常刷卡的刷卡金額分佈圖

f,(ax1,ax2) = plt.subplots(2, 1, sharex=True, figsize=(12,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()

信用卡被盜刷發生的金額與信用卡正常用戶發生的金額相比,比較小。這說明信用卡盜刷者爲了不引起信用卡卡主的注意,更偏向選擇小金額消費

2)正常刷卡與盜刷時間分佈

plt.figure(figsize=[16,4])
sns.distplot(data[data['Class']==1]['Hour'],bins=50)
sns.distplot(data[data['Class']==0]['Hour'],bins=100)

信用正常刷卡與盜刷時間分佈從大體上看並沒有太大差別,由此推測盜刷者爲了減小被識別的風險,將盜刷時間放在正常刷卡時間集中區域。因此建立模型預測時,可以將該特徵過濾。

3)查看其它特徵分佈

plt.figure(figsize=(12,28*4))
v_features = data.ix[:,1:29].columns
gs = gridspec.GridSpec(28, 1)
for i, cn in enumerate(data[v_features]):
    ax = plt.subplot(gs[i])
    sns.distplot(data[data.Class == 1][cn], bins=50)
    sns.distplot(data[data.Class == 0][cn], bins=50)
    ax.set_xlabel('')
    ax.set_title('histogram of feature:' + str(cn))
plt.show()

上圖是不同變量在信用卡被盜刷和信用卡正常的不同分佈情況,我們將選擇在不同信用卡狀態下的分佈有明顯區別的變量。因此剔除變量V8、V13 、V15 、V20 、V21 、V22、 V23 、V24 、V25 、V26 、V27 和V28變量這也與我們開始用相關性圖譜觀察得出結論一致,同時剔除變量Time。

4 模型訓練

1)處理不平衡樣本

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,:]

X_undersample = under_sample_data.ix[:, under_sample_data.columns != 'Class']
y_undersample = under_sample_data.ix[:, under_sample_data.columns == 'Class']

X_train, X_test, y_train, y_test = train_test_split(X,y,test_size = 0.3, random_state = 0)
X_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split(X_undersample
                                                                                                   ,y_undersample
                                                                                                   ,test_size = 0.3)

進行數據重新抽樣,使樣本集比例爲1:1;同時獲取一個原比例的訓練集,用於做模型的對比。

2)訓練模型

在這個模型中,我們可以使用召回率來進行模型評價,該指標能準確的表達我們捕獲盜刷交易的準確度。

精度:Accuracy = (TP+TN)/total

準確率:precision=TP/(TP+FP)

召回率:recall=TP/(TP+FN)

更多詳情查看理解準確率(accuracy)、精度(precision)、查全率(recall)、F1、

a.對模型進行迭代,獲取訓練結果較好的正則化參數

def printing_Kfold_scores(x_train_data,y_train_data):
    fold = KFold(len(y_train_data),5,shuffle=False)
    c_param_range = [0.01,0.1,1,10,100] 
    
    results_table = pd.DataFrame(index=range(len(c_param_range),2), columns=['C_parameter','Mean recall score'])
    results_table['C_parameter'] = c_param_range
    j = 0
    for c_param in c_param_range:
        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)
        results_table.ix[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'].idxmax()]['C_parameter']
    return best_c
Iteration  1 : recall score =  0.952380952381
Iteration  2 : recall score =  0.986111111111
Iteration  3 : recall score =  0.984848484848
Iteration  4 : recall score =  1.0
Iteration  5 : recall score =  0.942857142857

Mean recall score  0.97323953824

Iteration  1 : recall score =  0.904761904762
Iteration  2 : recall score =  0.902777777778
Iteration  3 : recall score =  0.878787878788
Iteration  4 : recall score =  0.9375
Iteration  5 : recall score =  0.871428571429

Mean recall score  0.899051226551

Iteration  1 : recall score =  0.920634920635
Iteration  2 : recall score =  0.902777777778
Iteration  3 : recall score =  0.893939393939
Iteration  4 : recall score =  0.9375
Iteration  5 : recall score =  0.871428571429

Mean recall score  0.905256132756

Iteration  1 : recall score =  0.920634920635
Iteration  2 : recall score =  0.902777777778
Iteration  3 : recall score =  0.893939393939
Iteration  4 : recall score =  0.9375
Iteration  5 : recall score =  0.9

Mean recall score  0.91097041847

Iteration  1 : recall score =  0.920634920635
Iteration  2 : recall score =  0.902777777778
Iteration  3 : recall score =  0.909090909091
Iteration  4 : recall score =  0.9375
Iteration  5 : recall score =  0.914285714286

Mean recall score  0.916857864358

b)選擇C=0.01進行建模,並畫出混淆矩陣

def plot_confusion_matrix(cm, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Blues):
    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)
    
    if normalize:
        cm = cm.astype('float')/cm.sum(axis=1)[:,np.newaxis]
    else:
        print('Confusion matrix, without normalization')
    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')
    
#畫出重取樣數據的混淆矩陣
lr = LogisticRegression(C = best_c, penalty = 'l1')
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)
print("Recall metric in the testing dataset: ", 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()
#畫出原數據的混淆矩陣
lr = LogisticRegression(C = best_c, penalty = 'l1')
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)
print("Recall metric in the testing dataset: ", 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()

該模型在重採樣數據上獲得了0.9554的召回率,在原數據上獲得了 0.9319的召回率。總體來說,這是一個不錯的數字

c.畫出ROC曲線,對模型進行評估

lr = LogisticRegression(C = best_c, penalty = 'l1')
y_pred_undersample_score = lr.fit(X_train_undersample,y_train_undersample.values.ravel()).decision_function(X_test_undersample.values)
fpr, tpr, thresholds = roc_curve(y_test_undersample.values.ravel(),y_pred_undersample_score)
roc_auc = auc(fpr,tpr)
# Plot ROC
plt.title('Receiver Operating Characteristic')
plt.plot(fpr, tpr, 'b',label='AUC = %0.2f'% roc_auc)
plt.legend(loc='lower right')
plt.plot([0,1],[0,1],'r--')
plt.xlim([-0.1,1.0])
plt.ylim([-0.1,1.01])
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.show()

一般來說,如果ROC是光滑的,那麼基本可以判斷沒有太大的overfitting,但是由於數據不均衡,只用ROC曲線來進行評估並不準確。

d.直接使用原數據進行模型訓練並進行評估

best_c = printing_Kfold_scores(X_train,y_train)
lr = LogisticRegression(C = best_c, penalty = 'l1')
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)
print("Recall metric in the testing dataset: ", 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()
lr = LogisticRegression(C = best_c, penalty = 'l1')
y_pred_score = lr.fit(X_train,y_train.values.ravel()).decision_function(X_test.values)
fpr, tpr, thresholds = roc_curve(y_test.values.ravel(),y_pred_score)
roc_auc = auc(fpr,tpr)

plt.title('Receiver Operating Characteristic')
plt.plot(fpr, tpr, 'b',label='AUC = %0.2f'% roc_auc)
plt.legend(loc='lower right')
plt.plot([0,1],[0,1],'r--')
plt.xlim([-0.1,1.0])
plt.ylim([-0.1,1.01])
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.show() 

可以看直接使用模型訓練並不能獲得很好的召回率,也就是說並不能很好的辨別盜刷交易。然後該模型在ROC曲線仍然有很好的表現,因此應更好的評估方法來評估模型。

總結:

1.在數據不均衡的機器學習模型中,應根據具體情況選擇是否重新調整數據集比例。一般說來precision與recall是評價模型兩個不同角度。例如:對於地震的預測,我們希望的是RECALL非常高,也就是說每次地震我們都希望預測出來。這個時候我們可以犧牲PRECISION。情願發出1000次警報,把10次地震都預測正確了;也不要預測100次對了8次漏了兩次。在嫌疑人定罪方面,基於不錯怪一個好人的原則,對於嫌疑人的定罪我們希望是非常準確的。及時有時候放過了一些罪犯(recall低),但也是值得的。但是有時候錯判是有成本的,應根據具體情況具體選擇。

2.原始數據集剝離了業務場景,因此在進行數據分析時,並不能看到盜刷交易的具體特徵。在具體場景中,每個信用卡用戶都具有自己的消費特性,如常用消費地點,消費時間,消費金額,可以根據這些特徵倆進行建模



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章