機器學習之類別不平衡問題 (3) —— 採樣方法


機器學習之類別不平衡問題 (1) —— 各類評估指標

機器學習之類別不平衡問題 (2) —— ROC和PR曲線

機器學習之類別不平衡問題 (3) —— 採樣方法



前兩篇主要談類別不平衡問題的評估方法,重心放在各種評估指標以及ROC和PR曲線上,只有在明確了這些後,咱們才能據此選擇具體的處理類別不平衡問題的方法。本篇介紹的採樣方法是其中比較經常使用的方法,其主要目的是經過改變原有的不平衡樣本集,以期得到一個平衡的樣本分佈,進而學習出合適的模型。html

採樣方法大體可分爲過採樣 (oversampling) 和欠採樣 (undersampling) ,雖然過採樣和降採樣主題思想簡單,但這些年來研究出了不少變種,本篇挑一些來具體闡述。見下思惟導圖:python




\(\scriptsize{\spadesuit}\) 過採樣


1. 隨機過採樣

隨機過採樣顧名思義就是從樣本少的類別中隨機抽樣,再將抽樣得來的樣本添加到數據集中。然而這種方法現在已經不大使用了,由於重複採樣每每會致使嚴重的過擬合,於是如今的主流過採樣方法是經過某種方式人工合成一些少數類樣本,從而達到類別平衡的目的,而這其中的鼻祖就是SMOTE。git


2. SMOTE

SMOTE (synthetic minority oversampling technique) 的思想歸納起來就是在少數類樣本之間進行插值來產生額外的樣本。具體地,對於一個少數類樣本\(\mathbf{x}_i\)使用K近鄰法(k值須要提早指定),求出離\(\mathbf{x}_i\)距離最近的k個少數類樣本,其中距離定義爲樣本之間n維特徵空間的歐氏距離。而後從k個近鄰點中隨機選取一個,使用下列公式生成新樣本:github

\[ \mathbf{x}_{new}=\mathbf{x}_{i}+(\mathbf{\hat{x}}_{i}-\mathbf{x}_{i}) \times \delta \tag{1.1} \]
其中\(\mathbf{\hat{x}}\)爲選出的k近鄰點,\(\delta\in[0,1]\)是一個隨機數。下圖就是一個SMOTE生成樣本的例子,使用的是3-近鄰,能夠看出SMOTE生成的樣本通常就在\(\mathbf{x}_{i}\)\(\mathbf{\hat{x}}_{i}\)相連的直線上:算法


SMOTE會隨機選取少數類樣本用以合成新樣本,而不考慮周邊樣本的狀況,這樣容易帶來兩個問題:dom

  1. 若是選取的少數類樣本週圍也都是少數類樣本,則新合成的樣本不會提供太多有用信息。這就像支持向量機中遠離margin的點對決策邊界影響不大。
  2. 若是選取的少數類樣本週圍都是多數類樣本,這類的樣本多是噪音,則新合成的樣本會與周圍的多數類樣本產生大部分重疊,導致分類困難。

總的來講咱們但願新合成的少數類樣本能處於兩個類別的邊界附近,這樣每每能提供足夠的信息用以分類。而這就是下面的 Border-line SMOTE 算法要作的事情。機器學習


3. Border-line SMOTE

這個算法會先將全部的少數類樣本分紅三類,以下圖所示:學習

  • "noise" : 全部的k近鄰個樣本都屬於多數類
  • "danger" : 超過一半的k近鄰樣本屬於多數類
  • "safe": 超過一半的k近鄰樣本屬於少數類


Border-line SMOTE算法只會從處於」danger「狀態的樣本中隨機選擇,而後用SMOTE算法產生新的樣本。處於」danger「狀態的樣本表明靠近」邊界「附近的少數類樣本,而處於邊界附近的樣本每每更容易被誤分類。於是 Border-line SMOTE 只對那些靠近」邊界「的少數類樣本進行人工合成樣本,而 SMOTE 則對全部少數類樣本一視同仁。測試

Border-line SMOTE 分爲兩種: Borderline-1 SMOTEBorderline-2 SMOTEBorderline-1 SMOTE 在合成樣本時\((1.1)\)式中的\(\mathbf{\hat{x}}\)是一個少數類樣本,而 Borderline-2 SMOTE 中的\(\mathbf{\hat{x}}\)則是k近鄰中的任意一個樣本。fetch


4. ADASYN

