일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Brightics EDA
- 파이썬 SQL 연동
- 데이터 분석 플랫폼
- pymysql
- 브라이틱스 분석
- 비전공자를 위한 데이터 분석
- 삼성 SDS
- Brigthics Studio
- 머신러닝
- Brightics 서포터즈
- 딥러닝
- 브라이틱스 서포터즈
- 삼성 SDS 서포터즈
- Python
- 분석 툴
- 브라이틱스 AI
- Activation Function
- 데이터 분석
- paper review
- 브라이틱스 프로젝트
- michigan university deep learning for computer vision
- Brightics AI
- Brightics studio
- 파이썬 내장 그래프
- 범주형 변수 처리
- 서포터즈 촬영
- 브라이틱스 스태킹
- Random Forest
- Deep Learning for Computer Vision
- 검증 평가 지표
- Today
- Total
하마가 분석하마
[machine learning class] 5. 범주형 변수 처리-3 본문
범주형 변수가 존재하는 데이터를 분석할 때 알고리즘의 성능을 높이기 위한 방법에는 여러 개가 있다. 유명한 '미국 성인 인구조사 데이터'를 통해서 실습해보겠다. 프로젝트의 목적은 성인 데이터를 가지고 소득 범위를 예측하는 것이다.
대부분의 변수의 의미가 무엇인지 자명하므로 바로 전처리 및 모델링을 해보겠다. 먼저 타겟 변수 (income)가 이진 범주이므로 AUC를 검증 메트릭으로 사용한다. 범주형 변수의 처리에 따른 모델 성능을 보는 것이 목적이기에 단순화를 위해 수치형 변수들은 제거하였다.
원핫인코딩과 로지스틱 회귀를 시도해보자
## ohe_logres.py -> 로지스틱 회귀 모델
import pandas as pd
from sklearn import linear_model
from sklearn import metrics
from sklearn import preprocessing
def run(fold):
# 폴드 값이 있는 학습 데이터를 불러온다.
df = pd.read_csv("adult_folds.csv")
# 수치형 열의 목록
num_cols = [
"fnlwgt",
"age",
"capital.gain",
"capital.loss",
"hours.per.week"
]
# 수치형 목록을 제거한다.
df = df.drop(num_cols, axis=1)
# 타겟 변수를 0 과 1 로 매핑한다.
target_mapping = {
"<=50K": 0,
">50K": 1
}
df.loc[:, "income"] = df.income.map(target_mapping)
# income 과 kfold 열을 제외한 모든 열을 피쳐로 사용한다.
features = [
f for f in df.columns if f not in ("kfold", "income")
]
# 모든 NaN 값을 NONE 으로 채운다
# 모든 열을 문자열로 변환함을 주목. 범주형으로 처리할 것이므로
# 원래 타입이 무엇이든지 상관 없다.
for col in features:
df.loc[:, col] = df[col].astype(str).fillna("NONE")
# 폴드 값으로 학습 데이터를 추출한다.
df_train = df[df.kfold != fold].reset_index(drop=True)
# 폴드 값으로 검증 데이터를 추출한다.
df_valid = df[df.kfold == fold].reset_index(drop=True)
# scikit-learn 의 OneHotEncoder 객체를 초기화 한다.
ohe = preprocessing.OneHotEncoder()
# 학습 + 검증 데이터로 학습한다.
full_data = pd.concat(
[df_train[features], df_valid[features]],
axis=0
)
ohe.fit(full_data[features])
# 학습 데이터를 변환한다.
x_train = ohe.transform(df_train[features])
# 검증 데이터를 변환한다.
x_valid = ohe.transform(df_valid[features])
# 로지스틱 회귀 모델을 초기화 한다.
model = linear_model.LogisticRegression()
# 변환한 데이터로 모델을 학습한다.
model.fit(x_train, df_train.income.values)
# 검증 데이터로 예측한다.
# AUC 를 계산할 것이므로 확률 값이 필요하다.
# 범주 1 의 확률을 사용한다
valid_preds = model.predict_proba(x_valid)[:, 1]
# auc 값을 계산한다.
auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)
# auc 값을 출력한다.
print(f"Fold = {fold}, AUC = {auc}")
if __name__ == "__main__":
for fold_ in range(5):
run(fold_)
데이터의 특성 때문인지 모르겠으나 간단한 알고리즘으로도 꽤 높은 성능이 나왔다. 다음은 XGBoost를 적용해보자.
## lbl_xgb.py -> xgboost
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessing
def run(fold):
# 폴드 값이 있는 학습 데이터를 불러온다.
df = pd.read_csv("adult_folds.csv")
# 수치형 열의 목록
num_cols = [
"fnlwgt",
"age",
"capital.gain",
"capital.loss",
"hours.per.week"
]
# 수치형 목록을 제거한다.
df = df.drop(num_cols, axis=1)
# 타겟 변수를 0 과 1 로 매핑한다.
target_mapping = {
"<=50K": 0,
">50K": 1
}
df.loc[:, "income"] = df.income.map(target_mapping)
# income 과 kfold 열을 제외한 모든 열을 피쳐로 사용한다.
features = [
f for f in df.columns if f not in ("kfold", "income")
]
# 모든 NaN 값을 NONE 으로 채운다
# 모든 열을 문자열로 변환함을 주목. 범주형으로 처리할 것이므로
# 원래 타입이 무엇이든지 상관 없다.
for col in features:
df.loc[:, col] = df[col].astype(str).fillna("NONE")
# 피쳐들을 레이블 인코딩한다.
for col in features:
# 각 피쳐 열에 대해 LabelEncoder를 초기화 한다.
lbI = preprocessing.LabelEncoder()
# 인코더를 모든 데이터로 학습한다.
lbI.fit(df[col])
# 모든 데이터를 변환한다.
df.loc[:, col] = lbI.transform(df[col])
# 폴드 값으로 학습 데이터를 추출한다.
df_train = df[df.kfold != fold].reset_index(drop=True)
# 폴드 값으로 검증 데이터를 추출한다.
df_valid = df[df.kfold == fold].reset_index(drop=True)
# 학습 데이터를 얻는다.
x_train = df_train[features].values
# 검증 데이터를 얻는다.
x_valid = df_valid[features].values
# xgb 모델을 초기화 한다.
model = xgb.XGBClassifier(
n_jobs = -1
)
# 변환한 데이터로 모델을 학습한다.
model.fit(x_train, df_train.income.values)
# 검증 데이터로 예측한다.
# AUC 를 계산할 것이므로 확률 값이 필요하다.
# 범주 1 의 확률을 사용한다
valid_preds = model.predict_proba(x_valid)[:, 1]
# auc 값을 계산한다.
auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)
# auc 값을 출력한다.
print(f"Fold = {fold}, AUC = {auc}")
if __name__ == "__main__":
for fold_ in range(5):
run(fold_)
코드가 길어서 파라미터 변경에 따른 XGBoost의 성능 변화는 넣지 않았다. 한 데이터에서 좋은 성능을 보인 파라미터들이 다른 데이터에서는 그렇지 않을 수 있다. 즉, 각 데이터마다 새로 튜닝을 해야 한다는 소리이다.
다음으로 수치형 변수들을 넣어서 XGBoost를 돌려보자. 수치형 변수는 레이블 인코딩을 하지 않는다. 따라서 최종 피쳐 행렬은 원본 수치형 변수 열들과 레이블 인코딩된 범주형 변수로 구성되어있다. 트리 기반의 알고리즘은 이러한 피쳐 조합을 쉽게 다룰 수 있다.
트리 기반의 모델을 사용할 때에는 수치형 변수를 표준화하지 않아도 된다. 하지만 선형 회귀나 로지스틱 회귀와 같은 모델을 사용할 때에는 반드시 수치형 변수를 표준화 해야 한다.
## lbl_xgb_num.py -> 앞에서 제거했던 수치형 변수들을 XGBoost에 추가
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessing
def run(fold):
# 폴드 값이 있는 학습 데이터를 불러온다.
df = pd.read_csv("adult_folds.csv")
# 수치형 열의 목록
num_cols = [
"fnlwgt",
"age",
"capital.gain",
"capital.loss",
"hours.per.week"
]
# 타겟 변수를 0과 1로 매핑한다.
target_mapping = {
}
# 타겟 변수를 0 과 1 로 매핑한다.
target_mapping = {
"<=50K": 0,
">50K": 1
}
df.loc[:, "income"] = df.income.map(target_mapping)
# income 과 kfold 열을 제외한 모든 열을 피쳐로 사용한다.
features = [
f for f in df.columns if f not in ("kfold", "income")
]
# 모든 NaN 값을 NONE 으로 채운다
# 모든 열을 문자열로 변환함을 주목. 범주형으로 처리할 것이므로
# 원래 타입이 무엇이든지 상관 없다.
for col in features:
# 수치형 열은 인코딩하지 않는다.
if col not in num_cols:
df.loc[:, col] = df[col].astype(str).fillna('NONE')
# 피쳐들을 레이블인코딩한다.
for col in features:
if col not in num_cols:
# 각 피쳐 열에 대해 LabelEncoder를 초기화 한다.
lbI = preprocessing.LabelEncoder()
# 인코더를 모든 데이터로 학습한다.
lbI.fit(df[col])
# 모든 데이터를 변환한다.
df.loc[:, col] = lbI.transform(df[col])
# 폴드 값으로 학습 데이터를 추출한다.
df_train = df[df.kfold != fold].reset_index(drop=True)
# 폴드 값으로 검증 데이터를 추출한다.
df_valid = df[df.kfold == fold].reset_index(drop=True)
# 학습 데이터를 얻는다.
x_train = df_train[features].values
# 검증 데이터를 얻는다.
x_valid = df_valid[features].values
# xgb 모델을 초기화 한다.
model = xgb.XGBClassifier(
n_jobs = -1
)
# 변환한 데이터로 모델을 학습한다.
model.fit(x_train, df_train.income.values)
# 검증 데이터로 예측한다.
# AUC 를 계산할 것이므로 확률 값이 필요하다.
# 범주 1 의 확률을 사용한다
valid_preds = model.predict_proba(x_valid)[:, 1]
# auc 값을 계산한다.
auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)
# auc 값을 출력한다.
print(f"Fold = {fold}, AUC = {auc}")
if __name__ == "__main__":
for fold_ in range(5):
run(fold_)
수치형 변수를 넣었을 때의 XGBoost의 성능은 따로 첨부하지는 않겠다.
이제 피쳐 가공을 해본다. 먼저 모든 범주형 변수를 가지고 2차원 조합을 구한다. 아래 코드의 feature engineering 함수를 보면 이 과정을 자세히 알 수 있다. 이 방법은 범주형 변수들로부터 새로운 피쳐를 생성하는 가장 단순한 방법이다. 실제로는 데이터를 살펴보고 가장 적합한 조합을 찾아 피쳐를 생성해야 한다. 피쳐 조합을 사용하여 새로운 피쳐를 생성할 때, 너무 많은 피쳐가 생성될 수 있다. 이 경우, 가장 좋은 피쳐들만 선택하는 피쳐 선택 방식을 적용해야 한다.
## lbl_xgb_num_feat.py -> 범주형 변수를 가지고 2차원 조합 구하기
import itertools
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessing
def feature_engineering(df, cat_cols):
"""
피쳐 가공을 위한 함수
:param df: 학습/시험 데이터가 있는 pandas 데이터프레임
:param cat_cols: 범주형 변수의 목록
:return: 새 피쳐를 포함한 데이터프레임
"""
# 아래 코드는 목록의 값들의 2 차원 조합을 생성한다.
# 예를 들어:
# list(itertools.combinations([1,2,3], 2)) ->
# [(1, 2), (1, 3), (2, 3)]
combi = list(itertools.combinations(cat_cols, 2))
for c1, c2 in combi:
df.loc[
:,
c1 + "_" + c2
] = df[c1].astype(str) + "_" + df[c2].astype(str)
return df
def run(fold):
# 폴드 값이 있는 학습 데이터를 불러온다.
df = pd.read_csv("adult_folds.csv")
# 수치형 열의 목록
num_cols = [
"fnlwgt",
"age",
"capital.gain",
"capital.loss",
"hours.per.week"
]
# 타겟 변수를 0과 1로 매핑한다.
target_mapping = {
}
# 타겟 변수를 0 과 1 로 매핑한다.
target_mapping = {
"<=50K": 0,
">50K": 1
}
df.loc[:, "income"] = df.income.map(target_mapping)
# 피쳐 가공을 위한 범주형 변수의 목록
cat_cols = [
c for c in df.columns if c not in num_cols
and c not in ("kfold", "income")
]
# 새 피쳐를 추가한다.
df = feature_engineering(df, cat_cols)
# kfold와 income 열을 제외한 모든 열을 피쳐로 사용한다.
features = [
f for f in df.columns if f not in ('kfold', 'income')
]
# 모든 NaN 값을 NONE 으로 채운다
# 모든 열을 문자열로 변환함을 주목. 범주형으로 처리할 것이므로
# 원래 타입이 무엇이든지 상관 없다.
for col in features:
# 수치형 열은 인코딩하지 않는다.
if col not in num_cols:
df.loc[:, col] = df[col].astype(str).fillna('NONE')
# 피쳐들을 레이블인코딩한다.
for col in features:
if col not in num_cols:
# 각 피쳐 열에 대해 LabelEncoder를 초기화 한다.
lbI = preprocessing.LabelEncoder()
# 인코더를 모든 데이터로 학습한다.
lbI.fit(df[col])
# 모든 데이터를 변환한다.
df.loc[:, col] = lbI.transform(df[col])
# 폴드 값으로 학습 데이터를 추출한다.
df_train = df[df.kfold != fold].reset_index(drop=True)
# 폴드 값으로 검증 데이터를 추출한다.
df_valid = df[df.kfold == fold].reset_index(drop=True)
# 학습 데이터를 얻는다.
x_train = df_train[features].values
# 검증 데이터를 얻는다.
x_valid = df_valid[features].values
# xgb 모델을 초기화 한다.
model = xgb.XGBClassifier(
n_jobs = -1
)
# 변환한 데이터로 모델을 학습한다.
model.fit(x_train, df_train.income.values)
# 검증 데이터로 예측한다.
# AUC 를 계산할 것이므로 확률 값이 필요하다.
# 범주 1 의 확률을 사용한다
valid_preds = model.predict_proba(x_valid)[:, 1]
# auc 값을 계산한다.
auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)
# auc 값을 출력한다.
print(f"Fold = {fold}, AUC = {auc}")
if __name__ == "__main__":
for fold_ in range(5):
run(fold_)
결과는 깃허브를 통해 알 수 있다. 결론적으로 파라미터를 추가하거나 수정하지 않고 새로운 피쳐를 추가한 것만으로도 성능이 향상된 것을 볼 수 있다.
아직 희귀 값, 이진 피쳐, 원핫인코딩과 레이블 인코딩의 조합 등 범주형 변수에 적용할 수 있는 방법들이 많이 남아 있다. 그중에서 타겟 인코딩에 대해 보겠다.
타겟 인코딩은 범주형 변수의 각각의 범주를 해당 범주의 타겟 변수 값의 평균으로 매핑한다. 타겟 인코딩은 타겟 변수를 사용하기 때문에 오버피팅의 위험이 있다. 따라서 항상 교차검증을 함께 사용해야 한다. 폴드를 생성하여 데이터를 나누고 각각의 폴드에 대해 모델을 학습하고 예측하는 것과 동일한 방식으로 타겟 인코딩을 하는 것이다. 또한 평균값으로 범주를 대체하기 때문에 데이터가 적으면 적을수록 좋은 방법이 되지 못한다. 따라서 기본적으로 데이터 관측치가 많아야하는 조건이 따른다.
타겟 인코딩의 장점은, 데이터의 부피에 영향을 주지 않는다는 장점이 있으며, 빠른 학습에 효과적이다. 그러나 보통, 이 방법은 overfitting이 많이 나타나기로 악명이 높다. 그러므로, cross validation 및 정규화(regularization)가 대부분 반드시 같이 사용되어야 한다.
- 변환시키고자 하는 범주형 변수를 선택한다.
- 범주형 변수를 그룹화(group by)시키고, 타깃 변수에 대한 총합(sum) 합계를 얻는다. (예: 범주형 변수의 각 범주에 대한 1의 총합)
- 범주형 변수를 그룹화시키고, 타깃에 대한 빈도수 (count) 합계를 얻는다.
- 2의 결과를 3으로 나누고, 훈련 데이터의 본래 범주 값들에 업데이트한다.
## target_encoding.py -> 타겟 인코딩
import copy
import pandas as pd
from sklearn import metrics
from sklearn import preprocessing
import xgboost as xgb
def mean_target_encoding(data):
# 데이터프레임을 복사한다.
df = copy.deepcopy(data)
# 수치형 변수의 목록
num_cols = [
"fnlwgt",
"age",
"capital.gain",
"capital.loss",
"hours.per.week"
]
# 타겟 변수를 0 과 1 로 매핑한다.
target_mapping = {
"<=50K": 0,
">50K": 1
}
df.loc[:, "income"] = df.income.map(target_mapping)
# income 과 kfold 를 제외한 모든 열들을 피쳐로 사용한다.
features = [
f for f in df.columns if f not in ("kfold", "income")
and f not in num_cols
]
# 모든 NaN 값을 NONE 으로 채운다
# 모든 열을 문자열로 변환함을 주목. 범주형으로 처리할 것이므로
# 원래 타입이 무엇이든지 상관 없다.
for col in features:
# 수치형 열은 인코딩하지 않는다.
if col not in num_cols:
df.loc[:, col] = df[col].astype(str).fillna('NONE')
# 피쳐들을 레이블인코딩한다.
for col in features:
if col not in num_cols:
# 각 피쳐 열에 대해 LabelEncoder를 초기화 한다.
lbI = preprocessing.LabelEncoder()
# 인코더를 모든 데이터로 학습한다.
lbI.fit(df[col])
# 모든 데이터를 변환한다.
df.loc[:, col] = lbI.transform(df[col])
# 5 개의 검증 데이터프레임을 저장할 빈 목록
encoded_dfs = []
# 모든 폴드에 대해 계산한다.
for fold in range(5):
# 학습 데이터와 검증 데이터를 추출한다.
df_train = df[df.kfold != fold].reset_index(drop=True)
df_valid = df[df.kfold == fold].reset_index(drop=True)
# for all feature columns, i.e. categorical columns
for column in features:
# 범주:타겟 평균의 사전을 만든다.
mapping_dict = dict(
df_train.groupby(column)["income"].mean()
)
# column_enc 이 평균 인코딩을 저장하는 새 열이다.
df_valid.loc[
:, column + "_enc"
] = df_valid[column].map(mapping_dict)
# 인코딩된 검증 데이터프레임의 목록에 추가한다.
encoded_dfs.append(df_valid)
# 전체 데이터프레임을 생성하여 반환한다.
encoded_df = pd.concat(encoded_dfs, axis=0)
return encoded_df
def run(df, fold):
# 이전과 동일한 범주를 사용한다.
# 폴드로 학습 데이터를 추출한다.
df_train = df[df.kfold != fold].reset_index(drop = True)
# 폴드로 검증 데이러를 추출한다.
df_valid = df[df.kfold == fold].reset_index(drop = True)
# income과 kfold를 제외한 모든 열들을 피쳐로 사용한다.
features = [
f for f in df.columns if f not in ('kfold', 'income')
]
# 학습 데이터를 얻는다.
x_train = df_train[features].values
# 검증 데이터를 얻는다.
x_valid = df_valid[features].values
# xgb 모델을 초기화 한다.
model = xgb.XGBClassifier(
n_jobs = -1
)
# 변환한 데이터로 모델을 학습한다.
model.fit(x_train, df_train.income.values)
# 검증 데이터로 예측한다.
# AUC 를 계산할 것이므로 확률 값이 필요하다.
# 범주 1 의 확률을 사용한다
valid_preds = model.predict_proba(x_valid)[:, 1]
# auc 값을 계산한다.
auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)
# auc 값을 출력한다.
print(f"Fold = {fold}, AUC = {auc}")
if __name__ == "__main__":
# 데이터를 불러 온다.
df = pd.read_csv('adult_folds.csv')
# 타겟 인코딩을 한다.
df = mean_target_encoding(df)
# 5개의 폴드에 대해 학습과 검증을 수행한다.
for fold_ in range(5):
run(df, fold_)
타겟 인코딩을 사용할 때는 인코딩 된 값에 노이즈를 추가하거나 스무딩 (smoothing)을 적용하는 것이 좋다. 스무딩은 모델의 과적합을 막는 정규화 역할을 한다. 사이킷런의 contrib 저장소 중에는 스무딩을 적용한 타겟 인코딩을 제공하는 코드가 있다. 또한 스무딩은 구현이 그리 어렵지 않으므로 직접 코드를 작성하는 것도 가능하다.
범주형 변수를 다루는 것은 복잡한 작업이다. 다양한 자료와 많은 정보가 있다. 이 장은 다양한 범주형 문제를 접근하는데 도움이 될 것이다. 하지만 대부분의 문제의 경우 one-hot 인코딩과 레이블 인코딩이면 충분하다. 그 이상 모델을 향상시키기 위해서는 훨씬 더 많은 것이 필요하다.
필자는 딥러닝에 대해서는 잘 알지 못하여 엔터티 임베딩에 관한 내용은 제외하였다. 이 부분은 신경망에 대한 공부가 먼저 수행된 뒤에 해야할 것 같다.
범주형 변수를 처리하는 데 많은 방법이 있음을 알았다. 한번에 모든 개념을 이해하고 코드로 구현하는 것은 힘들 수 있으나 많이 익숙해지면 나중에 저절로 떠오를 거라 생각한다.
[참고 자료, 깃허브]
데이터과학 유망주의 매일 글쓰기 — 일곱번째 일요일
범주형 데이터의 다양한 인코딩(Encoding)
conanmoon.medium.com
github.com/rch1025/Machine-learning-class
rch1025/Machine-learning-class
Several concepts and several tips that are important for data analysis can be learned. I studied based on 'Machine Learning Master Class'. - rch1025/Machine-learning-class
github.com
'Machine learning class' 카테고리의 다른 글
[machine learning class] 7. feature choice (0) | 2021.03.10 |
---|---|
[machine learning class] 6. feature engineering (0) | 2021.03.07 |
[machine learning class] 5. 범주형 변수 처리-2 (0) | 2021.03.01 |
[machine learning class] 5. 범주형 변수 처리-1 (0) | 2021.02.26 |
[machine learning class] 4. 프로젝트 관리 (0) | 2021.02.23 |