1. 程式人生 > >從零開始實現過抽樣演算法smote

從零開始實現過抽樣演算法smote

本系列文章的所有原始碼都將會開源,需要原始碼的小夥伴可以去我的 Github fork!

本文將主要詳細介紹一下SMOTE(Synthetic Minority Oversampling Technique)演算法從原理到程式碼實踐,SMOTE主要是用來解決類不平衡問題的,在講解SMOTE演算法之前,我們先解釋一下什麼是類不平衡問題、為什麼類不平衡帶來的問題以及相應的解決方法。

1. 什麼是類不平衡問題

類不平衡(class-imbalance)是指在訓練分類器中所使用的訓練集的類別分佈不均。比如說一個二分類問題,1000個訓練樣本,比較理想的情況是正類、負類樣本的數量相差不多;而如果正類樣本有995個、負類樣本僅5個,就意味著存在類不平衡。

在後文中,把樣本數量過少的類別稱為“少數類”。

但實際上,資料集上的類不平衡到底有沒有達到需要特殊處理的程度,還要看不處理時訓練出來的模型在驗證集上的效果。有些時候是沒必要處理的。

2. 類不平衡引發的問題

2.1 從模型的訓練過程來看

從訓練模型的角度來說,如果某類的樣本數量很少,那麼這個類別所提供的“資訊”就太少。

使用經驗風險(模型在訓練集上的平均損失)最小化作為模型的學習準則。設損失函式為0-1 loss(這是一種典型的均等代價的損失函式),那麼優化目標就等價於錯誤率最小化(也就是accuracy最大化)。考慮極端情況:1000個訓練樣本中,正類樣本999個,負類樣本1個。訓練過程中在某次迭代結束後,模型把所有的樣本都分為正類,雖然分錯了這個負類,但是所帶來的損失實在微不足道,accuracy已經是99.9%,於是滿足停機條件或者達到最大迭代次數之後自然沒必要再優化下去,ok,到此為止,訓練結束!於是這個模型……

模型沒有學習到如何去判別出少數類,這時候模型的召回率會非常低。

2.2 從模型的預測過程來看

考慮二項Logistic迴歸模型。輸入一個樣本 x ,模型輸出的是其屬於正類的概率 ŷ 。當 ŷ >0.5 時,模型判定該樣本屬於正類,否則就是屬於反類。

為什麼是0.5呢?可以認為模型是出於最大後驗概率決策的角度考慮的,選擇了0.5意味著當模型估計的樣本屬於正類的後驗概率要大於樣本屬於負類的後驗概率時就將樣本判為正類。但實際上,這個後驗概率的估計值是否準確呢?

從機率(odds)的角度考慮:機率表達的是樣本屬於正類的可能性與屬於負類的可能性的比值。模型對於樣本的預測機率為 ŷ 1

ŷ 

模型在做出決策時,當然希望能夠遵循真實樣本總體的正負類樣本分佈:設 θ 等於正類樣本數除以全部樣本數,那麼樣本的真實機率為 θ1θ 。當觀測機率大於真實機率時,也就是 ŷ >θ 時,那麼就判定這個樣本屬於正類。

雖然我們無法獲悉真實樣本總體,但之於訓練集,存在這樣一個假設:訓練集是真實樣本總體的無偏取樣。正是因為這個假設,所以認為訓練集的觀測機率θ̂ 1θ̂ 就代表了真實機率 θ1θ

所以,在這個假設下,當一個樣本的預測機率大於觀測機率時,就應該將樣本判斷為正類。

3. 解決類不平衡問題的方法

目前主要有三種辦法:

3.1 調整 θ 值

根據訓練集的正負樣本比例,調整 θ 值。   

這樣做的依據是上面所述的對訓練集的假設。但在給定任務中,這個假設是否成立,還有待討論。

3.2 過取樣

對訓練集裡面樣本數量較少的類別(少數類)進行過取樣,合成新的樣本來緩解類不平衡。下面將介紹一種經典的過取樣演算法:SMOTE。

3.3 欠取樣

對訓練集裡面樣本數量較多的類別(多數類)進行欠取樣,拋棄一些樣本來緩解類不平衡。

4. SMOTE演算法原理

SMOTE,合成少數類過取樣技術.它是基於隨機過取樣演算法的一種改進方案,由於隨機過取樣採取簡單複製樣本的策略來增加少數類樣本,這樣容易產生模型過擬合的問題,即使得模型學習到的資訊過於特別(Specific)而不夠泛化(General),SMOTE演算法的基本思想是對少數類樣本進行分析並根據少數類樣本人工合成新樣本新增到資料集中,演算法流程如下。

4.1 SMOTE演算法流程

對於正樣本資料集X(minority class samples),遍歷每一個樣本:

(1) 對於少數類(X)中每一個樣本x,計算它到少數類樣本集(X)中所有樣本的距離,得到其k近鄰。

