5 분 소요

Mean target encoding


안녕하세요. 데이터 사이언티스트를 위한 정보를 공유하고 있습니다.

M1 Macbook Air를 사용하고 있으며, 블로그의 모든 글은 Mac을 기준으로 작성된 점 참고해주세요.


지난 데이터 인코딩 포스팅 이어서 글 작성하겠습니다.

3. Mean (target) encoding

Mean encoding 또는 Mean Target encoding이라고도 하는데요.

Mean encoding은 앞선 One-Hot encoding의 high cardinality 문제,

즉 새로운 피처가 지나치게 많이 발생하는 문제를 개선하여 빠른 속도로 학습을 진행할 수 있습니다.

Mean encoding은 카테고리 피처의 타겟값에 의존하는데

카테고리 별 타겟값의 평균값을 인코딩의 결과로 변환하는 방식입니다.


역시 글로는 쉽게 와닿지 않을 수 있을 것 같아 바로 예시로 보여드리겠습니다.

캐글의 타이타닉 데이터를 활용하겠습니다.(Kaggle Titanic data)

타이타닉 데이터에 대해 간단히 설명드리자면

성별, 나이, 티켓 등급 등의 피처들이 있고, 생존 여부를 타겟값으로 하는 데이터로서

생존 여부를 분류 예측하기 위한 데이터 세트이며,

판다스나 사이킷런 등의 라이브러리 실습을 위해 자주 사용되는 데이터 세트입니다.

먼저 타이타닉 데이터를 가져와 ‘Survived’ 칼럼을 타겟으로 지정해 주겠습니다.

In:

import pandas as pd

titanic_df = pd.read_csv("/content/drive/MyDrive/Colab Notebooks/titanic_train.csv")
# 타이타닉 데이터 불러오기
titanic_df["Target"] = titanic_df["Survived"]
# 생존 여부를 타겟으로 지정
titanic_df.head()

Out:

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Target
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 0
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 1
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 1
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S 1
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S 0

Target 값이 0이면 생존하지 못한 것이고, 1이면 생존한 것입니다.

In:

sex_mean = titanic_df.groupby("Sex")["Target"].mean()
# 'male'과 'female'로 각각 그룹화한 후, 각 그룹의 'Target'의 평균값을 도출
sex_mean

Out:

Sex
female    0.742038
male      0.188908
Name: Target, dtype: float64

In:

titanic_df["Sex_mean"] = titanic_df["Sex"].map(sex_mean)
# 타이타닉 데이터의 성별과 위에서 도출했던 성별 별 타겟의 평균값을 매핑
titanic_df[["Sex", "Sex_mean", "Survived"]]
# 인코딩 결과를 확인하기 위해 세 개의 칼럼만 확인

Out:

Sex Sex_mean Survived
0 male 0.188908 0
1 female 0.742038 1
2 female 0.742038 1
3 female 0.742038 1
4 male 0.188908 0
... ... ... ...
886 male 0.188908 0
887 female 0.742038 1
888 female 0.742038 0
889 male 0.188908 1
890 male 0.188908 0

891 rows × 3 columns

성별마다 생존한 사람과 그렇지 못한 사람이 존재하겠지만,

Mean encoding을 적용해 보니 생존율의 평균이 남성은 약 0.19, 여성은 0.74인 것을 확인할 수 있습니다.

이는 남성은 0이 많고, 여성은 1이 많다는 뜻으로 타이타닉호 침몰 사고에서 여성의 생존율이 더 높았다는 뜻입니다.

이처럼 Mean encoding이 적용되면 인코딩된 값과 타겟값 간의 실질적인 연관이 생기게 됩니다.


Mean encoding도 역시 문제점이 있습니다.

첫 번째로 구현과 검증이 어렵습니다.

두 번째는 data leakage 문제인데 data leakage란 머신러닝에서 모델을 만들 때,

학습 데이터 외부로 부터 유입된 정보가 사용되는 것을 의미합니다.

Mean encoding의 경우 타겟값의 평균값을 사용하기 때문에 타겟값에 대한 정보가 피처로 들어가게 되는 것입니다.

이는 모델이 학습 데이터에 과적합(overfitting)되는 현상을 초래합니다.

또한 학습 데이터와 테스트 데이터의 분포가 다른 경우에도 문제가 생길 수 있습니다.

평균값을 사용하는 인코딩이기 때문에

학습 데이터에서 변환한 특정 피처에 대한 평균값이, 즉 인코딩한 값이

학습 데이터와 분포가 다른 테스트 데이터에서는 해당 피처를 대표하는 값이 아닐 수도 있는 겁니다.


Mean encoding의 문제점을 개선하기 위한 여러 가지 방식이 존재합니다.

  1. K-Fold Regularization
  2. Expanding Mean
  3. Smoothing

각 문제 해결에 대해서 자세히 다루기에는 내용이 방대하므로 간략하게 설명하도록 하겠습니다.


