상호작용 항과 인코딩으로 설계하는 머신러닝 피처 엔지니어링
상호작용 항, 다항 특성, One-Hot·Label·Target Encoding의 선택 기준과 누수 방지 파이프라인을 정리한다.
2026-08-14 · 최초 발행 2025-10-14
변수 관계를 모델에 전달하는 방법
피처 엔지니어링은 모델이 원천 데이터에서 어떤 관계를 읽을 수 있게 만들지 결정하는 작업이다. 수치형과 범주형이 섞인 테이블 데이터에서는 상호작용 항, 다항 특성, 인코딩 전략이 그 역할을 나눠 맡는다.
상호작용 항은 두 변수의 곱이나 결합 효과를 별도 피처로 넣는 방식이다. device_type × hour, price × discount_rate처럼 개별 변수만으로는 드러나지 않는 관계를 표현한다. 선형 모델의 비선형 관계 표현을 보완하고, 트리 모델에서는 필요한 분할 깊이를 줄이는 데도 쓰인다.
다항 특성은 기존 피처를 제곱·세제곱 등의 다항식으로 변환하고 교호항까지 확장한다. 선형 모델에 polynomial basis를 제공해 표현력을 높이지만, 차원이 빠르게 늘고 다중공선성 위험도 함께 커진다. Ridge/Lasso 같은 정규화와 표준화를 결합하는 이유다.
인코딩은 범주형 값을 수치형 표현으로 바꾸는 과정이다. 대표 선택지는 One-Hot, Label/Ordinal, Target(mean) Encoding이다. 이 가운데 Target Encoding은 타깃 누수 가능성이 있으므로 Fold-wise 통계와 Nested CV를 전제로 다뤄야 한다.
입력은 정제된 수치·범주 혼합 데이터와 컬럼 타입, 결측 정책을 담은 스키마다. 데이터를 나눈 뒤 전처리하고, 상호작용·다항 피처 생성, 인코딩, 스케일링·정규화, 학습과 검증으로 이어진다. 결과물은 학습 모델뿐 아니라 피처 사전, 검증 리포트, 재현 가능한 파이프라인까지 포함한다.
운영 단계에서는 미지 범주, 과도한 고유값 수, 누수, 다중공선성을 함께 처리해야 한다. OneHotEncoder에는 handle_unknown="ignore"를 설정하고, 고유값이 지나치게 많은 피처는 상위 N 유지와 Rare bucket 또는 Hashing을 검토한다. Target Encoding의 통계는 반드시 Fold 안에서 계산하며, 다항·교호항을 확장한 뒤에는 VIF와 L1/L2 규제를 점검한다.
피처 형태와 인코딩 선택 기준
상호작용 항은 피처가 함께 나타날 때의 효과를 반영한다. 비선형 경계를 근사하거나 세그먼트별 이질성을 포착할 때 유용하다. 후보는 도메인 가설에 MI와 SHAP을 조합해 고르고, 피처 폭증을 막기 위해 규제와 함께 사용한다.
다항 특성은 degree와 interaction_only로 확장 범위를 제어한다. 표준화한 뒤 선형 모델에 연결하면 안정성이 높아진다. 고차수는 과적합과 연산량 증가로 이어질 수 있어 2~3차를 중심으로 점진적으로 탐색한다.
One-Hot Encoding은 저·중카디널리티 변수에 잘 맞는다. 선형 모델과 신경망에 적합하고, 트리에서도 안정적으로 동작한다. 다만 희소 행렬과 메모리 부담이 생길 수 있으므로 카디널리티가 큰 피처는 병합이나 해싱을 함께 고려한다.
Label/Ordinal Encoding은 범주를 정수로 매핑한다. XGBoost, LightGBM, CatBoost 같은 트리 기반 모델에서 실용적이지만, 순서성이 있는 것처럼 왜곡될 가능성이 있다. 선형 모델에는 권장하지 않으며 unknown 처리 정책이 필요하다.
Target Encoding은 범주별 타깃 평균이나 베이지안 스무딩을 이용한다. 고카디널리티와 희소 데이터에 강점이 있지만, 누수와 분산 증가를 경계해야 한다. Fold-wise 통계, smoothing, noise 추가가 이를 완화하는 수단이다.
| 방식 | 성능(일반) | 확장성(카디널리티↑) | 일관성(분포 변화) | 안정성(누수/과적합) | 운영 편의 |
|---|---|---|---|---|---|
| One-Hot | 중~높음(저카드) | 낮음(차원 폭증) | 높음(handle_unknown로 보완) | 높음 | 중(희소 최적화 필요) |
| Label/Ordinal | 중(트리에 적합) | 높음 | 중(값 재배치 영향 적음) | 높음 | 높음(간단 매핑) |
| Target | 높음(고카드 강점) | 높음(스칼라 추가) | 중(분포 변동 민감) | 중(누수 위험) | 중(폴드/스무딩 운영) |
데이터 성격에 따른 적용 장면
전자상거래 전환 예측에서는 device × hour, campaign × region 같은 상호작용을 추가하고, 저카디널리티 속성은 One-Hot으로, 상품ID는 Target Encoding으로 다룰 수 있다. 데이터 규모와 품질에 따라 AUC는 +0.81.5pp, LogLoss는 -13%p 수준으로 향상될 수 있다.
신용평가·부도 예측에서는 직군과 고용형태처럼 고카디널리티인 범주를 Nested CV 기반 Target Encoding으로 처리하고, 금액형 변수에는 2차 다항 항을 적용해 비선형 수익-위험 곡선을 반영한다. 결과 지표로 KS +12pp, Gini +0.51.5pp 개선과 과적합 감소를 기대할 수 있다.
제조 품질·불량 예측에서는 센서 x 센서 교호항과 2차 다항을 통해 물리적 상호작용을 근사한다. 트리 모델과 결합하면 분기 깊이를 줄일 수 있으며, F1 +1~3pp와 주요 교호항 가시화에 따른 설명가능성 개선이 가능하다.
다항 특성과 One-Hot을 묶는 파이프라인
Python 3.10+, scikit-learn 1.3+, pandas 2.x, category-encoders 2.6+ 환경을 전제로 한 예시다. 재현성을 위한 코드이므로 실제 서비스에 적용하기 전에는 데이터 스키마와 분할 정책을 고정해야 한다.
# python>=3.10, scikit-learn>=1.3, pandas>=2.0
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, StandardScaler, PolynomialFeatures
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
# 샘플 데이터 생성
rng = np.random.RandomState(42)
n = 5000
df = pd.DataFrame({
"price": rng.gamma(2.0, 20.0, size=n),
"discount": rng.beta(2, 5, size=n),
"device": rng.choice(["mobile", "desktop", "tablet"], size=n, p=[0.6, 0.3, 0.1]),
"region": rng.choice(["NA","EU","APAC"], size=n),
})
# 타깃: 비선형/상호작용 포함
logit = -3.0 + 0.02*df["price"] + 2.5*df["discount"] + 0.005*df["price"]*df["discount"]
p = 1 / (1 + np.exp(-logit))
y = (rng.rand(n) < p).astype(int)
num_cols = ["price", "discount"]
cat_cols = ["device", "region"]
numeric = Pipeline(steps=[
("poly", PolynomialFeatures(degree=2, include_bias=False, interaction_only=False)),
("scaler", StandardScaler())
])
categorical = OneHotEncoder(handle_unknown="ignore", sparse_output=True)
preprocess = ColumnTransformer(
transformers=[
("num", numeric, num_cols),
("cat", categorical, cat_cols)
],
remainder="drop"
)
clf = Pipeline(steps=[
("prep", preprocess),
("model", LogisticRegression(max_iter=200, n_jobs=-1, penalty="l2"))
])
X_train, X_test, y_train, y_test = train_test_split(df, y, test_size=0.2, stratify=y, random_state=42)
scores = cross_val_score(clf, X_train, y_train, cv=5, scoring="roc_auc")
print("CV AUC:", scores.mean(), "+/-", scores.std())
clf.fit(X_train, y_train)
print("Test AUC:", clf.score(X_test, y_test)) # note: for classifiers, use roc_auc_score separately
이 구성에서는 PolynomialFeatures가 2차 항과 교호항을 만들고, 뒤이어 스케일링이 적용된다. OneHotEncoder(handle_unknown="ignore")는 학습에 없던 범주가 들어왔을 때의 처리를 맡는다.
Target Encoding의 누수 경계선
Target Encoding은 훈련 폴드에서 계산한 통계를 검증 폴드에 적용하는 경계를 지켜야 한다. 아래 예시는 smoothing과 min_samples_leaf로 분산과 과적합을 완화하고, 외부 StratifiedKFold로 평가한다.
# python>=3.10, scikit-learn>=1.3, category-encoders>=2.6
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OrdinalEncoder
from sklearn.linear_model import LogisticRegression
from category_encoders.target_encoder import TargetEncoder
# 예시 데이터: 고카디널리티 범주 추가
rng = np.random.default_rng(0)
n = 6000
df = pd.DataFrame({
"user_id": rng.integers(0, 4000, size=n).astype(str), # 고카드
"campaign": rng.choice(list("ABCDEFGH"), size=n),
"value": rng.normal(0, 1, size=n),
})
# 타깃: user_id별 베이스레이트 차등 + 신호
user_signal = pd.Series(rng.normal(0, 0.5, 4000), index=[str(i) for i in range(4000)])
logit = -1.5 + 0.8*df["value"] + user_signal[df["user_id"]].values
p = 1 / (1 + np.exp(-logit))
y = (rng.random(n) < p).astype(int)
num_cols = ["value"]
high_card_cols = ["user_id"]
low_card_cols = ["campaign"]
# TargetEncoder는 훈련 폴드별 통계 사용이 핵심
# scikit-learn Pipeline에 바로 넣어도 내부적으로 CV 분리 시 누수를 막기 어려울 수 있음.
# 안전 패턴: Nested CV 또는 KFoldTargetEncodingWrapper 사용. 여기선 category_encoders의 built-in smoothing 사용 + CV 기반 파이프라인 구성.
te = TargetEncoder(cols=high_card_cols, smoothing=10.0, min_samples_leaf=20)
preprocess = ColumnTransformer(
transformers=[
("target", te, high_card_cols),
("onehot", OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1), low_card_cols),
("num", StandardScaler(), num_cols),
],
remainder="drop"
)
pipe = Pipeline([
("prep", preprocess),
("clf", LogisticRegression(max_iter=200, n_jobs=-1))
])
# 누수 완화: 외부 StratifiedKFold로 평가
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, df, y, cv=cv, scoring="roc_auc")
print("CV AUC (fold-wise TE):", scores.mean(), "+/-", scores.std())
진정한 누수 차단을 위해서는 Nested CV에서 외부 폴드 성능을 보고하거나, CatBoost와 같은 순차 통계 방식을 고려한다.
성능과 운영 안정성을 함께 확인할 때
피처 확장만으로 동일 모델의 성능을 확보하면서 과도한 모델 복잡도 증가를 억제할 수 있다. 데이터와 도메인에 따라 AUC/KS/Gini는 0.53.0pp 개선되고, LogLoss는 15% 상대 개선될 수 있다.
도메인 가설을 검증하기 쉬워지고 핵심 교호항을 시각화해 설명가능성도 높일 수 있다. 저차 교호항과 2차 다항부터 시작해 인코딩 조합을 탐색하고, 누수와 과적합을 점검한 뒤 운영 파이프라인을 고정하는 흐름이 적절하다. 데이터 분할을 먼저 수행하고, handle_unknown, smoothing, 규제를 결합해 성능과 안정성의 균형을 맞춘다.