(2) 根據樣本不平衡比例設定一個取樣比例以確定取樣倍率sampling_rate,對於每一個少數類樣本x,從其k近鄰中隨機選擇sampling_rate個近鄰,假設選擇的近鄰為 x(1),x(2),...,x(sampling_rate)

(3) 對於每一個隨機選出的近鄰 x(i)(i=1,2,...,sampling_rate),分別與原樣本按照如下的公式構建新的樣本

xnew=x+rand(0,1)(x(i)x)

4.2 SMOTE演算法程式碼實現

下面我們就用程式碼來實現一下:

import random
from sklearn.neighbors import NearestNeighbors
import numpy as np

class Smote:
    """
    SMOTE過取樣演算法.


    Parameters:
    -----------
    k: int
        選取的近鄰數目.
    sampling_rate: int
        取樣倍數, attention sampling_rate < k.
    newindex: int
        生成的新樣本(合成樣本)的索引號.
    """
    def __init__(self, sampling_rate=5, k=5):
        self.sampling_rate = sampling_rate
        self.k = k
        self.newindex = 0

    def fit(self, X, y=None):
        if y is not None:
            negative_X = X[y==0]
            X = X[y==1]

        n_samples, n_features = X.shape
        # 初始化一個矩陣, 用來儲存合成樣本
        self.synthetic = np.zeros((n_samples * self.sampling_rate, n_features))

        # 找出正樣本集(資料集X)中的每一個樣本在資料集X中的k個近鄰
        knn = NearestNeighbors(n_neighbors=self.k).fit(X)
        for i in range(len(X)):
            k_neighbors = knn.kneighbors(X[i].reshape(1,-1), 
                                         return_distance=False)[0]
            # 對正樣本集(minority class samples)中每個樣本, 分別根據其k個近鄰生成
            # sampling_rate個新的樣本
            self.synthetic_samples(X, i, k_neighbors)

        if y is not None:
            return ( np.concatenate((self.synthetic, X, negative_X), axis=0), 
                     np.concatenate(([1]*(len(self.synthetic)+len(X)), y[y==0]), axis=0) )

        return np.concatenate((self.synthetic, X), axis=0)


    # 對正樣本集(minority class samples)中每個樣本, 分別根據其k個近鄰生成sampling_rate個新的樣本
    def synthetic_samples(self, X, i, k_neighbors):
        for j in range(self.sampling_rate):
            # 從k個近鄰里面隨機選擇一個近鄰
            neighbor = np.random.choice(k_neighbors)
            # 計算樣本X[i]與剛剛選擇的近鄰的差
            diff = X[neighbor] - X[i]
            # 生成新的資料
            self.synthetic[self.newindex] = X[i] + random.random() * diff
            self.newindex += 1

X=np.array([[1,2,3],[3,4,6],[2,2,1],[3,5,2],[5,3,4],[3,2,4]])
y = np.array([1, 1, 1, 0, 0, 0])
smote=Smote(sampling_rate=1, k=5)
print(smote.fit(X))

4.3 SMOTE演算法的缺陷

該演算法主要存在兩方面的問題:一是在近鄰選擇時,存在一定的盲目性。從上面的演算法流程可以看出,在演算法執行過程中,需要確定k值,即選擇多少個近鄰樣本,這需要使用者自行解決。從k值的定義可以看出,k值的下限是sampling_rate(sampling_rate為從k個近鄰中隨機挑選出的近鄰樣本的個數,且有 sampling_rate < k ), sampling_rate的大小可以根據負類樣本數量、正類樣本數量和資料集最後需要達到的平衡率決定。但k值的上限沒有辦法確定,只能根據具體的資料集去反覆測試。因此如何確定k值,才能使演算法達到最優這是未知的。

另外,該演算法無法克服非平衡資料集的資料分佈問題,容易產生分佈邊緣化問題。由於正類樣本(少數類樣本)的分佈決定了其可選擇的近鄰,如果一個正類樣本處在正類樣本集的分佈邊緣,則由此正類樣本和相鄰樣本產生的“人造”樣本也會處在這個邊緣,且會越來越邊緣化,從而模糊了正類樣本和負類樣本的邊界,而且使邊界變得越來越模糊。這種邊界模糊性,雖然使資料集的平衡性得到了改善,但加大了分類演算法進行分類的難度。

針對SMOTE演算法的進一步改進

針對SMOTE演算法存在的邊緣化和盲目性等問題,很多人紛紛提出了新的改進辦法,在一定程度上改進了演算法的效能,但還存在許多需要解決的問題。

Han等人Borderline-SMOTE: A New Over-Sampling Method in Imbalanced Data Sets Learning 在SMOTE演算法基礎上進行了改進,提出了Borderhne.SMOTE演算法,解決了生成樣本重疊(Overlapping)的問題該演算法在執行的過程中,查詢一個適當的區域,該區域可以較好地反應資料集的性質,然後在該區域內進行插值,以使新增加的“人造”樣本更有效。這個適當的區域一般由經驗給定,因此演算法在執行的過程中有一定的侷限性。

參考文獻: