大部分內容來自: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)
再來看看其中不同類的數量:
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 是最低的。