머신러닝 특징 선택 정리: Filter, Wrapper, Embedded와 Boruta 실습

Alchemist·약 23시간 전

KT AIVLE

목록 보기
12/17

데이터 전처리를 공부하다 보면 처음에는 결측치 처리, 이상치 제거, 인코딩, 스케일링처럼 데이터의 형태를 바꾸는 작업에 집중하게 된다.

그런데 실제 머신러닝에서는 입력 변수의 개수가 많아질수록 또 다른 문제가 생긴다. 모든 변수가 예측에 도움이 되는 것은 아니고, 비슷한 정보를 반복해서 담고 있는 변수도 있으며, 오히려 불필요한 변수가 모델의 복잡도와 연산량만 늘릴 수도 있다.

이때 사용하는 방법이 특징 선택(Feature Selection) 이다.

전체 특징
    ↓
예측에 도움이 되는 특징 평가
    ↓
필요한 특징만 선택
    ↓
더 단순한 입력 데이터
    ↓
모델 학습

특징 선택의 핵심은 새로운 변수를 만드는 것이 아니라, 이미 존재하는 변수 중 어떤 것을 남길지 결정하는 것이다.


특징 선택은 왜 필요할까?

입력 변수가 많다고 해서 항상 모델이 좋아지는 것은 아니다.

예를 들어 사용자 이탈 예측 모델에 다음과 같은 값이 있다고 해보자.

최근 로그인 경과일
최근 30일 접속 횟수
평균 세션 시간
페이지 조회 수
검색 횟수
장바구니 횟수
구매 횟수
문의 횟수
푸시 알림 수신 여부
앱 테마 설정
프로필 배경 색상
...

이 중 어떤 변수는 이탈 여부와 밀접한 관계가 있을 수 있지만, 어떤 값은 거의 영향을 주지 않을 수도 있다.

모든 변수를 무조건 사용하면 다음과 같은 문제가 생길 수 있다.

  • 학습해야 할 데이터의 차원이 커진다.
  • 모델 학습과 예측에 필요한 연산량이 증가한다.
  • 불필요한 노이즈까지 학습할 수 있다.
  • 서로 비슷한 특징이 많으면 해석이 어려워진다.
  • 데이터가 적은 상황에서는 과적합 위험이 커질 수 있다.

따라서 특징 선택은 단순히 컬럼을 줄이는 작업이 아니라 모델에 필요한 정보의 밀도를 높이는 과정으로 이해하는 것이 좋다.


Feature Selection과 Feature Engineering의 차이

두 개념은 비슷해 보이지만 목적이 다르다.

구분Feature EngineeringFeature Selection
목적새로운 특징 생성기존 특징 중 필요한 특징 선택
예시구매 수 / 방문 수 → 구매 전환율30개 변수 중 8개만 선택
결과새로운 컬럼이 추가될 수 있음컬럼 수가 줄어듦
핵심 질문어떤 새로운 정보를 만들까?어떤 정보만 남길까?

예를 들어 접속 시간에서 요일, 시간대, 주말 여부를 만드는 것은 Feature Engineering이다.

반대로 만들어진 여러 특징 중 실제 이탈 예측에 도움이 되는 값만 남기는 것은 Feature Selection이다.


특징 선택의 대표적인 세 가지 접근

특징 선택은 크게 Filter, Wrapper, Embedded 방식으로 나눠볼 수 있다.

방식판단 기준모델 학습 필요장점단점
Filter통계적 관계보통 필요 없음빠르고 단순함변수 간 조합 효과를 놓칠 수 있음
Wrapper실제 모델 성능필요모델과 잘 맞는 조합 탐색 가능계산 비용이 큼
Embedded모델 학습 과정필요학습과 선택을 함께 수행특정 모델 특성에 영향을 받음

세 방법 중 하나가 항상 가장 좋은 것은 아니다.

데이터 크기, 변수 수, 사용할 모델, 계산 시간에 따라 적합한 방법이 달라진다.


1. Filter 방식: 모델을 만들기 전에 통계적으로 걸러내기

Filter 방식은 각각의 특징을 통계적인 기준으로 평가하고 점수나 순위를 이용해 선택한다.

feature_1 ─→ 통계 점수 ─→ 유지
feature_2 ─→ 통계 점수 ─→ 제거
feature_3 ─→ 통계 점수 ─→ 유지

