資料建模學習筆記 -- 類別不平衡問題

TianCMCC發表於2020-12-09

1. 什麼是類別不平衡問題:

在很多工中,正負樣本數量通常是不平衡的,例如在欺詐、失效檢測等任務中,正樣本的數量遠遠多於負樣本的數量。在類別不平衡問題中,我們將數量多的類別稱為“大類”,數量少的類別成為“小類”。

由於類別不平衡問題的資料集中被大類主導,追求高分類精度是毫無意義的。例如在信用卡欺詐任務中,當類別不平衡度為1000時,即正負樣本的比例為 1000 : 1,將所有的樣本全部分為正樣本即可獲得 99.99% 的分類精度,雖然分類精度很高,但是無法檢測出任何負樣本,顯然,這樣的模型是毫無意義的。

但是需要注意的是,類別不平衡學習有一個隱含的假設:小類具有比大類更高的代價。 如果大類比小類更重要,那麼由大類主導的學習就不會帶來問題,相反,如果小類更重要的話,由大類主導的學習才會導致問題。


2. 類別不平衡問題中的效能指標:

在第一節中,我們提到了在類別不平衡問題中使用分類準確率作為評估指標是無意義的,因為我們需要更關注的是模型對小類的識別效果。因此,在類別不平衡問題中,通常使用 ROC曲線AUC值G均值F值 等等作為效能評估指標。

ROC曲線(Receiver Operating Characteristic),中文名為“受試者工作特徵曲線”,如下圖所示,ROC曲線的 y軸 是真正例率(真-正例率,True Positive Rate,簡稱TPR),x軸 是假正例率(假-正例率,False Positive Rate,簡稱FPR)。

T P R = T P T P + F N = T P m + \begin{aligned} TPR=\frac{TP}{TP+FN}=\frac{TP}{m_{+}} \end{aligned} TPR=TP+FNTP=m+TP

F P R = F P F P + T N = F P m − \begin{aligned} FPR=\frac{FP}{FP+TN}=\frac{FP}{m_{-}} \end{aligned} FPR=FP+TNFP=mFP

在這裡插入圖片描述

在 ROC 空間中,一個分類器對應一個點,若該分類器將所有正樣本分為正類,則有 TPR=1 且 FPR=1;若該分類器將所有樣本分為負類,則有 TPR=0 且 FPR=0;若該分類器將所有樣本均正確區分,則有TPR=1 且 FPR=0;對於一個訓練好的二分類模型,通過移動其分類閾值,則可繪製出一條從 (0,0) 到(1,1) 的ROC曲線,具體的繪製流程可參考,如何畫ROC曲線

AUC值(Area Under ROC Curve) ,顧名思義,就是ROC曲線下的面積大小,該準則整合了一個分類器在不同假正例率(FPR)下的綜合效能,AUC值的統計解釋是一個模型 h h h 將正樣本預測為比負樣本具有更高的輸出值的概率,也就是:

A U C ( h ) = P ( h ( x + ) > h ( x − ) ) \begin{aligned} AUC(h)=P(h(x_{+})>h(x_{-})) \end{aligned} AUC(h)=P(h(x+)>h(x))

G值(G-mean),也稱為幾何均值,指的是每個類準確率的幾何均值:

G m e a n = T P m + × T N m − \begin{aligned} Gmean=\sqrt{\frac{TP}{m_{+}}\times\frac{TN}{m_-}} \end{aligned} Gmean=m+TP×mTN

F值(F-measure),定義為查全率和查準率的調和平均數。由於查準率(度量有多少被分為正類的樣本是真正的正類)不包括任何的假陰性樣本 FN 的資訊,查全率(度量有多少正樣本被正確的分為了正類)不包括任何假陽性樣本 FP 的資訊,因此兩者均不能完整地評估模型的學習效果,且互相補充。雖然需要同時追求高查準率和高查全率,但是隨著 TP 的增加,FP 同樣也會增加,這兩個目標是衝突的,因此為了折中,引入了查全率和查準率的調和平均數,即F值:

F α = ( α 1 R e c a l l + ( 1 − α ) 1 P r e c i s i o n ) − 1 \begin{aligned} F_{\alpha}=(\alpha\frac{1}{Recall}+(1-\alpha)\frac{1}{Precision})^{-1} \end{aligned} Fα=(αRecall1+(1α)Precision1)1

其中 α \alpha α 是用於調節查準率和查全率重要性的引數,通常情況下,設 α = 0.5 \alpha=0.5 α=0.5 以表示查全率和查準率的重要程度相當。


3. 類別不平衡問題的解決方法:

一般有如下幾種方法用於處理類別不平衡問題:

  1. 重取樣方法
  2. 代價敏感學習方法
  3. 轉化為異常檢測方法

重取樣 可進一步的分為 降低大類樣本的 欠取樣(sub-sample / down-sample),以及增加小類樣本的 過取樣(up-sample)。 欠取樣和過取樣均可通過有放回和無放回的隨機取樣實現。需要注意的是,隨機重複小類樣本有過擬合風險,同時,刪除大類樣本也可能有丟失有用資訊的風險。因此,為了緩解這些問題,有許多研究提出了各種重取樣方法,例如 單側取樣法(one-sided sampling)、SMOTE演算法 等等。

代價敏感學習 研究處理不均等代價的方法,其目標是在學習過程中最小化總體代價(如測試代價、教學代價、介入代價等),而不是最小化錯誤率或分類精度。 代價敏感學習中最常見的總體代價是誤分代價(misclassification cost),有兩種型別的誤分代價:基於樣本的代價和基於類別的代價。前者假設代價是伴隨著每個樣本的,即每個樣本都有自己的誤分代價;後者假設代價是伴隨著每個類別,即每個類別有著自己的誤分代價,通常情況下使用的是基於類別的代價(獲取每個類別的代價比較方便,可行性高)。

最常見的代價敏感學習方法是 “再縮放”(Rescaling),該方法在學習過程中調節各個類別的影響,並使其和它對應的代價成正比。 “再縮放” 是一個通用框架,可以通過不同方法來實現,例如可以通過 重賦權(re-weighting)來實現:給每個不同類別的訓練樣本指定不同的權重,並將調整過權重的訓練樣本交由任何可以處理加權樣本的非代價敏感型學習演算法處理(與Adaboost的思想類似,在Adaboost中,是將錯誤分類的樣本賦予較大權重),也可以通過 重取樣(re-sampling)來實現:從訓練資料中按 縮放比例 τ i , j \tau_{i,j} τi,j 來取樣資料,然後將重取樣後的資料交由學習演算法來處理。其中縮放比例 τ i , j \tau_{i,j} τi,j 定義為將第 i i i 類誤分為第 j j j 類的代價除以第 j j j 類誤分為第 i i i 類的代價:

τ i , j = c o s t i , j c o s t j , i \begin{aligned} \tau_{i,j}=\frac{cost_{i,j}}{cost_{j,i}} \end{aligned} τi,j=costj,icosti,j

轉換異常檢測方法 從不同於分類的角度去解決資料不均衡性問題,我們可以把那些小類的樣本作為異常點(outliers),因此該問題便轉化為異常點檢測(anomaly detection)與變化趨勢檢測問題。常用的演算法有 One Class SVM 等等


4. 類別不平衡問題實戰環節:

本節參考自Kaggle信用卡欺詐檢測競賽 Notebook,Credit fraud dealing with imbalanced datasets

Outline:

  1. 瞭解資料

  2. 資料預處理
    a). Scaling and Distributing
    b). 劃分資料集
    c). 隨機下采樣
    d). 相關矩陣
    e). 異常檢測
    f ). 資料降維與視覺化

  3. 建立模型

一. 瞭解資料

首先去比賽頁面下載資料集,由於該資料集涉及到客戶隱私,主辦方將特徵屬性進行了脫密處理(原始資料進行PCA後降維脫密),因此我們無從得知該資料集的各列代表什麼含義,只能通過分析資料集的數值特徵來初探資料的面貌。

import numpy as np 
import pandas as pd 
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA, TruncatedSVD
import matplotlib.patches as mpatches
import time

# 分類器庫函式
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
import collections


# 其他庫函式
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from imblearn.pipeline import make_pipeline as imbalanced_make_pipeline
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import NearMiss
from imblearn.metrics import classification_report_imbalanced
from sklearn.metrics import precision_score, recall_score
from sklearn.metrics import f1_score, roc_auc_score, accuracy_score, classification_report
from collections import Counter
from sklearn.model_selection import KFold, StratifiedKFold
import warnings
warnings.filterwarnings("ignore")

