🦡

不均衡データ分類とundersampling + bagging、正例:負例の比率による調整

2022/12/05に公開

概要

機械学習でクラス分類を行う際にクラス間のサンプル数に偏りがあるデータは、不均衡データ(imbalanced data)と呼ばれているが、これを扱う手法としてundersampling + baggingがあり、以下で紹介されている。

https://tjo.hatenablog.com/entry/2017/08/11/162057

本稿では、undersampling + baggingを試し、undersamplingの際に正例:負例の比率を操作して検知性能の調整を試してみた。

undersampling + bagging

undersampling + baggingは以下の論文がオリジナルである。

B. C. Wallace, K. Small, C. E. Brodley and T. A. Trikalinos, "Class Imbalance, Redux," 2011 IEEE 11th International Conference on Data Mining, 2011, pp. 754-763, doi: 10.1109/ICDM.2011.33.

https://doi.org/10.1109/ICDM.2011.33

詳細については、上記を読んでいただくとして、簡単な説明としては、上記論文の著者の一人のC. E. Brodleyが2015年に講義した際の資料がわかりやすい。

https://course.ccs.neu.edu/cs6140sp15/4_boosting/slides/wallace_imbalance_icdm_11_for_class_2012_final.pptx
※ ppt注意

以下ではこの資料の図に基づいて説明する。(全ての図の引用元は上記の講義資料である)

不均衡データ分類の問題点

以下のような不均衡データの標本を仮定する。

真の境界は{w^*}で示したものであるが、限られた標本からは偏った境界\hat{W}が得られてしまう。

一次元で表現すると以下のようになる。

標本の数が少なく、Pで表した分布の一部しか観察できていない(もっと右側のxが必要)ため、偏った境界\hat{W}が得られてしまう。

オーバーサンプリングでなくアンダーサンプリングする理由

上記のminority classに対して、オーバーサンプリング(SMOTE; Synthetic Minority Oversamping Technique)を実施したとしよう。

SMOTEは、観測された標本同士の内挿により、合成された標本を生成する手法である。

次の図で観測された標本同士で内挿した場合、

以下のように、2つのx間のデータが増えるだけで、偏った境界\hat{W}を修正できない。

バギングが有効な理由

majority classのアンダーサンプリングを繰り返して多くの境界wを候補とすることで、真の境界{w^*}と同等の分類を意図したものがバギングである。

以下のように、アンダーサンプリングを繰り返していく。




以下のように繰り返して得られた境界は偏りを少なくしている。

以上が、オーバーサンプリングせずにアンダーサンプリングし、かつバギングと組み合わせること(undersampling + bagging)が有効とされている理由である。

ここでは、Classification on imbalanced data  |  TensorFlow Coreに出てくるKaggleの Credit Card Fraud Detectionを使って、undersampling + baggingを試してみた。検知性能の算出などもClassification on imbalanced data  |  TensorFlow Coreの方法に準じたものにした。

notebook全体は以下に公開したので参照いただきたい。
https://gist.github.com/kn1kn1/5a7843a58bd3dc9db91aa28862be6fa9

元の記事では、random seedが固定されておらず、実行するたびに異なるデータ分割が行われていたため、以下の部分で設定するよう変更した。

# Use a utility from sklearn to split and shuffle your dataset.
train_df, test_df = train_test_split(cleaned_df, test_size=0.2, random_state=42)
train_df, val_df = train_test_split(train_df, test_size=0.2, random_state=42)

尚、random seedを固定した状態で元の記事のnotebook(kerasによるシンプルなNN)を実行したものが以下である。

https://gist.github.com/kn1kn1/faa3e8b78afcfb26d2925642f3f7a922

ここでは、上記のデータ分割に加え、keras用に以下のコードを追加している。

import random
import numpy as np
import tensorflow as tf

seed = 42
random.seed(seed)
np.random.seed(seed)
tf.random.set_seed(seed)

アンダーサンプリングなし

では、最初にアンダーサンプリングせずにバギングのみのランダムフォレストの結果を見てみよう。

コードとしては以下である。random_stateの指定、n_jobsの指定(コアを全て使う)以外のパラメータはデフォルトである。

from sklearn.ensemble import RandomForestClassifier

neg, pos = np.bincount(train_labels)
total = neg + pos
print('Examples:\n    Total: {}\n    Positive: {} ({:.2f}% of total)\n'.format(
    total, pos, 100 * pos / total))
train_f_count = neg
train_t_count = pos

classifier = RandomForestClassifier(
    random_state = 42,
    n_jobs = -1
)

classifier.fit(train_df, train_labels)

今回のデータだとMinority(正例)が330件に対し、Majority(負例)が182,276件で、Minority:Majority(正例:負例)は、1:552であった。

Confusion Matrixは以下である。不均衡データでよくあるのは、Trueの予測が全く無くTPが全く無い結果だったりするが、そのような状況ではないようだ。FPが少なく、FNが多い点に注意。

正例:負例を1:1にする

次に、Minority:Majority(正例:負例)を1:1で試してみる。この例だとMajority(負例)が182,276件あるので、これをMinority(正例)の数330と同数までアンダーサンプリングする。

from imblearn.pipeline import Pipeline
from imblearn.under_sampling import RandomUnderSampler
from sklearn.ensemble import RandomForestClassifier

neg, pos = np.bincount(train_labels)
total = neg + pos
print('Examples:\n    Total: {}\n    Positive: {} ({:.2f}% of total)\n'.format(
    total, pos, 100 * pos / total))
train_f_count = neg
train_t_count = pos

under_sampling_rate = 1
sampler = RandomUnderSampler(
    sampling_strategy = {0 : int(train_t_count * under_sampling_rate), 1 : train_t_count}, 
    random_state = 42
)

classifier = RandomForestClassifier(
    random_state = 42,
    n_jobs = -1
)

train_res_df, train_res_labels = sampler.fit_resample(train_df, train_labels)
classifier.fit(train_res_df, train_res_labels)

Confusion Matrixは以下である。FPが非常に多く、FNが比較的少ない結果になった。

ここでの状況は、Undersampling + baggingで不均衡データに対処した際の予測確率のバイアスを補正して、その結果を可視化してみる - 渋谷駅前で働くデータサイエンティストのブログ でTJOさんの仰っている

「undersampling + baggingで不均衡データを補正するとfalse positiveは物凄く多くなる」

と同様のケースと思われる。

正例:負例の比率によるグリッドサーチ

次に、Minority:Majority(正例:負例)の比率を1:1〜1:10まで調整した結果を見てみよう。

f1, precision, recallをそれぞれプロットしているが、

  • f1, recall重視であればratio=1
  • precision重視であればratio=7

ということになりそうである。

また、全体的な傾向としては、

  • Majority(負例)が増えるとFNが増え、FPが減る
  • Minority(正例)が増えるとFNが減り、FPが増える

ということのようである。

正例:負例を1:7にする

というわけで、Minority:Majority(正例:負例)=1:7を試してみよう。

from imblearn.pipeline import Pipeline
from imblearn.under_sampling import RandomUnderSampler
from sklearn.ensemble import RandomForestClassifier

neg, pos = np.bincount(train_labels)
total = neg + pos
print('Examples:\n    Total: {}\n    Positive: {} ({:.2f}% of total)\n'.format(
    total, pos, 100 * pos / total))
train_f_count = neg
train_t_count = pos

under_sampling_rate = 7
sampler = RandomUnderSampler(
    sampling_strategy = {0 : int(train_t_count * under_sampling_rate), 1 : train_t_count}, 
    random_state = 42
)

classifier = RandomForestClassifier(
    random_state = 42,
    n_jobs = -1
)

train_res_df, train_res_labels = sampler.fit_resample(train_df, train_labels)
classifier.fit(train_res_df, train_res_labels)

1:1と比較すると、FPの数がだいぶ少なくなった。

この性能が妥当かどうかは、実務においてはビジネス要件(FNが絶対に許容されないのか、FPが多いことが問題になるか)如何と思われるが、正例:負例の比率を操作することで、precision, recallを調整できそうなことは分かった。

学習データ・テストデータのROC曲線を見ると全体的に過学習なので、ここから別途max_features, min_samples_split, min_samples_leaf, max_depthといったパラメータで調整することになる。

reference

以下は、undersampling + baggingについては取り上げていないが、不均衡データについて比較的簡潔に書かれたもので、undersampling + bagging以外の手法を検討する際にもしかしたら役に立つかもしれない。

以下ランダムフォレスト関連

GitHubで編集を提案

Discussion