Python sklearn 實現過采樣和欠采樣


 

Imblearn package study

1. 准備知識

Sparse input

For sparse input the data is converted to the Compressed Sparse Rows representation (see scipy.sparse.csr_matrix) before being fed to the sampler. To avoid unnecessary memory copies, it is recommended to choose the CSR representation upstream.

1.1 Compressed Sparse Rows(CSR) 壓縮稀疏的行

稀疏矩陣中存在許多0元素, 按矩陣A進行存儲會占用很大的空間(內存).

CSR方法采取按行壓縮的辦法, 將原始的矩陣用三個數組進行表示:

  1.  
    data = np.array([1, 2, 3, 4, 5, 6])
  2.  
    indices = np.array([0, 2, 2, 0, 1, 2])
  3.  
    indptr = np.array([0, 2, 3, 6])

data數組: 存儲着矩陣A中所有的非零元素;

indices數組: data數組中的元素在矩陣A中的列索引

indptr數組: 存儲着矩陣A中每行第一個非零元素在data數組中的索引.

  1.  
    from scipy import sparse
  2.  
    mtx = sparse.csr_matrix((data,indices,indptr),shape=(3,3))
  3.  
    mtx.todense()
  4.  
     
  5.  
    Out[27]:
  6.  
    matrix([[1, 0, 2],
  7.  
    [ 0, 0, 3],
  8.  
    [ 4, 5, 6]])

為什么會有針對不平衡數據的研究? 當我們的樣本數據中, 正負樣本的數據占比極其不均衡的時候, 模型的效果就會偏向於多數類的結果. 具體的可參照官網利用支持向量機進行可視化不同正負樣本比例情況下的模型分類結果.

2. 過采樣(Over-sampling)

2.1 實用性的例子

2.1.1 朴素隨機過采樣

針對不平衡數據, 最簡單的一種方法就是生成少數類的樣本, 這其中最基本的一種方法就是: 從少數類的樣本中進行隨機采樣來增加新的樣本, RandomOverSampler 函數就能實現上述的功能.

  1.  
    from sklearn.datasets import make_classification
  2.  
    from collections import Counter
  3.  
    X, y = make_classification(n_samples= 5000, n_features=2, n_informative=2,
  4.  
    n_redundant= 0, n_repeated=0, n_classes=3,
  5.  
    n_clusters_per_class= 1,
  6.  
    weights=[ 0.01, 0.05, 0.94],
  7.  
    class_sep= 0.8, random_state=0)
  8.  
    Counter(y)
  9.  
    Out[ 10]: Counter({0: 64, 1: 262, 2: 4674})
  10.  
     
  11.  
    from imblearn.over_sampling import RandomOverSampler
  12.  
     
  13.  
    ros = RandomOverSampler(random_state= 0)
  14.  
    X_resampled, y_resampled = ros.fit_sample(X, y)
  15.  
     
  16.  
     
  17.  
    sorted(Counter(y_resampled).items())
  18.  
    Out[ 13]:
  19.  
    [( 0, 4674), (1, 4674), (2, 4674)]

以上就是通過簡單的隨機采樣少數類的樣本, 使得每類樣本的比例為1:1:1.

2.1.2 從隨機過采樣到SMOTEADASYN

相對於采樣隨機的方法進行過采樣, 還有兩種比較流行的采樣少數類的方法: (i) Synthetic Minority Oversampling Technique (SMOTE); (ii) Adaptive Synthetic (ADASYN) .

SMOTE: 對於少數類樣本a, 隨機選擇一個最近鄰的樣本b, 然后從a與b的連線上隨機選取一個點c作為新的少數類樣本;

