機器學習 - 案例 - 樣本不均衡數據分析 - 信用卡詐騙 ( 標準化處理, 數據不均處理, 交叉驗證, 評估, Recall值, 混淆矩陣, 閾值 )

案例背景 

銀行評判用戶的信用考量規避信用卡詐騙數組

▒ 數據

數據共有 31 個特徵, 爲了安全起見數據已經向了模糊化處理沒法讀出真實信息目標安全

其中數據中的 class 特徵標識爲是否正經常使用戶 (0 表明正常, 1 表明異常)app

▒ 目標

本質依舊是一個分類問題, 0/1 的問題判斷是否爲信用卡詐騙用戶dom

而在數據中 class 已經進行標識, 並且此次的樣本數據的兩項結果是極度的不均衡機器學習

既正經常使用戶的樣本數量是遠遠大於異常數據的.函數

不均衡的數據處理方式能夠進行 下采樣, 或者上採樣學習

下采樣 -  對多的數據進行消減到和少的數據同樣少測試

上採樣 -  對少的數據進行填充到和多的數據同樣多spa

案例統計

▒ 準備 - 三件套

1 import pandas as pd
2 import matplotlib.pyplot as plt
3 import numpy as np

▒ 樣本數據查看 

▒ 異常數量統計

count_classes = pd.value_counts(data['Class'], sort = True).sort_index()  # 某一列的查詢按照數據排序
count_classes.plot(kind = 'bar') # 條形圖
plt.title("Fraud class histogram") # 標題
plt.xlabel("Class") # x 軸
plt.ylabel("Frequency") # y 軸

能夠看出正經常使用戶多達28w, 而異常樣本大概有幾百個左右, 3d

數據預處理 - 標準化

▒ 概念  

機器學習的常規認知是對較大的特徵數值基於更大的影響, 所以當特徵的的取值大小之間的不一樣,

會致使認爲較大數值的特徵比較小數值特徵的影響更大, 所以須要對數據特徵進行歸一化處理

好比都限制在取值在 0-1 之間這樣機器學習會將全部的特徵按照統一的標準進行考量

▒ 操做代碼 

from sklearn.preprocessing import StandardScaler 

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

▨ 詳解

引用  sklearn  模塊進行預處理操做

reshape  方法進行維度的重處理

  取值  (-1, 1)  中的 -1 表示系統自動判斷, 後面是提供參考值 (  好比原來是 2,3 的維度,  你輸入 (-1,1) 則表示轉換爲  ( 6,1 ), 系統對 -1 進行自動計算 )

而後生成的新特徵填充到樣本數據中, 刪除掉轉換前的特徵, 以及沒有用的特徵

 fit_transform  對數據進行歸一化處理, 具體流程爲先擬合後進行歸一處理

下采樣

▒ 概念

下采樣的方法爲將多的那類數據樣本降和少的那類同樣的少

▨ 操做代碼

from sklearn.linear_model import LogisticRegression # 邏輯迴歸計算
from sklearn.model_selection import KFold # 交叉驗證計算
from sklearn.model_selection import cross_val_score 
from sklearn.metrics import confusion_matrix 
from sklearn.metrics import recall_score # 召回率計算
from sklearn.metrics import classification_report
X = data.ix[:, data.columns != 'Class'] # 除了 "Class" 列的其餘列的全部數據
y = data.ix[:, data.columns == 'Class'] # "Class" 列的數據

number_records_fraud = len(data[data.Class == 1]) # 全部的異常(class == 1)的數據的個數
fraud_indices = np.array(data[data.Class == 1].index) # 全部的異常(class == 1)的數據的索引

normal_indices = data[data.Class == 0].index # 全部的正常(class == 0)數據的索引

# np 的隨機模塊進行選擇 參數:( 被選容器(正常數據), 個數(異常數據個數), 是否代替(不代替) )
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 , y 
X_undersample = under_sample_data.ix[:, under_sample_data.columns != 'Class']
y_undersample = under_sample_data.ix[:, under_sample_data.columns == 'Class']

將異常數據的個數計算出來後, 而後在政策數據集中隨機篩選出異常數據集個數的數據, 而後組合爲新的數據集

從而保證異常數據集和正常數據集爲 1:1 比例

▨ 結果

# Showing ratio
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))

▒ 交叉驗證

▨ 概念

數據集在最開始的時候會劃分爲 訓練集測試集

訓練集用於建模, 測試集用於對模型進行驗證

而建模階段訓練集一般會進行 n 等分, 而後彼此再次劃分 訓練集和測試集

目的是爲了獲取正確的參數, 從而須要進行屢次的訓練集和測試集的互換從而交叉驗證 

▨ 劃分數據集

from sklearn.model_selection import train_test_split

# 原始數據集切分數據 - 0.3 的測試集, 0.7 的訓練集 , 設定隨機參數
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))

# 下采樣數據集切分數據 - 0.3 的測試集, 0.7 的訓練集 , 設定隨機參數
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))

評估方法

▒ 精度

一般的評估方法中可使用精度計算

精度計算的評估方法很大程度取決於樣本數據自己的狀況

尤爲是不對稱的數據中會形成精度計算的極大不許確性

如圖所示, 如1000 數據中 990 數據爲正樣本數據, 則計算出的結果則爲 99.9% 則負樣本預測不出來. 實則無用

▒ Recall 

根據目標來指定標準, 好比 1000數據中的異常數據爲 10 , 目標則是找出異常數據

則根據檢測出的異常數據於原有的異常數據進行比對來判斷計算 recall

▨ 概念

  兩個維度, 1 是否符合預期 (P/N) , 2 判斷是否正確 (T/F)

  • TP - 目標找女生, 找出女生判斷是女生 - 符合預期 ( 正例 ),  正確判斷
  • FP - 目標找女生, 找出男生判斷爲女生 - 符合預期 ( 正例 ),  錯誤判斷
  • FN - 目標找女生, 找出女生判斷爲男生 - 不符合預期 ( 負例 ),  錯誤判斷
  • TN - 目標找女生, 找出男生判斷爲男生 - 不符合預期 ( 負例 ),  正確判斷

▨ 公式

Recall = TP/(TP+FN)

正則化懲罰

▒ 概念 

 

在不一樣的模型中可能存在最終的 Recall 都同樣的狀況, 若是 Recall 相同的是否能夠理解爲兩個模型效果相同?

可是模型本質可能仍是存在不一樣, 所以須要在另外一個維度進行分析, 圖示的 A 和 B 的參數可見實際上是不一樣的

A 的浮動明顯很大, B 的浮動小不少, 浮動過大多是過擬合的問題致使, 而此時引入一個懲罰概念進行篩選

對浮動過大的進行懲罰更大, 由此進行區分, 懲罰方式能夠選擇 L1/L2 等, 具體的原酸都是加入一個值進行區分

懲罰粒度也能夠限制從  0.1 ,1,10,100 不等, 而這個粒度則須要交叉驗證進行選擇, 既比對參數 

▒ 操做代碼

