【機械学習】不均衡なデータへの対応メモ

# はじめに

普段の業務の傍ら趣味程度に機械学習やら統計学やらいじっているのですが、すこし前に所謂不均衡なデータの分類問題に触れることがあったので、その対応方法についてメモします。

# 対象読者

本記事は初歩的なことしか書いていなですが、以下のような人には読む価値あるかもしれません。

* 機械学習初心者
* 不均衡でないデータの分類問題はやったことあるが、不均衡なデータはよくわからないという人

# 不均衡なデータとは
そもそも「不均衡なデータとは何か」について

学習データの内、片方のクラスのデータの数がもう片方のクラスのデータの数より極端に多いデータのことです。

例えば以下のように、陽性のデータの数が陰性のデータの数の100分の1のようなデータです。
病気の検査や異常値の検出などがイメージしやすいかと思います。

| クラス | データ数 |
|-------|-----------|
| 陽性       |       100      |
| 陰性       |     10000      |


## 不均衡だと何が問題なのか？

工夫せず普通のデータと同じようにモデルに学習させた場合、少数データに対する精度が極端に低くなる場合があります。

作成するモデルの評価指標が正解率(Accuracy)だけであれば問題ないかもしれませんが、
再現率(Recall)の高さやF値が重視される場合は、かなりクリティカルな問題です。

極端な例ですが、発症率1%の病気を検査する際、「常に陰性と判断するモデル」を利用しても、正解率(Accuracy)は99%という高い値を出してしまいます。

ここでモデルに求められていることは、「実際に病気に罹っている(陽性の)人を、病気だと検知できる」ことなのですが、
モデル学習時、学習データの正解値の多くが陰性であるため、モデルは「何でもかんでも「陰性」と回答しておけば正解率が上がる」と間違った学習をしてしまうことがあります。

このような間違った学習をさせないための戦略としては色々ありますが、本記事では「学習データの数を均衡させる」方法としてOverSamplingとUnderSamplingの簡単な説明と、pythonでの実装・動作結果を説明します。

## データ概要

本記事では以下のデータを使用します。
詐欺に利用された取引を含む、過去のクレジットカードの取引データ・・・のようです（すみません、、ちゃんとデータ読んでないです）。
見ての通り、詐欺に利用された取引率(陽性率)が極端に低い。

|  |  |
|----------|-----------|
|データ名 | Credit Card Fraud Detection|
|URL|https://www.kaggle.com/mlg-ulb/creditcardfraud|
|データ数| 284807 |
| 陽性データ数 | 492 |
| 陰性データ数 | 284315|
| 陽性率 | 約0.17% |


# 普通のモデルによる不均衡データの分類結果

まずは特に工夫せずモデル作成〜学習させた結果を確認します。
コードは以下の通り。

なお、今回学習させるモデルはロジスティック回帰で、パラメータチューニング等はしていません。

```python
import pandas as pd
import numpy as np

from sklearn.feature_selection import SelectFromModel
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from imblearn.pipeline import Pipeline 

#データの読み込みとデータ処理
data = pd.read_csv('creditcard.csv').sample(frac=1)

# 学習用データとテスト用データで分割
X_train, X_test, Y_train, Y_test = train_test_split(data.iloc[:,:-1], data.iloc[:,-1], train_size=0.7)

select = SelectFromModel(RandomForestClassifier(n_estimators=100, n_jobs=-1))
scaler = StandardScaler()
clf = LogisticRegression()

# 一連の処理(Pipeline)を定義
# 特徴量抽出→標準化→識別器訓練
estimator = [
    ('select',select),
    ('scaler',scaler),
    ('clf',clf)
]

pipe = Pipeline(estimator)
pipe.fit(X_train, Y_train)
```

学習終了後、scoreメソッドで正解率を見てみると、正解率は99.9%です。
一見高精度に見えますが、上述した通り、この評価指標だけでモデルの良し悪しは判断できません。

```python
pipe.score(X_test, Y_test)
```
>0.99916903666771995

ここで確認したいのはF値です。
F値はscikit-learnのf1_scoreを使用することで簡単に確認できます。
上記で学習させたモデルをテストすると、F値は約0.71という結果で、まだ上げられそうです。

```
from sklearn.metrics import f1_score

predict = pipe.predict(X_test)
f1_score(predict, Y_test)
```
>0.70781893004115226

以下、OverSamplingとUnderSamplingの結果を確認します。

# OverSampling
少数クラス(陽性)のデータ数を水増し、多数クラス(陰性)データの数との差を埋めることで、上記問題を避ける方法です。

pythonでOverSamplingするためには、imbalanced-learnのSMOTEを利用します。
ここでは、陽性のデータ数を10倍に増やしてみて結果を見てみましょう。

```python
from imblearn.over_sampling import SMOTE 

select = SelectFromModel(RandomForestClassifier(n_estimators=100, n_jobs=-1))
scaler = StandardScaler()
sm = SMOTE(ratio={0:sum(Y_train==0), 1:sum(Y_train==1)*10})
clf = LogisticRegression()

# 一連の処理(Pipeline)を定義
# SMOTEで水増し→特徴量抽出→標準化→識別器訓練
estimator = [
    ('sm', sm),
    ('select', select),
    ('scaler', scaler),
    ('clf', clf)
]

pipe_smote = Pipeline(estimator)
pipe_smote.fit(X_train, Y_train)
```

OverSamplingしたデータで学習させたモデルのF値を確認すると、約0.83と、かなり向上しています。

```python
predict = pipe_smote.predict(X_test)
f1_score(Y_test, predict)
```
> 0.82550335570469791


# UnderSampling

OverSamplingとは逆に、多数クラス(陰性)データ数を間引き、少数クラス(陽性)データの数との差を埋める方法です。

pythonでUnderSamplingするためには、imbalanced-learnのRandomUnderSamplerを利用します。
ここでは、陰性のデータ数を10分の1に減らしてみて結果を見てみましょう。

```python
from imblearn.under_sampling import RandomUnderSampler

select = SelectFromModel(RandomForestClassifier(n_estimators=100, n_jobs=-1))
scaler = StandardScaler()
rus = RandomUnderSampler(ratio={0:int(sum(Y_train==0)/10), 1:sum(Y_train==1)}, random_state=0)
clf = LogisticRegression()

# 一連の処理(Pipeline)を定義
# 特徴量抽出→UnderSampling→標準化→識別器訓練
estimator = [
    ('rus', rus),
    ('select', select),
    ('scaler', scaler),
    ('clf', clf)
]

pipe_rus = Pipeline(estimator)
pipe_rus.fit(X_train, Y_train)
```

UnderSamplingしたデータで学習させたモデルのF値を確認すると、約0.79と、OverSamplingほどではないですが向上しています。


```python
predict = pipe_rus.predict(X_test)
f1_score(Y_test, predict)
```
> 0.79207920792079223

# まとめ

本記事では、不均衡なデータに対してデータ数の差を埋めるだけでどれだけモデルの精度が上がるかを確認しました。
データの件数をもう少し弄ったり他のモデルを試したりパラメータチューニングしたりすればもう少し精度は上がると思いますが、本題ではないので割愛します。

以前別の分類問題で、UnderSamplingしたデータを複数用意して別々のモデルに学習させて、各モデルの多数決をとるという方法を試したところ、かなり精度が上がったことがありましたが、今回のデータでは今ひとつ精度が上がらなかったので紹介は省きました。

機会があればその辺の説明もしたいと思ってます。

# 最後に

記載に何か気になる点・不明点などあれば、コメントください。