ADASYN: 關注的是在那些基於K最近鄰分類器被錯誤分類的原始樣本附近生成新的少數類樣本

  1.  
    from imblearn.over_sampling import SMOTE, ADASYN
  2.  
     
  3.  
    X_resampled_smote, y_resampled_smote = SMOTE().fit_sample(X, y)
  4.  
     
  5.  
    sorted(Counter(y_resampled_smote).items())
  6.  
    Out[ 29]:
  7.  
    [( 0, 4674), (1, 4674), (2, 4674)]
  8.  
     
  9.  
    X_resampled_adasyn, y_resampled_adasyn = ADASYN().fit_sample(X, y)
  10.  
     
  11.  
    sorted(Counter(y_resampled_adasyn).items())
  12.  
    Out[ 30]:
  13.  
    [( 0, 4674), (1, 4674), (2, 4674)]

2.1.3 SMOTE的變體

相對於基本的SMOTE算法, 關注的是所有的少數類樣本, 這些情況可能會導致產生次優的決策函數, 因此SMOTE就產生了一些變體: 這些方法關注在最優化決策函數邊界的一些少數類樣本, 然后在最近鄰類的相反方向生成樣本.

SMOTE函數中的kind參數控制了選擇哪種變體, (i) borderline1, (ii) borderline2, (iii) svm:

  1.  
    from imblearn.over_sampling import SMOTE, ADASYN
  2.  
    X_resampled, y_resampled = SMOTE(kind= 'borderline1').fit_sample(X, y)
  3.  
     
  4.  
    print sorted(Counter(y_resampled).items())
  5.  
    Out[ 31]:
  6.  
    [( 0, 4674), (1, 4674), (2, 4674)]

2.1.4 數學公式

SMOTE算法與ADASYN都是基於同樣的算法來合成新的少數類樣本: 對於少數類樣本a, 從它的最近鄰中選擇一個樣本b, 然后在兩點的連線上隨機生成一個新的少數類樣本, 不同的是對於少數類樣本的選擇.

原始的SMOTEkind='regular' , 隨機選取少數類的樣本.

The borderline SMOTEkind='borderline1' or kind='borderline2'

此時, 少數類的樣本分為三類: (i) 噪音樣本(noise), 該少數類的所有最近鄰樣本都來自於不同於樣本a的其他類別; (ii) 危險樣本(in danger), 至少一半的最近鄰樣本來自於同一類(不同於a的類別); (iii) 安全樣本(safe), 所有的最近鄰樣本都來自於同一個類.

這兩種類型的SMOTE使用的是危險樣本來生成新的樣本數據, 對於 Borderline-1 SMOTE, 最近鄰中的隨機樣本b與該少數類樣本a來自於不同的類; 不同的是, 對於 Borderline-2 SMOTE , 隨機樣本b可以是屬於任何一個類的樣本;

SVM SMOTEkind='svm', 使用支持向量機分類器產生支持向量然后再生成新的少數類樣本.

3. 下采樣(Under-sampling)

3.1 原型生成(prototype generation)

給定數據集S, 原型生成算法將生成一個子集S’, 其中|S’| < |S|, 但是子集並非來自於原始數據集. 意思就是說: 原型生成方法將減少數據集的樣本數量, 剩下的樣本是由原始數據集生成的, 而不是直接來源於原始數據集.

ClusterCentroids函數實現了上述功能: 每一個類別的樣本都會用K-Means算法的中心點來進行合成, 而不是隨機從原始樣本進行抽取.

  1.  
    from imblearn.under_sampling import ClusterCentroids
  2.  
     
  3.  
    cc = ClusterCentroids(random_state= 0)
  4.  
    X_resampled, y_resampled = cc.fit_sample(X, y)
  5.  
     
  6.  
    print sorted(Counter(y_resampled).items())
  7.  
    Out[ 32]:
  8.  
    [( 0, 64), (1, 64), (2, 64)]

ClusterCentroids函數提供了一種很高效的方法來減少樣本的數量, 但需要注意的是, 該方法要求原始數據集最好能聚類成簇. 此外, 中心點的數量應該設置好, 這樣下采樣的簇能很好地代表原始數據.

3.2 原型選擇(prototype selection)

與原型生成不同的是, 原型選擇算法是直接從原始數據集中進行抽取. 抽取的方法大概可以分為兩類: (i) 可控的下采樣技術(the controlled under-sampling techniques) ; (ii) the cleaning under-sampling techniques(不好翻譯, 就放原文, 清洗的下采樣技術?). 第一類的方法可以由用戶指定下采樣抽取的子集中樣本的數量; 第二類方法則不接受這種用戶的干預.

