反欺詐模型(資料不平衡)
大部分內容來自:https://mp.weixin.qq.com/s/vAHTNidkZp6GprxK4ikysQ
解決資料不平衡的方法:
整個流程:
注意事項:
- 評估指標:使用精確度(Precise Rate)、召回率(Recall Rate)、Fmeasure或ROC曲線、準確度召回曲線(precision-recall curve);不要使用準確度(Accurate Rate)
- 不要使用模型給出的標籤,而是要概率估計;得到概率估計之後,不要盲目地使用0.50的決策閥值來區分類別,應該再檢查表現曲線之後再自己決定使用哪個閾值。
問:為什麼資料處理的幾種取樣方法都只對訓練集進行操作?
答:因為原始資料集的 0-1 比為 1:99,所以隨即拆分成的訓練集和測試集的 0-1 比也差不多是 1:99,又因為我們用訓練集來訓練模型,如果不對訓練集的資料做任何操作,得出來模型就會在預測分類0的準度上比1高,而我們希望的是兩者都要兼顧,所以我們才要使用欠取樣或者過取樣對訓練集進行處理,使訓練集的 0-1 比在我們之前聊到的 1:1 ~ 1:10 這個比較合適的區間,用這樣的訓練集訓練出來的模型的泛化能力會更強。以打靶作為比喻,靶心面積很小,對應了佔比小的違約客戶群體。在 0-1 比為 1:99 的測試集的嚴酷考驗下,模型打中靶心(成功預測違約客戶)與打中靶心周圍(成功預測履約客戶)的概率都得到了保證。
欠取樣和過取樣:
過取樣會隨機複製少數樣例以增大它們的規模。欠取樣則隨機地少採樣主要的類。一些資料科學家(天真地)認為過取樣更好,因為其會得到更多的資料,而欠取樣會將資料丟掉。但請記住複製資料不是沒有後果的——因為其會得到複製出來的資料,它就會使變數的方差表面上比實際上更小。而過取樣的好處是它也會複製誤差的數量:如果一個分類器在原始的少數類資料集上做出了一個錯誤的負面錯誤,那麼將該資料集複製五次之後,該分類器就會在新的資料集上出現六個錯誤。相對地,欠取樣會讓獨立變數(independent variable)的方差看起來比其實際的方差更高。
Tomek Link法欠取樣
上圖為 Tomek Link 欠取樣法的核心。不難發現左邊的分佈中 0-1 兩個類別之間並沒有明顯的分界。Tomek Link 法處理後,將佔比多的一方(0),與離它(0)最近的一個少的另一方 (1) 配對,而後將這個配對刪去,這樣一來便如右邊所示構造出了一條明顯一些的分界線。所以說欠取樣需要在佔比少的那一類的資料量比較大的時候使用(大型網際網路公司與銀行),畢竟一命抵一命...
Random Over Sampling 隨機過取樣
隨機過取樣並不是將原始資料集中佔比少的類簡單的乘個指定的倍數,而是對較少類按一定比例進行一定次數的隨機抽樣,然後將每次隨機抽樣所得到的資料集疊加。但如果只是簡單的隨機抽樣也難免會出現問題,因為任意兩次的隨機抽樣中,可能會有重複被抽到的資料,所以經過多次隨機抽樣後疊加在一起的資料中可能會有不少的重複值,這便會使資料的變異程度減小。所以這是隨機過取樣的弊端。
SMOTE 過取樣
SMOTE 過取樣法的出現正好彌補了隨機過取樣的不足,其核心步驟如下圖:
但SMOTE 並不是一點壞處都沒有。上圖的資料分佈 SMOTE 方法的步驟示意圖是比較理想的情況(兩個類別分得還比較開),通常資料不平衡的散點圖應該是像下面這樣的:
而這個時候如果我們依然使用 SMOTE 來過取樣的話就會出現下面的問題:
理想情況下的圖中我們可以看出黑點的分佈似乎是可以用一條線連起來的,而現實情況中的資料往往太過分散,比如上圖中的黑點是呈現U型曲線的分佈,在這個情況下,SMOTE 演算法的第四步作中間插值後,可能這個新插入的點剛好就是某個白點所在的點。本來是 0 的地盤,密密集集的0當中突然給生硬的插進去了一個1......這就使資料又重複了。
綜合取樣
綜合取樣的核心:先使用過取樣,擴大樣本後再對處在膠著狀態的點用 Tomek Link 法進行刪除,有時候甚至連 Tomek Link 都不用,直接把離得近的對全部刪除,因為在進行過取樣後,0 和 1 的樣本量已經達到了 1:1。
Python實戰:
1、匯入相應的包
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns
2、載入資料集
train = pd.read_csv('imb_train.csv') test = pd.read_csv('imb_test.csv') print(f'訓練集資料長度:{len(train)},測試集資料長度:{len(test)}') train.sample(3)
訓練集資料長度:14000,測試集資料長度:6000
X1 | X2 | X3 | X4 | X5 | cls | |
---|---|---|---|---|---|---|
6787 | 0.450326 | -0.831631 | 0.875786 | -0.892056 | -1.051274 | 0 |
8702 | -1.615948 | 2.055870 | -1.547509 | -0.796759 | 0.110135 | 0 |
5806 | -0.458761 | 0.499241 | -0.251615 | -0.589709 | -0.301734 | 0 |
再來看看其中不同類的數量:
print(train['cls'].agg(['value_counts']).T) print('='*55 + '\n') print('測試集中,因變數 cls 分類情況:') print(test['cls'].agg(['value_counts']).T)
0 1 value_counts 13644 356 ======================================================= 測試集中,因變數 cls 分類情況: 0 1 value_counts 5848 152
可知訓練集和測試集中的佔比少的類別 1 實在是太少了,比較嚴重的不平衡,我們還可以使用 Counter 庫統計一下兩個資料集中因變數的分類情況,不難發現數據不平衡問題還是比較嚴重。
from collections import Counter print('訓練集中因變數 cls 分類情況:{}'.format(Counter(train['cls']))) print('測試集因變數 cls 分類情況:{}'.format(Counter(test['cls'])))
訓練集中因變數 cls 分類情況:Counter({0: 13644, 1: 356})
測試集因變數 cls 分類情況:Counter({0: 5848, 1: 152})
3、進行不同的抽樣
在處理前再次重申兩點:
- 測試集不做任何處理!保留嚴峻的比例考驗來測試模型。
- 訓練模型時用到的資料才是經過處理的,0-1 比例在 1:1 ~ 1:10 之間拆分自變數與因變數
(1)拆分自變數和因變數
y_train = train['cls']; y_test = test['cls'] X_train = train.loc[:, :'X5']; X_test = test.loc[:, :'X5'] X_train.sample()
X1 | X2 | X3 | X4 | X5 | |
---|---|---|---|---|---|
7391 | -1.20531 | 1.360892 | 1.696717 | -1.337349 | -0.598543 |
y_train[:1]
0 0
Name: cls, dtype: int64
(2)抽樣的幾種方法
- Random Over Sampling:隨機過抽樣
- SMOTE 方法過抽樣
- SMOTETomek 綜合抽樣
們將用到imbalance learning這個包,pip install imblearn
安裝一下即可,下面是不同抽樣方法的核心程式碼,具體如何使用請看註釋:
from imblearn.over_sampling import RandomOverSampler print('不經過任何取樣處理的原始 y_train 中的分類情況:{}'.format(Counter(y_train))) # 取樣策略 sampling_strategy = 'auto' 的 auto 預設抽成 1:1, ## 如果想要另外的比例如傑克所說的 1:5,甚至底線 1:10,需要根據文件自行調整引數 ## 文件:https://imbalanced-learn.readthedocs.io/en/stable/generated/imblearn.over_sampling.RandomOverSampler.html # 先定義好好,未開始正式訓練擬合 ros = RandomOverSampler(random_state=0, sampling_strategy='auto') X_ros, y_ros = ros.fit_sample(X_train, y_train) print('隨機過取樣後,訓練集 y_ros 中的分類情況:{}'.format(Counter(y_ros))) # 同理,SMOTE 的步驟也是如此 from imblearn.over_sampling import SMOTE sos = SMOTE(random_state=0) X_sos, y_sos = sos.fit_sample(X_train, y_train) print('SMOTE過取樣後,訓練集 y_sos 中的分類情況:{}'.format(Counter(y_sos))) # 同理,綜合取樣(先過取樣再欠取樣) ## # combine 表示組合抽樣,所以 SMOTE 與 Tomek 這兩個英文單詞寫在了一起 from imblearn.combine import SMOTETomek kos = SMOTETomek(random_state=0) # 綜合取樣 X_kos, y_kos = kos.fit_sample(X_train, y_train) print('綜合取樣後,訓練集 y_kos 中的分類情況:{}'.format(Counter(y_kos)))
不經過任何取樣處理的原始 y_train 中的分類情況:Counter({0: 13644, 1: 356}) 隨機過取樣後,訓練集 y_ros 中的分類情況:Counter({0: 13644, 1: 13644}) SMOTE過取樣後,訓練集 y_sos 中的分類情況:Counter({0: 13644, 1: 13644}) 綜合取樣後,訓練集 y_kos 中的分類情況:Counter({0: 13395, 1: 13395})
不難看出兩種過取樣方法都將原來 y_train 中的佔比少的分類 1 提到了與 0 數量一致的情況,但因為綜合取樣在過取樣後會使用欠取樣,所以數量會稍微少一點點。
(3)決策樹建模
看似高大上的梯度優化其實也被業內稱為硬調優
,即每個模型引數都給幾個潛在值,而後讓模型將其自由組合,根據模型精度結果記錄並輸出最佳組合,以用於測試集的驗證。首先匯入相關包。
from sklearn.tree import DecisionTreeClassifier from sklearn import metrics from sklearn.model_selection import GridSearchCV
現在建立決策樹類,但並沒有正式開始訓練模型。
clf = DecisionTreeClassifier(criterion='gini', random_state=1234) # 梯度優化 param_grid = {'max_depth':[3, 4, 5, 6], 'max_leaf_nodes':[4, 6, 8, 10, 12]} # cv 表示是建立一個類,還並沒有開始訓練模型 cv = GridSearchCV(clf, param_grid=param_grid, scoring='f1')
如下是模型的訓練資料的組合,注意!這裡的資料使用大有玄機,第一組資料X,y_train是沒有經過任何操作的,第二組ros
為隨機過取樣,第三組sos
為SMOTE過取樣,最後一組kos
則為綜合取樣。
data = [[X_train, y_train],
[X_ros, y_ros],
[X_sos, y_sos],
[X_kos, y_kos]]
現在對四組資料分別做模型,要注意其實recall
和precision
的用處都不大,看auc
即可,recall:覆蓋率,預測出分類為0且正確的,但本來資料集中分類為0的佔比本來就很大。而且recall是以閾值為 0.5 來計算的,那我們就可以簡單的認為預測的欺詐概率大於0.5就算欺詐了嗎?還是說如果他的潛在欺詐概率只要超過 20% 就已經算為欺詐了呢?
for features, labels in data: cv.fit(features, labels) # 對四組資料分別做模型 # 注意:X_test 是從來沒被動過的,迴應了理論知識: ## 使用比例優良的(1:1~1:10)訓練集來訓練模型,用殘酷的(分類為1的僅有2%)測試集來考驗模型 predict_test = cv.predict(X_test) print('auc:%.3f' %metrics.roc_auc_score(y_test, predict_test), 'recall:%.3f' %metrics.recall_score(y_test, predict_test), 'precision:%.3f' %metrics.precision_score(y_test, predict_test))
auc:0.747 recall:0.493 precision:0.987
auc:0.824 recall:0.783 precision:0.132
auc:0.819 recall:0.757 precision:0.143
auc:0.819 recall:0.757 precision:0.142
可以發現並不一定是綜合取樣就一定高分,畢竟每份資料集都有屬於它自己的特徵,不過一點都不處理的模型的 auc 是最低的。