특정 머신러닝 모델을 반복해서 학습할 필요가 없기 때문에 비교적 빠르다.

대표적으로 다음과 같은 방법을 생각할 수 있다.

상관관계 기반 선택

연속형 변수와 목표값 사이의 관계를 확인하거나, 입력 변수끼리 지나치게 높은 상관관계를 가지는지 살펴볼 수 있다.

corr = df.corr(numeric_only=True)

target_corr = (
    corr["churn"]
    .abs()
    .sort_values(ascending=False)
)

print(target_corr)

상관계수의 절댓값이 큰 변수는 선형적인 관점에서 목표값과 관계가 강하다고 볼 수 있다.

다만 상관계수 하나만으로 변수의 중요성을 확정하면 안 된다.

상관관계는 주로 선형 관계를 보여주기 때문에 비선형 패턴이나 여러 변수의 상호작용은 놓칠 수 있다.


카이제곱 기반 선택

범주형 또는 이산형 데이터와 분류 목표 사이의 관계를 확인할 때 Chi-square(카이제곱) 계열의 방법을 사용할 수 있다.

Scikit-learn에서는 SelectKBest와 chi2를 조합할 수 있다.

from sklearn.feature_selection import SelectKBest, chi2

selector = SelectKBest(
    score_func=chi2,
    k=5
)

X_selected = selector.fit_transform(X_train, y_train)

chi2를 사용할 때 입력 값은 음수가 없어야 한다.

따라서 표준화 결과처럼 음수가 포함될 수 있는 데이터에 그대로 적용하기보다는 데이터 특성과 변환 방법을 먼저 확인해야 한다.


2. Wrapper 방식: 실제 모델 성능으로 특징 조합을 찾기

Filter 방식은 각 특징을 상대적으로 독립적으로 평가한다.

반면 Wrapper 방식은 실제 머신러닝 모델을 반복적으로 학습하면서 어떤 특징 조합이 좋은지 찾는다.

특징 조합 생성
    ↓
모델 학습
    ↓
성능 평가
    ↓
특징 추가 / 제거
    ↓
다시 학습

모델 입장에서 좋은 특징 조합을 찾을 수 있다는 장점이 있지만, 반복 학습이 필요하기 때문에 변수 수가 많아질수록 비용이 커진다.

대표적인 방법 중 하나가 RFE(Recursive Feature Elimination) 다.


RFE: 중요하지 않은 특징을 반복해서 제거하기

RFE는 지정한 모델을 학습하고, 상대적으로 중요도가 낮은 특징을 제거하는 과정을 반복한다.

예를 들어 10개의 특징 중 4개만 남기고 싶다면 개념적으로 다음과 같이 진행된다.

10개 특징
    ↓
모델 학습
    ↓
중요도가 낮은 특징 제거
    ↓
다시 학습
    ↓
...
    ↓
4개 특징

Scikit-learn에서는 RFE를 사용할 수 있다.

아래 예시는 사용자 행동 데이터를 분류하는 상황을 가정한 최소 예제다.

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.feature_selection import RFE

X, y = make_classification(
    n_samples=600,
    n_features=12,
    n_informative=6,
    n_redundant=3,
    random_state=1
)

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    random_state=1
)

SVM을 사용한다면 먼저 Scaling

SVM은 입력 변수의 스케일 차이에 영향을 받을 수 있기 때문에 먼저 표준화를 적용한다.

scaler = StandardScaler()

scaler.fit(X_train)

X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

여기서 중요한 점은 학습 데이터에만 fit()을 수행한다는 것이다.

X_train
    ↓
scaler.fit()

X_train, X_test
    ↓
같은 scaler로 transform()

테스트 데이터까지 포함해 스케일 기준을 계산하면 평가 데이터의 정보가 학습 과정에 들어갈 수 있다.


Linear SVM + RFE

RFE에서 특징의 중요도를 판단하려면 사용하는 모델이 특징 중요도 또는 계수 정보를 제공할 수 있어야 한다.

Linear SVM을 사용하면 다음처럼 적용할 수 있다.

estimator = SVC(kernel="linear")

rfe = RFE(
    estimator=estimator,
    n_features_to_select=5
)

rfe.fit(X_train_scaled, y_train)

print(rfe.ranking_)

ranking_에서 값이 1인 특징이 최종 선택된 특징이다.

컬럼명이 있는 DataFrame이라면 다음처럼 확인할 수 있다.