3.2.1 Controlled under-sampling techniques

RandomUnderSampler函數是一種快速並十分簡單的方式來平衡各個類別的數據: 隨機選取數據的子集.

  1.  
    from imblearn.under_sampling import RandomUnderSampler
  2.  
    rus = RandomUnderSampler(random_state= 0)
  3.  
    X_resampled, y_resampled = rus.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 33]:
  7.  
    [( 0, 64), (1, 64), (2, 64)]

通過設置RandomUnderSampler中的replacement=True參數, 可以實現自助法(boostrap)抽樣.

  1.  
    import numpy as np
  2.  
     
  3.  
    np.vstack({ tuple(row) for row in X_resampled}).shape
  4.  
    Out[ 34]:
  5.  
    ( 192L, 2L)

很明顯, 使用默認參數的時候, 采用的是不重復采樣;

  1.  
    from imblearn.under_sampling import RandomUnderSampler
  2.  
    rus = RandomUnderSampler(random_state= 0, replacement=True)
  3.  
    X_resampled, y_resampled = rus.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 33]:
  7.  
    [( 0, 64), (1, 64), (2, 64)]
  8.  
     
  9.  
    np.vstack({ tuple(row) for row in X_resampled}).shape
  10.  
    Out[ 34]:
  11.  
    ( 181L, 2L)

NearMiss函數則添加了一些啟發式(heuristic)的規則來選擇樣本, 通過設定version參數來實現三種啟發式的規則.

  1.  
    from imblearn.under_sampling import NearMiss
  2.  
    nm1 = NearMiss(random_state= 0, version=1)
  3.  
    X_resampled_nm1, y_resampled = nm1.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 35]:
  7.  
    [( 0, 64), (1, 64), (2, 64)]

下面通過一個例子來說明這三個啟發式的選擇樣本的規則, 首先我們假設正樣本是需要下采樣的(多數類樣本), 負樣本是少數類的樣本.

NearMiss-1: 選擇離N個近鄰的負樣本的平均距離最小的正樣本;

NearMiss-2: 選擇離N個負樣本最遠的平均距離最小的正樣本;

NearMiss-3: 是一個兩段式的算法. 首先, 對於每一個負樣本, 保留它們的M個近鄰樣本; 接着, 那些到N個近鄰樣本平均距離最大的正樣本將被選擇.

3.2.2 Cleaning under-sampling techniques

3.2.2.1 Tomek’s links

TomekLinks : 樣本x與樣本y來自於不同的類別, 滿足以下條件, 它們之間被稱之為TomekLinks; 不存在另外一個樣本z, 使得d(x,z) < d(x,y) 或者 d(y,z) < d(x,y)成立. 其中d(.)表示兩個樣本之間的距離, 也就是說兩個樣本之間互為近鄰關系. 這個時候, 樣本x或樣本y很有可能是噪聲數據, 或者兩個樣本在邊界的位置附近.

TomekLinks函數中的auto參數控制Tomek’s links中的哪些樣本被剔除. 默認的ratio='auto' 移除多數類的樣本, 當ratio='all'時, 兩個樣本均被移除.

3.2.2.2 Edited data set using nearest neighbours

EditedNearestNeighbours這種方法應用最近鄰算法來編輯(edit)數據集, 找出那些與鄰居不太友好的樣本然后移除. 對於每一個要進行下采樣的樣本, 那些不滿足一些准則的樣本將會被移除; 他們的絕大多數(kind_sel='mode')或者全部(kind_sel='all')的近鄰樣本都屬於同一個類, 這些樣本會被保留在數據集中.

  1.  
    print sorted(Counter(y).items())
  2.  
     
  3.  
    from imblearn.under_sampling import EditedNearestNeighbours
  4.  
    enn = EditedNearestNeighbours(random_state= 0)
  5.  
    X_resampled, y_resampled = enn.fit_sample(X, y)
  6.  
     
  7.  
    print sorted(Counter(y_resampled).items())
  8.  
     
  9.  
    Out[ 36]:
  10.  
    [( 0, 64), (1, 262), (2, 4674)]
  11.  
    [( 0, 64), (1, 213), (2, 4568)]

