探索SMOTE演算法

ihades發表於2020-02-18

原文連結

摘要

SMOTE是一種綜合取樣人工合成資料演算法,用於解決資料類別不平衡問題(Imbalanced class problem),以Over-sampling少數類和Under-sampling多數類結合的方式來合成資料。本文將以Nitesh V. Chawla(2002)的論文為藍本,闡述SMOTE的核心思想以及實現其樸素演算法,在傳統分類器(貝葉斯和決策樹)上進行對比演算法效能並且討論其演算法改進的途徑。

1. 引言

類別不平衡是一種在分類器模型訓練過程中常見的問題之一,如通過大量胸透圖片來學習判斷一個人是否有癌症,又如在網路流日誌中學習檢測可能是攻擊行為的資料模式,這一類的任務中都是正常的類多於異常(診斷屬於癌症,屬於攻擊行為)的類,在類別不平衡資料下訓練出來的分類器要非常的小心,即使該分類器擁有很高的精度,因為它很可能會習得大部分的都是正常的,而我們可能需要的是它能夠最大程度的識別異常行為,哪怕精度低於前者。

為了解決這一問題,業內已經有以下5種公認的方法去擴充資料集[1],以至於類別均勻:

  1. 隨機的增大少數類的樣本數量。
  2. 隨機的增大特定少數類樣本的數量。
  3. 隨機的減少多數類樣本的數量。
  4. 隨機的減少特定多數類樣本的數量。
  5. 修改代價函式,使得少數類出錯的代價更高。

本文要介紹的SMOTE演算法就是一種綜合1,3方法的改進方式,它以每個樣本點的k個最近鄰樣本點為依據,隨機的選擇N個鄰近點進行差值乘上一個[0,1]範圍的閾值,從而達到合成資料的目的。這種演算法的核心是:特徵空間上鄰近的點其特徵都是相似的。它並不是在資料空間上進行取樣,而是在特徵空間中進行取樣,所以它的準確率會高於傳統的取樣方式。這也是為什麼到目前為止SMOTE以及其派生的演算法仍然是較為主流的取樣技術的原因。

dif

如上圖所示,假設資料點A在特徵空間上有4個鄰近點,若N為2,則SMOTE會隨機選擇其中2個鄰近點B,C,分別計算A->B, A->C的距離,如圖中綠線和紅線所示,在綠線或紅線上的所有采樣點都是合理的,如點A1。為了確保資料點儘可能的多樣(不重疊),故乘上一個[0, 1]之間的隨機因子。

本文將會在第2章根據SMOTE的核心以及其虛擬碼實現該演算法,並應用在測試資料集上;第3章會使用第三方imbalanced-learn庫中實現的SMOTE演算法進行取樣,以驗證我們實現的演算法的準確性,當然這個庫中的演算法要優於樸素的SMOTE演算法,之後我們會以決策樹和高斯貝葉斯分類器為工具,對測試原始資料應用我們所實現的SMOTE取樣後產生的資料以及應用第三方庫SMOTE產生的資料三者分別產生的資料集進行效能比較;第4章會討論樸素SMOTE演算法更加魯棒和表現更好的優化途徑;第5章是對本文的總結。

2. 演算法分析與實現

下圖是在SMOTE論文中提出的虛擬碼,由兩個函式SMOTE(T, N, K)Populate(N, i, nnarray)組成。

algorithm

SMOTE負責接受要取樣的類資料集X,返回一個經過SMOTE取樣後的資料集,大小為(N/100)*T,函式有三個引數,分別是T: 需要處理的資料集X的樣本數量; N: 取樣比例,一般為100, 200, 300等整百數,對應即1倍,2倍,3倍;K: 為取樣的最近鄰數量,論文中預設為5SMOTE程式碼思想非常簡單,掃描每一個樣本點,計算每一個樣本點的K個最近鄰,將每一個最近鄰樣本點的索引記錄在nnarray中,之後傳入Populate(N, i, nnarray)中即完成一個樣本點的取樣。