def printing_Kfold_scores(x_train_data,y_train_data):
    fold = KFold(5,shuffle=False) # 劃分爲 5 等分, 即 5次交叉驗證, shuffle 不洗牌

    # 默認的懲罰粒度參數容器
    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

    # the k-fold will give 2 lists: 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.split(x_train_data)): # 交叉驗證

            # 創建邏輯迴歸模型實例化, 傳入懲罰項粒度, 以及懲罰模式 能夠選擇 l1 或者 l2 
            # solver='liblinear' 是爲了不 FutureWarning 提示
            lr = LogisticRegression(C = c_param, penalty = 'l1',solver='liblinear')

            # 訓練模型 
            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.loc[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'].values.argmax()]['C_parameter']
    
    # 打印最好的選擇
    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次不洗牌的交叉驗證後,  爲 0.01 的 recall 值最高, 爲最優參數

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

Iteration  0 : recall score =  0.958904109589041
Iteration  1 : recall score =  0.9178082191780822
Iteration  2 : recall score =  1.0
Iteration  3 : recall score =  0.9594594594594594
Iteration  4 : recall score =  0.9848484848484849

Mean recall score  0.9642040546150135

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

Iteration  0 : recall score =  0.8493150684931506
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9152542372881356
Iteration  3 : recall score =  0.918918918918919
Iteration  4 : recall score =  0.8939393939393939

Mean recall score  0.8880882634539471

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

Iteration  0 : recall score =  0.863013698630137
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9661016949152542
Iteration  3 : recall score =  0.9459459459459459
Iteration  4 : recall score =  0.9090909090909091

Mean recall score  0.9094331894424765

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

Iteration  0 : recall score =  0.863013698630137
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9830508474576272
Iteration  3 : recall score =  0.9324324324324325
Iteration  4 : recall score =  0.9242424242424242

Mean recall score  0.9131506202785514

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

Iteration  0 : recall score =  0.863013698630137
Iteration  1 : recall score =  0.863013698630137
Iteration  2 : recall score =  0.9830508474576272
Iteration  3 : recall score =  0.9324324324324325
Iteration  4 : recall score =  0.9242424242424242

Mean recall score  0.9131506202785514

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

▨ 對比正常數據直接操做

best_c = printing_Kfold_scores(X_train,y_train)
-------------------------------------------
C parameter:  0.01
-------------------------------------------

Iteration  0 : recall score =  0.4925373134328358
Iteration  1 : recall score =  0.6027397260273972
Iteration  2 : recall score =  0.6833333333333333
Iteration  3 : recall score =  0.5692307692307692
Iteration  4 : recall score =  0.45

Mean recall score  0.5595682284048672

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

Iteration  0 : recall score =  0.5671641791044776
Iteration  1 : recall score =  0.6164383561643836
Iteration  2 : recall score =  0.6833333333333333
Iteration  3 : recall score =  0.5846153846153846
Iteration  4 : recall score =  0.525

Mean recall score  0.5953102506435158

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

Iteration  0 : recall score =  0.5522388059701493
Iteration  1 : recall score =  0.6164383561643836
Iteration  2 : recall score =  0.7166666666666667
Iteration  3 : recall score =  0.6153846153846154
Iteration  4 : recall score =  0.5625

Mean recall score  0.612645688837163

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

Iteration  0 : recall score =  0.5522388059701493
Iteration  1 : recall score =  0.6164383561643836
Iteration  2 : recall score =  0.7333333333333333
Iteration  3 : recall score =  0.6153846153846154
Iteration  4 : recall score =  0.575

Mean recall score  0.6184790221704963

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

Iteration  0 : recall score =  0.5522388059701493
Iteration  1 : recall score =  0.6164383561643836
Iteration  2 : recall score =  0.7333333333333333
Iteration  3 : recall score =  0.6153846153846154
Iteration  4 : recall score =  0.575

Mean recall score  0.6184790221704963

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

能夠看出在不均衡的數據中的計算recall 值是至關糟糕

只有 0.61 和 通過下采樣的計算 0.91 相差甚遠

混淆矩陣

▒ 概念

混淆矩陣用來更直觀的平面展現模型的狀況以方便評估

這裏再拉回來以前的評估方法公式回憶一下

Recall = TP/(TP+FN)

▒ 操做代碼

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',solver='liblinear')
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()

 

應用實際數據集

以上是在下采樣的數據集上進行的測試, 還須要在原始的數據集上進行測試才行

而在全量數據集的在進行判斷纔看是否精準才能夠判斷模型的可行性

▨ 操做代碼

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

▨ 測試結果

經過測試結果就能夠看出異常了,  TP (  正例判對 ) 問題不大

可是 問題是 FP ( 正例判錯 ) 高達到 8000 就明顯說不過去了 - 判斷正經常使用戶爲詐騙犯, 誤殺

這個的數據異常確實不會影響到 recall 的計算

由於  recall 值和 TP 和 FN ( 反例正判 ) 有關

所以這個的結果會對精度有必定的影響, 這也是下采樣的弊端

調整閾值

在默認的迴歸模型中的閾值爲 0.5 , 即比例徹底均分的判斷, 

而閾值調整提高則可讓檢測更加嚴格, 反之更加容易經過

應用於此實例中則爲

0.99 ----  不像是個詐騙犯的要死的程度是不會認爲這人是詐騙犯

0.01 ----- 特麼稍微有點異常動做你就是個詐騙犯了

▒ 操做代碼

lr = LogisticRegression(C = 0.01, penalty = 'l1',solver='liblinear')
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.1 時 ,recall 值很高, 可是 精度很低, 由於將全部的都預測成了詐騙犯, 所以錯殺不少

可是在 0.9 時 ,recall 值很低, 錯殺問題解決了. 沒法檢測到的卻不少了.

因而可知, 結合實際考慮以後, 大概在 0.5 - 0.7 之間的較爲合適,

固然通常也會提供數據要求誤殺率不能高於多少, 精度要大於多少, recall 要大於多少之類的, 再結合模型進行適當的選擇

過採樣

▒ 概念

過 ( 上 ) 採樣的方法爲將少的那類數據樣本增長的和多的那類同樣的多

▒ 代碼實現

▨ 所需包引入

不一樣於sklearn模塊,  imblearn 是須要額外本身手動安裝的包   pip install imblearn 

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

▨ 具體代碼

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

columns=credit_cards.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) # 指定每次生成數據相同, 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)
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
*********************************************************************************

 能夠看出 100 爲最優選擇

 繼續計算混淆矩陣

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 值相對較低, 不以下采樣的高

可是 誤殺的程度要低不少. 只有 517 個, 比起下采樣的 8000個要好不少

而精度固然也是 過採樣 更好一些

總結

▒ 流程

▨ 觀察數據

觀察數據的特徵 - 標準化處理/特徵工程

觀察數據的分佈 - 是否均衡 - 不均衡的話怎麼處理 - 下采樣/過採樣

下采樣和過採樣的選擇問題

  優先仍是選擇過採樣. 儘管下采樣的 Recall 更高

  可是數據量越大對於模型的穩定性是越高的, 由此也更可靠.

  所以比起削減數據. 增長數據是更好的選擇

▨ 計算參數選擇

交叉驗證計算 Recall 比對選擇最合適的參數

▨ 閾值選擇

邏輯迴歸中的閾值選擇

▨ 評估

根據混淆矩陣, 計算 TP / FP / TN / FN / Recall / 精度 

相關文章
相關標籤/搜索