在此基礎上, 延伸出了RepeatedEditedNearestNeighbours算法, 重復基礎的EditedNearestNeighbours算法多次.

  1.  
    from imblearn.under_sampling import RepeatedEditedNearestNeighbours
  2.  
    renn = RepeatedEditedNearestNeighbours(random_state= 0)
  3.  
    X_resampled, y_resampled = renn.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 37]:
  7.  
    [( 0, 64), (1, 208), (2, 4551)]

RepeatedEditedNearestNeighbours算法不同的是, ALLKNN算法在進行每次迭代的時候, 最近鄰的數量都在增加.

  1.  
    from imblearn.under_sampling import AllKNN
  2.  
    allknn = AllKNN(random_state= 0)
  3.  
    X_resampled, y_resampled = allknn.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 38]:
  7.  
    [( 0, 64), (1, 220), (2, 4601)]

3.2.2.3 Condensed nearest neighbors and derived algorithms

CondensedNearestNeighbour 使用1近鄰的方法來進行迭代, 來判斷一個樣本是應該保留還是剔除, 具體的實現步驟如下:

  1. 集合C: 所有的少數類樣本;
  2. 選擇一個多數類樣本(需要下采樣)加入集合C, 其他的這類樣本放入集合S;
  3. 使用集合S訓練一個1-NN的分類器, 對集合S中的樣本進行分類;
  4. 將集合S中錯分的樣本加入集合C;
  5. 重復上述過程, 直到沒有樣本再加入到集合C.
  1.  
    from imblearn.under_sampling import CondensedNearestNeighbour
  2.  
    cnn = CondensedNearestNeighbour(random_state= 0)
  3.  
    X_resampled, y_resampled = cnn.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 39]:
  7.  
    [( 0, 64), (1, 24), (2, 115)]

顯然, CondensedNearestNeighbour方法對噪音數據是很敏感的, 也容易加入噪音數據到集合C中.

因此, OneSidedSelection 函數使用 TomekLinks 方法來剔除噪聲數據(多數類樣本).

  1.  
    from imblearn.under_sampling import OneSidedSelection
  2.  
    oss = OneSidedSelection(random_state= 0)
  3.  
    X_resampled, y_resampled = oss.fit_sample(X, y)
  4.  
     
  5.  
    print(sorted(Counter(y_resampled).items()))
  6.  
    Out[ 39]:
  7.  
    [( 0, 64), (1, 174), (2, 4403)]

NeighbourhoodCleaningRule 算法主要關注如何清洗數據而不是篩選(considering)他們. 因此, 該算法將使用

EditedNearestNeighbours和 3-NN分類器結果拒絕的樣本之間的並集.

  1.  
    from imblearn.under_sampling import NeighbourhoodCleaningRule
  2.  
    ncr = NeighbourhoodCleaningRule(random_state= 0)
  3.  
    X_resampled, y_resampled = ncr.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 39]:
  7.  
    [( 0, 64), (1, 234), (2, 4666)]

3.2.2.4 Instance hardness threshold

InstanceHardnessThreshold是一種很特殊的方法, 是在數據上運用一種分類器, 然后將概率低於閾值的樣本剔除掉.

  1.  
    from sklearn.linear_model import LogisticRegression
  2.  
    from imblearn.under_sampling import InstanceHardnessThreshold
  3.  
    iht = InstanceHardnessThreshold(random_state= 0,
  4.  
    estimator=LogisticRegression())
  5.  
    X_resampled, y_resampled = iht.fit_sample(X, y)
  6.  
     
  7.  
    print(sorted(Counter(y_resampled).items()))
  8.  
    Out[ 39]:
  9.  
    [( 0, 64), (1, 64), (2, 64)]

4. 過采樣與下采樣的結合

