1. 程式人生 > 其它 >如何處理機器學習中類的不平衡問題

如何處理機器學習中類的不平衡問題

不平衡類使機器學習的“準確性”受到破壞。這在機器學習(特別是分類)中是一個非常普遍的問題,在每個類中都有一個不成比例的資料集。標準的準確性不再可靠地度量效能,這使得模型培訓更加棘手。 在本教程中,我們將探討5種處理不平衡類的有效方法。

在我們開始之前的重要說明:

首先,請注意,我們不會分離出一個單獨的測試集,調優超引數,或者實現交叉驗證。 換句話說,我們不打算遵循最佳實踐。相反,本教程的重點是解決不平衡的類。 此外,並不是所有的技術都適用於每個問題。然而,10次中有9次,其中至少有一種技術是可以獲得成功的。

Balance Scale資料集

此教程中,我們將使用一個名為Balance Scale Data的合成數據集,你可以從UCI機器學習儲存庫中下載。下載地址:   http://archive.ics.uci.edu/ml/datasets/balance+scale 這個資料集最初是用來模擬心理實驗結果的,但是它對我們很有用,因為它是一個可管理的規模並且有著不平衡的類。

import pandas as pd
import numpy as np
 
# Read dataset
df = pd.read_csv('balance-scale.data', 
                 names=['balance', 'var1', 'var2', 'var3', 'var4'])
 
# Display example observations
df.head()

這個資料集包含關於一個天平是否平衡的資訊,基於兩個天平臂的重量和距離。

  • 它有一個目標變數,我們把它標記為balance。
  • 它有四個輸入特性,我們通過var4把它標記為var1。

目標變數有三個類

  • 右重的R,即當var3 * var4 > var1 * var2時
  • 左重的L,即當var3 * var4 < var1 * var2時
  • 平衡的B,即當var3 * var4 = var1 * var2時
df['balance'].value_counts()
# R    288
# L    288
# B     49
# Name: balance, dtype: int64

但是在本教程,我們將把它轉換成一個二進位制的分類問題。如果天平平衡,我們把每個觀察標記為1(正類) 或者如果比例不平衡的話,每個觀察標記為0(負類):

# Transform into binary classification
df['balance'] = [1 if b=='B' else 0 for b in df.balance]
 
df['balance'].value_counts()
# 0    576
# 1     49
# Name: balance, dtype: int64
# About 8% were balanced

正如你所看到的,只有8%的觀測結果是平衡的。 因此,如果我們總是預測0,我們就能達到92%的準確率。

不平衡類的危害

現在我們有了一個數據集,我們可以真正地展示不平衡類的危害。首先,讓我們匯入邏輯迴歸演算法和 Scikit-Learn的精確性度量。

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

接下來,我們將使用一個非常簡單的模型來使用所有的預設設定。

# Separate input features (X) and target variable (y)
y = df.balance
X = df.drop('balance', axis=1)
 
# Train model
clf_0 = LogisticRegression().fit(X, y)
 
# Predict on training set
pred_y_0 = clf_0.predict(X)

正如上面所提到的,許多機器學習演算法的設計是為了在預設情況下最大化總體的精確性。 我們可以證實這一點:

# How's the accuracy?
print( accuracy_score(pred_y_0, y) )
# 0.9216

所以我們的模型有92%的準確率,但這是因為它只預測了一個類嗎?

# Should we be excited?
print( np.unique( pred_y_0 ) )
# [0]

正如你所看到的,這個模型只預測0,這意味著它完全忽略了少數類,而偏向於多數類。 接下來,我們將研究處理不平衡類的第一個技巧:對少數類進行取樣。

1.上取樣少數類

上取樣是隨機複製少數類的觀察結果,以強化其訊號。這樣做有幾個啟發,但最常用的方法是簡單地用替換來重新取樣。 首先,我們將從scikit-learn匯入重取樣模組:

from sklearn.utils import resample

接下來,我們將建立一個帶有上取樣的少數類的新DataFrame。 下面是步驟:

  1. 首先,我們將把每個類的觀察分離到不同的DataFrames。
  2. 接下來,我們將用替換來對少數類進行重新取樣,並設定與多數類相匹配的樣本數量。
  3. 最後,我們將把上取樣的少數類DataFrame與原始的多數類DataFrame合併在一起。

程式碼:

# Separate majority and minority classes
df_majority = df[df.balance==0]
df_minority = df[df.balance==1]
 
# Upsample minority class
df_minority_upsampled = resample(df_minority, 
                                 replace=True,     # sample with replacement
                                 n_samples=576,    # to match majority class
                                 random_state=123) # reproducible results
 