ADASYN名爲自適應合成抽樣(adaptive synthetic sampling),其最大的特色是採用某種機制自動決定每一個少數類樣本須要產生多少合成樣本,而不是像SMOTE那樣對每一個少數類樣本合成同數量的樣本。具體流程以下:

  1. 首先計算須要合成的樣本總量:
    \[ G = (S_{maj} - S_{min}) \times \beta \]
    其中\(S_{maj}\)爲多數類樣本數量,\(S_{min}\)爲少數類樣本數量,\(\beta \in [0,1]\)爲係數。G即爲總共想要合成的少數類樣本數量,若是\(\beta=1\)則是合成後各種別數目相等。

  2. 對於每一個少類別樣本\(\mathbf{x}_i\),找出其K近鄰個點,並計算:
    \[ \Gamma_i = \frac{\Delta_i\,/\,K}{Z} \]
    其中\(\Delta_i\)爲K近鄰個點中多數類樣本的數量,Z爲規範化因子以確保 \(\Gamma\) 構成一個分佈。這樣若一個少數類樣本\(\mathbf{x}_i\)的周圍多數類樣本越多,則其 \(\Gamma_i\) 也就越高。

  3. 最後對每一個少類別樣本\(\mathbf{x}_i\)計算須要合成的樣本數量\(g_i\),再用SMOTE算法合成新樣本:
    \[ g_i = \Gamma_i \times G \]


能夠看到ADASYN利用分佈\(\Gamma\)來自動決定每一個少數類樣本所須要合成的樣本數量,這等因而給每一個少數類樣本施加了一個權重,周圍的多數類樣本越多則權重越高。ADASYN的缺點是易受離羣點的影響,若是一個少數類樣本的K近鄰都是多數類樣本,則其權重會變得至關大,進而會在其周圍生成較多的樣本。


下面利用sklearn中的 make_classification 構造了一個不平衡數據集,各種別比例爲{0:54, 1:946}。原始數據,SMOTEBorderline-1 SMOTEBorderline-2 SMOTEADASYN的比較見下圖,左側爲過採樣後的決策邊界,右側爲過採樣後的樣本分佈狀況,能夠看到過採樣後原來少數類的決策邊界都擴大了,致使更多的多數類樣本被劃爲少數類了



從上圖咱們也能夠比較幾種過採樣方法各自的特色。用 SMOTE 合成的樣本分佈比較平均,而Border-line SMOTE合成的樣本則集中在類別邊界處。ADASYN的特性是一個少數類樣本週圍多數類樣本越多,則算法會爲其生成越多的樣本,從圖中也能夠看到生成的樣本大都來自於原來與多數類比較靠近的那些少數類樣本。




\(\scriptsize{\blacklozenge}\) 欠採樣


1. 隨機欠採樣

隨機欠採樣的思想一樣比較簡單,就是從多數類樣本中隨機選取一些剔除掉。這種方法的缺點是被剔除的樣本可能包含着一些重要信息,導致學習出來的模型效果很差。


2. EasyEnsemble 和 BalanceCascade

EasyEnsemble和BalanceCascade採用集成學習機制來處理傳統隨機欠採樣中的信息丟失問題。

  • EasyEnsemble將多數類樣本隨機劃分紅n個子集,每一個子集的數量等於少數類樣本的數量,這至關於欠採樣。接着將每一個子集與少數類樣本結合起來分別訓練一個模型,最後將n個模型集成,這樣雖然每一個子集的樣本少於整體樣本,但集成後總信息量並不減小。

  • 若是說EasyEnsemble是基於無監督的方式從多數類樣本中生成子集進行欠採樣,那麼BalanceCascade則是採用了有監督結合Boosting的方式。在第n輪訓練中,將從多數類樣本中抽樣得來的子集與少數類樣本結合起來訓練一個基學習器H,訓練完後多數類中能被H正確分類的樣本會被剔除。在接下來的第n+1輪中,從被剔除後的多數類樣本中產生子集用於與少數類樣本結合起來訓練,最後將不一樣的基學習器集成起來。BalanceCascade的有監督表如今每一輪的基學習器起到了在多數類中選擇樣本的做用,而其Boosting特色則體如今每一輪丟棄被正確分類的樣本,進然後續基學習器會更注重那些以前分類錯誤的樣本。


3. NearMiss

