見出し画像

【機械学習の初学者向け】不均衡データではRandomUnderSamplerを使ってみよう

機械学習の分類問題では、
陽性クラス・陰性クラスのデータ数に偏りがある
不均衡データによく直面します。

このような不均衡データでは、
モデルが多数クラスの特徴を過剰に学習し、
少数クラスを適切に識別できなくなることで、
性能に悪影響を及ぼします。

特に少数クラスの予測は難しくなります。

この問題に対処するための方法はいくつかあります。

最も手っ取り早いのは、
陽性クラスと陰性クラスの数をなるべく
あわせる
ことです。
そのために、
少数クラスを増やしたり=オーバーサンプリング
多数クラスを減らしたり=ダウンサンプリング
する方法があります。

この記事では、
機械学習用のPythonライブラリーの一つ
RandomUnderSamplerを活用した
ダウンサンプリングの方法についてご紹介します。


RandomUnderSamplerの原理


RandomUnderSamplerは、
多数クラスからランダムにデータを削除することで、
クラス間のバランスを調整するものです。

具体的には、
少数クラスと多数クラスのデータ数を
パラメータとして与えた割合に
することが可能です。

例えば、
もともとのデータが、
少数クラス:多数クラス =1:9の割合だったとして、
これを
少数クラス:多数クラス =1:3の割合にしたり
少数クラス:多数クラス =1:1の割合にしたり
することが可能です。

これにより、モデルがデータに適した学習をすることができます。

RandomUnderSamplerの実装


imbalanced-learnライブラリからインポートします。
インスタンス化し、
不均衡データ(X)と
陽性・陰性のターゲットデータ(y)を与えるだけです。

from imblearn.under_sampling import RandomUnderSampler

# RandomUnderSamplerのインスタンス化
rus = RandomUnderSampler(sampling_strategy=1.0 , random_state=42)

# データセットの再サンプリング
X_res, y_res = rus.fit_resample(X, y)

陽性・陰性の比率の調整は、sampling_strategyで行います。
少数クラス÷多数クラスの割合を指定します。

sampling_strategy=1.0とすると、
少数クラス:多数クラス=1:1になります。
sampling_strategy=0.5とすると、
少数クラス:多数クラス=1:2になります。

ダウンサンプリングの留意点


RandomUnderSamplerの主な利点は、
実装の容易さと、
クラス間のバランスを効果的に改善できることです。

しかしながら、
多数クラスからランダムにデータを削除することで、
重要な情報が失われる可能性があります。
これはダウンサンプリングの宿命と言っていいかもしれません。

乱数シードを活用した実装


ダウンサンプリングにおける
情報喪失の問題を少しでも和らげるため、
乱数シードを活用する方法があります。

ダウンサンプリングは
多数クラスからデータをランダムに削減し、
少数クラスとの均衡を図ります。

多数クラスの中からどのデータを削除するかは
乱数(正確には乱数シードの数)によって決まることになります。

この乱数(乱数シード)を複数発生させれば、
複数通りのサンプリングが可能となり、
情報喪失の問題が少しでも和らぐことになります。


以下では、具体的なPythonコードを示しています。
LightGBMの学習部分だけを取り出したものです。

rus_seedsで複数の乱数シードを設定し、
for文で回すことで
trainデータ(訓練データ)を
何通りもアンダーサンプリングします。

これにより情報喪失の問題が少し軽減できますね。

from imblearn.under_sampling import RandomUnderSampler
import lightgbm as lgb
import numpy as np
import pandas as pd


rus_seeds = [2, 12, 32, 42, 52, 62, 72]
num_rus = len(rus_seeds)
 #lightgbm 
def lightgbm_train(x_train, y_train, x_valid, y_valid , features, categorical_features):
    valid_pred = np.zeros(len(x_valid))
    models_seed =[]

    for seed in rus_seeds:
        # インスタンス化
        rus = RandomUnderSampler(sampling_strategy=0.5, random_state=seed)

        # クロスバリデーションの中のtrainデータだけをアンダーサンプリング
        x_rus, y_rus = rus.fit_resample(x_train, y_train)


        lgb_train = lgb.Dataset(x_rus, y_rus, categorical_feature=categorical_features)
        lgb_valid = lgb.Dataset(x_valid, y_valid, categorical_feature=categorical_features)

        model = lgb.train(
            params = lgb_params,
            train_set = lgb_train,
            num_boost_round = num_boost_round,
            valid_sets = [lgb_train, lgb_valid],
            feval = lgb_metric,
            callbacks=[lgb.early_stopping(stopping_rounds=early_stopping_round,verbose=verbose)])


        valid_pred += model.predict(x_valid)/ num_rus
        models_seed.append(model)
  
    return models_seed, valid_pred


なお、乱数シードの発生にあたっては、
クロスバリデーション(K-分割交差検証)とセットで
行う
のが良いでしょう。

上記もクロスバリデーションの中で
学習することを想定したコードになっています。

【クロスバリデーション(Kー分割交差検証)とは】
データをk個のグループに分割して,k個のうちひとつのグループをテストデータとして,残りのデータを訓練データとします。全てのグループがテストデータになるようk回繰り返します。これにより、モデルの汎化性能を高めようとするものです。


この記事が気に入ったらサポートをしてみませんか?