Populate則負責根據nnarray中的索引去隨機生成N個與觀測樣本i相似的樣本。該函式會計算隨機鄰近點nn與觀測樣本i點的每一個特徵之間的差距dif,將其差距乘上一個[0, 1]隨機因子gap,再將dif*gap的值加上觀測點i即完成了一個特徵的合成。

在Python中實現如下:

注:為了保證本文中所有程式碼的可復現性,設定的random_state均為666

def NaiveSMOTE(X, N=100, K=5):
    """
    {X}: minority class samples;
    {N}: Amount of SMOTE; default 100;
    {K} Number of nearest; default 5;
    """
    # {T}: Number of minority class samples; 
    T = X.shape[0]
    if N < 100:
        T = (N/100) * T
        N = 100
    N = (int)(N/100)
    
    numattrs = X.shape[1]
    samples = X[:T]
    neigh = NearestNeighbors(n_neighbors=K)
    neigh.fit(samples)
    Synthetic = np.zeros((T*N, numattrs))
    newindex = 0
    
    def Populate(N, i, nns, newindex):
        """
        Function to generate the synthetic samples.
        """
        for n in range(N):
            nn = np.random.randint(0, K)
            for attr in range(numattrs):
                dif = samples[nns[nn], attr] - samples[i, attr]
                gap = np.random.random()
                Synthetic[newindex, attr] = samples[i, attr] + gap*dif
            newindex += 1
        return newindex
    
    for i in range(T):
        nns = neigh.kneighbors([samples[i]], K, return_distance=False)
        newindex = Populate(N, i, nns[0], newindex)
    return Synthetic
複製程式碼

這裡沒有采用矩陣運算,而是完完全全的按照論文中的方式復現(所以稱為NaiveSMOTE),其中最近鄰的計算我們使用scikit-learn提供的NearestNeighbors類完成。

接下來我們使用scikit-learn中的make_classification來生成測試分類資料集,模擬不平衡類資料,當然有興趣的讀者也可以去尋找論文中所使用的資料集。

from sklearn.datasets import make_classification
X, y = make_classification(n_samples=500,
                           n_features=9,
                           n_redundant=3,
                           weights=(0.1, 0.9),
                           n_clusters_per_class=2,
                           random_state=666)   # 為了可復現性
print(X.shape, y.shape)       # ((500, 9), (500,))

# 檢視y的各類樣本數 
print(view_y(y))              # class 0: 50  class 1: 450
複製程式碼

原資料集的分佈如下圖所示,其中紅色圓圈為正類即少數類,藍色×為負類即多數類。

original_dataset

將我們實現的NaiveSMOTE應用在此測試資料上:

X_over_sampling = NaiveSMOTE(X[y==0], N=800)
print(X_over_sampling.shape)         # (400, 9) 新增了400個樣本

# 將合成資料與原資料集合並
new_X = np.r_[X, X_over_sampling]
new_y = np.r_[y, np.zeros((X_over_sampling.shape[0]))]
print(new_X.shape, new_y.shape)      # ((900, 9), (900,))

print(view_y(new_y))                 # class 0: 450 class 1: 450
複製程式碼

接下來我們將原資料集與經過NaiveSMOTE合成後的資料集進行比對:

ori_vs_Naive

可以很清晰的看見原來的類增大至一個滿意的水平,並且生成的類之間距離都相距不遠。

3. 演算法效能比對

本章我們將引入第三方庫imbalanced-learn中提供的SMOTE類與依據論文實現的NaiveSMOTE進行比較。兩者都是基於同一個論文的思想去實現的,只是第三方庫中實現的SMOTE更為魯棒,並且能夠綜合考慮所有的類,是一種完全意義上的Combination of Over-sampling minority class and Under-sampling majority class技術。因此我們引入它只為了驗證我們所復現的方法的準確性。

from imblearn.over_sampling import SMOTE

sm = SMOTE(random_state=666)
X_res, y_res = sm.fit_resample(X, y)     # 即完成了合成
print(X_res.shape, y_res.shape)          # ((900, 9), (900,))
複製程式碼

