LESSON

サンプリング手法

「不均衡データへの最もシンプルな対策はサンプリングだ。だが、やり方を間違えると逆効果になる。」

田中VPoEがホワイトボードに図を描く。

「増やすか、減らすか、あるいは両方か。それぞれの特性と落とし穴を押さえておこう。」

オーバーサンプリング

少数クラス(不正取引)のデータを人工的に増やす手法である。

ランダムオーバーサンプリング

最も単純な方法。少数クラスのサンプルをランダムに複製する。

from imblearn.over_sampling import RandomOverSampler

ros = RandomOverSampler(random_state=42)
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)

print(f"リサンプリング前: {dict(zip(*np.unique(y_train, return_counts=True)))}")
print(f"リサンプリング後: {dict(zip(*np.unique(y_resampled, return_counts=True)))}")

# リサンプリング前: {0: 199020, 1: 345}
# リサンプリング後: {0: 199020, 1: 199020}

問題点: 同じデータを複製するだけなので過学習しやすい。

SMOTE(Synthetic Minority Over-sampling Technique)

少数クラスのサンプル間を補間して、新しい合成サンプルを生成する。

from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state=42, k_neighbors=5)
X_smote, y_smote = smote.fit_resample(X_train, y_train)

print(f"SMOTE後: {dict(zip(*np.unique(y_smote, return_counts=True)))}")

SMOTEのアルゴリズム:

1. 少数クラスのサンプル x_i を選択
2. x_i のk近傍(k=5がデフォルト)から1つをランダムに選択: x_nn
3. 合成サンプル = x_i + rand(0,1) * (x_nn - x_i)
4. 目標数に達するまで繰り返し

イメージ:
  x_i ●──────────● x_nn

        この線分上にランダムに新サンプルを生成

注意点:

  • ノイズサンプルの近傍にも合成サンプルが生成される(ノイズの増幅)
  • 高次元データでは近傍の概念が曖昧になる

ADASYN(Adaptive Synthetic Sampling)

SMOTEの改良版。学習が難しい(境界付近の)サンプルに重点的に合成サンプルを生成する。

from imblearn.over_sampling import ADASYN

adasyn = ADASYN(random_state=42)
X_adasyn, y_adasyn = adasyn.fit_resample(X_train, y_train)

SMOTEとの違い:

SMOTE:  全ての少数サンプルに均等に合成
ADASYN: 多数クラスに囲まれた(学習困難な)サンプルに重点的に合成

結果: 決定境界付近のサンプルが増え、境界の学習が改善

アンダーサンプリング

多数クラス(正常取引)のデータを減らす手法である。

ランダムアンダーサンプリング

from imblearn.under_sampling import RandomUnderSampler

rus = RandomUnderSampler(random_state=42)
X_under, y_under = rus.fit_resample(X_train, y_train)

print(f"アンダーサンプリング後: {dict(zip(*np.unique(y_under, return_counts=True)))}")
# アンダーサンプリング後: {0: 345, 1: 345}

問題点: 多数クラスの情報を大量に捨てるため、モデルの汎化性能が低下する。

クラス境界付近の「ノイジーな」多数クラスサンプルを除去する。

from imblearn.under_sampling import TomekLinks

tomek = TomekLinks()
X_tomek, y_tomek = tomek.fit_resample(X_train, y_train)

Tomek Linksの定義:

サンプル a(クラス0)と b(クラス1)が Tomek Link であるとは:
  - aの最近傍が b
  - bの最近傍が a
  - aとbが異なるクラス

→ クラス境界のあいまいなサンプルを除去して境界を明確化

NearMiss

少数クラスに近い多数クラスサンプルを優先的に残す。

from imblearn.under_sampling import NearMiss

nm = NearMiss(version=1)
X_nm, y_nm = nm.fit_resample(X_train, y_train)

ハイブリッド手法

from imblearn.combine import SMOTETomek

smt = SMOTETomek(random_state=42)
X_smt, y_smt = smt.fit_resample(X_train, y_train)

処理の流れ:

元データ → SMOTE(少数クラスを増やす)→ Tomek Links(境界のノイズを除去)

SMOTE + ENN(Edited Nearest Neighbours)

from imblearn.combine import SMOTEENN

smote_enn = SMOTEENN(random_state=42)
X_se, y_se = smote_enn.fit_resample(X_train, y_train)

手法の比較

手法メリットデメリット推奨場面
Random Over実装が簡単過学習しやすいベースライン
SMOTE多様なサンプル生成ノイズ増幅のリスク一般的な不均衡
ADASYN境界付近を重点的に学習計算コスト高境界が複雑な場合
Random Under学習が高速情報損失が大きいデータが大量の場合
Tomek Links境界を明確化不均衡解消は限定的前処理として
SMOTE+Tomekバランスの良い手法計算コスト中一般的な推奨

実装上の注意点

# 重要: サンプリングは学習データにのみ適用する
# テストデータには絶対に適用しない!

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# サンプリングは学習データにのみ
smote = SMOTE(random_state=42)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)

# テストデータはそのまま(元の分布を維持)
# model.predict(X_test) で評価

まとめ

項目ポイント
オーバーサンプリングSMOTE/ADASYNで少数クラスの合成サンプル生成
アンダーサンプリングTomek Links等で多数クラスのノイズ除去
ハイブリッドSMOTE+Tomekが実用的
鉄則サンプリングは学習データにのみ適用

チェックリスト

  • SMOTEのアルゴリズムを説明できる
  • ADASYNとSMOTEの違いを説明できる
  • Tomek Linksの役割を理解した
  • サンプリングを学習データにのみ適用する理由を説明できる

次のステップへ

サンプリング手法を理解したところで、次はコスト敏感学習によるアルゴリズムレベルの対処法を学ぼう。

推定読了時間: 30分