NearMiss本質上是一種原型選擇(prototype selection)方法,即從多數類樣本中選取最具表明性的樣本用於訓練,主要是爲了緩解隨機欠採樣中的信息丟失問題。NearMiss採用一些啓發式的規則來選擇樣本,根據規則的不一樣可分爲3類:

  • NearMiss-1:選擇到最近的K個少數類樣本平均距離最近的多數類樣本
  • NearMiss-2:選擇到最遠的K個少數類樣本平均距離最近的多數類樣本
  • NearMiss-3:對於每一個少數類樣本選擇K個最近的多數類樣本,目的是保證每一個少數類樣本都被多數類樣本包圍

NearMiss-1和NearMiss-2的計算開銷很大,由於須要計算每一個多類別樣本的K近鄰點。另外,NearMiss-1易受離羣點的影響,以下面第二幅圖中合理的狀況是處於邊界附近的多數類樣本會被選中,然而因爲右下方一些少數類離羣點的存在,其附近的多數類樣本就被選擇了。相比之下NearMiss-2和NearMiss-3不易產生這方面的問題。


4. 數據清洗方法 (data cleaning tichniques)

這類方法主要經過某種規則來清洗重疊的數據,從而達到欠採樣的目的,而這些規則每每也是啓發性的,下面進行簡要闡述:

  • Tomek Link:Tomek Link表示不一樣類別之間距離最近的一對樣本,即這兩個樣本互爲最近鄰且分屬不一樣類別。這樣若是兩個樣本造成了一個Tomek Link,則要麼其中一個是噪音,要麼兩個樣本都在邊界附近。這樣經過移除Tomek Link就能「清洗掉」類間重疊樣本,使得互爲最近鄰的樣本皆屬於同一類別,從而能更好地進行分類。


    下圖左上爲原始數據,右上爲SMOTE後的數據,左下虛線標識出Tomek Link,右下爲移除Tomek Link後的數據集,能夠看到不一樣類別之間樣本重疊減小了不少。



  • Edited Nearest Neighbours(ENN):對於屬於多數類的一個樣本,若是其K個近鄰點有超過一半都不屬於多數類,則這個樣本會被剔除。這個方法的另外一個變種是全部的K個近鄰點都不屬於多數類,則這個樣本會被剔除。

最後,數據清洗技術最大的缺點是沒法控制欠採樣的數量。因爲都在某種程度上採用了K近鄰法,而事實上大部分多數類樣本週圍也都是多數類,於是能剔除的多數類樣本比較有限。



\(\scriptsize{\clubsuit}\) 過採樣和欠採樣結合



上文中提到SMOTE算法的缺點是生成的少數類樣本容易與周圍的多數類樣本產生重疊難以分類,而數據清洗技術剛好能夠處理掉重疊樣本,因此能夠將兩者結合起來造成一個pipeline,先過採樣再進行數據清洗。主要的方法是 SMOTE + ENNSMOTE + Tomek ,其中 SMOTE + ENN 一般能清除更多的重疊樣本,以下圖:



綜上,本文簡要介紹了幾種過採樣和欠採樣的方法,其實還有更多的變種,可參閱imbalanced-learn最後列出的的References






\(\scriptsize{\bigstar}\) 採樣方法的效果



最後固然還有一個最重要的問題,本文列舉了多種不一樣的採樣方法,那麼哪一種方法效果好呢? 下面用兩個數據集對各種方法進行比較,一樣結論也是基於這兩個數據集。本文代碼主要使用了imbalanced-learn這個庫,算是scikit-learn的姐妹項目。


第一個數據集爲 us_crime,多數類樣本和少數類樣本的比例爲12:1。

us_crime = fetch_datasets()['us_crime']
X_train, X_test, y_train, y_test = train_test_split(us_crime.data, us_crime.target, 
                                                    test_size=0.5, random_state=42, stratify=us_crime.target)  
                                                    # 分爲訓練集和測試集


總共用9個模型,1個 base_model (即不進行採樣的模型) 加上8個過採樣和欠採樣方法。imbalanced-learn 中大部分採樣方法均可以使用 make_pipeline 將採樣方法和分類模型鏈接起來,但兩種集成方法,EasyEnsembleBalanceCascade 沒法使用 make_pipeline (由於本質上是集成了好幾個分類模型),因此須要自定義方法。

sampling_methods = [Original(), 
                    SMOTE(random_state=42), 
                    SMOTE(random_state=42, kind='borderline1'), 
                    ADASYN(random_state=42), 
                    EasyEnsemble(random_state=42), 
                    BalanceCascade(random_state=42),
                    NearMiss(version=3, random_state=42), 
                    SMOTEENN(random_state=42), 
                    SMOTETomek(random_state=42)]