下圖對比imblearnSMOTE與我們復現的NaiveSMOTE生成的資料集:

nav_vs_third

能看出NaiveeSMOTE合成的資料更加傾向於中部,而第三方的SMOTE能夠綜合考慮全域性情況下方區域生成的資料要比NaiveSMOTE的多。

接下來我們使用DecisionTreeGaussianNaive來驗證3個資料集(原資料集、NaiveSMOTE合成的資料集和第三方SMOTE合成的資料集)的ROC曲線,具體程式碼見附錄中的Notebook檔案

原資料的ROC曲線

ori_roc

NaiveSMOTE生成的資料的ROC曲線

naive_roc

第三方SMOTE生成的資料的ROC曲線

third_roc

可以看出NaiveSMOTEimblearnSMOTE生成的資料的AUC面積均大於原始資料的面積。imblearnSMOTE生成的資料在GaussianNaiveBayes分類器上的表現要好於NaiveSMOTE所生成的資料訓練出來的分類器。

4. 演算法改進

這部分我們從NaiveSMOTE的三個方面進行優化討論:

  1. 處理速度。 NaiveSMOTE中有許多處都可以改成使用矩陣運算的方式,這樣會提高資料處理的速度。並且Populate函式也顯得非常冗餘,可以用矩陣運算將其改寫。

  2. 全域性合理性。 全域性合理性包括兩個方面:合成資料比率的合理性和合成資料在全域性的合理性。

    合成資料比率的合理性:在NaiveSMOTE中可以知道樣本的數量有N合成比率來控制,只能合成其整數倍,本文中使用的資料集恰好是1:9,只要合成原始資料的8倍即可是兩類都到達一個相對數量同等的水平,但是在現實資料集中大部分都不具備成倍的數量關係,因此可以考慮更換一個更好的生成比率,使得每個類均能處於相對數量近似的水平,避免出現合成後的原少數類變多數類的情況。

    合成資料在全域性的合理性:回想在NaiveSMOTEimblearn SMOTE各自合成的資料對比中可以發現,NaiveSMOTE更加容易使得合成的資料聚集在某一樣本點附近,而imblearn SMOTE所合成的資料更為稀疏且分佈均勻,更加接近原始資料的概率分佈。其原因在於NaiveSMOTE在進行合成時只考慮原始的資料樣本,沒不考慮合成後的資料樣本會如何影響全域性資料。可以考慮在每次合成資料後將其加入資料集,在處理過程中將合成資料也加入考慮範圍。

  3. 魯棒性。 不難發現NaiveSMOTE僅能夠處理數值型的資料並且其距離計算公式很有可能產生誤解。在現實中有許多非數值型的資料,如性別, 職業等等。當然可以將其全部重新編碼成可以應用數值處理的資料,如將性別進行OneHot編碼,但是此時的距離計算公式就會出現誤解,可以考慮更換為歐氏距離、曼哈頓距離或者馬氏距離等。

Note:在對性別進行OneHot編碼時情況如下:

男性: 0 1
女性: 1 0
複製程式碼

如果按照NaiveSMOTE原始的距離計算公式,很有可能會將其理解為男性和女性的差距為1,因此產生誤解。

5. 結論

本文對三種資料進行對比,經過NaiveSMOTEimblearn SMOTE合成後的資料在傳統分類器上的表現均好於原始資料(即不做任何修改),且imblearn SMOTE在魯棒性上要高於NaiveSMOTE。討論NaiveSMOTE的不足與其可能的優化方向。建議在實際應用中優先考慮魯棒性更高的imlearn SMOTE而不是自己造輪子,imblearn SMOTE的實現更加符合主流標準。但不能因此就忽略了NaiveSMOTE的意義,任何的優化有必要要基於原有的基礎。理解NaiveSMOTE才能去更好的使用和優化它。

附錄

實驗Notebook

參考文獻

  1. Japkowicz, Nathalie, and Shaju Stephen. "The class imbalance problem: A systematic study." Intelligent data analysis 6.5 (2002): 429-449.

相關文章