1. K-Fold Regularization

교차 검증에 대해서 포스팅한 바가 있는데요.

교차 검증을 방식을 통해 폴드 수 만큼 Mean encoding을 진행하는 방식입니다.

교차 검증 포스팅에서는 StratifiedKFold() 사용하는 대신 cross_val_score()를 사용했습니다.

이번에는 교차 검증 결과를 반환하는 것이 아니라 폴드를 나누어 반복문을 진행하는 것이 목적입니다.

따라서 다음 코드에서 StratifiedKFold() 사용법을 같이 익혀주면 좋을 것 같습니다.

In:

from sklearn.model_selection import train_test_split, StratifiedKFold

import numpy as np

train_titanic, test_titanic = train_test_split(titanic_df, test_size = 0.2, random_state = 4)
# 데이터를 학습 데이터, 테스트 데이터로 분리

new_train_titanic = train_titanic.copy()
new_train_titanic[:] = np.nan
# 인코딩 한 데이터를 저장하기 위해 titanic 데이터를 복사해서 비워둠

X_train = train_titanic.drop(["Target", "Survived"], axis = 1)
# 피처 데이터
y_train = train_titanic["Target"]
# 타겟 데이터

skfold = StratifiedKFold(n_splits = 5, shuffle = True, random_state = 4)
# 5개의 폴드로 나누는 StratifiedKFold 객체를 만들기

for train_index, validation_index in skfold.split(X_train, y_train):
  X_train = train_titanic.iloc[train_index]
  # 학습 데이터 지정
  X_validation = train_titanic.iloc[validation_index]
  # 검증 데이터 지정

  sex_mean = X_train.groupby("Sex")["Target"].mean()
  X_validation["Sex_mean"] = X_validation["Sex"].map(sex_mean)
  # Mean encoding으로 변환된 학습 데이터의 'Sex_mean' 칼럼을 하나의 폴드(X_validation)에만 저장
  new_train_titanic.iloc[validation_index] = X_validation
  # 인덱스에 맞춰 새로 만들어 놓은 new_train_titanic에 저장

new_train_titanic.head()

Out:

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Target Sex_mean
42 43.0 0.0 3.0 Kraeff, Mr. Theodor male NaN 0.0 0.0 349253 7.8958 NaN C 0.0 0.195055
684 685.0 0.0 2.0 Brown, Mr. Thomas William Solomon male 60.0 1.0 1.0 29750 39.0000 NaN S 0.0 0.191136
605 606.0 0.0 3.0 Lindell, Mr. Edvard Bengtsson male 36.0 1.0 0.0 349910 15.5500 NaN S 0.0 0.195055
409 410.0 0.0 3.0 Lefebre, Miss. Ida female NaN 3.0 1.0 4133 25.4667 NaN S 0.0 0.747619
740 741.0 1.0 1.0 Hawksford, Mr. Walter James male NaN 0.0 0.0 16988 30.0000 D45 S 1.0 0.189415

In:

new_train_titanic.groupby(["Sex", "Sex_mean"])["PassengerId"].count()
# 성별로 그룹화하고, 폴드 별로 타겟값 평균값을 확인

Out:

Sex     Sex_mean
female  0.709677    45
        0.742718    56
        0.747619    52
        0.751196    53
        0.752427    56
male    0.189415    91
        0.191136    89
        0.195055    86
        0.198347    87
        0.203966    97
Name: PassengerId, dtype: int64

폴드를 5개로 나누어 5번씩 인코딩을 진행했으므로

‘male’과 ‘female’ 카테고리가 각각 인코딩된 값이 5개씩 있다는 것을 확인할 수 있습니다.

카테고리 별로 평균값이 다양해지면서 data leakage를 줄여주는 효과가 있으며,

카테고리가 더 세분화되면서 트리 기반 모델에서 더 좋은 성능을 보일 수 있습니다.


2. Expandimng Mean Regularization

폴드를 나눈 방식처럼 인코딩 되는 카테고리를 세분화하는 방식인데

보다 더 잘게 나누어 카테고리를 더 다양하게 만들어주는 방식입니다.

그러다 보니 data leakage를 더 잘 줄여준다고 합니다.

범주형 변수를 분류할 때 유용한 모델인 CatBoost 모델에 내장된 기능이라고 하네요.


3. Smoothing

학습 데이터와 테스트 데이터의 분포에 큰 차이가 있을 때의 문제를 해결해 주는 방법입니다.

인코딩으로 변환된 값을 테스트 데이터를 포함한 전체 데이터에서 구한 평균값에 가깝게 보정하여 문제를 해결합니다.

Mean encoding의 문제를 해결하는 방식들에 대해서는 다음 기회에 더 자세히 다루도록 하겠습니다.


읽어주셔서 감사합니다.

정보 공유의 목적으로 만들어진 블로그입니다.

미흡한 점은 언제든 댓글로 지적해주시면 감사하겠습니다.


업데이트:

댓글남기기