Target Mean Encodingとは
カテゴリカル変数はそのままでは、機械学習モデルの入力にすることができません。
普通は、One Hot Encoding(ダミー変数化)をするかと思います。(自分はこれしか知りませんでした。)
カテゴリカル変数のEncoding手法の一つであるTarget Mean Encoding(Likelihood Encoding)は、目的変数の情報を利用し、カテゴリカル変数を数値に変換する方法で、Kaggle等の機械学習コンペではよく用いられているようです。
目的変数がBoolean表現の場合、カテゴリごとの確率を、数値であればカテゴリごとの平均を特徴量とします。
実行例
例として、以下のようなデータセットでTarget Mean Encoding(以下、Target Encoding)を試してみます。
df = pd.DataFrame({ 'category': ['A','A','B','B','B','C','C','C','C','D'],
'label': [1,0,1,0,0,1,0,1,1,1]
})
category label
0 A 1
1 A 0
2 B 1
3 B 0
4 B 0
5 C 1
6 C 0
7 C 1
8 C 1
9 D 1
Pandasのgroupbyを使った実行方法を試してみます。
方法は以下の2つです。
# 1
label_mean = df.groupby('category').label.mean()
df = df.assign(target_enc=df['category'].map(label_mean).copy())
# 2
df = df.assign(target_enc2=df.groupby('category')['label'].transform('mean').copy())
得られる結果はこちらです。
category label target_enc target_enc2
0 A 1 0.500000 0.500000
1 A 0 0.500000 0.500000
2 B 1 0.333333 0.333333
3 B 0 0.333333 0.333333
4 B 0 0.333333 0.333333
5 C 1 0.750000 0.750000
6 C 0 0.750000 0.750000
7 C 1 0.750000 0.750000
8 C 1 0.750000 0.750000
9 D 1 1.000000 1.000000
Target Encodingできていることが確認できました。
Leakageについて
Leakageとは、モデルを学習する際に、本来知らないはずの情報を不当に使ってしまうことです。
Kaggleのドキュメントには、Leakageについての項目が設けられています。
ここでは以下の具体例が挙げられています。
- 患者が前立腺癌かどうかを予測するためのデータに「前立腺の手術を受けたか」という変数を含めていた
Target Encodingでは、学習データで得られた各カテゴリの特徴量をテストデータ内の各カテゴリに適用します。これが、Leakageが引き起こしてしまいます。
本記事では、Leakageによる過学習を防ぐための手法について、2つ紹介します。
Leave One Out(LOO)
この手法は、値を計算するときに、その行データ以外で計算するというものです。
通常のTarget Encoding
category | label | target_enc | |
---|---|---|---|
0 | A | 1 | 0.50 |
1 | A | 0 | 0.50 |
2 | B | 1 | 0.33 |
3 | B | 0 | 0.33 |
4 | B | 0 | 0.33 |
5 | C | 1 | 0.75 |
6 | C | 0 | 0.75 |
7 | C | 1 | 0.75 |
8 | C | 1 | 0.75 |
9 | D | 1 | 1.00 |
Target Encoding with LOO
category | label | target_enc | |
---|---|---|---|
0 | A | 1 | 0.00 |
1 | A | 0 | 1.00 |
2 | B | 1 | 0.00 |
3 | B | 0 | 0.50 |
4 | B | 0 | 0.50 |
5 | C | 1 | 0.66 |
6 | C | 0 | 1.00 |
7 | C | 1 | 0.66 |
8 | C | 1 | 0.66 |
9 | D | 1 | 0.00 |
Cについて見てみましょう。
5行目のCのtarget_encは
(0+1+1)/3 = 2/3 = 0.66
となります。
また、6行目のCのtarget_encは
(1+1+1)/3 = 3/3 = 1.00
となります。
ただ、この手法だと余計にLeakageを起こしているように見えます。
(例えばBについて、labelの0, 1がtarget_encの0.5, 0に対応してしまっている)
Smoothing
この手法は、データ数が少ないカテゴリを持つカテゴリカル変数に対して効果的にTarget Encodingを用いるための手法です。
データ数が十分あるカテゴリについては通常のTarget Encodingに近い値を示し、データ数が少ないカテゴリについてはデータセット全体での値に近づくようにスムージングを行います。
詳しく知りたい方は、こちらのブログがわかりやすいです。
S_i = λ(n_i)\frac{n_iy}{n_i}+(1−λ(n_i))\frac{n_y}{n_tr}\\
λ(n_i) = \frac{1}{1+exp(-\frac{n_i−k}{f})}
k=0, f=1
(λは標準シグモイド関数になる)でカテゴリA,Bについて計算してみると、以下のようになります。
λ(2) = \frac{1}{1+exp(-2)} = 0.880\\
S_A = 0.880*\frac{1}{2} + (1−0.880)*\frac{6}{10} = 0.512
λ(3) = \frac{1}{1+exp(-3)} = 0.953\\
S_B = 0.953*\frac{1}{3} + (1−0.953)*\frac{6}{10} = 0.346
変換時の注意点
以下でも実行できますが、どちらも警告が出ます。
# 1
label_mean = df.groupby('type').label.mean()
df['target_enc'] = df['type'].map(label_mean)
# 2
df['target_enc2'] = df.groupby('type')['label'].transform('mean')
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
ドキュメントの注意事項を参照しても、どう対応すればいいのかわからなかったのですが、
実行例で示したようにすることで警告は出なくなりました。
参考
[カテゴリカル変数のEncoding手法について]
(http://nami3373.hatenablog.com/entry/2018/07/26/230655)
[Feature Enginnering]
(https://www.slideshare.net/0xdata/feature-engineering-83511751)
[TargetEncodingのスムーシング]
(https://mikebird28.hatenablog.jp/entry/2018/06/14/172132)