피처 엔지니어링
피처 엔지니어링(Feature Engineering)은 도메인 지식과 데이터 분석 결과를 활용하여 모델에 유용한 새 피처를 만들거나, 기존 피처를 변환하거나, 불필요한 피처를 제거하는 과정입니다. 모델 성능 향상의 가장 효과적인 방법 중 하나입니다.
학습 목표
- 기존 피처를 조합하여 새로운 피처를 생성할 수 있다
- 수치형 피처를 비닝, 다항식 변환 등으로 변환할 수 있다
- 필터, 래퍼, 임베디드 방법으로 피처를 선택할 수 있다
- 도메인 지식을 피처 생성에 활용할 수 있다
왜 중요한가
“데이터와 피처가 ML 모델의 성능 상한을 결정하고, 알고리즘은 그 상한에 도달하는 역할을 한다”는 말처럼, 좋은 피처는 복잡한 알고리즘보다 효과적입니다. Kaggle 대회에서도 피처 엔지니어링이 상위 성적의 핵심 요인입니다.
피처 생성 — 새로운 피처 만들기
수학적 조합
import numpy as np
import pandas as pd
np.random.seed(42)
df = pd.DataFrame({
'price': np.random.uniform(1000, 50000, 200),
'quantity': np.random.randint(1, 100, 200),
'area': np.random.uniform(20, 150, 200),
'rooms': np.random.randint(1, 6, 200),
'year_built': np.random.randint(1990, 2024, 200),
'income': np.random.normal(5000, 1500, 200)
})
# 곱셈: 총 매출
df['total_revenue'] = df['price'] * df['quantity']
# 나눗셈: 평당 가격
df['price_per_area'] = df['price'] / df['area']
# 비율: 방 하나당 면적
df['area_per_room'] = df['area'] / df['rooms']
# 차이: 건물 연식
df['building_age'] = 2026 - df['year_built']
# 로그 변환: 편향된 분포 보정
df['log_price'] = np.log1p(df['price'])
df['log_income'] = np.log1p(df['income'])
print("생성된 피처:")
print(df[['total_revenue', 'price_per_area', 'area_per_room', 'building_age']].describe().round(1))
비닝 — 연속형을 범주형으로
# 등간격 비닝
df['price_bin'] = pd.cut(df['price'], bins=5, labels=['매우낮음', '낮음', '보통', '높음', '매우높음'])
# 등빈도 비닝 (각 구간에 동일한 수의 데이터)
df['income_quartile'] = pd.qcut(df['income'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
# 도메인 기반 비닝
df['age_category'] = pd.cut(df['building_age'],
bins=[0, 5, 15, 30, 100],
labels=['신축', '준신축', '구축', '노후'])
print("비닝 결과:")
print(df['age_category'].value_counts())
날짜/시간 피처
# 날짜 기반 피처 생성
dates = pd.date_range('2024-01-01', periods=200, freq='D')
df['date'] = dates[:200]
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day_of_week'] = df['date'].dt.dayofweek # 0=월요일
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
df['quarter'] = df['date'].dt.quarter
# 순환 피처: 월의 주기성 반영
df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
print("날짜 피처:")
print(df[['date', 'month', 'is_weekend', 'quarter', 'month_sin']].head())
텍스트 기반 피처
# 텍스트에서 피처 추출
reviews = pd.Series([
'아주 좋은 제품입니다. 강력 추천!',
'보통이에요. 그냥 그렇습니다.',
'최악의 경험... 다시는 안 삽니다.',
'가격 대비 괜찮습니다. 만족합니다.',
'배송이 빠르고 제품도 좋아요!'
])
# 텍스트 길이
text_df = pd.DataFrame({'review': reviews})
text_df['char_count'] = text_df['review'].str.len()
text_df['word_count'] = text_df['review'].str.split().str.len()
text_df['has_exclaim'] = text_df['review'].str.contains('!').astype(int)
print(text_df)
피처 변환
다항식 피처
from sklearn.preprocessing import PolynomialFeatures
# 2차 다항식 피처 생성
X = df[['area', 'rooms']].head(5)
poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=False)
X_poly = poly.fit_transform(X)
print("원본 피처:", X.columns.tolist())
print("다항식 피처:", poly.get_feature_names_out(['area', 'rooms']).tolist())
print(f"피처 수: {X.shape[1]} → {X_poly.shape[1]}")
다항식 피처는 피처 수가 급격히 증가합니다. degree=2에서 n개 피처는 n + n*(n+1)/2개로 늘어납니다. interaction_only=True로 교호작용만 생성하면 피처 수를 줄일 수 있습니다.
피처 선택 — 불필요한 피처 제거
필터 방법 — 상관계수 기반
# 타겟과의 상관계수
target = 'price'
numeric_features = df.select_dtypes(include='number').columns.drop(target)
correlations = df[numeric_features].corrwith(df[target]).abs().sort_values(ascending=False)
print("타겟과의 상관계수:")
print(correlations.round(3))
# 높은 상관: 유지, 낮은 상관: 제거 후보
threshold = 0.1
low_corr = correlations[correlations < threshold].index.tolist()
print(f"\n상관 < {threshold}: {low_corr}")
래퍼 방법 — 순차 피처 선택
from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.linear_model import LinearRegression
# 전진 선택 (Forward Selection)
X = df[numeric_features].dropna()
y = df.loc[X.index, target]
sfs = SequentialFeatureSelector(
LinearRegression(),
n_features_to_select=5,
direction='forward',
cv=5
)
sfs.fit(X, y)
selected = X.columns[sfs.get_support()].tolist()
print(f"선택된 피처 ({len(selected)}개): {selected}")
임베디드 방법 — 모델 기반 중요도
from sklearn.ensemble import RandomForestRegressor
# Random Forest 피처 중요도
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X, y)
importance = pd.Series(rf.feature_importances_, index=X.columns)
importance = importance.sort_values(ascending=False)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
importance.head(10).plot(kind='barh', ax=ax, color='#4a9eca')
ax.set_title('Random Forest 피처 중요도')
ax.set_xlabel('중요도')
plt.tight_layout()
plt.show()
피처 선택 방법 비교
| 방법 | 장점 | 단점 | 대표 기법 |
|---|
| 필터 | 빠름, 모델 독립적 | 피처 간 상호작용 무시 | 상관계수, 분산, 카이제곱 |
| 래퍼 | 정확도 높음, 상호작용 고려 | 느림, 과적합 위험 | 순차 선택, RFE |
| 임베디드 | 모델 학습과 동시 진행 | 모델 의존적 | L1 정규화, 트리 중요도 |
AI/ML에서의 활용
- 도메인 피처: 비즈니스 지식을 활용한 피처가 자동 생성 피처보다 효과적인 경우가 많습니다
- 교호작용: 두 피처의 곱이나 비율이 타겟을 더 잘 설명할 수 있습니다
- 차원 축소: 과도한 피처는 과적합을 유발하므로 선택이 필요합니다
- 자동화:
featuretools, autofeat 같은 도구로 피처 생성을 자동화할 수 있습니다
아닙니다. 피처가 많으면 차원의 저주(curse of dimensionality)로 모델 성능이 오히려 떨어질 수 있습니다. 의미 없는 피처는 노이즈를 추가하고 학습 시간을 늘립니다. 정보 있는 피처만 선별하는 것이 중요합니다.
체크리스트
다음 문서