機械学習モデルの訓練において、データセット中のクラスの出現頻度が大きく偏っている「不均衡データ」は、しばしば課題となります。
不均衡データをそのまま扱うと、モデルは多数派クラスに偏った予測を行うようになり、予測精度が低下する可能性があります。
本稿では、不均衡データ対策の手法と、より効果的なモデル構築のための包括的な戦略を紹介します。
初学者から実務家まで、幅広い読者に情報を提供することを目的としています。
1. 不均衡データとは
不均衡データとは、分類問題において、各クラスのデータ数が大きく異なるデータセットを指します。
例えば、クレジットカード詐欺検出では、詐欺取引 (Positive) は正常な取引 (Negative) に比べて圧倒的に少ないため、不均衡データとなります。
2. 不均衡データ対策の重要性
不均衡データをそのまま用いてモデルを学習すると、モデルは多数派クラスのデータに過剰適合し、少数派クラスに対する予測精度が低下する傾向があります。
これは、モデルがデータの偏りを反映した結果であり、実際の問題設定においては深刻な問題を引き起こす可能性があります。
例えば、医療診断における不均衡データ問題では、疾患を見逃すことが重大な結果につながる可能性があります。
したがって、不均衡データに適切に対処することは、機械学習モデルの性能と信頼性を確保する上で不可欠です。
3. 対策手法
3.1 評価指標の選択
不均衡データを用いたモデル評価では、Accuracy (正答率) だけでは不十分です。
これは、Accuracyは単に全体における正答数を測るだけで、クラスごとの予測精度を考慮しないためです。
代わりに、以下の指標を用いることが推奨されます。
- Precision (適合率): Positiveと予測したデータのうち、実際にPositiveだったデータの割合。偽陽性を抑えたい場合に重要。
- Recall (再現率): 実際にPositiveなデータのうち、Positiveと予測できたデータの割合。偽陰性を抑えたい場合に重要。
- F1-score: PrecisionとRecallの調和平均。両方の指標をバランス良く評価したい場合に有効。
- ROC AUC Score: しきい値に依存しない評価が可能。
- Matthews Correlation Coefficient (MCC): 不均衡データでも偏りなく評価できる指標。
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score, matthews_corrcoef
y_true = [0, 1, 0, 1, 0] # 真のラベル
y_pred = [0, 0, 0, 1, 1] # 予測ラベル
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)
roc_auc = roc_auc_score(y_true, y_pred)
mcc = matthews_corrcoef(y_true, y_pred)
print(f"Precision: {precision:.2f}")
print(f"Recall: {recall:.2f}")
print(f"F1-score: {f1:.2f}")
print(f"ROC AUC Score: {roc_auc:.2f}")
print(f"MCC: {mcc:.2f}")
出力:
Precision: 0.50
Recall: 0.50
F1-score: 0.50
ROC AUC Score: 0.50
MCC: 0.00
3.2 データレベルの手法
データレベルの手法は、データセットそのものを操作することで不均衡を解消しようとします。
3.2.1 アンダーサンプリング
多数派クラスのデータを減らすことで、クラス間のデータ数を均衡化します。ランダムにデータを削除するランダムアンダーサンプリングが一般的ですが、重要なデータが失われる可能性もあります。
メリット:
- 簡単に実装できる
- 訓練データ数が減るため、計算コストが下がる
デメリット:
- 多数派クラスの情報が失われる可能性がある
- 極端にアンダーサンプリングすると、モデルの性能が低下する
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X, y)
バギングとの組み合わせ
アンダーサンプリングを、バギング (bootstrap aggregating) と組み合わせることで、よりロバストなモデルを構築できます。
from sklearn.base import BaseEstimator
from xgboost import XGBClassifier
from imblearn.under_sampling import RandomUnderSampler
import numpy as np
class XgboostBagging(BaseEstimator):
def __init__(self, ratio: float = 1.0, repeats: int = 10, **params):
self.ratio = ratio
self.repeats = repeats
self.params = params
self.algs = []
def fit(self, X_train: np.ndarray, y_train: np.ndarray, eval_metric: str = 'auc'):
for _ in range(self.repeats):
alg = XGBClassifier(**self.params)
us = RandomUnderSampler(sampling_strategy=self.ratio, random_state=None)
X_res, y_res = us.fit_resample(X_train, y_train)
alg.fit(X_res, y_res, eval_metric=eval_metric)
self.algs.append(alg)
return self
def predict_proba(self, X_test: np.ndarray) -> np.ndarray:
probs = [alg.predict_proba(X_test) for alg in self.algs]
return np.mean(probs, axis=0)
3.2.2 オーバーサンプリング
少数派クラスのデータを増加させることで、クラス間のデータ数を均衡化します。代表的な手法としてSMOTE (Synthetic Minority Over-sampling Technique) が挙げられます。SMOTEは、少数派クラスのデータから人工的にデータを生成することで、データ数を増加させます。
メリット:
- 少数派クラスの情報を失わずに、データ数を増やせる
- 特徴空間を効果的に埋めることができる
デメリット:
- 計算コストが高くなる
- 生成されたデータが不自然である可能性がある
from imblearn.over_sampling import SMOTE, ADASYN, BorderlineSMOTE
# SMOTE
smote = SMOTE(random_state=42)
X_smote, y_smote = smote.fit_resample(X, y)
# ADASYN
adasyn = ADASYN(random_state=42)
X_adasyn, y_adasyn = adasyn.fit_resample(X, y)
# BorderlineSMOTE
bsmote = BorderlineSMOTE(random_state=42)
X_bsmote, y_bsmote = bsmote.fit_resample(X, y)
オーバーサンプリングにおける注意点
オーバーサンプリングしたデータに対して交差検証を行う際は、データリークを防ぐために注意が必要です。
データリークを防ぐためには、StratifiedKFoldなどを用いて、オーバーサンプリングを行う前にデータを分割する必要があります。
from sklearn.model_selection import StratifiedKFold
from imblearn.over_sampling import SMOTE
from sklearn.metrics import roc_auc_score
skf = StratifiedKFold(n_splits=5)
scores = []
for train_index, test_index in skf.split(X, y):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
smote = SMOTE(random_state=42)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)
clf.fit(X_train_resampled, y_train_resampled)
y_pred_proba = clf.predict_proba(X_test)[:, 1]
score = roc_auc_score(y_test, y_pred_proba)
scores.append(score)
print(f"CV scores: {scores}")
print(f"Mean ROC AUC Score: {np.mean(scores):.3f} +/- {np.std(scores):.3f}")
3.3 アルゴリズムレベルの手法
アルゴリズムレベルの手法は、機械学習アルゴリズムそのものを不均衡データに適応させる方法です。
3.3.1 コスト感知学習
コスト感知学習では、誤分類のコストをクラスごとに変更することで、モデルに少数派クラスの重要性を認識させます。
例えば、少数派クラスの誤分類に大きなペナルティを与えることで、モデルは少数派クラスのデータも重視して学習するようになります。
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
# RandomForest
rf = RandomForestClassifier(class_weight='balanced')
# XGBoost
xgb = XGBClassifier(scale_pos_weight=sum(y == 0) / sum(y == 1))
メリット:
- 追加のデータ処理が不要
- アルゴリズムに直接組み込める
デメリット:
- 適切なコストの設定が難しい場合がある
- アルゴリズムに依存する
3.3.2 アンサンブル学習
複数のモデルを組み合わせることで、個々のモデルの弱点を補い、より強力なモデルを構築します。
不均衡データ対策として、バギングやブースティングなどのアンサンブル学習手法が有効です。
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier
from imblearn.ensemble import BalancedRandomForestClassifier
lgbm = LGBMClassifier(is_unbalance=True)
catboost = CatBoostClassifier(auto_class_weights='Balanced')
brf = BalancedRandomForestClassifier()
メリット:
- 個々のモデルの弱点を補える
- 複数のモデルを組み合わせることで、より堅牢なモデルを構築できる
デメリット:
- 計算コストが高くなる
- ハイパーパラメータのチューニングが複雑になる
3.4 深層学習における手法
深層学習モデルにおいても、不均衡データ対策は重要です。
ここでは、代表的な手法であるFocal LossとClass-Balanced Lossを紹介します。
3.4.1 Focal Loss
Focal Lossは、クロスエントロピー損失関数を拡張したもので、不均衡データセットに対して効果を発揮します。
Focal Lossは、誤分類しやすいデータに対するペナルティを大きくすることで、モデルが少数派クラスのデータも学習するように促します。
import tensorflow as tf
def focal_loss(gamma=2., alpha=0.25):
def focal_loss_fixed(y_true, y_pred):
pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))
pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))
return -tf.reduce_sum(alpha * tf.pow(1. - pt_1, gamma) * tf.math.log(pt_1)) - tf.reduce_sum((1-alpha) * tf.pow(pt_0, gamma) * tf.math.log(1. - pt_0))
return focal_loss_fixed
model.compile(optimizer='adam', loss=focal_loss())
3.4.2 Class-Balanced Loss
Class-Balanced Lossは、クラス頻度の逆数を用いて損失関数に重みを付けることで、不均衡データに対処します。
これにより、少数派クラスの影響力を増幅し、モデルがバランスの取れた学習を行うことができます。
import tensorflow as tf
def class_balanced_loss(beta=0.99):
def loss(y_true, y_pred):
class_counts = tf.cast(tf.unique_with_counts(tf.argmax(y_true, axis=1))[2], tf.float32)
weights = (1 - beta) / (1 - tf.pow(beta, class_counts))
y_true_weighted = y_true * tf.expand_dims(weights, axis=1)
return tf.keras.losses.categorical_crossentropy(y_true_weighted, y_pred)
return loss
model.compile(optimizer='adam', loss=class_balanced_loss())
4. エシカルな考慮事項
不均衡データへの対策は、倫理的な観点からも重要です。
不均衡データは、社会的な偏見や差別を反映している場合があり、安易な対策が倫理的な問題を引き起こす可能性があります。
例えば、人種や性別に基づいた差別的な予測モデルが構築される可能性も考えられます。
モデルの公平性を評価し、必要であれば対策を講じることで、倫理的な問題を回避する必要があります。
Fairlearn などのツールを使用すると、モデルの公平性を評価できます。
from fairlearn.metrics import demographic_parity_difference
dpd = demographic_parity_difference(
y_true=y_test,
y_pred=y_pred,
sensitive_features=sensitive_features_test
)
5. Pythonライブラリ
- imbalanced-learn: 不均衡データ対策に特化したライブラリで、多様なサンプリング手法を提供しています。
- ImbalancedDatasetSampler: PyTorchのDatasetクラスを拡張し、不均衡データのサンプリングを行うライブラリです。
参考文献
- He, H., & Garcia, E. A. (2009). Learning from imbalanced data. IEEE Transactions on knowledge and data engineering, 21(9), 1263-1284.
- Chawla, N. V., Bowyer, K. W., Hall, L. O., & Kegelmeyer, W. P. (2002). SMOTE: synthetic minority over-sampling technique. Journal of artificial intelligence research, 16, 321-357.
- Lin, T. Y., Goyal, P., Girshick, R., He, K., & Dollár, P. (2017). Focal loss for dense object detection. In Proceedings of the IEEE international conference on computer vision (pp. 2980-2988).
- Cui, Y., Jia, M., Lin, T. Y., Song, Y., & Belongie, S. (2019). Class-balanced loss based on effective number of samples. In Proceedings of the IEEE/CVF conference on computer vision and pattern recognition (pp. 9268-9277).
- imbalanced-learn documentation
- Dask documentation