# Combine majority class with upsampled minority class
df_upsampled = pd.concat([df_majority, df_minority_upsampled])
 
# Display new class counts
df_upsampled.balance.value_counts()
# 1    576
# 0    576
# Name: balance, dtype: int64

正如你所看到的,新的DataFrame比原始資料有更多的觀察值,而這兩個類的比率現在是1:1。 讓我們用邏輯迴歸來訓練另一個模型,這次是在平衡資料集上:

# Separate input features (X) and target variable (y)
y = df_upsampled.balance
X = df_upsampled.drop('balance', axis=1)
 
# Train model
clf_1 = LogisticRegression().fit(X, y)
 
# Predict on training set
pred_y_1 = clf_1.predict(X)
 
# Is our model still predicting just one class?
print( np.unique( pred_y_1 ) )
# [0 1]
 
# How's our accuracy?
print( accuracy_score(y, pred_y_1) )
# 0.513888888889

很好,現在模型不再只預測一個類了。雖然準確性也在急劇下降,但作為一個性能指標,它現在更有意義了。

2.下采樣多數類

為了防止它的訊號在學習演算法中占主導地位,下采樣會隨機地從多數類中去除觀察結果。最常見的做法是重新抽樣,而且不需要替換。這個過程類似於上取樣的過程。 下面是步驟:

  1. 首先,我們將把每個類的觀察分離到不同的DataFrames。
  2. 接下來,我們將在沒有替換的情況下對多數類進行重新取樣,並設定與少數類相匹配的樣本數量。
  3. 最後,我們將把下采樣的多數類DataFrame與原始的少數類DataFrame合併在一起。

程式碼:

# Separate majority and minority classes
df_majority = df[df.balance==0]
df_minority = df[df.balance==1]
 
# Downsample majority class
df_majority_downsampled = resample(df_majority, 
                                 replace=False,    # sample without replacement
                                 n_samples=49,     # to match minority class
                                 random_state=123) # reproducible results
 
# Combine minority class with downsampled majority class
df_downsampled = pd.concat([df_majority_downsampled, df_minority])
 
# Display new class counts
df_downsampled.balance.value_counts()
# 1    49
# 0    49
# Name: balance, dtype: int64

這一次,新的DataFrame的觀測資料比原來的少,而這兩個類的比率現在是1:1。 讓我們再一次用邏輯迴歸來訓練一個模型:

# Separate input features (X) and target variable (y)
y = df_downsampled.balance
X = df_downsampled.drop('balance', axis=1)
 
# Train model
clf_2 = LogisticRegression().fit(X, y)
 
# Predict on training set
pred_y_2 = clf_2.predict(X)
 
# Is our model still predicting just one class?
print( np.unique( pred_y_2 ) )
# [0 1]
 
# How's our accuracy?
print( accuracy_score(y, pred_y_2) )
# 0.581632653061

這個模型並不是只預測一個類,而且它的準確性更高。我們仍然希望在一個不可見的測試資料集上驗證模型。

3.改變你的效能指標

到目前為止,我們已經研究了通過重新取樣資料集來解決不平衡類的兩種方法。接下來,我們將考慮使用其他效能指標來評估模型。 愛因斯坦曾經說過:“如果你根據能不能爬樹來判斷一條魚的能力,那你一生都會認為它是愚蠢的。”這句話確實強調了選擇正確的評價指標的重要性。對於一個用於分類的通用度量標準,我們推薦在ROC曲線下的區域(AUROC)。

  • 我們不會在本教程中詳細介紹它的細節,但你可以在這裡閱讀更多相關內容。
  • 直觀地說,AUROC代表了你的模型將觀察與兩個類區分開來的可能性。
  • 換句話說,如果你隨機地從每個類中選擇一個觀察,你的模型能夠正確地排列它們的概率是多少?

我們可以從Scikit-Learn中匯入這個指標:

from sklearn.metrics import roc_auc_score

要計算AUROC,你需要預測類的概率,而不是預測類。你可以使用.predictproba()函式來獲得它們:

# Predict class probabilities
prob_y_2 = clf_2.predict_proba(X)
 
# Keep only the positive class
prob_y_2 = [p[1] for p in prob_y_2]
 
prob_y_2[:5] # Example
# [0.45419197226479618,
#  0.48205962213283882,
#  0.46862327066392456,
#  0.47868378832689096,
#  0.58143856820159667]

那麼,這個模型(在下采樣的資料集上的訓練)是如何在AUROC上做的呢?

print( roc_auc_score(y, prob_y_2) )
# 0.568096626406

好吧……這與在不平衡的資料集上訓練的原始模型相比如何?