selected_features = X_train.columns[rfe.support_]

print(selected_features)

직접 실행하면서 확인한 RFE 결과

30개의 입력 특징을 가진 데이터에서 Linear SVM 기반 RFE를 실행하고 5개의 특징을 선택하도록 했을 때 다음 결과를 확인할 수 있었다.

선택 특징 수: 5
Accuracy: 0.95105
AUC:      0.94659

같은 조건에서 선택 특징 수를 10개로 늘렸을 때는 다음과 같았다.

선택 특징 수: 10
Accuracy: 0.93706
AUC:      0.93523

이 결과에서 기억할 점은 특징을 많이 남긴다고 성능이 반드시 높아지는 것은 아니라는 것이다.

특징 선택은 최대한 많은 변수를 보존하는 작업이 아니라, 모델에 필요한 정보를 효율적으로 남기는 작업이다.

참고로 ROC AUC는 일반적으로 클래스 예측값 자체보다 모델의 score나 probability를 이용해 계산하는 경우가 많다. 분류 모델을 비교할 때는 어떤 값을 AUC 입력으로 사용했는지도 함께 확인하는 것이 좋다.


3. Embedded 방식: 모델이 학습하면서 특징도 선택하기

Embedded 방식은 모델 학습 과정에서 얻어지는 중요도나 계수를 이용해 특징을 선택한다.

Wrapper처럼 다양한 조합을 반복해서 탐색하지 않고, 모델 자체가 학습한 정보를 활용한다.

대표적인 예가 다음과 같다.

L1 Regularization
Decision Tree
Random Forest
Gradient Boosting

트리 계열 모델은 각 특징이 분할에 얼마나 기여했는지를 기반으로 중요도를 계산할 수 있다.


Random Forest + SelectFromModel

Scikit-learn의 SelectFromModel을 사용하면 모델이 계산한 특징 중요도를 기준으로 필요한 변수만 선택할 수 있다.

from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectFromModel

rf = RandomForestClassifier(
    n_estimators=100,
    random_state=1
)

selector = SelectFromModel(rf)

selector.fit(X_train, y_train)

선택 여부는 get_support()로 확인할 수 있다.

support = selector.get_support()

selected_features = X_train.columns[support]

print(selected_features)

직접 실행한 결과에서는 30개의 입력 특징 중 9개 특징이 선택되는 것을 확인했다.

30 features
    ↓ Random Forest + SelectFromModel
9 selected features

RFE가 모델을 반복적으로 학습하면서 특징을 제거했다면, Embedded 방식은 모델이 한 번의 학습 과정에서 계산한 중요도 정보를 특징 선택에 활용한다는 차이가 있다.


4. Boruta: 랜덤 특징과 비교해서 진짜 중요한 특징 찾기

특징 선택을 공부하면서 흥미로웠던 방법이 Boruta다.

Boruta는 Random Forest 계열 모델을 이용하며, 원래 특징을 무작위로 섞어 만든 Shadow Feature와 실제 특징의 중요도를 비교한다.

개념을 단순화하면 다음과 같다.

실제 특징
    +
무작위 Shadow 특징
    ↓
Random Forest 학습
    ↓
특징 중요도 비교
    ↓
Shadow보다 의미 있는 실제 특징 선택

단순히 상위 몇 개를 자르는 방식과 달리, 무작위 특징보다 충분히 의미가 있는지 비교한다는 점이 핵심이다.


Boruta 설치

처음 import를 실행했을 때 다음 오류가 발생했다.

ModuleNotFoundError: No module named 'boruta'

현재 Python 환경에 패키지가 설치되지 않았다는 의미다.

Jupyter Notebook

현재 노트북 커널에 설치하려면 다음처럼 실행할 수 있다.

%pip install Boruta

실행 결과에서는 Boruta-0.4.3이 정상적으로 설치되는 것을 확인했다.

설치 후 다시 import한다.

from boruta import BorutaPy

노트북 환경에 따라 설치 직후에도 import가 되지 않는다면 현재 Notebook 커널과 패키지를 설치한 Python 환경이 같은지 먼저 확인하는 것이 좋다.

터미널

Windows CMD, PowerShell, macOS, Linux에서는 현재 사용 중인 Python에 명시적으로 설치하는 방식이 안전하다.

python -m pip install Boruta

Conda 환경을 사용한다면 먼저 원하는 환경을 활성화한 뒤 설치한다.