df = pd.read_csv('creditcard.csv')
df.head()
df.info()
df.describe()

# 檢視資料集中的空值數量
df.isnull().sum().max()

# 檢視資料集的屬性列名
df.columns

# 檢視正負樣本的不平衡度
print('No Frauds', round(df['Class'].value_counts()[0]/len(df) * 100,2), '% of the dataset')
print('Frauds', round(df['Class'].value_counts()[1]/len(df) * 100,2), '% of the dataset')

檢視唯一沒有脫敏的金額和時間的分佈情況:

fig, ax = plt.subplots(1, 2, figsize=(18,4))

amount_val = df['Amount'].values
time_val = df['Time'].values

sns.distplot(amount_val, ax=ax[0], color='r')
ax[0].set_title('Distribution of Transaction Amount', fontsize=14)
ax[0].set_xlim([min(amount_val), max(amount_val)])

sns.distplot(time_val, ax=ax[1], color='b')
ax[1].set_title('Distribution of Transaction Time', fontsize=14)
ax[1].set_xlim([min(time_val), max(time_val)])

plt.show()

在這裡插入圖片描述

二. 資料預處理

1. Scaling and Distributing

由於給定的資料集已經經過了歸一化處理(除了 Time 和 Amount),所以接下來我們將對 Time 和 Amount 這兩個屬性也進行歸一化處理。

# Since most of our data has already been scaled,
# we should scale the columns that are left to scale (Amount and Time)
from sklearn.preprocessing import StandardScaler, RobustScaler

# 使用 RobustScaler 不太容易出現異常值
std_scaler = StandardScaler()
rob_scaler = RobustScaler()

df['scaled_amount'] = rob_scaler.fit_transform(df['Amount'].values.reshape(-1,1))
df['scaled_time'] = rob_scaler.fit_transform(df['Time'].values.reshape(-1,1))

df.drop(['Time','Amount'], axis=1, inplace=True)

2. 劃分資料集

在開始重取樣之前,我們需要先劃分原始資料集得到驗證集。因為雖然我們之後會進行重取樣使得模型能夠更好的學習欺詐行為,但在最終測試模型的泛化預測能力時,我們必須保證在原始的資料分佈前提下進行測試。所以我們切分驗證集這一步要在重取樣之前做

總結: 在重取樣的資料集上進行訓練,在原始分佈的資料集上進行驗證。

from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedShuffleSplit

print('No Frauds', round(df['Class'].value_counts()[0]/len(df) * 100,2), '% of the dataset')
print('Frauds', round(df['Class'].value_counts()[1]/len(df) * 100,2), '% of the dataset')

X = df.drop('Class', axis=1)
y = df['Class']

# StratifiedKFold用法類似Kfold,但是它是分層取樣,確保訓練集,測試集中各類別樣本的比例與原始資料集中相同
sss = StratifiedKFold(n_splits=5, random_state=None, shuffle=False)

for train_index, test_index in sss.split(X, y):
    print("Train:", train_index, "Test:", test_index)
    original_Xtrain, original_Xtest = X.iloc[train_index], X.iloc[test_index]
    original_ytrain, original_ytest = y.iloc[train_index], y.iloc[test_index]

# 檢查劃分出來的標籤分佈是否與原資料集相同

# 轉換為 array
original_Xtrain = original_Xtrain.values
original_Xtest = original_Xtest.values
original_ytrain = original_ytrain.values
original_ytest = original_ytest.values

# See if both the train and test label distribution are similarly distributed
train_unique_label, train_counts_label = np.unique(original_ytrain, return_counts=True)
test_unique_label, test_counts_label = np.unique(original_ytest, return_counts=True)
print('----------------')

print('Label Distributions: \n')
print(train_counts_label/ len(original_ytrain))
print(test_counts_label/ len(original_ytest))
=====================================================================================================

輸出:

No Frauds 99.83 % of the dataset
Frauds 0.17 % of the dataset
Train: [ 30473  30496  31002 ... 284804 284805 284806] Test: [    0     1     2 ... 57017 57018 57019]
Train: [     0      1      2 ... 284804 284805 284806] Test: [ 30473  30496  31002 ... 113964 113965 113966]
Train: [     0      1      2 ... 284804 284805 284806] Test: [ 81609  82400  83053 ... 170946 170947 170948]
Train: [     0      1      2 ... 284804 284805 284806] Test: [150654 150660 150661 ... 227866 227867 227868]
Train: [     0      1      2 ... 227866 227867 227868] Test: [212516 212644 213092 ... 284804 284805 284806]
----------------------------------------------------------------------------------------------------
Label Distributions: 

[0.99827076 0.00172924]   # 訓練集上的正負樣本比例
[0.99827952 0.00172048]	  # 測試集上的正負樣本比例

3. 隨機下采樣

我們需要將從大類樣本中隨機抽取若干與小類樣本組成一個新的子資料集,從而使得正負樣本比例相同。

Steps:

  1. 確認資料的不均衡分佈程度(小類樣本數量和大類樣本數量)
  2. 得到了小類樣本的數量後(也就是欺詐樣本的數量),我們從大類樣本中隨機抽樣出小類數量的樣本,從而使得正負樣本比例相同。

完成這兩步後,我們就得到了一個下采樣後的資料集,接下來打混(shuffle)我們的資料集。下采樣的最大問題是資訊丟失,其實簡單想想就想得通,從284315個樣本中,只抽取出492個樣本,這肯定會造成資訊丟失的問題~

隨機下采樣程式碼如下:

df = df.sample(frac=1)   # 打散資料

# 負樣本的數量為 492個
fraud_df = df.loc[df['Class'] == 1]
non_fraud_df = df.loc[df['Class'] == 0][:492]

normal_distributed_df = pd.concat([fraud_df, non_fraud_df])

# 打散經下采樣後得到的子資料集
new_df = normal_distributed_df.sample(frac=1, random_state=42)

new_df.head()

4. 相關矩陣

特徵之間的相關性對於我們理解資料非常的重要,其一是我們想要知道哪些特徵與標籤高度相關,其二是我們也想知道特徵與特徵之間的相關性。

在類別不平衡問題上,我們也要關注利用重取樣得到的子資料集中特徵與特徵、特徵與標籤之間的相關性:

f, (ax1, ax2) = plt.subplots(2, 1, figsize=(24,20))

# 繪製原始資料集的相關矩陣
corr = df.corr()
sns.heatmap(corr, cmap='coolwarm_r', annot_kws={'size':20}, ax=ax1)
ax1.set_title("Imbalanced Correlation Matrix \n (don't use for reference)", fontsize=14)

# 繪製下采樣後資料集的相關矩陣
sub_sample_corr = new_df.corr()
sns.heatmap(sub_sample_corr, cmap='coolwarm_r', annot_kws={'size':20}, ax=ax2)
ax2.set_title('SubSample Correlation Matrix \n (use for reference)', fontsize=14)
plt.show()

原始資料集的相關矩陣
原始資料集的相關矩陣
下采樣後資料集的相關矩陣
在這裡插入圖片描述
從上面的相關矩陣圖中我們可以觀察到如下相關的關係:

  • 與標籤呈負相關的特徵有:V17, V14, V12 V10 等
  • 與標籤呈正相關的特徵有:V2, V4, V11, V19 等

我們還可以通過繪製箱線圖從而更好地理解這些與標籤呈較高相關性的特徵在正負樣本中的分佈:

f, axes = plt.subplots(ncols=4, figsize=(20,4))

# Negative Correlations with our Class (The lower our feature value the more likely it will be a fraud transaction)
sns.boxplot(x="Class", y="V17", data=new_df, ax=axes[0])
axes[0].set_title('V17 vs Class Negative Correlation')

sns.boxplot(x="Class", y="V14", data=new_df, ax=axes[1])
axes[1].set_title('V14 vs Class Negative Correlation')


sns.boxplot(x="Class", y="V12", data=new_df, ax=axes[2])
axes[2].set_title('V12 vs Class Negative Correlation')


sns.boxplot(x="Class", y="V10", data=new_df, ax=axes[3])
axes[3].set_title('V10 vs Class Negative Correlation')

plt.show()

在這裡插入圖片描述
在這裡插入圖片描述

5. 異常檢測

