信用卡欺詐檢測(機器學習)

練習:信用卡欺詐檢測

我們拿到的數據都是經過帥選拿到的數據集,這是因為這些數據涉及到相關的隱私,但是這并不妨礙我們測試模型和預測。

  • 數據導入

    import pandas as pd
    import matplotlib.pyplot as plt
    
    # 注意:pandas 通常不會完全顯示
    # pd.set_option('display.max_columns', None)  # 顯示所有列
    # pd.set_option('display.max_rows', None)  # 顯示所有行
    # pd.set_option('max_colwidth', 100)  # 設置 value 的顯示長度為100,默認為50
    # pd.set_option('display.width', 1000)  # 當 console 中輸出的列數超過1000的時候才會換行
    
    # import data
    data = pd.read_csv('creditcard.csv')
    print(data.head())
    

運行結果為:

可以看出前6行的結果,然而這并不能看出什么,其實這些都是提取好的特征,可以方便我們進行建模

  • 拿到數據后,先將數據分成兩類:0(正常數據),1(異常數據),注意正常的樣本數據一定遠大于異常數據。在class列中,0表示沒有被詐騙,1表示被詐騙過

    # import data
    data = pd.read_csv('creditcard.csv')
    print(data.head())
    
    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.xticks(rotation=45)                     # 將x軸數據旋轉45°
    plt.ylabel('Frequency')                     # y軸添加文字
    
    plt.show()
    

運行結果為:
  • 樣本不均衡操作
    通過柱狀圖可以發現兩個樣本不均衡,可以通過上下采樣調整樣本分布均勻,使得0和1的樣本數目一致,再進行分析。再者就是數據中Amount這一列數據值區間較大,機器學習時會認為數值大的數據重要程度偏大,需要對其進行歸一化或者標準化處理。

    from sklearn.preprocessing import StandardScaler
    
    # 標準化處理
    data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1, 1))
    # fit_transform 對數據進行變換
    # 注:這里 reshape 前面要加'.value'
    
    data = data.drop(['Time', 'Amount'], axis=1)            # 去除不需要的特征
    print(data.head())
    

運行結果為:

此時,normAmount代替了Amount這一列的數據,數值為經過標準化處理的值。

  • 數據處理
    由于異常樣本和正常樣本的數據量不一樣,所以需要對數據進行下采樣處理,使得異常樣本和正常樣本的數據量一致。

     import numpy as np
     import pandas as pd
    
     data = pd.read_csv('creditcard.csv')
    
     # 下采樣處理,使得0(正樣品)和1(負樣品)數據一樣少
     # 注:ix 已經被棄用,可以使用 loc 或者 iloc
     X = data.loc[:, data.columns != 'Class']
     y = data.loc[:, data.columns == 'Class']
    
     # 計算出負樣品的樣本數,并獲取它們的索引,轉換成 array 格式
     number_records_fraud = len(data[data.Class == 1])
     fraud_indices = np.array(data[data.Class == 1].index)
    
     # 獲取正樣品的索引
     normal_indices = data[data.Class == 0].index
    
     # 在正樣品的索引索引中隨機選擇樣本,樣本數為 number_records_fraud,然后獲取新的索引,轉換成 array 格式
     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_under_samples = under_sample_data.loc[:, under_sample_data.columns != 'Class']
     y_under_samples = under_sample_data.loc[:, 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))
    

運行結果為:
  • 交叉驗證

    from sklearn.model_selection import train_test_split
    
    # 交叉驗證,將數據切分成測試集和訓練集,測試數據集設為0.3
    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 train dataset: ", len(y_test))
    print("Total number of transactions: ", len(X_train) + len(X_test))
    
    #下采樣數據集
    X_train_undersample, X_test_unsersample, y_train_undersample, y_test_undersample = train_test_split(X_under_samples, y_under_samples, test_size = 0.3, random_state = 0)
    
    print('')
    print("Number transcations train dataset: ", len(X_train_undersample))
    print("Number transcations test dataset: ", len(X_test_unsersample))
    print("Total number of transactions: ", len(X_train_undersample)+len(X_test_unsersample))
    