conda activate data-env
python -m pip install Boruta

Boruta 적용하기

Random Forest 모델을 만든 뒤 BorutaPy에 전달한다.

from sklearn.ensemble import RandomForestClassifier
from boruta import BorutaPy

rf = RandomForestClassifier(
    random_state=1
)

boruta = BorutaPy(
    estimator=rf,
    n_estimators="auto",
    random_state=1
)

boruta.fit(
    X_train.to_numpy(),
    y_train.to_numpy().ravel()
)

특징별 순위는 ranking_으로 확인할 수 있다.

print(boruta.ranking_)

일반적으로 1로 표시된 특징을 선택된 특징으로 확인할 수 있다.

selected = X_train.columns[
    boruta.ranking_ == 1
]

print(selected)

직접 실행하면서 확인한 Boruta 결과

30개의 특징을 대상으로 실행한 결과에서는 23개 특징이 선택되고 7개 특징이 제외되는 것을 확인했다.

전체 특징: 30개

Boruta 선택:
23개

Boruta 제외:
7개

앞서 Random Forest의 SelectFromModel에서는 9개만 선택됐지만, Boruta에서는 23개가 선택됐다.

두 방법의 결과가 다른 것은 이상한 일이 아니다.

특징을 중요하다고 판단하는 기준 자체가 다르기 때문이다.

SelectFromModel은 모델 중요도의 임계값을 이용해 특징을 줄이는 방식이고, Boruta는 Shadow Feature와 반복적으로 비교하면서 관련성이 있는 특징을 찾는 방식이다.

따라서 숫자만 보고 어느 방법이 무조건 더 낫다고 판단하기보다는 선택 목적과 이후 모델 성능을 함께 확인해야 한다.


Filter, RFE, Random Forest, Boruta를 어떻게 선택할까?

실무에서는 특징 선택 알고리즘 이름보다 현재 문제에서 무엇을 원하는지를 먼저 정하는 것이 좋다.

상황고려할 방법
변수가 매우 많고 빠르게 1차 정리하고 싶음Filter
특정 모델에서 좋은 변수 조합을 찾고 싶음Wrapper / RFE
트리 모델의 중요도를 바로 활용하고 싶음Embedded
관련 있는 특징을 비교적 폭넓게 찾고 싶음Boruta
계산 비용이 중요한 상황Filter 또는 Embedded
특징 간 조합까지 적극적으로 탐색하고 싶음Wrapper

특징 선택 결과는 사용하는 모델에 따라 달라질 수 있다.

예를 들어 SVM-RFE로 선택된 특징이 Random Forest에서도 동일하게 중요한 특징이라는 보장은 없다.

즉,

특징 중요도
≠
데이터 자체에 고정된 절대적인 값

이라고 이해하는 편이 좋다.


특징 선택에서 데이터 누수를 조심하자

머신러닝 전처리에서 가장 중요하게 기억할 부분 중 하나가 Data Leakage다.

특징 선택도 학습 과정의 일부이므로 전체 데이터에서 먼저 특징을 고른 뒤 Train/Test를 나누면 테스트 데이터의 정보가 선택 과정에 반영될 수 있다.

잘못된 흐름은 다음과 같다.

전체 데이터
    ↓
Feature Selection
    ↓
Train / Test Split

일반적으로는 다음과 같은 흐름이 필요하다.

전체 데이터
    ↓
Train / Test Split
    ↓
Train 데이터로 Feature Selection 학습
    ↓
같은 선택 기준을 Test에 적용

Scikit-learn에서는 이런 실수를 줄이기 위해 Pipeline을 활용할 수 있다.


Pipeline으로 Scaling과 Feature Selection 묶기

Linear SVM과 RFE를 예로 들면 다음처럼 구성할 수 있다.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import RFE
from sklearn.svm import SVC

pipeline = Pipeline([
    (
        "scaler",
        StandardScaler()
    ),
    (
        "feature_selection",
        RFE(
            estimator=SVC(kernel="linear"),
            n_features_to_select=5
        )
    ),
    (
        "classifier",
        SVC(kernel="linear")
    )
])

pipeline.fit(X_train, y_train)

Pipeline으로 묶어두면 학습 데이터에서 계산해야 하는 전처리 과정을 하나의 흐름으로 관리하기 쉬워진다.

교차 검증과 함께 사용할 때도 데이터 누수를 방지하는 데 도움이 된다.