異常檢測的目的是為了從與標籤高度相關的特徵中刪除具有“極端異常值”的樣本,從而提升我們模型的效能。我們使用的異常檢測方法是 IQR法(Interquartile Range Method),即 四分位距法:

Q 1 Q_1 Q1 (第一四分位數)和 Q 3 Q_3 Q3 (第三四分位數)的 n n n 倍作為閾值,當特徵的值大於 n Q 3 nQ_3 nQ3 或小於 n Q 1 nQ_1 nQ1 時,該特徵的值將被判定為異常值。(可通過箱線圖很直觀的觀察到異常值)

在這裡插入圖片描述

通過調節 n n n 來調節閾值的大小,當 n n n 越大時,移除的異常值就越多,但是我們希望更多地關注“極端異常值”,而不僅僅是異常值。因為移除異常值可能會導致資訊丟失的風險,這將導致降低模型的效能。我們可以通過調節這個閾值,來觀察異常值是如何影響分類模型的準確性。

剔除 V14、V12、V10 等 與標籤正負呈較高相關性的特徵中的極端異常值樣本:


# # -----> 從 V14 移除 異常值樣本 (Highest Negative Correlated with Labels)

v14_fraud = new_df['V14'].loc[new_df['Class'] == 1].values
q25, q75 = np.percentile(v14_fraud, 25), np.percentile(v14_fraud, 75)   # 計算 Q1, Q3 值
print('Quartile 25: {} | Quartile 75: {}'.format(q25, q75))
v14_iqr = q75 - q25
print('iqr: {}'.format(v14_iqr))

v14_cut_off = v14_iqr * 1.5  
v14_lower, v14_upper = q25 - v14_cut_off, q75 + v14_cut_off   # 計算 異常值的閾值
print('Cut Off: {}'.format(v14_cut_off))
print('V14 Lower: {}'.format(v14_lower))
print('V14 Upper: {}'.format(v14_upper))

outliers = [x for x in v14_fraud if x < v14_lower or x > v14_upper]
print('Feature V14 Outliers for Fraud Cases: {}'.format(len(outliers)))
print('V10 outliers:{}'.format(outliers))

new_df = new_df.drop(new_df[(new_df['V14'] > v14_upper) | (new_df['V14'] < v14_lower)].index)

print('---------------------------')

# -----> V12 removing outliers from fraud transactions
v12_fraud = new_df['V12'].loc[new_df['Class'] == 1].values
q25, q75 = np.percentile(v12_fraud, 25), np.percentile(v12_fraud, 75)
v12_iqr = q75 - q25

v12_cut_off = v12_iqr * 1.5
v12_lower, v12_upper = q25 - v12_cut_off, q75 + v12_cut_off
print('V12 Lower: {}'.format(v12_lower))
print('V12 Upper: {}'.format(v12_upper))
outliers = [x for x in v12_fraud if x < v12_lower or x > v12_upper]
print('V12 outliers: {}'.format(outliers))
print('Feature V12 Outliers for Fraud Cases: {}'.format(len(outliers)))
new_df = new_df.drop(new_df[(new_df['V12'] > v12_upper) | (new_df['V12'] < v12_lower)].index)
print('Number of Instances after outliers removal: {}'.format(len(new_df)))

print('---------------------------')


# Removing outliers V10 Feature
v10_fraud = new_df['V10'].loc[new_df['Class'] == 1].values
q25, q75 = np.percentile(v10_fraud, 25), np.percentile(v10_fraud, 75)
v10_iqr = q75 - q25

v10_cut_off = v10_iqr * 1.5
v10_lower, v10_upper = q25 - v10_cut_off, q75 + v10_cut_off
print('V10 Lower: {}'.format(v10_lower))
print('V10 Upper: {}'.format(v10_upper))
outliers = [x for x in v10_fraud if x < v10_lower or x > v10_upper]
print('V10 outliers: {}'.format(outliers))
print('Feature V10 Outliers for Fraud Cases: {}'.format(len(outliers)))
new_df = new_df.drop(new_df[(new_df['V10'] > v10_upper) | (new_df['V10'] < v10_lower)].index)
print('Number of Instances after outliers removal: {}'.format(len(new_df)))

輸出結果:

Quartile 25: -9.692722964972385 | Quartile 75: -4.282820849486866
iqr: 5.409902115485519
Cut Off: 8.114853173228278
V14 Lower: -17.807576138200663
V14 Upper: 3.8320323237414122
Feature V14 Outliers for Fraud Cases: 4
V10 outliers:[-19.2143254902614, -18.8220867423816, -18.4937733551053, -18.049997689859396]
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
V12 Lower: -17.3430371579634
V12 Upper: 5.776973384895937
V12 outliers: [-18.683714633344298, -18.047596570821604, -18.4311310279993, -18.553697009645802]
Feature V12 Outliers for Fraud Cases: 4
Number of Instances after outliers removal: 976
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
V10 Lower: -14.89885463232024
V10 Upper: 4.920334958342141
V10 outliers: [-24.403184969972802, -18.9132433348732, -15.124162814494698, -16.3035376590131, -15.2399619587112, -15.1237521803455, -14.9246547735487, -16.6496281595399, -18.2711681738888, -24.5882624372475, -15.346098846877501, -20.949191554361104, -15.2399619587112, -23.2282548357516, -15.2318333653018, -22.1870885620007, -17.141513641289198, -19.836148851696, -22.1870885620007, -16.6011969664137, -16.7460441053944, -15.563791338730098, -14.9246547735487, -16.2556117491401, -22.1870885620007, -15.563791338730098, -22.1870885620007]
Feature V10 Outliers for Fraud Cases: 27
Number of Instances after outliers removal: 947

6. 資料降維與視覺化

由於一個樣本有近30個特徵屬性,我們希望將這些高維的資料降至二維空間,並按照標籤類別進行視覺化,從而觀察不同簇群之間的聚集情況。

常用的降維方法有 PCA, T-SNE, Truncated SVD 等,並且 scipy 中也提供了封裝好的庫函式,直接呼叫即可實現降維:

# New_df is from the random undersample data (fewer instances)
X = new_df.drop('Class', axis=1)
y = new_df['Class']

# T-SNE Implementation
X_reduced_tsne = TSNE(n_components=2, random_state=42).fit_transform(X.values)

# PCA Implementation
X_reduced_pca = PCA(n_components=2, random_state=42).fit_transform(X.values)

# TruncatedSVD
X_reduced_svd = TruncatedSVD(n_components=2, algorithm='randomized', random_state=42).fit_transform(X.values)

# ==================== 在二維空間中按照標籤來標記降維後的樣本特徵 ==============================

f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(24,6))
# labels = ['No Fraud', 'Fraud']
f.suptitle('Clusters using Dimensionality Reduction', fontsize=14)

blue_patch = mpatches.Patch(color='#0A0AFF', label='No Fraud')
red_patch = mpatches.Patch(color='#AF0000', label='Fraud')

# t-SNE scatter plot
ax1.scatter(X_reduced_tsne[:,0], X_reduced_tsne[:,1], c=(y == 0), cmap='coolwarm', label='No Fraud', linewidths=2)
ax1.scatter(X_reduced_tsne[:,0], X_reduced_tsne[:,1], c=(y == 1), cmap='coolwarm', label='Fraud', linewidths=2)
ax1.set_title('t-SNE', fontsize=14)

ax1.grid(True)

ax1.legend(handles=[blue_patch, red_patch])


# PCA scatter plot
ax2.scatter(X_reduced_pca[:,0], X_reduced_pca[:,1], c=(y == 0), cmap='coolwarm', label='No Fraud', linewidths=2)
ax2.scatter(X_reduced_pca[:,0], X_reduced_pca[:,1], c=(y == 1), cmap='coolwarm', label='Fraud', linewidths=2)
ax2.set_title('PCA', fontsize=14)

ax2.grid(True)

ax2.legend(handles=[blue_patch, red_patch])

# TruncatedSVD scatter plot
ax3.scatter(X_reduced_svd[:,0], X_reduced_svd[:,1], c=(y == 0), cmap='coolwarm', label='No Fraud', linewidths=2)
ax3.scatter(X_reduced_svd[:,0], X_reduced_svd[:,1], c=(y == 1), cmap='coolwarm', label='Fraud', linewidths=2)
ax3.set_title('Truncated SVD', fontsize=14)

ax3.grid(True)

ax3.legend(handles=[blue_patch, red_patch])