在之前的SMOTE方法中, 當由邊界的樣本與其他樣本進行過采樣差值時, 很容易生成一些噪音數據. 因此, 在過采樣之后需要對樣本進行清洗. 這樣, 第三節中涉及到的TomekLink 與 EditedNearestNeighbours方法就能實現上述的要求. 所以就有了兩種結合過采樣與下采樣的方法: (i) SMOTETomek and (ii) SMOTEENN.

  1.  
    from imblearn.combine import SMOTEENN
  2.  
    smote_enn = SMOTEENN(random_state= 0)
  3.  
    X_resampled, y_resampled = smote_enn.fit_sample(X, y)
  4.  
     
  5.  
    print sorted(Counter(y_resampled).items())
  6.  
    Out[ 40]:
  7.  
    [( 0, 4060), (1, 4381), (2, 3502)]
  8.  
     
  9.  
     
  10.  
    from imblearn.combine import SMOTETomek
  11.  
    smote_tomek = SMOTETomek(random_state= 0)
  12.  
    X_resampled, y_resampled = smote_tomek.fit_sample(X, y)
  13.  
     
  14.  
    print sorted(Counter(y_resampled).items())
  15.  
    Out[ 40]:
  16.  
    [( 0, 4499), (1, 4566), (2, 4413)]

5. Ensemble的例子

5.1 例子

一個不均衡的數據集能夠通過多個均衡的子集來實現均衡, imblearn.ensemble模塊能實現上述功能.

EasyEnsemble 通過對原始的數據集進行隨機下采樣實現對數據集進行集成.

  1.  
    from imblearn.ensemble import EasyEnsemble
  2.  
    ee = EasyEnsemble(random_state= 0, n_subsets=10)
  3.  
    X_resampled, y_resampled = ee.fit_sample(X, y)
  4.  
     
  5.  
    print X_resampled.shape
  6.  
    print sorted(Counter(y_resampled[0]).items())
  7.  
    Out[ 40]:
  8.  
    ( 10L, 192L, 2L)
  9.  
    [( 0, 64), (1, 64), (2, 64)]

EasyEnsemble 有兩個很重要的參數: (i) n_subsets 控制的是子集的個數 and (ii) replacement 決定是有放回還是無放回的隨機采樣.

與上述方法不同的是, BalanceCascade(級聯平衡)的方法通過使用分類器(estimator參數)來確保那些被錯分類的樣本在下一次進行子集選取的時候也能被采樣到. 同樣, n_max_subset 參數控制子集的個數, 以及可以通過設置bootstrap=True來使用bootstraping(自助法).

  1.  
    from imblearn.ensemble import BalanceCascade
  2.  
    from sklearn.linear_model import LogisticRegression
  3.  
    bc = BalanceCascade(random_state= 0,
  4.  
    estimator=LogisticRegression(random_state= 0),
  5.  
    n_max_subset= 4)
  6.  
    X_resampled, y_resampled = bc.fit_sample(X, y)
  7.  
     
  8.  
    print X_resampled.shape
  9.  
     
  10.  
    print sorted(Counter(y_resampled[0]).items())
  11.  
    Out[ 41]:
  12.  
    ( 4L, 192L, 2L)
  13.  
    [( 0, 64), (1, 64), (2, 64)]

5.2 Chaining ensemble of samplers and estimators

在集成分類器中, 裝袋方法(Bagging)在不同的隨機選取的數據集上建立了多個估計量. 在scikit-learn中這個分類器叫做BaggingClassifier. 然而, 該分類器並不允許對每個數據集進行均衡. 因此, 在對不均衡樣本進行訓練的時候, 分類器其實是有偏的, 偏向於多數類.

  1.  
    from sklearn.model_selection import train_test_split
  2.  
    from sklearn.metrics import confusion_matrix
  3.  
    from sklearn.ensemble import BaggingClassifier
  4.  
    from sklearn.tree import DecisionTreeClassifier
  5.  
     
  6.  
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
  7.  
    bc = BaggingClassifier(base_estimator=DecisionTreeClassifier(),
  8.  
    random_state= 0)
  9.  
    bc. fit(X_train, y_train)
  10.  
     
  11.  
    y_pred = bc. predict(X_test)
  12.  
    confusion_matrix(y_test, y_pred)
  13.  
    Out[35]:
  14.  
    array([[ 0, 0, 12],
  15.  
    [ 0, 0, 59],
  16.  
    [ 0, 0, 1179]], dtype=int64)