모델 성능만 보고 특징을 선택하면 안 되는 이유

특징 선택에서 정확도나 AUC는 중요한 기준이지만 그것만 볼 필요는 없다.

실제 서비스에서는 다음과 같은 요소도 함께 고려하게 된다.

모델 성능
+
특징 개수
+
데이터 수집 비용
+
추론 속도
+
특징 계산 비용
+
해석 가능성

예를 들어 정확도 차이가 거의 없는데 30개 변수가 필요한 모델과 5개 변수만 필요한 모델이 있다면, 후자가 운영 측면에서 더 유리할 수도 있다.

특히 실시간 추천이나 API 기반 추론에서는 한 개의 특징을 얻기 위해 추가 데이터베이스 조회나 외부 API 요청이 필요한 경우도 있다.

따라서 특징 하나를 선택한다는 것은 단순히 DataFrame의 컬럼 하나를 선택하는 문제가 아닐 수 있다.


개발자 관점에서 생각해보기

웹 서비스의 사용자 이탈 예측을 만든다고 가정해보자.

원본 데이터에 100개의 행동 특징이 있다고 해서 100개를 모두 모델에 전달할 필요는 없다.

100개 서비스 지표
    ↓
Filter로 빠른 후보 정리
    ↓
RFE 또는 Embedded 방식으로 재평가
    ↓
실제 모델 성능 검증
    ↓
운영 비용까지 고려
    ↓
최종 특징 결정

이 과정에서 중요한 것은 선택 알고리즘의 결과를 정답처럼 받아들이지 않는 것이다.

RFE가 선택한 변수, Random Forest가 중요하다고 판단한 변수, Boruta가 남긴 변수는 서로 다를 수 있다.

그 차이를 확인하면서 어떤 특징이 문제를 실제로 설명하는지 분석하는 과정이 필요하다.


핵심 정리

  • Feature Selection은 기존 특징 중 예측에 필요한 변수만 남기는 과정이다.
  • 특징 수를 줄이면 계산량, 복잡도, 노이즈를 줄이고 모델 해석을 쉽게 만드는 데 도움이 될 수 있다.
  • Filter 방식은 통계적인 점수나 관계를 이용해 빠르게 특징을 평가한다.
  • Wrapper 방식은 실제 모델을 반복 학습하며 좋은 특징 조합을 탐색하고, 대표적으로 RFE가 있다.
  • Embedded 방식은 모델 학습 과정에서 계산되는 계수나 특징 중요도를 이용한다.
  • SVM 기반 RFE에서는 변수 스케일의 영향을 고려해 StandardScaler를 함께 사용할 수 있다.
  • 직접 실행한 RFE에서는 5개 특징을 선택한 결과가 10개를 선택한 경우보다 높은 Accuracy와 AUC를 보였다.
  • Random Forest 기반 SelectFromModel에서는 30개 중 9개의 특징이 선택됐다.
  • Boruta에서는 Shadow Feature와 비교해 30개 중 23개 특징이 선택되고 7개가 제외됐다.
  • 특징 선택은 반드시 Train 데이터에서 학습하고, 같은 선택 기준을 Test 데이터에 적용해야 한다.
  • 최종 특징은 모델 성능뿐 아니라 수집 비용, 추론 속도, 해석 가능성까지 함께 고려해서 결정하는 것이 좋다.

정리하며

특징 선택을 처음 보면 단순히 컬럼을 삭제하는 작업처럼 보이지만, 실제로는 모델이 어떤 정보를 보고 판단하게 할 것인지 결정하는 과정에 가깝다.

Filter는 빠르게 후보를 좁히는 데 유용하고, Wrapper는 특정 모델과 잘 맞는 조합을 찾는 데 도움이 되며, Embedded 방식은 모델 학습과 특징 선택을 자연스럽게 연결한다.

Boruta처럼 랜덤 특징과 비교하는 방법까지 살펴보면 특징의 중요도를 판단하는 방법이 하나만 있는 것이 아니라는 점도 알 수 있다.

결국 중요한 것은 특정 알고리즘을 무조건 사용하는 것이 아니라,

왜 이 특징을 남겼는가?
왜 이 특징을 제거했는가?
선택 결과가 실제 모델 성능과 운영 환경에서도 유효한가?

를 설명할 수 있는 것이다.

profile
html_programming_language

0개의 댓글