Skip to main content

피처 엔지니어링

피처 엔지니어링(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)로 모델 성능이 오히려 떨어질 수 있습니다. 의미 없는 피처는 노이즈를 추가하고 학습 시간을 늘립니다. 정보 있는 피처만 선별하는 것이 중요합니다.

체크리스트

  • 수학적 조합으로 새 피처를 생성할 수 있다
  • 비닝으로 연속형 변수를 범주화할 수 있다
  • 날짜에서 유용한 피처를 추출할 수 있다
  • 필터/래퍼/임베디드 방법으로 피처를 선택할 수 있다
  • 피처 중요도를 시각화할 수 있다

다음 문서