BalancedBaggingClassifier 允許在訓練每個基學習器之前對每個子集進行重抽樣. 簡而言之, 該方法結合了EasyEnsemble采樣器與分類器(如BaggingClassifier)的結果.

  1.  
    from imblearn.ensemble import BalancedBaggingClassifier
  2.  
    bbc = BalancedBaggingClassifier(base_estimator=DecisionTreeClassifier(),
  3.  
    ratio= 'auto',
  4.  
    replacement=False,
  5.  
    random_state= 0)
  6.  
    bbc.fit(X, y)
  7.  
     
  8.  
    y_pred = bbc.predict(X_test)
  9.  
    confusion_matrix(y_test, y_pred)
  10.  
    Out[ 39]:
  11.  
    array( [[ 12, 0, 0],
  12.  
    [ 0, 55, 4],
  13.  
    [ 68, 53, 1058]], dtype=int64)

6. 數據載入

imblearn.datasets 包與sklearn.datasets 包形成了很好的互補. 該包主要有以下兩個功能: (i)提供一系列的不平衡數據集來實現測試; (ii) 提供一種工具將原始的平衡數據轉換為不平衡數據.

6.1 不平衡數據集

fetch_datasets 允許獲取27個不均衡且二值化的數據集.

  1.  
    from collections import Counter
  2.  
    from imblearn.datasets import fetch_datasets
  3.  
    ecoli = fetch_datasets()[ 'ecoli']
  4.  
    ecoli.data.shape
  5.  
     
  6.  
    print sorted(Counter(ecoli.target).items())
  7.  
    Out[ 40]:
  8.  
    [(- 1, 301), (1, 35)]

6.2 生成不平衡數據

make_imbalance 方法可以使得原始的數據集變為不平衡的數據集, 主要是通過ratio參數進行控制.

  1.  
    from sklearn.datasets import load_iris
  2.  
    from imblearn.datasets import make_imbalance
  3.  
    iris = load_iris()
  4.  
    ratio = { 0: 20, 1: 30, 2: 40}
  5.  
    X_imb, y_imb = make_imbalance(iris.data, iris.target, ratio=ratio)
  6.  
     
  7.  
    sorted(Counter(y_imb).items())
  8.  
    Out[ 37]:
  9.  
    [( 0, 20), (1, 30), (2, 40)]
  10.  
     
  11.  
    #當類別不指定時, 所有的數據集均導入
  12.  
    ratio = { 0: 10}
  13.  
    X_imb, y_imb = make_imbalance(iris.data, iris.target, ratio=ratio)
  14.  
     
  15.  
    sorted(Counter(y_imb).items())
  16.  
    Out[ 38]:
  17.  
    [( 0, 10), (1, 50), (2, 50)]
  18.  
     
  19.  
    #同樣亦可以傳入自定義的比例函數
  20.  
    def ratio_multiplier(y):
  21.  
    multiplier = { 0: 0.5, 1: 0.7, 2: 0.95}
  22.  
    target_stats = Counter(y)
  23.  
    for key, value in target_stats.items():
  24.  
    target_stats[key] = int(value * multiplier[key])
  25.  
    return target_stats
  26.  
    X_imb, y_imb = make_imbalance(iris.data, iris.target,
  27.  
    ratio=ratio_multiplier)
  28.  
     
  29.  
    sorted(Counter(y_imb).items())
  30.  
    Out[ 39]:
  31.  
    [( 0, 25), (1, 35), (2, 47)]

以上就是在研究不平衡(不均衡)數據時所查詢的一些資料, 內容更多的是來自於Imblearn包的官方用戶手冊, 主要涉及一些下采樣、過采樣的方法與技術.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM