Python

機械学習における不均衡データの扱いについて解説

はじめに

今回は、機械学習における不均衡データの扱いについて見ていきます。これは、機械学習で分類モデルを作ろうとするときに生じる問題で、例えば2値分類を行う際に、データ全体の99%がクラスA、1%がクラスBといったように、データが大きく偏っている場合です。
製造業の世界で設備の異常を検出するような場合、ほとんどが正常データで異常データはごくわずかといったデータセットになることが、普通にあります。
このような不均衡データで機械学習を行う際は、注意が必要となるので、その問題点と対策を見ていきます。

機械学習における不均衡データの問題

ある製造ラインで、めったに発生しない製品の異常を検出するモデル作りたいと仮定しましょう。品質は非常に安定しており、異常品はごくわずかしか発生しません。従って、正常品が9990個、異常品が10個というように、得られるデータのほとんどが正常品ということになります。
このようなデータでそのまま予測モデルを作ると、通常はすべて正常品と判定するモデルができ上がってしまいます。これは、極端に不均衡なデータの場合、そのように判定しても予測精度は高いという結果が得られるためです。
混同行列を使って、詳しく見てみます(表1)。

表1では、10個の異常品すべてを正常品と誤判定していますが、精度を計算すると、9990/(9990+10)=99.9% と見かけ上は非常に精度の高い予測モデルができたことになります。しかし、いくら精度が良くても、この異常が致命的なもので、何としても見つけなければならないという場面では、全く使い物にならないモデルとなってしまいます。そのような場面では、たとえ一部の正常品を異常品と誤判定しても構わないので、異常品をすべて見つけられるモデルが求められるのです。

このような不均衡なデータで、少数のデータを確実に予測できるモデルを作りたいときは、多数のクラスのデータを減らすか(アンダーサンプリング)、少数のクラスのデータを増やして(オーバーサンプリング)、両者のバランスを調整するとうまく予測できることがあります。

そこで、アンダーサンプリングとオーバーサンプリングのそれぞれで、元データを処理し、分類モデルを作ってみましょう。

不均衡データを用いた分類モデルの作成

それでは、不均衡なデータを人工的に作り、アンダーサンプリングとオーバーサンプリングで分類モデルを作ってみましょう。

1) 解析用データの作成

人工のデータは、scikit-learnのmake_classification関数を使えば、簡単に作成できます。

import pandas as pd
from sklearn.datasets import make_classification

# サンプル数10000、特徴量10のデータを人工的に作成。陰性クラス(0):陽性クラス(1)=99.9:0.1

data_base = make_classification(
    n_samples = 10000, n_features = 10, n_informative = 2, n_redundant = 0, 
    n_repeated = 0, n_classes = 2, n_clusters_per_class = 2, weights = [0.999, 0.001], 
    flip_y = 0, class_sep = 1.0, hypercube = True, shift = 0.0, 
    scale = 1.0, shuffle = True, random_state = 100)

df = pd.DataFrame(data_base[0], columns = ['特徴量1', '特徴量2', '特徴量3', '特徴量4', '特徴量5', '特徴量6', '特徴量7', '特徴量8', '特徴量9', '特徴量10'])
df['クラス'] = data_base

print(df.shape)
df.head()

(10000, 11)

df['クラス'].value_counts()
0    9990
1      10
Name: クラス, dtype: int64

目論見どおり、不均衡なデータを作ることができましたので、このデータを使って分類していきます。

2) データが不均衡のまま分類モデルを作成した場合

アンダーサンプリングとオーバーサンプリングで分類モデルを作る前に、データが不均衡のままで分類モデルを作成するとどうなるか見てみましょう。

分類のためのアルゴリズムは色々ありますが、今回はシンプルなロジスティック回帰で、分類モデルを作成することとします。

#不均衡データでロジスティック回帰

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix

# データを学習用と検証用に分ける
x = df.iloc[:, 0:10]
y = df['クラス']
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state = 100)

# 分類モデル作成
clf = LogisticRegression()
clf.fit(x_train, y_train)

# 作成したモデルで、テストデータを予測値
y_pred = clf.predict(x_test)

#精度を求める
print("精度:{:.4f}".format(accuracy_score(y_test, y_pred)))

精度:0.9988

精度は99.88%と、一見、非常に精度の高い予測モデルができたように見えます。では、テストデータについて、混同行列を見てみましょう。

tn, fp, fn, tp = confusion_matrix(y_test, y_pred).flatten()
(tn, fp, fn, tp)

(2497, 1, 2, 0)

作成したモデルは、1個のみクラス1と予測しましたがこれは誤判定で、正確にクラス1を予測できませんでした(tp=0)。このように、精度だけでモデルの良し悪しを判断してしまうと、判断を誤ることがあるので、不均衡なデータに対するモデルの予測性能を、精度だけで判断するのは不十分ということになります。
そこで、適合率(tp/(tp+fp))と再現率(tp/(tp+fn))を計算してみます。

print("適合率:{:.4f}".format(tp/(tp+fp)))
print("再現率:{:.4f}".format(tp/(tp+fn)))
適合率:0.0000
再現率:0.0000

tp=0のため、適合率と再現率はともに0%と、散々な結果であることが分かります。そこで、以降はモデルの指標として、精度、適合率、再現率を見ていきます。

3) アンダーサンプリングでモデルを作成した場合

では、クラス1が学習用データの10%になるように、クラス0のサンプル数を減らします。

#アンダーサンプリングで学習用データ作成

from imblearn.under_sampling import RandomUnderSampler

# クラス1の数を保存
count_train_class_one = y_train.sum()
print('クラス1のサンプル数:{}'.format(count_train_class_one)) #クラス1のサンプル数表示

# クラス1が全体の10%になるまでクラス0を減らす
under = RandomUnderSampler(ratio={0:count_train_class_one*9, 1:count_train_class_one}, random_state=100)

# 学習用データに反映
x_train_under, y_train_under = under.fit_sample(x_train, y_train)

print(x_train_under.shape) #学習用データのサンプル数確認
クラス1のサンプル数:8
(80, 10)

クラス1のサンプル数は8なので、それに合わせてクラス0のサンプル数を減らし、全体で80個の学習用データができました。

この学習用データで、ロジスティック回帰を行ってみます。

#アンダーサンプリングでロジスティック回帰

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix

# 分類モデル作成
clf_under = LogisticRegression()
clf_under.fit(x_train_under, y_train_under)

# 作成したモデルで、テストデータを予測値
y_under_pred = clf_under.predict(x_test)

# 混同行列を作る
tn, fp, fn, tp = confusion_matrix(y_test, y_under_pred).flatten()
print((tn, fp, fn, tp))

#精度、適合率、再現率を求める
print("精度:{:.4f}".format(accuracy_score(y_test, y_under_pred)))
print("適合率:{:.4f}".format(tp/(tp+fp)))
print("再現率:{:.4f}".format(tp/(tp+fn)))
(2471, 27, 1, 1)
精度:0.9888
適合率:0.0357
再現率:0.5000

精度は落ちましたが、クラス1の1個を正しく予測できたため、適合率と再現率は少し向上しました。

4) オーバーサンプリングでモデルを作成した場合

今度は、クラス1がクラス0の10%になるように、クラス1のサンプル数を増やします。オーバーサンプリングの方法として、今回はSMOTE(Synthetic Minority Oversampling TEchnique)を使います。


#SMOTEで学習用データ作成

from imblearn.over_sampling import SMOTE

# クラス1の数を保存
count_train_class_one = y_train.sum()
print('クラス1のサンプル数:{}'.format(count_train_class_one)) #クラス1のサンプル数表示

# クラス0:クラス1=9:1になるまでクラス1を増やす
smote = SMOTE(sampling_strategy = 0.1, random_state=100)

# 学習用データに反映
x_train_smote, y_train_smote = smote.fit_sample(x_train, y_train)

print(x_train_smote.shape) #学習用データのサンプル数確認
print("SMOTE後のクラス1のサンプル数:{}".format(y_train_smote.sum())) #クラス1のサンプル数

クラス1のサンプル数:8
(8241, 10)
SMOTE後のクラス1のサンプル数:749

もともとの学習用データには、クラス1は8個しかありませんでしたが、SMOTEにより749個にまで増え、トータルの学習用データは8241個になりました。

このデータで予測モデルを作り、モデルの検証を行ってみます。

#SMOTEでロジスティック回帰

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix

# 分類モデル作成
clf_smote = LogisticRegression()
clf_smote.fit(x_train_smote, y_train_smote)

# 作成したモデルで、テストデータを予測値
y_smote_pred = clf_smote.predict(x_test)

# 混同行列を作る
tn, fp, fn, tp = confusion_matrix(y_test, y_smote_pred).flatten()
print((tn, fp, fn, tp))

#精度、適合率、再現率を求める
print("精度:{:.4f}".format(accuracy_score(y_test, y_smote_pred)))
print("適合率:{:.4f}".format(tp/(tp+fp)))
print("再現率:{:.4f}".format(tp/(tp+fn)))
(2477, 21, 0, 2)
精度:0.9916
適合率:0.0870
再現率:1.0000

2個のクラス1のデータはすべて正しく予測でき、再現率は100%となりました。ただし、クラス0をクラス1と予測した誤判定が21件あるため、適合率はあまりよくありませんでした。

しかし、例えば冒頭の製造ラインの例であれば、怪しい製品はすべて検出しておいて、本当に異常かどうかを人間が目視で再確認する、といった目的で使うのであれば、確実に異常品を検出できるこのモデルは、十分実用に値すると判断することができます。

おわりに

今回は不均衡データの扱いについて見てきました。今回の例では、オーバーサンプリングで有効な結果が得られましたが、元々のデータによっては、オーバーサンプリングでもうまくいかないときはあります。従って、不均衡なデータで機械学習を行う際は、サンプリング方法を色々変えてみて、最適な方法を見つける必要があります。

参考サイト

機械学習における不均衡データの問題点と対処法について

https://blog.amedama.jp/entry/imbalanced-data

不均衡データに対するClassification

https://qiita.com/ryouta0506/items/619d9ac0d80f8c0aed92

データが足りないなら増やせば良いじゃない。

https://qiita.com/cvusk/items/aa628e84e72cdf0a6e77


ABOUT ME
北爪 聖也
ダメ営業マンからデータサイエンティストへキャリアチェンジ。 技術とビジネスサイドの橋渡しが出来るため、ダメ営業マンの経験も役に立ちました。 広告代理店ADKにて3年勤務→データ分析受託の会社DATUM STUDIOにて1.2年勤務後、独立。
【事例集プレゼント】業務効率化したい医薬業界の方

株式会社piponでは医薬業界の企業様向けにDXの成功事例を集めた医薬DX事例集をe-bookとしてご提供しております。

ご興味ある方がいらっしゃいましたらこちらのフォームよりご連絡頂けると嬉しいです。