names = ['Base model', 
         'SMOTE', 
         'Borderline SMOTE', 
         'ADASYN', 
         'EasyEnsemble', 
         'BalanceCascade', 
         'NearMiss', 
         'SMOTE+ENN', 
         'SMOTE+Tomek']

def ensemble_method(method):  # EasyEnsemble和BalanceCascade的方法
    count = 0
    xx, yy = method.fit_sample(X_train, y_train)
    y_pred, y_prob = np.zeros(len(X_test)), np.zeros(len(X_test))
    for X_ensemble, y_ensemble in zip(xx, yy):
        model = LogisticRegression()  
        model.fit(X_ensemble, y_ensemble)
        y_pred += model.predict(X_test)
        y_prob += model.predict_proba(X_test)[:, 1]
        count += 1
    return np.where(y_pred >= 0, 1, -1), y_prob/count

# 畫ROC曲線
plt.figure(figsize=(15,8))
for (name, method) in zip(names, sampling_methods):
    t0 = time.time()
    if name == 'EasyEnsemble' or name == 'BalanceCascade':
        y_pred, y_prob = ensemble_method(method)
    else:
        model = make_pipeline(method, LogisticRegression())  
        model.fit(X_train, y_train)
        y_pred = model.predict(X_test)
        y_prob = model.predict_proba(X_test)[:, 1]
    fpr, tpr, thresholds = roc_curve(y_test, y_prob, pos_label=1)
    plt.plot(fpr, tpr, lw=3, label='{} (AUC={:.2f}, time={:.2f}s)'.
             format(name, auc(fpr, tpr), time.time() - t0))
    plt.xlabel("FPR", fontsize=17)
    plt.ylabel("TPR", fontsize=17)
    plt.legend(fontsize=14)



# 畫PR曲線
plt.figure(figsize=(15,8))
for (name, method) in zip(names, sampling_methods):
    t0 = time.time()
    if name == 'EasyEnsemble' or name == 'BalanceCascade':
        y_pred, y_prob = ensemble_method(method)
    else:
        model = make_pipeline(method, LogisticRegression())
        model.fit(X_train, y_train)
        y_pred = model.predict(X_test)
        y_prob = model.predict_proba(X_test)[:, 1]
    precision, recall, thresholds = precision_recall_curve(y_test, y_prob, pos_label=1)
    plt.plot(recall, precision, lw=3, label='{} (AUC={:.2f}, time={:.2f}s)'.
             format(name, auc(recall, precision), time.time() - t0))
    plt.xlabel("Recall", fontsize=17)
    plt.ylabel("Precision", fontsize=17)
    plt.legend(fontsize=14, loc="upper right")





第二個數據集是 abalone,多數類樣本和少數類樣本的比例爲130:1,很是懸殊。

abalone_19 = fetch_datasets()['abalone_19']
X_train, X_test, y_train, y_test = train_test_split(abalone_19.data, abalone_19.target, test_size=0.5, 
                                                    random_state=42, stratify=abalone_19.target)

# 畫ROC曲線和PR曲線
plt.figure(figsize=(15,8))
for (name, method) in zip(names, sampling_methods):
    t0 = time.time()
    if name == 'EasyEnsemble' or name == 'BalanceCascade':
        y_pred, y_prob = ensemble_method(method)
    else:
        model = make_pipeline(method, LogisticRegression()) 
        model.fit(X_train, y_train)
        y_pred = model.predict(X_test)
        y_prob = model.predict_proba(X_test)[:, 1]
    fpr, tpr, thresholds = roc_curve(y_test, y_prob, pos_label=1)
    plt.plot(fpr, tpr, lw=3, label='{} (AUC={:.2f}, time={:.2f}s)'.
             format(name, auc(fpr, tpr), time.time() - t0))
    plt.xlabel("FPR", fontsize=17)
    plt.ylabel("TPR", fontsize=17)
    plt.legend(fontsize=14)
    