運行結果為:


交叉驗證可以參考:https://www.cnblogs.com/sddai/p/5696834.html

  • 模型評估:
    精度:數據樣本分布不均,雖然樣本數很高,但很可能一個異常樣本都沒檢測出來,所以經常用recall(召回率)來做評估標準。
    正則化懲罰:L2正則化,機器學習中幾乎都可以看到損失函數后面會添加一個額外項,常用的額外項一般有兩種,一般稱作L1正則化和L2正則化,L1正則化和L2正則化可以看做是損失函數的懲罰項。所謂懲罰是指對損失函數中的某些參數做一些限制。

    # 建模,Recall = TP/(TP+FN)
    
    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import confusion_matrix, recall_score, classification_report
    from sklearn.model_selection import KFold, cross_val_score
    # 版本不同,KFold 從 model_selection 中導入
    
    import warnings
    warnings.filterwarnings("ignore")
    # 注:會出現警告,我們可以使用上面的代碼來忽視它
    
    # K折交叉驗證
    def printing_Kfold_scores(x_train_data,y_train_data):
        fold = KFold(5, shuffle=True)                      # KFold 用法
    
        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:
            print('-'*30)
            print('C parameter: ', c_param)
            print('-'*30)
            print('')
    
            recall_accs = []
            # 交叉驗證
            for iteration,indices in enumerate(fold.split(x_train_data)):
    
                # 使用C參數調用回歸模型
                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.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'].astype('float64').idxmax()]['C_parameter']
        # 注:idxmax()前要加‘.astype('float64')’
    
        print('*'*30)
        print('Best model to choose from cross validation is with C parameter =', best_c)
        print('*'*30)
        return best_c
    
    best_c = printing_Kfold_scores(X_train_undersample, y_train_undersample)
    print(best_c)
    

運行結果為:

通過評估后發現 recall 值符合下采樣組合要求,但是誤殺太大,超過了允許的范圍。

  • 混合矩陣

    import itertools
    import matplotlib.pyplot as plt
    
    # 混合矩陣
    def plot_confusion_matrix(cm, classes,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)
    
        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, 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 metric in the testing dataset: ', cnf_matrix)
    
    # 非歸一化混合矩陣
    class_name = [0,1]
    plt.figure()
    plot_confusion_matrix(cnf_matrix, classes=class_name, title='Confusion matrix')
    plt.show()
    

運行結果為:

可以看出,模型中有許多欺詐沒有找出來

  • 混合矩陣作用于低采樣數據集

    lr = LogisticRegression(C = best_c, penalty='l1')
    lr.fit(X_train_undersample, y_train_undersample.values.ravel())
    y_pred_undersamples = lr.predict(X_test_unsersample.values)
    
    # 計算低采樣數據集混合矩陣
    cnf_matrix = confusion_matrix(y_test_undersample, y_pred_undersamples)
    np.set_printoptions(precision=2)
    
    print('Recall metric in the testing dataset: ', cnf_matrix)
    
    # 非歸一化混合矩陣
    class_name = [0,1]
    plt.figure()
    plot_confusion_matrix(cnf_matrix, classes=class_name, title='Confusion matrix')
    plt.show()
    

運行結果為:

可以看出,有9個欺詐的數據沒有查找出來,同時有18個正常數據被誤殺。
之前我們使用的是Sigmoid函數中默認的閾值:0.5,如果我們自己指定閾值,會對結果產生什么影響呢?

  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_unsersample.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

      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]))

      class_names = [0,1]
      plot_confusion_matrix(cnf_matrix, classes=class_names, title='Threshold > %s' %i)
  plt.show()

運行結果為:

從圖像中可以看出,當閾值為0.1-0.3時,recall值為1,說明太過嚴苛。隨著閾值越來越大,模型的要求越來越寬松。這里需要根據實際需要,選定一個合適的模型。

  • 過采樣:通常進行數據分析時,我們需要有效樣本越多越好。過采樣就是當目前兩個樣本的數量不同時,為了讓樣本一樣多,將負樣本填充到和正號樣本數量一樣多的采樣方法。
    SMOTE算法:擴充少數類樣本的算法
    參考資料:https://www.cnblogs.com/Determined22/p/5772538.html

    import pandas as pd
    import numpy as np
    import warnings
    import itertools
    import matplotlib.pyplot as plt
    
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.metrics import confusion_matrix,recall_score
    from sklearn.model_selection import train_test_split, KFold
    from sklearn.linear_model import LogisticRegression
    
    from imblearn.over_sampling import SMOTE
    
    warnings.filterwarnings("ignore")
    # 注:會出現警告,我們可以使用上面的代碼來忽視它
    
    # 數據讀取及劃分
    credit_card = pd.read_csv('creditcard.csv')
    
    # 建模,Recall = TP/(TP+FN)
    def printing_Kfold_scores(x_train_data,y_train_data):
        fold = KFold(5, shuffle=True)
    
        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:
            print('-'*30)
            print('C parameter: ', c_param)
            print('-'*30)
            print('')
    
            recall_accs = []
            # 交叉驗證
            for iteration,indices in enumerate(fold.split(x_train_data)):
    
                # 使用C參數調用回歸模型
                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.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'].astype('float64').idxmax()]['C_parameter']
        # 注:idxmax()前要加‘.astype('float64')’
    
        print('*'*30)
        print('Best model to choose from cross validation is with C parameter =', best_c)
        print('*'*30)
        return best_c
    
    columns = credit_card.columns
    feathres_columns = columns.delete(len(columns)-1)
    
    feathres = credit_card[feathres_columns]
    labels = credit_card['Class']
    
    feathres_train, feathres_test, labels_train, labels_test = train_test_split(
        feathres, labels, test_size=0.2, random_state=0)
    
    # SMOTE 處理訓練集
    oversample = SMOTE(random_state=0)
    os_feathres, os_labels = oversample.fit_resample(feathres_train, labels_train)
    print(len(os_labels[os_labels == 1]))
    
    # 交叉驗證
    os_feathres = pd.DataFrame(os_feathres)
    os_labels = pd.DataFrame(os_labels)
    best_c = printing_Kfold_scores(os_feathres, os_labels)
    

運行結果為:

混合矩陣

# 混合矩陣
def plot_confusion_matrix(cm, classes,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)

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')

運行結果為:

如圖,過采樣的測試結果明顯優于下采樣的測試結果。

小結

采用下采樣分析時,Recall值可以達到較高水平,但是誤傷的概率較高,預測出的小概率事件發生量明顯上升。采用過采樣分析時,可以避免這個問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 原來想的 旅游 重心在游 現在感悟 旅游 重心在旅 在路上 放松身心 在路上 放飛心情 在路上 走進自然 在路上 ...
    燚格格閱讀 295評論 0 0
  • 以前黑屋子里沒有光, 人在黑屋子里生活的很好, 也不需要光。 后來人無意把黑屋子房頂捅出了個小洞, 光照了進來。 ...
    竹溪寒閱讀 212評論 0 1
  • 我愛的芒果,你愛的西瓜。 可是芒果村里沒有芒果也沒有你。 情人灣的珊瑚又白又美,最丑最大的你卻是我最愛,想帶你回家...
    碧林霖閱讀 453評論 0 1
  • 《于千萬人之中和你》 于千萬人之中和你相識, 你只是佛祖手中跌落凡塵的佛珠, 等佛祖醒過來 你就會被收回 那樣你會...
    寫情書的九林閱讀 192評論 0 0