대부분의 머신러닝 알고리즘은 입력값으로 수치형 변수만을 사용하므로, 범주형 변수는 수치형으로 변환해야 한다.
흔히 쓰이는 방법으로는 Ordinal Encoding과 One-Hot Encoding이 있는데, 다음과 같은 특징과 단점을 갖고 있다.
(https://scikit-learn.org/stable/modules/preprocessing.html#preprocessing-categorical-features)
- Ordinal Encoding: 훈련 데이터에 나타나는 범주값들을 (사전순으로 정렬한 뒤) 서수로 변환 (0, 1, 2, ...)
- 단점: 전혀 관계가 없는 범주들 사이에 서수적인 관계가 생김으로써, 모델의 정확도가 떨어지거나 예기치 못한 문제가 발생할 수 있음
- One-Hot Encoding: 모든 범주값들에 대해 0/1 값을 갖는 변수를 추가한다.
- 단점: 범주마다 변수를 추가하므로 훈련 성능이 나빠진다.
위 단점들을 보완하는 방법으로 Target Encoding이 있다.
Target Encoding
범주형 변수의 각각의 범주를 타겟 변수의 특정 통계치, 예를 들어 타겟값 평균으로 변환하는 방식이다.
통계치라 하면, 이진 분류에서는 타겟값이 1인 확률, 회귀 모델에서는 평균이 되는데, (이진 분류의 타겟값이 0 또는 1이므로) 결국은 타겟값의 평균값으로 볼 수 있다. Target encoding이 Mean encoding으로 불리기도 하는 이유이다.
평균 대신 분산, 왜도(skewness), 첨도(kurtosis) 등의 higher moments를 사용하는 방법도 있다. (https://towardsdatascience.com/target-encoding-and-bayesian-target-encoding-5c6a6c58ae8c)
Target encoding 예시
genre 카테고리 별 이진 target 값을 갖는 다음 데이터셋을 가정해 보자.
import pandas as pd
import numpy as np
np.random.seed(123)
target = list(np.random.randint(0, 2, 20))
genres = ["Sci Fi", "Drama", "Romance", "Fantasy", "Nonfiction"]
genre = [genres[i] for i in np.random.randint(0, len(genres), 20)]
df = pd.DataFrame({"genre": genre, "target": target})
df
"Sci Fi" genre에 대한 인코딩 값은 ("Sci Fi" genre 중 target이 1인 수) / ("Sci Fi" genre 수), 즉, $1/5$이 된다.
모든 genre에 대한 인코딩 값(genre_encoded_prob)은다음과 같다.
categories = df['genre'].unique()
targets = df['target'].unique()
cat_list = []
for cat in categories:
aux_dict = {}
aux_dict['category'] = cat
aux_df = df[df['genre'] == cat]
counts = aux_df['target'].value_counts()
aux_dict['count'] = sum(counts)
for t in targets:
aux_dict['target_' + str(t)] = counts[t] if t in counts else 0
cat_list.append(aux_dict)
cat_df = pd.DataFrame(cat_list)
cat_df['genre_encoded_prob'] = cat_df['target_1'] / cat_df['count']
cat_df
기존 데이터셋에 합친 모습은 다음과 같다.
df2 = df.join(cat_df[['category', 'genre_encoded_prob']].set_index('category'), on='genre')
df2
타겟 인코딩 값은 타겟값의 평균이므로, 다음과 같이 간결하게 구할 수도 있다.
stats = df['target'].groupby(df['genre']).agg(['count', 'mean'])
df3 = df.join(stats[['mean']], on='genre').rename(columns = {'mean' : 'genre_encoded_mean'})
df3
Smoothing 처리
평균값은 각각의 범주 단위로 계산되는데, 해당 범주 개수가 적을 경우 극단적으로 0 또는 1에 가깝게 나올 수 있다.
이를 완화하기 위해 전체 평균값을 일정 비율 섞을 수 있는데, 이를 smoothing이라고 한다. smoothing 정도를 결정하는 $\alpha$ 파라미터를 사용하여 다음과 같이 계산할 수 있다.
$ encoding = \alpha \cdot p(t=1|x=c_i) + (1-\alpha)\cdot p(t=1) $
$\alpha$값은 보통 다음 값을 사용한다.
$ \alpha = \cfrac{1}{1 + e^{-\frac{n-k}{f}}} $
- f: smoothing_factor
- k: min_samples_leaf
smoothing_factor = 1.0 # The f of the smoothing factor equation
min_samples_leaf = 1 # The k of the smoothing factor equation
prior = df['target'].mean()
smoove = 1 / (1 + np.exp(-(stats['count'] - min_samples_leaf) / smoothing_factor))
smoothing = prior * (1 - smoove) + stats['mean'] * smoove
encoded = pd.Series(smoothing, name = 'genre_encoded_smoothing')
df4 = df3.join(encoded, on='genre')
df4
Target Leakage 문제
Target Encoding의 큰 이슈는 "모델이 예측하려고 하는 타겟 자체의 값이 변수값에 사용된다"는 점으로, 이를 target leakage라고 부른다.
Target leakage로 인해 심각한 과적합(overfitting)이 발생할 수 있는데, 이를 보완하기 위해 아래와 같은 방법들이 병행되어야 한다.
- Leave-One-Out Target Encoding: 자기 자신의 target 값을 제외한 나머지 target들의 평균을 취하는 방식.
- n개의 폴드로 나누어서 자신이 속한 폴드를 제외한 나머지 데이터들로만 계산하는 Leave-One-Fold-Out 방식도 있음.
- Smoothing: 앞서 소개한 smoothing 기법도 과적합을 방지하는 하나의 방법이다. (regularization 측면)
Scikit-learn TargetEncoder
https://scikit-learn.org/stable/modules/preprocessing.html#target-encoder
scikit-learn에도 1.3 버전부터 TargetEncoder가 포함되었다.
from sklearn.preprocessing import TargetEncoder
encoder = TargetEncoder()
df4['genre_encoded_sklearn'] = encoder.fit_transform(df[['genre']], df['target'])
df4
Target leakage를 보완하기 위해 앞서 소개한 Leave-One-Fold-Out 방식을 사용한다. (cv 파라미터로 지정, default=5)
이진 분류 타겟에 대한 수식은 다음과 같다.
$ S_i = \lambda_i \cfrac{n_{iY}}{n_i} + (1 - \lambda_i) \cfrac{n_Y}{n} $
- $S_i$: 범주 $i$의 인코딩 값
- $n_{iY}$: $i$ 범주 중 $Y=1$인 데이터 개수
- $\lambda_i$: shrinkage factor (smoothing)
smoothing을 위한 $\lambda_i$ 수식은 아래와 같다. (앞에서 설명한 smoothing 내용과는 조금 다름)
$ \lambda_i = \cfrac{n_i}{m + n_i} $
$m$은 smoothing 파라미터로서 TargetEncoder smooth 인자로 지정할 수 있다. default 값인 smooth="auto"를 사용하면 $m=\sigma_i^2 / \tau^2$이 사용되는데, $\sigma_i^2$은 $i$ 범주에 대한 타겟 분산값, $\tau^2$은 전체 타겟의 분산값이다.
수치형 타겟에 대한 수식은 다음과 같다. (이진 분류와 유사)
$ S_i = \lambda_i \cfrac{\sum_{k \in L_i} Y_k}{n_i} + (1 - \lambda_i) \cfrac{\sum_{k=1}^{n} Y_k}{n} $
'AI,머신러닝 > 용어' 카테고리의 다른 글
L1, L2 정규화(Regularization) (0) | 2023.09.09 |
---|---|
크로스 엔트로피 (Cross-Entropy) (0) | 2023.09.05 |
경사 하강법 (Gradient Descent) (0) | 2023.09.05 |
지니 불순도 (Gini Impurity) (0) | 2023.08.18 |
정보량, 엔트로피 (0) | 2023.08.16 |