メモ帳

python, juliaで機械学習をやっていく

不均衡データへの対処法: ダウンサンプリング

不均衡データに対する対処法であるダウンサンプリングについて調べました。

不均衡データ

f:id:atelier-0213:20190915194248p:plain
不均衡データ: label 2がデータの9割を占めている

クラス毎にサンプル数が大きく異なっているデータを不均衡データと呼びます。 不均衡データで学習すると、学習データの中で比率が著しく小さいものは一切判定結果にださない学習器ができてしまうことが多いです。 直感的には、学習データにほとんど含まれていないクラスAに正解するよりも、たくさん含まれているクラスBに不正解してしまうリスクを避けるほうが簡単に低いlossを維持できるからだと理解できます。 この声質は二値分類だと顕著で、学習データが正例が99%を占めている場合を考えると、すべてのデータに対して正であると判定しておけば、それだけで標準的なlossは極めて小さい値になりますし、学習データのaccuracyは99%になります。しかし、この学習器が使い物にならないことは明らかだと思います。

こういった問題に対処するための手法をまとめていきたいと思います。

主な手法

主に以下のような、アプローチがあります。

  • ダウンサンプリング: 多いクラスのデータを削除
  • アップサンプリング: 少ないクラスのデータを拡張
  • 重み付けされたlossで学習
  • ダウンサンプリング + bagging
  • 異常検知の問題として扱う

今回はダウンサンプリング(別名: under-sampling)の手法について書きます。

ダウンサンプリング

全てのデータが、一番少ないクラスのデータ量とおなじ規模になるようにリサンプリングします。under-samplingとも言われています。 リサンプリングの方法によって色々な種類があります。 今回は一番簡単なrandom samplingを紹介します。

random sampling

最もデータ数の少ないクラスのデータ量に一致するように、各クラスのデータを無作為に抽出します。 pandasで書くと以下のようになります。

import numpy as np
import pandas as pd
from sklearn.datasets import make_classification

# テストデータ作成
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
                            n_redundant=0, n_repeated=0, n_classes=3,
                            n_clusters_per_class=1,
                            weights=[0.01, 0.05, 0.94],
                            class_sep=0.8, random_state=0)

# pandas.dataframeに変形
data = np.concatenate([y.reshape((-1, 1)), X], 1)
df = pd.DataFrame(data)

label_key = 0
# 最小データ数
minimum_num = df[label_key].value_counts().min()
# ラベル毎に最小データ数だけサンプリング
dfs = [d.sample(minimum_num, random_state=0) for name, d in df.groupby(label_key)]
# 結合。ラベル順に並んでいるのでshuffleする
under_resampled_df = pd.concat(dfs).sample(frac=1, random_state=0)

X_resampled = under_resampled.drop(labels=label_key, axis=1).to_numpy()
y_resampled = under_resampled[label_key].to_numpy()

コード

imbalanced-learnというライブラリに手軽に使えるものがあります。

from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_resample(X, y)

Instance Hardness Threshold

弱い学習器で学習させ、推論確率が低い(分類が難しい)データを削除する。データの複雑さが低減できると言われており、少ないサンプルでの学習の効率化が期待されています。

アルゴリズム

  1. データをn分割 (以下、n-th cross validation)
  2. n-1個のブロックをtrain data, 1個のブロックをtest dataとする
  3. 弱い学習器でtrain dataを学習させる
  4. test dataの予測確率を保存する
  5. 2 ~ 4をn通り繰り返す
  6. クラス毎に、保存した予測確率の小さいものを取り除いて最もデータ数が小さいクラスのデータサイズにあわせる

コード

この手法もimbalanced-learnというライブラリに手軽に使えるものがあります。 estimatorという引数にはsklearnのpredict_probaメソッドが使える分類モデルであればなんでも使えます。線形回帰やk近傍法やSVCのような複雑すぎないモデルの方が効果があるようです。

from sklearn.linear_model import LogisticRegression
from imblearn.under_sampling import InstanceHardnessThreshold
iht = InstanceHardnessThreshold(
    random_state=0,
    estimator=LogisticRegression(solver='lbfgs', multi_class='auto'))
X_resampled, y_resampled = iht.fit_resample(X, y)

以上が主な手法になります。

ダウンサンプリングは不均衡データに対する処世の中で最も手軽に効果が出やすいと思います。 少なくともランダムサンプリングだけでも試す価値はあるはずです。 Instance Hardness Thresholdは使う学習器によってサンプリング結果がかわるので計算リソースに余裕のある場合に試すという形になると思います。

他の手法についても順次まとめていく予定です!