機械学習モデル、特にGBDT(Gradient Boosting Decision Tree)では、カテゴリ変数の処理が精度に大きく影響します。そこで注目されるのがTarget Encodingです。
Target Encodingとは、カテゴリ変数を目的変数の統計量(例:平均、最大値、最小値、標準偏差など)に置き換える手法です。
この記事では、なぜTarget EncodingがGBDTに有効なのか、また平均だけでなく、最大値、最小値、欠損の数、標準偏差などの統計量を活用する方法について解説します。
なぜTarget EncodingがGBDTに有効なのか?
直感的な説明
例えば、カテゴリ変数「店舗名」があり、各店舗の売上データ(目的変数)があるとします。各店舗ごとに単純な「平均売上」を求めると、店舗の実績を数字に置き換えることができ、GBDTにとって有用な数値特徴量となります。
大手店舗は常に高い売上を示す傾向があるため、モデルは「店舗名=大手」という関係性を直接捉えることができるようになります。
また、平均以外の統計量(例えば最大売上、最小売上、標準偏差など)を取り入れることにより、さらに細かな店舗の特性(例:売上のブレ具合や異常値の有無)を学習でき、モデルの表現力が向上することも望めます。
GBDTとの相性
GBDTは決定木を多数組み合わせた手法であり、入力特徴量が数値であることが望ましいです。Target Encodingはカテゴリ変数を目的変数の統計量に置き換えるため、直接数値として扱えるという利点があります。
また、GBDTは非線形な関係性も学習できるため、各カテゴリが持つ固有の統計分布(平均、最大、最小、標準偏差など)をうまく活かすことができます。
平均だけではない:多様な統計量によるTarget Encoding
Target Encodingと聞くと「平均」に着目することが多いですが、実際には以下のような統計量を利用することができます。
- 平均(Mean): 各カテゴリの中心的傾向を表現
- 最大値(Max): 異常に高い値が存在する場合の影響を考慮
- 最小値(Min): 異常に低い値を捉える
- 標準偏差(Std): 値のばらつきを表現
- 欠損の数(Count/NaN数): データの信頼性や希少性を示す
等々…。あくまでTarget Encodingとは目的変数の統計量で置き換える手法ですので、一通りではありません。
一つだけでなく、これらの統計量を組み合わせることで、カテゴリ変数に対するより豊かな情報表現が可能になり、モデルの予測性能が向上する可能性があります。
データリークへの注意:ネストされたfoldの必要性
Target Encodingを行う際の注意点として、データリークが挙げられます。具体的には、エンコードに全データ(テストデータも含む)を用いると、目的変数の情報が未来の予測に漏れてしまうリスクがあります。
対策方法
- クロスバリデーション(fold): 学習データを分割し、各foldごとに別々にTarget Encodingを行う。
- 二重のkfold: さらに、fold内で内部検証用に別のfoldを用意することで、より厳格なデータ分離が可能になります。
このようにして、モデルが未知のデータに対して正確に予測できるように、データリークを防ぐ工夫が必要です。※最後に実装例があります。
実際の式とスムージング
Target Encodingでは、カテゴリごとの統計量の推定に対して**スムージング(平滑化)**を行うことで、サンプル数が少ないカテゴリに対する過学習を抑制します。
一般的なスムージングの式は次のように表されます:$$\text{encoded_value} = \frac{n \times \text{statistic}_{\text{category}} + k \times \text{statistic}_{\text{global}}}{n + k}$$
ここで、
- $n$ : カテゴリに属するサンプル数
- $k$ : スムージングパラメータ(大きいほどグローバルな統計量が重視される)
- $\text{statistic}_{\text{category}}$ : 各カテゴリの統計量(平均、最大、最小、標準偏差など)
- $\text{statistic}_{\text{global}}$ : 全体の統計量
この式により、サンプル数が少ないカテゴリは全体の統計量に近い値となり、サンプル数が多い場合はそのカテゴリ固有の統計量が反映される仕組みになっています。
5. 平均以外の統計量も含めたオリジナル実装コード
以下は、Pythonを用いてTarget Encodingを実装するサンプルコードです。ここでは、平均に加えて、最大値、最小値、標準偏差、そして欠損の数(Count)もエンコードする例を示します。
(※ネストしたkfoldを用いた実装例も含め、データリークを防ぐ処理を意識しています。)
import pandas as pd
import numpy as np
from sklearn.model_selection import KFold
def target_encode(train_df, test_df, categorical_col, target, n_splits=5, smoothing=10):
# 新たな列名を用意:mean, max, min, std, count
new_cols = {
'mean': f"{categorical_col}_target_mean",
'max': f"{categorical_col}_target_max",
'min': f"{categorical_col}_target_min",
'std': f"{categorical_col}_target_std",
'count': f"{categorical_col}_target_count"
}
# 全体の統計量
global_mean = train_df[target].mean()
global_max = train_df[target].max()
global_min = train_df[target].min()
global_std = train_df[target].std()
# 各統計量の初期化
for col in new_cols.values():
train_df[col] = np.nan
test_df[col] = np.nan
kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
for train_index, val_index in kf.split(train_df):
train_fold = train_df.iloc[train_index]
val_fold = train_df.iloc[val_index]
# 集約統計量をカテゴリごとに計算
agg = train_fold.groupby(categorical_col)[target].agg(['mean', 'max', 'min', 'std', 'count']).reset_index()
# スムージング適用(平均の例。その他も同様に計算可能)
agg['mean_smooth'] = (agg['count'] * agg['mean'] + smoothing * global_mean) / (agg['count'] + smoothing)
agg['max_smooth'] = (agg['count'] * agg['max'] + smoothing * global_max) / (agg['count'] + smoothing)
agg['min_smooth'] = (agg['count'] * agg['min'] + smoothing * global_min) / (agg['count'] + smoothing)
agg['std_smooth'] = (agg['count'] * agg['std'] + smoothing * global_std) / (agg['count'] + smoothing)
# countはそのまま利用
agg = agg[[categorical_col, 'mean_smooth', 'max_smooth', 'min_smooth', 'std_smooth', 'count']]
# マージしてエンコード値を代入
val_fold = val_fold.merge(agg, on=categorical_col, how='left')
for stat in ['mean_smooth', 'max_smooth', 'min_smooth', 'std_smooth', 'count']:
stat_name = stat.split('_')[0] if stat != 'count' else 'count'
train_df.loc[val_index, new_cols[stat_name]] = val_fold[stat].values
# テストデータに対しては、全体の統計量を基にエンコード
agg_full = train_df.groupby(categorical_col)[target].agg(['mean', 'max', 'min', 'std', 'count']).reset_index()
agg_full['mean_smooth'] = (agg_full['count'] * agg_full['mean'] + smoothing * global_mean) / (agg_full['count'] + smoothing)
agg_full['max_smooth'] = (agg_full['count'] * agg_full['max'] + smoothing * global_max) / (agg_full['count'] + smoothing)
agg_full['min_smooth'] = (agg_full['count'] * agg_full['min'] + smoothing * global_min) / (agg_full['count'] + smoothing)
agg_full['std_smooth'] = (agg_full['count'] * agg_full['std'] + smoothing * global_std) / (agg_full['count'] + smoothing)
test_df = test_df.merge(agg_full[[categorical_col, 'mean_smooth', 'max_smooth', 'min_smooth', 'std_smooth', 'count']], on=categorical_col, how='left')
for stat in ['mean_smooth', 'max_smooth', 'min_smooth', 'std_smooth', 'count']:
stat_name = stat.split('_')[0] if stat != 'count' else 'count'
test_df[new_cols[stat_name]] = test_df[stat]
# 必要な列のみ返す(または元のデータフレームに追加)
return train_df, test_df
if __name__ == "__main__":
# サンプルデータの作成
data = {
'店舗名': ['A', 'B', 'A', 'C', 'B', 'A', 'C', 'B', 'C', 'A'],
'売上': [100, 150, 200, 80, 170, 130, 90, 160, 85, 110]
}
df = pd.DataFrame(data)
# 仮
train_df = df.copy()
test_df = df.copy()
train_encoded, test_encoded = target_encode(train_df, test_df, categorical_col='店舗名', target='売上', n_splits=5, smoothing=10)
print(train_encoded.head())
コードのポイント
- クロスバリデーション: KFoldを用いて学習データ内でネストされたfoldを実装し、データリークを防止。
- 複数統計量: 平均に加え、最大値、最小値、標準偏差、そしてサンプル数(count)も計算・エンコードしています。
- スムージング: 各統計量に対してスムージングを実施し、サンプル数が少ないカテゴリに対して過度な影響を防止しています。
まとめ
Target Encodingは、カテゴリ変数を目的変数の統計量に変換する手法であり、特にGBDTのような数値特徴量を重視するモデルに対して強力な効果を発揮します。
- 直感的な利点: カテゴリごとの売上傾向など、直接的な情報を数値化することで、モデルが効果的に学習できる。
- 多様な統計量の活用: 平均だけでなく、最大値、最小値、標準偏差、欠損の数なども利用することで、より細かな情報を表現。
- データリーク対策: ネストしたkfoldの実装により、未来の情報が漏れるリスクを軽減。
- スムージングの効果: サンプル数が少ないカテゴリに対して、グローバル統計量を適用することで、過学習を防止。
これらのポイントを押さえることで、Target Encodingはより強力で堅牢な特徴量変換手法として機能し、GBDTなどのモデルの性能向上に寄与します。
コメント