prob_y_0 = clf_0.predict_proba(X)
prob_y_0 = [p[1] for p in prob_y_0]
 
print( roc_auc_score(y, prob_y_0) )
# 0.530718537415

請記住,我們在不平衡的資料集上訓練的原始模型的精確度是92%,這遠遠高於在取樣資料集中訓練的模型的58%的準確率。然而,後面的模型有57%的AUROC,高於原始模型53%的比例(但不是很多)。 注意:如果你有一個0.47的AUROC,它只是意味著你需要對預測進行反轉,因為Scikit-Learn正在曲解正類。AUROC應該>= 0.5。

4.懲罰演算法(代價敏感訓練)

下一個策略是使用懲罰的學習演算法來增加少數群體的分類錯誤的成本。這種技術的一個流行演算法是Penalized-SVM:

from sklearn.svm import SVC

在訓練過程中,我們可以用“class_weight='balanced'”來懲罰少數群體的錯誤,這與他們所代表的不足比例成正比。如果我們想要支援SVM演算法的概率估計,我們還需要包含probability=True。 讓我們在原始不平衡的資料集上使用Penalized-SVM來訓練一個模型:

# Separate input features (X) and target variable (y)
y = df.balance
X = df.drop('balance', axis=1)
 
# Train model
clf_3 = SVC(kernel='linear', 
            class_weight='balanced', # penalize
            probability=True)
 
clf_3.fit(X, y)
 
# Predict on training set
pred_y_3 = clf_3.predict(X)
 
# Is our model still predicting just one class?
print( np.unique( pred_y_3 ) )
# [0 1]
 
# How's our accuracy?
print( accuracy_score(y, pred_y_3) )
# 0.688
 
# What about AUROC?
prob_y_3 = clf_3.predict_proba(X)
prob_y_3 = [p[1] for p in prob_y_3]
print( roc_auc_score(y, prob_y_3) )
# 0.5305236678

同樣,我們的目的只是為了說明這個技巧。要真正確定這些策略中哪一種最適合這個問題,你需要在一個測試集上對模型進行評估。

5.使用樹型結構演算法

我們將考慮的最後一種策略是使用樹型結構演算法。 決策樹通常在不平衡的資料集上表現良好,因為它們的層次結構允許它們從兩個類中學習訊號。 在現代的應用機器學習中,樹群(隨機的森林,梯度增長的樹木等)幾乎總是比奇異的決策樹表現得更好,所以我們直接跳到那裡:

from sklearn.ensemble import RandomForestClassifier

現在,讓我們在原始不平衡的資料集上使用一個隨機的森林來訓練一個模型。

# Separate input features (X) and target variable (y)
y = df.balance
X = df.drop('balance', axis=1)
 
# Train model
clf_4 = RandomForestClassifier()
clf_4.fit(X, y)
 
# Predict on training set
pred_y_4 = clf_4.predict(X)
 
# Is our model still predicting just one class?
print( np.unique( pred_y_4 ) )
# [0 1]
 
# How's our accuracy?
print( accuracy_score(y, pred_y_4) )
# 0.9744
 
# What about AUROC?
prob_y_4 = clf_4.predict_proba(X)
prob_y_4 = [p[1] for p in prob_y_4]
print( roc_auc_score(y, prob_y_4) )
# 0.999078798186

哇! 97%的準確率和100%的AUROC? 然而,雖然這些結果是令人鼓舞的,但是模型可能會過於合適,所以在做出最終決定之前,你仍然應該在一個看不見的測試集中評估你的模型。 注意:由於演算法的隨機性,你的數字可能略有不同。你可以為可再生的結果設定一個隨機的“種子”。 此外,有一些策略並沒有進入到本教程中:

建立合成樣本(資料增加)

建立合成樣本是近距離抽樣的“近親”,有些人可能把它們歸為一種。例如, SMOTE演算法是一種從少數類中重新取樣的方法,同時略微擾動特徵值,從而建立“新的”樣本。你可以在 imblearn庫中找到一個SMOTE的實現。

結合少數類

將目標變數的少數類組合在一起可能適合於一些多類問題。 例如,假設你希望預測信用卡欺詐。在你的資料集中,每一種欺騙方法都可能被單獨標記,但是你可能不關心如何去區分它們。你可以將它們組合成一個單一的“欺詐”類,並將此問題作為二進位制分類。

結論與展望

在本教程中,我們討論了5個處理機器學習不平衡類的方法。這些策略受制於沒有No Free Lunch theorem這個定理,你應該嘗試它們其中的幾個,並使用來自測試集的結果來決定你的問題的最佳解決方案。