plt.figure(figsize=(15,8))
for (name, method) in zip(names, sampling_methods):
    t0 = time.time()
    if name == 'EasyEnsemble' or name == 'BalanceCascade':
        y_pred, y_prob = ensemble_method(method)
    else:
        model = make_pipeline(method, LogisticRegression())
        model.fit(X_train, y_train)
        y_pred = model.predict(X_test)
        y_prob = model.predict_proba(X_test)[:, 1]
    precision, recall, thresholds = precision_recall_curve(y_test, y_prob, pos_label=1)
    plt.plot(recall, precision, lw=3, label='{} (AUC={:.2f}, time={:.2f}s)'.
             format(name, auc(recall, precision), time.time() - t0))
    plt.xlabel("Recall", fontsize=17)
    plt.ylabel("Precision", fontsize=17)
    plt.legend(fontsize=14, loc="best")




從以上幾張圖中咱們能夠得出一些推論

  1. 就時間開銷而言,BalanceCascade以及兩種過採樣欠採樣結合的方法( SMOTE + ENNSMOTE + Tomek )耗時最高,若是追求速度的話這幾個可能並不是很好的選擇。

  2. 第一個數據集 us_crime 的多數類和少數類樣本比例爲 12:1,相差不是很懸殊,綜合ROC曲線和PR曲線的AUC來看,兩種集成方法EasyEnsembleBalanceCascade表現較好。
    對於第二個數據集 abalone_19 來講,多數類和少數類樣本比例爲 130:1,並且少數類樣本很是少,於是從結果來看幾種過採樣方法如Borderline SMOTE, SMOTE+Tomek等效果較好。可見在類別差別很大的狀況下,過採樣能必定程度上彌補少數類樣本的極端不足。然而從PR曲線上來看,其實結果都不盡如人意,對於這種極端不平衡的數據可能比較適合異常檢測的方法,之後有機會詳述。

  3. 上篇文章中提到 「ROC曲線一般會呈現一個過度樂觀的效果估計」,這裏再一次獲得體現。第一個數據集中大部分ROC曲線的AUC都在0.9左右,而PR曲線都在0.5左右。第二個數據集則更誇張,從PR曲線來看其實模型對於少數類的預測準確率是無限接近於0了,但在ROC曲線上卻很難看出這一點。

  4. 若是單純從ROC曲線和PR曲線上來看,表面上各類採樣方法和base model差異不大,但實際上這其中倒是暗流涌動。下面來看一下 us_crime 數據集中各方法的 classification report。


def ensemble_method_2(method):  # 定義一個簡化版集成方法
    xx, yy = method.fit_sample(X_train, y_train)
    y_pred, y_prob = np.zeros(len(X_test)), np.zeros(len(X_test))
    for X_ensemble, y_ensemble in zip(xx, yy):
        model = LogisticRegression()
        model.fit(X_ensemble, y_ensemble)
        y_pred += model.predict(X_test)
    return np.where(y_pred >= 0, 1, -1)


us_crime = fetch_datasets()['us_crime']
X_train, X_test, y_train, y_test = train_test_split(us_crime.data, us_crime.target, 
                                                    test_size=0.5, random_state=42, stratify=us_crime.target)

class_names = ['majority class', 'minority class']

model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("------------------------Base Model---------------------- \n", 
      classification_report(y_test, y_pred, target_names=class_names), '\n')

model = make_pipeline(SMOTE(random_state=42), LogisticRegression())
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("--------------------------SMOTE------------------------ \n",
      classification_report(y_test, y_pred, target_names=class_names), '\n')

model = make_pipeline(NearMiss(version=2, random_state=42), LogisticRegression())
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("------------------------NearMiss------------------------ \n",
      classification_report(y_test, y_pred, target_names=class_names), '\n')

y_pred = ensemble_method_2(EasyEnsemble(random_state=42))
class_names = ['majority class', 'minority class']
print("-----------------------EasyEnsemble--------------------- \n",
      classification_report(y_test, y_pred, target_names=class_names), '\n')


輸出:

------------------------Base Model---------------------- 
                 precision    recall  f1-score   support

majority class       0.95      0.98      0.97       922
minority class       0.62      0.35      0.44        75

   avg / total       0.92      0.93      0.93       997
 

--------------------------SMOTE------------------------ 
                 precision    recall  f1-score   support

majority class       0.98      0.90      0.94       922
minority class       0.38      0.75      0.51        75

   avg / total       0.93      0.89      0.91       997
 

------------------------NearMiss------------------------ 
                 precision    recall  f1-score   support

majority class       0.97      0.81      0.88       922
minority class       0.24      0.73      0.36        75

   avg / total       0.92      0.80      0.84       997
 

-----------------------EasyEnsemble--------------------- 
                 precision    recall  f1-score   support

