1. 程式人生 > >python 機器學習實戰:信用卡欺詐異常值檢測

python 機器學習實戰:信用卡欺詐異常值檢測

    今晚又實戰了一個小案例,把它總結出來:有些人利用信用卡進行詐騙等活動,如何根據使用者的行為,來判斷該使用者的信用卡賬單涉嫌欺詐呢?資料集見及連結:  在這個資料集中,由於原始資料有一定的隱私,因此,每一列(即特徵)的名稱並沒有給出。

    一開始,還是匯入庫:

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

data = pd.read_csv('creditcard.csv')
#data.head(10)
print (data.shape)

我們發現,最後一列"Class"要麼是 0 ,要麼是 1,而且是 0 的樣本很多,1 的樣本很少。這也符合正常情況:利用信用卡欺詐的情況畢竟是少數,大多數人都是守法的公民。由於樣本太多了,我們不可能去數 0 和 1 的個數到底是多少。因此,需要做一下統計:

count_class = pd.value_counts(data['Class'],sort = True).sort_index()
print (count_class)

輸出結果為:

0 284315
1 492
Name: Class, dtype: int64

結果也顯示了,0的個數遠遠多於1的個數。那麼,這樣就會使正負樣本的個數嚴重失衡。為了解決這個問題,我們需要針對樣本不均衡,提出兩種解決方案:過取樣和下采樣。

      在對樣本處理之前,我們需要對樣本中的資料先進行處理。我們發現,有一列為 Time ,這一列顯然與咱們的訓練結果沒有直接或間接關係,因此需要把這一列去掉。我們還發現,在 v1 ~v28特徵中,取值範圍大致在 -1 ~ +1 之間,而Amount 這一類數值非常大,如果我們不對這一列進行處理,這一列對結果的影響,可能非常巨大。因此,要對這一列做標準化:使均值為0,方差為1。 此部分程式碼如下:

from sklearn.preprocessing import StandardScaler    #匯入資料預處理模組
data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))   # -1表示系統自動計算得到的行,1表示1列
data = data.drop(['Time','Amount'],axis = 1)  # 刪除兩列,axis =1表示按照列刪除,即刪除特徵。而axis=0是按行刪除,是刪除樣本
#print (data.head(3))
#print (data['normAmount'])

     解決樣本不均衡:兩種方法可以採用——過取樣和下采樣。過取樣是對少的樣本(此例中是類別為 1 的樣本)再多生成些,使 類別為 1 的樣本和 0 的樣本一樣多。而下采樣是指,隨機選取類別為 0 的樣本,是類別為 0 的樣本和類別為 1 的樣本一樣少。下采樣的程式碼如下:

X = data.ix[:,data.columns != 'Class'] #ix 是通過行號和行標籤進行取值
y = data.ix[:,data.columns == 'Class']    # y 為標籤,即類別
number_records_fraud = len(data[data.Class==1])  #統計異常值的個數
#print (number_records_fraud)  # 492 個
#print (data[data.Class == 1].index)
fraud_indices = np.array(data[data.Class == 1].index)   #統計欺詐樣本的下標,並變成矩陣的格式
#print (fraud_indices)
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)
#print (len(random_normal_indices))  #492 個

under_sample_indices = np.concatenate([fraud_indices,random_normal_indices])  # 將正負樣本的索引進行組合
#print (under_sample_indices)   # 984個
under_sample_data = data.iloc[under_sample_indices,:]  # 按照索引進行取值
X_undersample = under_sample_data.iloc[:,under_sample_data.columns != 'Class']  #下采樣後的訓練集
y_undersample = under_sample_data.iloc[:,under_sample_data.columns == 'Class']   #下采樣後的標籤

print (len(under_sample_data[under_sample_data.Class==1])/len(under_sample_data)) # 正負樣本的比例都是 0.5

      下一步:交叉驗證。交叉驗證可以輔助調參,使模型的評估效果更好。舉個例子,對於一個數據集,我們需要拿出一部分作為訓練集(比如 80%),計算我們需要的引數。需要拿出剩下的樣本,作為測試集,評估我們的模型的好壞。但是,對於訓練集,我們也並不是一股腦地拿去訓練。比如把訓練集又分成三部分:1,2,3.第 1 部分和第 2 部分作為訓練集,第 3 部分作為驗證集。然後,再把第 2 部分和第 3 部分作為訓練集,第 1 部分作為驗證集,然後,再把第 1部分和第 3 部分作為訓練集,第 2 部分作為驗證集。

from sklearn.cross_validation import train_test_split   # 匯入交叉驗證模組的資料切分
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size = 0.3,random_state = 0) # 返回 4 個值
#print (len(X_train)+len(X_test))
#print (len(X))