plt.show()

在這裡插入圖片描述

觀察上圖(紅色為正樣本、藍色為負樣本)可見正負樣本的特徵之間可以被明顯的區分出來,因此預測模型在區分欺詐案件和非欺詐案件方面將會表現得相當好。


三. 建立模型

在本章中,我們將嘗試使用若干分類演算法來對經過預處理後的資料進行建模:

# Undersampling before cross validating (prone to overfit)
X = new_df.drop('Class', axis=1)
y = new_df['Class']

# Our data is already scaled we should split our training and test sets
from sklearn.model_selection import train_test_split

# This is explicitly used for undersampling.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Turn the values into an array for feeding the classification algorithms.
X_train = X_train.values
X_test = X_test.values
y_train = y_train.values
y_test = y_test.values

# Let's implement simple classifiers
classifiers = {
    "LogisiticRegression": LogisticRegression(),
    "KNearest": KNeighborsClassifier(),
    "Support Vector Classifier": SVC(),
    "DecisionTreeClassifier": DecisionTreeClassifier()
}

# Wow our scores are getting even high scores even when applying cross validation.
from sklearn.model_selection import cross_val_score

for key, classifier in classifiers.items():
    classifier.fit(X_train, y_train)
    training_score = cross_val_score(classifier, X_train, y_train, cv=5)
    print("Classifiers: ", classifier.__class__.__name__, "Has a training score of", round(training_score.mean(), 2) * 100, "% accuracy score")
    
模型的效果:

Classifiers:  LogisticRegression Has a training score of 93.0 % accuracy score
Classifiers:  KNeighborsClassifier Has a training score of 92.0 % accuracy score
Classifiers:  SVC Has a training score of 93.0 % accuracy score
Classifiers:  DecisionTreeClassifier Has a training score of 92.0 % accuracy score

我們發現使用邏輯迴歸效果還不錯,試著使用網格搜尋(Grid search)來尋找邏輯迴歸模型的最優超引數:

# Use GridSearchCV to find the best parameters.
from sklearn.model_selection import GridSearchCV


# Logistic Regression 
log_reg_params = {"penalty": ['l1', 'l2'], 'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000]}


grid_log_reg = GridSearchCV(LogisticRegression(), log_reg_params)
grid_log_reg.fit(X_train, y_train)
# We automatically get the logistic regression with the best parameters.
log_reg = grid_log_reg.best_estimator_

knears_params = {"n_neighbors": list(range(2,5,1)), 'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']}

grid_knears = GridSearchCV(KNeighborsClassifier(), knears_params)
grid_knears.fit(X_train, y_train)
# KNears best estimator
knears_neighbors = grid_knears.best_estimator_

# Support Vector Classifier
svc_params = {'C': [0.5, 0.7, 0.9, 1], 'kernel': ['rbf', 'poly', 'sigmoid', 'linear']}
grid_svc = GridSearchCV(SVC(), svc_params)
grid_svc.fit(X_train, y_train)

# SVC best estimator
svc = grid_svc.best_estimator_

# DecisionTree Classifier
tree_params = {"criterion": ["gini", "entropy"], "max_depth": list(range(2,4,1)), 
              "min_samples_leaf": list(range(5,7,1))}
grid_tree = GridSearchCV(DecisionTreeClassifier(), tree_params)
grid_tree.fit(X_train, y_train)

# tree best estimator
tree_clf = grid_tree.best_estimator_

log_reg_score = cross_val_score(log_reg, X_train, y_train, cv=5)
print('Logistic Regression Cross Validation Score: ', round(log_reg_score.mean() * 100, 2).astype(str) + '%')


knears_score = cross_val_score(knears_neighbors, X_train, y_train, cv=5)
print('Knears Neighbors Cross Validation Score', round(knears_score.mean() * 100, 2).astype(str) + '%')

svc_score = cross_val_score(svc, X_train, y_train, cv=5)
print('Support Vector Classifier Cross Validation Score', round(svc_score.mean() * 100, 2).astype(str) + '%')

tree_score = cross_val_score(tree_clf, X_train, y_train, cv=5)
print('DecisionTree Classifier Cross Validation Score', round(tree_score.mean() * 100, 2).astype(str) + '%')

未完待續… 2020/12/09

相關文章