majority class       0.98      0.85      0.91       922
minority class       0.31      0.84      0.45        75

   avg / total       0.93      0.85      0.88       997



這裏咱們主要關注少數類樣本,能夠看到 Base Model 的特色是precision高,recall低,而幾種採樣方法則相反,precision低,recall高。採樣方法廣泛擴大了少數類樣本的決策邊界(從上文中的決策邊界圖就能看出來),因此把不少多數類樣本也劃爲少數類了,致使precision降低而recall提高。固然這些都是分類閾值爲0.5的前提下得出的結論,若是進一步調整閾值的話能獲得更好的模型。策略是base model的閾值往下調,採樣方法的閾值往上調。


def ensemble_method_3(method):
    xx, yy = method.fit_sample(X_train, y_train)
    y_pred, y_prob = np.zeros(len(X_test)), np.zeros(len(X_test))
    for X_ensemble, y_ensemble in zip(xx, yy):
        model = LogisticRegression()
        model.fit(X_ensemble, y_ensemble)
        y_pred += np.where(model.predict_proba(X_test)[:, 1] >= 0.7, 1, -1)  # 閾值 > 0.7
    return np.where(y_pred >= 0, 1, -1)


us_crime = fetch_datasets()['us_crime']
X_train, X_test, y_train, y_test = train_test_split(us_crime.data, us_crime.target, 
                                                    test_size=0.5, random_state=42, stratify=us_crime.target)

model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = np.where(model.predict_proba(X_test)[:, 1] >= 0.3, 1, -1)
print("-----------------Base Model, threshold >= 0.3--------------- \n", 
      classification_report(y_test, y_pred, target_names=class_names), '\n')

model = make_pipeline(SMOTE(random_state=42), LogisticRegression())
model.fit(X_train, y_train)
y_pred = np.where(model.predict_proba(X_test)[:, 1] >= 0.9, 1, -1)
print("-----------------SMOTE, threshold >= 0.9--------------- \n", 
      classification_report(y_test, y_pred, target_names=class_names), '\n')

model = make_pipeline(NearMiss(version=2, random_state=42), LogisticRegression())
model.fit(X_train, y_train)
y_pred = np.where(model.predict_proba(X_test)[:, 1] >= 0.7, 1, -1)
print("-------------------NearMiss, threshold >= 0.7------------------- \n",
      classification_report(y_test, y_pred, target_names=class_names), '\n')

model = EasyEnsemble(random_state=42)
y_pred = ensemble_method_3(model)
class_names = ['majority class', 'minority class']
print("--------------EasyEnsemble, threshold >= 0.7-------------- \n",
      classification_report(y_test, y_pred, target_names=class_names), '\n')


輸出:


-----------------Base Model, threshold >= 0.3------------
                 precision    recall  f1-score   support

majority class       0.96      0.96      0.96       922
minority class       0.53      0.53      0.53        75

   avg / total       0.93      0.93      0.93       997
 

-----------------SMOTE, threshold >= 0.9---------------- 
                 precision    recall  f1-score   support

majority class       0.96      0.97      0.97       922
minority class       0.60      0.49      0.54        75

   avg / total       0.93      0.94      0.93       997
 

-------------------NearMiss, threshold >= 0.7------------
                 precision    recall  f1-score   support

majority class       0.96      0.90      0.93       922
minority class       0.32      0.59      0.42        75

   avg / total       0.92      0.88      0.89       997
 

--------------EasyEnsemble, threshold >= 0.7-------------
                 precision    recall  f1-score   support

majority class       0.97      0.92      0.95       922
minority class       0.42      0.69      0.53        75

   avg / total       0.93      0.91      0.92       997



在通過閾值調整後,各方法的總體F1分數都有提升,可見不少單指標如 precision,recall 等都會受到不一樣閾值的影響。因此這也是爲何在類別不平衡問題中用ROC和PR曲線來評估很是流行,由於它們不受特定閾值變化的影響,反映的是模型的總體預測能力。

不過在這裏我不得不得出一個比較悲觀的結論:就這兩個數據集的結果來看,若是自己數據偏斜不是很厲害,那麼採樣方法的提高效果很細微。若是自己數據偏斜很厲害,採樣方法縱使比base model好不少,但因爲base model自己的少數類預測能力不好,因此本質上也不盡如人意。這就像考試原來一直靠10分,採樣了以後考了30分,絕對意義上提高很大,但其實仍是差得遠了。






相關文章
相關標籤/搜索