X_undersample_train,X_undersample_test,y_undersample_train,y_undersample_train = train_test_split(X_undersample,y_undersample,test_size = 0.3,random_state = 0)
print (len(X_undersample_train)+len(X_undersample_test))
print (len(X_undersample))

      我們需要注意,對資料進行切分,既要對原始資料進行一定比例的切分,測試時能用到;又要對下采樣後的樣本進行切分,訓練的時候用。而且,切分之前,每次都要進行洗牌。

      下一步:模型評估。利用交叉驗證,選擇較好的引數

#Recall = TP/(TP+FN)
from sklearn.linear_model import LogisticRegression  #
from sklearn.cross_validation import KFold, cross_val_score  #
from sklearn.metrics import confusion_matrix,recall_score,classification_report #

      先說一個召回率 Recall,這個recall 是需要依據問題本身的含義的,比如,本例中讓預測異常值,假設有100個樣本,95個正常,5個異常。實際中,對於異常值,預測出來4個,所以,精度就是 80%。也就是說,我需要召回 5 個,實際召回 4 個。

      引入四個小定義:TP、TN、FP、FN。

TP,即 True Positive ,判斷成了正例,判斷正確;把正例判斷為正例了

TN,即 True negative, 判斷成了負例,判斷正確,這叫去偽;把負例判斷為負例了

FP,即False Positive ,判斷成了正例,但是判斷錯了,也就是把負例判斷為正例了

FN ,即False negative ,判斷成了負例,但是判斷錯了,也就是把正例判斷為負例了

      正則化懲罰項:為了提高模型的泛化能力,需要加了諸如 L1 、L2懲罰項一類的東西,對於這一部分內容,請參見我以前的部落格。

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

    # k折交叉驗證有兩個;列表: train_indices = indices[0], test_indices = 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,start=1):   #迴圈進行交叉驗證

            # Call the logistic regression model with a certain C parameter
            lr = LogisticRegression(C = c_param, penalty = 'l1')  #例項化邏輯迴歸模型,L1 正則化

            # Use the training data to fit the model. In this case, we use the portion of the fold to train the model
            # with indices[0]. We then predict on the portion assigned as the 'test cross validation' with indices[1]
            lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())#  套路:使訓練模型fit模型

            # Predict values using the test indices in the training data
            y_pred_undersample = lr.predict(x_train_data.iloc[indices[1],:].values)# 利用交叉驗證進行預測

            # Calculate the recall score and append it to a list for recall scores representing the current c_parameter
            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('')

    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

執行:
best_c = printing_Kfold_scores(X_train_undersample,y_train_undersample)

上面程式中,我們選擇進行 5 折的交叉驗證,對應地選擇了5個懲罰力度引數:0.01,0.1,1,10,100。不同的懲罰力度引數,得到的 Recall 是不一樣的。 

C parameter: 0.01
-------------------------------------------

Iteration 1 : recall score = 0.958904109589
Iteration 2 : recall score = 0.917808219178
Iteration 3 : recall score = 1.0
Iteration 4 : recall score = 0.972972972973
Iteration 5 : recall score = 0.954545454545

Mean recall score 0.960846151257

-------------------------------------------
C parameter: 0.1
-------------------------------------------

Iteration 1 : recall score = 0.835616438356
Iteration 2 : recall score = 0.86301369863
Iteration 3 : recall score = 0.915254237288
Iteration 4 : recall score = 0.932432432432
Iteration 5 : recall score = 0.878787878788

Mean recall score 0.885020937099

-------------------------------------------
C parameter: 1
-------------------------------------------

Iteration 1 : recall score = 0.835616438356
Iteration 2 : recall score = 0.86301369863
Iteration 3 : recall score = 0.966101694915
Iteration 4 : recall score = 0.945945945946
Iteration 5 : recall score = 0.893939393939

Mean recall score 0.900923434357

-------------------------------------------
C parameter: 10
-------------------------------------------

Iteration 1 : recall score = 0.849315068493
Iteration 2 : recall score = 0.86301369863
Iteration 3 : recall score = 0.966101694915
Iteration 4 : recall score = 0.959459459459
Iteration 5 : recall score = 0.893939393939

Mean recall score 0.906365863087

-------------------------------------------
C parameter: 100
-------------------------------------------

Iteration 1 : recall score = 0.86301369863
Iteration 2 : recall score = 0.86301369863
Iteration 3 : recall score = 0.966101694915
Iteration 4 : recall score = 0.959459459459
Iteration 5 : recall score = 0.893939393939

Mean recall score 0.909105589115

*********************************************************************************
Best model to choose from cross validation is with C parameter = 0.01
*********************************************************************************

 接下來,介紹混淆矩陣。雖然上面的

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')
混淆矩陣的計算:
import itertools
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)

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

結果如下:

Recall metric in the testing dataset: 0.931972789116


我們發現:

lr = LogisticRegression(C = best_c, 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()
Recall metric in the testing dataset: 0.918367346939

執行:

best_c = printing_Kfold_scores(X_train,y_train)

C parameter: 0.01
-------------------------------------------

Iteration 1 : recall score = 0.492537313433
Iteration 2 : recall score = 0.602739726027
Iteration 3 : recall score = 0.683333333333
Iteration 4 : recall score = 0.569230769231
Iteration 5 : recall score = 0.45

Mean recall score 0.559568228405

-------------------------------------------
C parameter: 0.1
-------------------------------------------

Iteration 1 : recall score = 0.567164179104
Iteration 2 : recall score = 0.616438356164
Iteration 3 : recall score = 0.683333333333
Iteration 4 : recall score = 0.584615384615
Iteration 5 : recall score = 0.525

Mean recall score 0.595310250644

-------------------------------------------
C parameter: 1
-------------------------------------------

Iteration 1 : recall score = 0.55223880597
Iteration 2 : recall score = 0.616438356164
Iteration 3 : recall score = 0.716666666667
Iteration 4 : recall score = 0.615384615385
Iteration 5 : recall score = 0.5625

Mean recall score 0.612645688837

-------------------------------------------
C parameter: 10
-------------------------------------------

Iteration 1 : recall score = 0.55223880597
Iteration 2 : recall score = 0.616438356164
Iteration 3 : recall score = 0.733333333333
Iteration 4 : recall score = 0.615384615385
Iteration 5 : recall score = 0.575

Mean recall score 0.61847902217

-------------------------------------------
C parameter: 100
-------------------------------------------

Iteration 1 : recall score = 0.55223880597
Iteration 2 : recall score = 0.616438356164
Iteration 3 : recall score = 0.733333333333
Iteration 4 : recall score = 0.615384615385
Iteration 5 : recall score = 0.575

Mean recall score 0.61847902217

*********************************************************************************
Best model to choose from cross validation is with C parameter = 10.0
*********************************************************************************

我們發現:

lr = LogisticRegression(C = best_c, penalty = 'l1')
lr.fit(X_train,y_train.values.ravel())
y_pred_undersample = lr.predict(X_test.values)

# Compute confusion matrix
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]))

# Plot non-normalized confusion matrix 
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix
                      , classes=class_names
                      , title='Confusion matrix')
plt.show()

結果:

Recall metric in the testing dataset: 0.619047619048


我們發現:

    設定閾值。

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) 

結果:


      換種思路,採用上取樣,進行資料增廣。

import pandas as pd
from imblearn.over_sampling import SMOTE #上取樣庫,匯入SMOTE演算法
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
credit_cards=pd.read_csv('creditcard.csv')

columns=credit_cards.columns
# The labels are in the last column ('Class'). Simply remove it to obtain features columns
features_columns=columns.delete(len(columns)-1)

features=credit_cards[features_columns]
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)# 使 0 和 1 樣本相等
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
best_c = printing_Kfold_scores(os_features,os_labels)

引數選擇的結果:

C parameter: 0.01
-------------------------------------------

Iteration 1 : recall score = 0.890322580645
Iteration 2 : recall score = 0.894736842105
Iteration 3 : recall score = 0.968861347792
Iteration 4 : recall score = 0.957595541926
Iteration 5 : recall score = 0.958430881173

Mean recall score 0.933989438728

-------------------------------------------
C parameter: 0.1
-------------------------------------------

Iteration 1 : recall score = 0.890322580645
Iteration 2 : recall score = 0.894736842105
Iteration 3 : recall score = 0.970410534469
Iteration 4 : recall score = 0.959980655302
Iteration 5 : recall score = 0.960178498807

Mean recall score 0.935125822266

-------------------------------------------
C parameter: 1
-------------------------------------------

Iteration 1 : recall score = 0.890322580645
Iteration 2 : recall score = 0.894736842105
Iteration 3 : recall score = 0.970454796946
Iteration 4 : recall score = 0.96014552489
Iteration 5 : recall score = 0.960596168431

Mean recall score 0.935251182603

-------------------------------------------
C parameter: 10
-------------------------------------------

Iteration 1 : recall score = 0.890322580645
Iteration 2 : recall score = 0.894736842105
Iteration 3 : recall score = 0.97065397809
Iteration 4 : recall score = 0.960343368396
Iteration 5 : recall score = 0.960530220596

Mean recall score 0.935317397966

-------------------------------------------
C parameter: 100
-------------------------------------------

Iteration 1 : recall score = 0.890322580645
Iteration 2 : recall score = 0.894736842105
Iteration 3 : recall score = 0.970543321899
Iteration 4 : recall score = 0.960211472725
Iteration 5 : recall score = 0.960903924995

Mean recall score 0.935343628474

*********************************************************************************
Best model to choose from cross validation is with C parameter = 100.0
*********************************************************************************

列印一下混淆矩陣的結果:

lr = LogisticRegression(C = best_c, 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()
Recall metric in the testing dataset: 0.90099009901

總結: