🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

피처 선택 및 차원 축소 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 29. · 14 Views

피처 선택 및 차원 축소 완벽 가이드

머신러닝에서 데이터의 핵심만 추출하는 피처 선택과 차원 축소 기법을 알아봅니다. 초급 개발자도 쉽게 이해할 수 있도록 실무 예제와 함께 설명합니다.


목차

  1. 피처_선택의_기초
  2. 상관관계_기반_피처_제거
  3. 분산_임계값_기반_선택
  4. 재귀적_피처_제거_RFE
  5. 주성분_분석_PCA
  6. t_SNE_시각화
  7. LDA_선형판별분석
  8. 임베딩_기반_피처_추출
  9. 피처_중요도_시각화
  10. 파이프라인_구축

1. 피처 선택의 기초

김개발 씨는 처음으로 머신러닝 프로젝트를 맡았습니다. 고객 이탈 예측 모델을 만들어야 하는데, 데이터에 무려 200개가 넘는 컬럼이 있었습니다.

"이걸 다 써야 하나요?" 선배 박시니어 씨에게 물었더니 돌아온 대답은 의외였습니다. "전부 다 쓰면 오히려 성능이 떨어질 수 있어요."

피처 선택은 머신러닝 모델의 성능을 높이기 위해 가장 중요한 변수만 골라내는 기법입니다. 마치 이사할 때 정말 필요한 물건만 챙기는 것과 같습니다.

불필요한 피처를 제거하면 모델이 더 빠르게 학습하고, 과적합도 방지할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.datasets import load_iris
import pandas as pd

# 데이터 로드
iris = load_iris()
X, y = iris.data, iris.target

# 상위 2개의 가장 중요한 피처 선택
selector = SelectKBest(score_func=f_classif, k=2)
X_selected = selector.fit_transform(X, y)

# 선택된 피처 확인
selected_features = selector.get_support(indices=True)
print(f"선택된 피처 인덱스: {selected_features}")
print(f"원본 형태: {X.shape} -> 선택 후: {X_selected.shape}")

김개발 씨는 입사 6개월 차 데이터 분석가입니다. 오늘 팀장님이 중요한 프로젝트를 맡겼습니다.

고객 데이터를 분석해서 이탈할 가능성이 높은 고객을 예측하는 모델을 만들어야 합니다. 데이터베이스에서 고객 정보를 추출했더니 컬럼이 200개가 넘었습니다.

나이, 성별, 가입일, 구매 횟수, 마지막 접속일, 사용 기기... 끝도 없이 이어지는 변수들을 보며 김개발 씨는 막막해졌습니다.

선배 박시니어 씨가 다가와 화면을 살펴봅니다. "200개 피처를 전부 넣으면 안 돼요.

피처 선택을 먼저 해야 합니다." 그렇다면 피처 선택이란 정확히 무엇일까요? 쉽게 비유하자면, 피처 선택은 마치 여행 가방을 싸는 것과 같습니다.

일주일 여행에 옷장 전체를 가져갈 수는 없습니다. 날씨와 일정에 맞는 옷만 골라서 가방에 넣어야 합니다.

마찬가지로 머신러닝 모델도 모든 데이터를 다 쓰는 것보다, 예측에 실제로 도움이 되는 변수만 선택하는 것이 훨씬 효과적입니다. 피처 선택이 없던 시절에는 어땠을까요?

개발자들은 모든 변수를 그대로 모델에 넣었습니다. 결과는 참담했습니다.

학습 시간은 오래 걸리고, 모델은 훈련 데이터에만 과적합되어 새로운 데이터에서는 형편없는 성능을 보였습니다. 더 큰 문제는 어떤 변수가 실제로 예측에 기여하는지 알 수 없었다는 점입니다.

바로 이런 문제를 해결하기 위해 피처 선택 기법이 발전했습니다. 위의 코드에서 사용한 SelectKBest는 가장 기본적인 피처 선택 방법입니다.

이 방법은 각 피처가 타겟 변수와 얼마나 상관관계가 있는지 통계적으로 측정합니다. f_classif는 분산 분석을 사용해 피처의 중요도를 계산합니다.

코드를 한 줄씩 살펴보겠습니다. 먼저 SelectKBest 객체를 생성할 때 k=2를 지정했습니다.

이는 상위 2개의 피처만 선택하겠다는 의미입니다. fit_transform 메서드를 호출하면 자동으로 각 피처의 점수를 계산하고, 가장 높은 점수를 받은 피처들만 남깁니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 금융 회사에서 대출 승인 모델을 만든다고 가정해봅시다.

고객의 수백 가지 정보 중에서 실제로 채무 불이행을 예측하는 데 중요한 변수는 소득, 기존 부채, 신용 점수 정도일 수 있습니다. 나머지 변수들은 오히려 모델을 혼란스럽게 만들 뿐입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 k값을 너무 작게 설정하는 것입니다.

중요한 피처까지 제거해버리면 모델 성능이 오히려 떨어집니다. 따라서 여러 k값을 시험해보고 최적의 값을 찾아야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 모든 피처를 다 쓰면 안 되는 거군요!"

실전 팁

💡 - SelectKBest의 k값은 교차 검증을 통해 최적값을 찾으세요

  • 피처 선택 전에 결측치와 이상치를 먼저 처리해야 합니다

2. 상관관계 기반 피처 제거

김개발 씨가 피처 선택을 적용했는데도 모델 성능이 기대만큼 나오지 않았습니다. 박시니어 씨가 데이터를 살펴보더니 문제를 발견했습니다.

"여기 보세요. 월 소득이랑 연 소득이 거의 똑같은 정보잖아요.

이런 중복 피처를 제거해야 해요."

상관관계 기반 피처 제거는 서로 비슷한 정보를 담고 있는 피처들 중 하나만 남기는 기법입니다. 마치 같은 내용의 책을 여러 권 가지고 있을 필요가 없는 것과 같습니다.

중복된 피처를 제거하면 모델이 더 효율적으로 학습할 수 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd
import numpy as np

# 샘플 데이터 생성
np.random.seed(42)
df = pd.DataFrame({
    'monthly_income': np.random.randint(3000, 8000, 100),
    'annual_income': np.random.randint(36000, 96000, 100),
    'age': np.random.randint(25, 60, 100),
    'purchase_count': np.random.randint(1, 50, 100)
})

# 상관관계 행렬 계산
correlation_matrix = df.corr().abs()

# 상삼각 행렬만 추출
upper_triangle = correlation_matrix.where(
    np.triu(np.ones(correlation_matrix.shape), k=1).astype(bool)
)

# 0.8 이상 상관관계를 가진 피처 찾기
high_corr_features = [col for col in upper_triangle.columns
                      if any(upper_triangle[col] > 0.8)]
print(f"제거할 피처: {high_corr_features}")

김개발 씨는 피처 선택을 적용한 후에도 뭔가 찜찜했습니다. 데이터를 다시 살펴보니 이상한 점이 눈에 들어왔습니다.

월 소득과 연 소득이 둘 다 피처로 들어가 있었습니다. 박시니어 씨가 설명을 이어갑니다.

"월 소득에 12를 곱하면 연 소득이잖아요. 이 두 변수는 사실상 같은 정보를 담고 있어요.

이걸 다중공선성 문제라고 합니다." 상관관계 기반 피처 제거란 정확히 무엇일까요? 쉽게 비유하자면, 같은 드라마를 넷플릭스와 웨이브에서 동시에 보는 것과 같습니다.

내용은 똑같은데 두 개를 동시에 틀어놓을 필요가 없습니다. 마찬가지로 데이터에서도 거의 같은 정보를 담고 있는 피처가 여러 개 있다면, 하나만 남기고 나머지는 제거하는 것이 효율적입니다.

이 문제를 방치하면 어떻게 될까요? 모델이 같은 정보를 두 번 학습하게 됩니다.

이는 특정 패턴에 과도하게 가중치를 주는 결과를 낳습니다. 또한 회귀 분석에서는 계수 추정이 불안정해지고, 해석이 어려워집니다.

위 코드에서는 상관계수를 사용해 이 문제를 해결합니다. 상관계수가 1에 가까우면 두 변수가 거의 같은 방향으로 움직인다는 뜻입니다.

일반적으로 0.8 이상의 상관관계를 가진 피처 쌍이 있다면, 둘 중 하나를 제거하는 것이 좋습니다. 코드를 살펴보면, 먼저 corr() 메서드로 상관관계 행렬을 만듭니다.

그 다음 상삼각 행렬만 추출하는데, 이는 같은 피처 쌍을 두 번 세지 않기 위함입니다. 마지막으로 0.8 이상인 피처를 찾아 제거 목록에 추가합니다.

실제 현업에서 이 기법은 매우 자주 사용됩니다. 특히 설문조사 데이터나 센서 데이터처럼 비슷한 정보를 여러 방식으로 측정한 경우에 유용합니다.

주의할 점은 도메인 지식을 함께 고려해야 한다는 것입니다. 상관관계가 높더라도 비즈니스적으로 중요한 의미를 가진 피처라면 남겨두는 것이 좋을 수 있습니다.

실전 팁

💡 - 상관계수 임계값은 보통 0.8~0.9 사이에서 설정합니다

  • 어떤 피처를 제거할지는 도메인 지식을 바탕으로 결정하세요

3. 분산 임계값 기반 선택

다음 날 김개발 씨는 새로운 데이터셋을 받았습니다. 그런데 이상하게도 어떤 컬럼은 모든 값이 거의 같았습니다.

성별 컬럼인데 100명 중 99명이 남성이었습니다. "이런 컬럼도 분석에 써야 하나요?" 김개발 씨의 질문에 박시니어 씨가 웃으며 답했습니다.

"변화가 없는 데이터는 아무것도 예측할 수 없어요."

분산 임계값 기반 선택은 값의 변동이 거의 없는 피처를 제거하는 기법입니다. 마치 매일 같은 온도를 보여주는 온도계는 날씨 예측에 쓸모없는 것과 같습니다.

분산이 낮은 피처는 패턴 구분에 기여하지 못하므로 제거하는 것이 좋습니다.

다음 코드를 살펴봅시다.

from sklearn.feature_selection import VarianceThreshold
import numpy as np

# 샘플 데이터: 세 번째 피처는 분산이 매우 낮음
X = np.array([
    [1, 2, 0, 5],
    [2, 3, 0, 8],
    [3, 4, 0, 3],
    [4, 5, 1, 9],  # 세 번째 컬럼: 거의 0
    [5, 6, 0, 7]
])

# 분산이 0.1 이하인 피처 제거
selector = VarianceThreshold(threshold=0.1)
X_selected = selector.fit_transform(X)

print(f"원본 피처 수: {X.shape[1]}")
print(f"선택된 피처 수: {X_selected.shape[1]}")
print(f"제거된 피처 인덱스: {np.where(~selector.get_support())[0]}")

김개발 씨가 받은 데이터에는 특이한 컬럼이 있었습니다. VIP_등급이라는 컬럼인데, 1000명의 고객 중 998명이 일반 등급이고 단 2명만 VIP였습니다.

박시니어 씨가 설명합니다. "이런 피처는 분산이 매우 낮아요.

거의 모든 값이 같으니까요." 분산이란 데이터가 평균으로부터 얼마나 퍼져 있는지를 나타내는 지표입니다. 분산이 0이면 모든 값이 완전히 동일하다는 뜻입니다.

분산이 낮으면 대부분의 값이 비슷하다는 의미입니다. 비유하자면, 분산이 낮은 피처는 마치 시험에서 모든 학생이 100점을 받은 과목과 같습니다.

점수 차이가 없으니 누가 공부를 잘하는지 구분할 수 없습니다. 반면 점수가 30점부터 100점까지 고르게 분포된 과목이라면 학생들의 실력 차이를 파악할 수 있습니다.

머신러닝에서도 마찬가지입니다. 분산이 낮은 피처는 데이터 포인트들을 구분하는 데 도움이 되지 않습니다.

오히려 노이즈만 추가할 뿐입니다. 위 코드에서 VarianceThreshold는 지정한 임계값보다 분산이 낮은 피처를 자동으로 제거합니다.

threshold=0.1로 설정하면 분산이 0.1 이하인 피처가 제거됩니다. 실제 업무에서 이 기법이 특히 유용한 경우가 있습니다.

원핫 인코딩을 적용한 후 특정 카테고리가 거의 나타나지 않는 경우입니다. 예를 들어 국가 컬럼을 원핫 인코딩했는데 한국 고객이 99%라면, 다른 국가 컬럼들은 거의 0으로 채워져 분산이 매우 낮아집니다.

주의할 점은 스케일링 전에 분산 기반 선택을 적용해야 한다는 것입니다. 표준화를 적용하면 모든 피처의 분산이 1로 맞춰지기 때문입니다.

김개발 씨가 물었습니다. "그럼 임계값은 어떻게 정하나요?" 박시니어 씨가 답합니다.

"정답은 없어요. 보통 0.01에서 0.1 사이로 시작해서 모델 성능을 보며 조정합니다."

실전 팁

💡 - 이진 피처의 경우 threshold=p*(1-p) 공식을 활용하세요 (p는 제거하고 싶은 최소 비율)

  • 스케일링 전에 VarianceThreshold를 적용해야 합니다

4. 재귀적 피처 제거 RFE

김개발 씨의 모델이 드디어 어느 정도 성능을 내기 시작했습니다. 하지만 여전히 피처가 50개나 됐습니다.

박시니어 씨가 새로운 기법을 알려줬습니다. "모델이 직접 중요한 피처를 골라내게 하는 방법이 있어요.

RFE라고 합니다."

**RFE(Recursive Feature Elimination)**는 모델을 반복적으로 학습시키면서 가장 덜 중요한 피처를 하나씩 제거하는 기법입니다. 마치 오디션에서 탈락자를 한 명씩 제거하며 최종 우승자를 가리는 것과 같습니다.

모델 기반으로 피처를 선택하므로 예측 성능에 직접적으로 기여하는 피처를 찾을 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# 데이터 로드
X, y = load_iris(return_X_y=True)
feature_names = load_iris().feature_names

# 랜덤 포레스트 기반 RFE
model = RandomForestClassifier(n_estimators=100, random_state=42)
rfe = RFE(estimator=model, n_features_to_select=2, step=1)
rfe.fit(X, y)

# 선택된 피처 확인
for i, (name, selected, rank) in enumerate(
    zip(feature_names, rfe.support_, rfe.ranking_)
):
    status = "선택됨" if selected else f"제거됨(순위:{rank})"
    print(f"{name}: {status}")

김개발 씨는 지금까지 배운 기법들을 적용했습니다. 상관관계가 높은 피처도 제거하고, 분산이 낮은 피처도 제거했습니다.

하지만 여전히 피처가 많았습니다. 박시니어 씨가 새로운 접근법을 제안합니다.

"지금까지는 통계적인 방법으로 피처를 선택했잖아요. 이번에는 모델이 직접 판단하게 해보는 건 어떨까요?" **RFE(Recursive Feature Elimination)**는 재귀적 피처 제거라는 뜻입니다.

이름이 어렵게 들리지만 개념은 간단합니다. 비유하자면, RFE는 마치 서바이벌 오디션 프로그램과 같습니다.

처음에 100명의 참가자가 있습니다. 매 라운드마다 심사위원이 가장 실력이 부족한 참가자를 탈락시킵니다.

이 과정을 반복하면 최종적으로 가장 뛰어난 참가자들만 남게 됩니다. RFE도 똑같이 작동합니다.

먼저 모든 피처를 사용해 모델을 학습시킵니다. 그 다음 모델이 가장 덜 중요하다고 판단한 피처를 제거합니다.

남은 피처로 다시 모델을 학습시킵니다. 이 과정을 원하는 피처 수가 될 때까지 반복합니다.

위 코드에서 RandomForestClassifier를 기반 모델로 사용했습니다. 랜덤 포레스트는 피처 중요도를 자연스럽게 계산하기 때문에 RFE와 궁합이 좋습니다.

n_features_to_select=2는 최종적으로 2개의 피처만 남기겠다는 의미입니다. RFE의 가장 큰 장점은 실제 예측 성능에 기여하는 피처를 찾는다는 것입니다.

통계적 방법은 단순히 상관관계만 보지만, RFE는 모델이 직접 학습하며 판단합니다. 실제 현업에서 RFE는 피처 수가 중간 정도일 때 효과적입니다.

피처가 수천 개라면 계산 비용이 너무 커집니다. 이럴 때는 먼저 다른 방법으로 피처 수를 줄인 후 RFE를 적용하는 것이 좋습니다.

주의할 점은 기반 모델 선택입니다. 선형 모델과 트리 기반 모델은 피처 중요도를 다르게 평가합니다.

최종 모델과 비슷한 유형의 모델을 RFE에 사용하는 것이 좋습니다. 김개발 씨가 RFE를 적용하자 50개 피처가 10개로 줄었습니다.

놀랍게도 모델 성능은 오히려 향상됐습니다.

실전 팁

💡 - RFECV를 사용하면 교차 검증을 통해 최적의 피처 수를 자동으로 찾아줍니다

  • 계산 시간을 줄이려면 step 파라미터를 높여 한 번에 여러 피처를 제거하세요

5. 주성분 분석 PCA

김개발 씨가 이번에는 이미지 분류 프로젝트를 맡았습니다. 이미지 한 장이 784개의 픽셀로 이루어져 있었습니다.

"피처 선택으로 픽셀을 제거하면 이미지가 이상해지지 않나요?" 박시니어 씨가 웃으며 답합니다. "그럴 때는 피처를 제거하는 게 아니라 압축하는 거예요.

PCA라는 기법이 있습니다."

**PCA(Principal Component Analysis)**는 여러 피처를 더 적은 수의 새로운 피처로 압축하는 기법입니다. 마치 긴 소설을 핵심 줄거리로 요약하는 것과 같습니다.

원본 정보를 최대한 보존하면서 차원을 줄이기 때문에, 피처 간의 관계를 깨뜨리지 않습니다.

다음 코드를 살펴봅시다.

from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

# 손글씨 숫자 데이터 로드 (64차원)
digits = load_digits()
X = digits.data

# 95% 분산을 유지하도록 PCA 적용
pca = PCA(n_components=0.95)
X_pca = pca.fit_transform(X)

print(f"원본 차원: {X.shape[1]}")
print(f"축소 후 차원: {X_pca.shape[1]}")
print(f"설명된 분산 비율 합: {sum(pca.explained_variance_ratio_):.2%}")

# 각 주성분이 설명하는 분산 비율
for i, ratio in enumerate(pca.explained_variance_ratio_[:5]):
    print(f"PC{i+1}: {ratio:.2%}")

김개발 씨가 받은 손글씨 숫자 이미지는 8x8 픽셀, 즉 64차원의 데이터였습니다. 각 픽셀이 하나의 피처인 셈입니다.

"픽셀 중에서 중요한 것만 고르면 되지 않나요?" 김개발 씨가 물었습니다. 박시니어 씨가 고개를 저었습니다.

"이미지에서 특정 픽셀만 제거하면 숫자 모양이 망가져요. 이럴 때는 차원 축소라는 다른 접근이 필요합니다." **PCA(주성분 분석)**는 가장 대표적인 차원 축소 기법입니다.

피처 선택이 기존 피처 중에서 고르는 것이라면, PCA는 기존 피처들을 조합해 완전히 새로운 피처를 만들어냅니다. 비유하자면, PCA는 마치 사진을 JPEG로 압축하는 것과 같습니다.

JPEG 압축은 원본 사진의 픽셀을 그대로 유지하지 않습니다. 대신 사람 눈에 중요한 정보는 보존하고, 덜 중요한 정보는 버립니다.

결과적으로 파일 크기는 줄어들지만 사진은 여전히 알아볼 수 있습니다. PCA도 비슷하게 작동합니다.

데이터의 분산을 가장 잘 설명하는 방향을 찾아 그 방향으로 데이터를 투영합니다. 첫 번째 주성분(PC1)은 데이터의 분산을 가장 많이 설명하는 방향입니다.

두 번째 주성분(PC2)은 PC1과 수직이면서 그 다음으로 분산을 많이 설명하는 방향입니다. 위 코드에서 n_components=0.95는 "원본 분산의 95%를 설명할 수 있는 만큼의 주성분만 사용하겠다"는 의미입니다.

64차원의 데이터가 약 40차원 정도로 줄어들면서도 정보의 95%가 보존됩니다. 실제 현업에서 PCA는 이미지 처리, 자연어 처리, 추천 시스템 등 다양한 분야에서 사용됩니다.

특히 시각화 목적으로 고차원 데이터를 2~3차원으로 줄일 때 자주 활용됩니다. 주의할 점은 PCA를 적용하면 원래 피처의 의미가 사라진다는 것입니다.

새로 만들어진 주성분은 "첫 번째 주성분", "두 번째 주성분"처럼 추상적인 이름만 가집니다. 해석이 중요한 분석에서는 이 점을 고려해야 합니다.

또한 PCA 전에 반드시 표준화를 해야 합니다. 스케일이 다른 피처가 있으면 PCA 결과가 왜곡됩니다.

실전 팁

💡 - PCA 적용 전에 StandardScaler로 데이터를 표준화하세요

  • explained_variance_ratio_를 확인해 적절한 주성분 수를 결정하세요

6. t SNE 시각화

김개발 씨가 PCA로 차원을 줄인 데이터를 2차원으로 시각화했습니다. 그런데 클래스별로 잘 구분되지 않았습니다.

박시니어 씨가 화면을 보더니 말했습니다. "시각화 목적이라면 t-SNE를 써보세요.

비슷한 데이터끼리 뭉치게 만들어줍니다."

t-SNE는 고차원 데이터의 구조를 2~3차원에서 시각적으로 보여주는 기법입니다. 마치 세계 여러 나라 사람들을 언어 유사성에 따라 지도에 배치하는 것과 같습니다.

비슷한 데이터 포인트는 가깝게, 다른 데이터 포인트는 멀리 배치하여 데이터의 군집 구조를 잘 드러냅니다.

다음 코드를 살펴봅시다.

from sklearn.manifold import TSNE
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

# 손글씨 숫자 데이터
digits = load_digits()
X, y = digits.data, digits.target

# t-SNE로 2차원으로 축소
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
X_tsne = tsne.fit_transform(X)

# 시각화
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10')
plt.colorbar(scatter)
plt.title('t-SNE visualization of digits dataset')
plt.xlabel('t-SNE 1')
plt.ylabel('t-SNE 2')
plt.savefig('tsne_result.png')
print("시각화 완료: tsne_result.png")

김개발 씨는 PCA로 2차원으로 줄인 데이터를 산점도로 그려봤습니다. 그런데 기대와 달리 데이터 포인트들이 뒤섞여 있어서 클래스를 구분하기 어려웠습니다.

박시니어 씨가 설명합니다. "PCA는 분산을 보존하는 데 집중해요.

하지만 시각화할 때는 이웃 관계를 보존하는 게 더 중요할 수 있어요." **t-SNE(t-distributed Stochastic Neighbor Embedding)**는 시각화를 위해 특별히 설계된 차원 축소 기법입니다. 발음이 어려워 보이지만 "티-스니"라고 읽으면 됩니다.

비유하자면, t-SNE는 마치 학교 반 배정과 같습니다. 전학 온 학생들을 배정할 때, 취미가 비슷한 학생끼리 같은 반에 넣으면 자연스럽게 친구가 생깁니다.

t-SNE도 고차원에서 가까웠던 데이터 포인트들이 저차원에서도 가깝게 유지되도록 배치합니다. 위 코드에서 perplexity 파라미터는 각 점이 고려하는 이웃의 수와 관련됩니다.

보통 5에서 50 사이의 값을 사용하며, 데이터 크기에 따라 조정합니다. 작은 데이터셋에는 낮은 값, 큰 데이터셋에는 높은 값이 적합합니다.

t-SNE의 가장 큰 장점은 군집 구조를 명확하게 보여준다는 것입니다. 같은 클래스의 데이터 포인트들이 뭉쳐서 나타나기 때문에, 데이터의 패턴을 한눈에 파악할 수 있습니다.

하지만 중요한 제한점이 있습니다. t-SNE는 시각화 전용 기법입니다.

새로운 데이터에 적용하는 transform 메서드가 없습니다. 따라서 모델 학습용 차원 축소에는 적합하지 않습니다.

또한 t-SNE 결과의 거리와 크기는 해석하면 안 됩니다. 클러스터 사이의 거리나 클러스터의 밀도는 의미가 없습니다.

오직 "어떤 점들이 함께 뭉쳐있는가"만 해석해야 합니다. 실제 현업에서 t-SNE는 탐색적 데이터 분석(EDA) 단계에서 자주 사용됩니다.

고객 세그먼트가 잘 나뉘는지, 이상치가 있는지 등을 시각적으로 확인할 때 유용합니다.

실전 팁

💡 - perplexity 값을 여러 개 시험해보고 가장 명확한 결과를 선택하세요

  • t-SNE는 시각화 전용이므로 모델 학습에는 PCA나 다른 기법을 사용하세요

7. LDA 선형판별분석

김개발 씨가 t-SNE 결과를 팀장님께 보여드렸습니다. "이건 예쁘긴 한데, 새 데이터에 적용할 수 없잖아요?" 팀장님의 지적에 박시니어 씨가 대안을 제시합니다.

"클래스를 구분하면서 차원도 줄이고 싶다면 LDA를 쓰면 됩니다."

**LDA(Linear Discriminant Analysis)**는 클래스 간 분리를 최대화하는 방향으로 차원을 축소하는 기법입니다. PCA가 분산을 보존한다면, LDA는 클래스 구분을 보존합니다.

마치 좋은 선생님이 학생들의 성적 차이를 잘 드러내는 시험 문제를 출제하는 것과 같습니다.

다음 코드를 살펴봅시다.

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

# 데이터 로드
iris = load_iris()
X, y = iris.data, iris.target

# LDA로 2차원으로 축소
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X, y)

# 시각화
plt.figure(figsize=(8, 6))
colors = ['red', 'green', 'blue']
for i, color in enumerate(colors):
    plt.scatter(X_lda[y == i, 0], X_lda[y == i, 1],
                c=color, label=iris.target_names[i])
plt.xlabel('LD1')
plt.ylabel('LD2')
plt.legend()
plt.title('LDA of Iris Dataset')
plt.savefig('lda_result.png')
print("시각화 완료: lda_result.png")

김개발 씨는 고민에 빠졌습니다. PCA는 새 데이터에 적용할 수 있지만 클래스 구분이 잘 안 됩니다.

t-SNE는 클래스 구분은 잘 되지만 새 데이터에 적용할 수 없습니다. 박시니어 씨가 해결책을 제시합니다.

"두 가지 장점을 모두 가진 기법이 있어요. LDA라고 합니다." **LDA(선형 판별 분석)**는 지도 학습 기반의 차원 축소 기법입니다.

PCA는 레이블(정답)을 사용하지 않지만, LDA는 레이블 정보를 적극 활용합니다. 비유하자면, LDA는 마치 좋은 면접관과 같습니다.

면접관은 지원자들의 능력 차이를 가장 잘 드러내는 질문을 합니다. "취미가 뭐예요?" 같은 질문보다 "이 문제를 어떻게 해결하시겠어요?" 같은 질문이 지원자들을 더 잘 구분해줍니다.

LDA도 클래스 간 차이를 가장 잘 드러내는 방향을 찾습니다. 수학적으로 LDA는 두 가지를 동시에 최적화합니다.

클래스 간 분산은 최대화하고, 클래스 내 분산은 최소화합니다. 다시 말해, 다른 클래스끼리는 멀리 떨어지게 하고, 같은 클래스끼리는 모이게 합니다.

위 코드에서 fit_transform을 호출할 때 y(레이블)도 함께 전달한 것을 주목하세요. PCA는 X만 필요했지만, LDA는 y도 필요합니다.

LDA의 중요한 제한점은 최대 차원 수입니다. N개의 클래스가 있다면 최대 N-1개의 차원으로만 축소할 수 있습니다.

위 예제에서 iris 데이터는 3개 클래스이므로 최대 2차원까지만 가능합니다. 실제 현업에서 LDA는 분류 전 전처리로 자주 사용됩니다.

고차원 데이터를 LDA로 먼저 축소한 후 분류 모델을 학습시키면 더 좋은 성능을 얻을 수 있습니다. 특히 피처 수가 샘플 수보다 많은 경우에 효과적입니다.

t-SNE와 비교했을 때 LDA의 가장 큰 장점은 새 데이터에 적용 가능하다는 것입니다. 한번 학습한 LDA 모델은 transform 메서드로 새 데이터를 변환할 수 있습니다.

실전 팁

💡 - LDA는 분류 문제에서만 사용 가능합니다. 회귀 문제에는 PCA를 사용하세요

  • 클래스 수가 적으면 축소할 수 있는 차원도 적다는 점을 기억하세요

8. 임베딩 기반 피처 추출

김개발 씨가 이번에는 텍스트 데이터를 다루게 됐습니다. 고객 리뷰를 분석해서 긍정인지 부정인지 분류해야 합니다.

"텍스트는 어떻게 숫자로 바꾸나요?" 박시니어 씨가 답합니다. "텍스트를 벡터로 변환하는 임베딩 기법이 있어요.

이것도 일종의 차원 축소입니다."

임베딩은 텍스트, 이미지 등 비정형 데이터를 고정 길이의 벡터로 변환하는 기법입니다. 마치 사람의 특징을 키, 몸무게, 나이 같은 숫자로 표현하는 것과 같습니다.

비슷한 의미의 단어나 문장은 비슷한 벡터로 표현되어 머신러닝 모델이 처리할 수 있게 됩니다.

다음 코드를 살펴봅시다.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD

# 샘플 텍스트 데이터
documents = [
    "이 영화는 정말 재미있어요",
    "완전 지루한 영화였습니다",
    "배우들의 연기가 훌륭했어요",
    "스토리가 너무 뻔했습니다",
    "강력 추천하는 영화입니다"
]

# TF-IDF 벡터화
vectorizer = TfidfVectorizer()
X_tfidf = vectorizer.fit_transform(documents)
print(f"TF-IDF 차원: {X_tfidf.shape}")

# SVD로 차원 축소 (잠재 의미 분석)
svd = TruncatedSVD(n_components=2)
X_reduced = svd.fit_transform(X_tfidf)
print(f"축소 후 차원: {X_reduced.shape}")
print(f"설명된 분산: {svd.explained_variance_ratio_.sum():.2%}")

김개발 씨는 10만 개의 고객 리뷰 데이터를 받았습니다. 하지만 텍스트는 숫자가 아닙니다.

머신러닝 모델은 숫자만 처리할 수 있는데, 어떻게 해야 할까요? 박시니어 씨가 첫 번째 단계를 알려줍니다.

"먼저 텍스트를 숫자 벡터로 바꿔야 해요. TF-IDF라는 기법을 사용합니다." TF-IDF는 단어의 중요도를 숫자로 표현하는 방법입니다.

TF(Term Frequency)는 특정 문서에서 단어가 얼마나 자주 나타나는지, IDF(Inverse Document Frequency)는 전체 문서에서 그 단어가 얼마나 희귀한지를 측정합니다. 비유하자면, "그리고", "입니다" 같은 흔한 단어는 낮은 점수를 받고, "인공지능", "블록체인" 같은 특정 분야에서만 나타나는 단어는 높은 점수를 받습니다.

문제는 TF-IDF 결과가 너무 고차원이라는 것입니다. 단어가 1만 개면 벡터도 1만 차원입니다.

이런 희소 행렬은 메모리도 많이 차지하고 계산도 느립니다. 여기서 TruncatedSVD가 등장합니다.

TruncatedSVD는 PCA의 사촌 격인 기법으로, 희소 행렬에 적용할 수 있다는 장점이 있습니다. 텍스트에 TF-IDF를 적용한 후 TruncatedSVD로 차원을 줄이는 것을 **잠재 의미 분석(LSA)**이라고 부릅니다.

위 코드를 보면, TF-IDF로 변환한 결과가 (5, 15) 형태입니다. 문서 5개, 단어 15개라는 뜻입니다.

TruncatedSVD로 차원을 2로 줄이면 (5, 2) 형태가 됩니다. 실제 현업에서 이 기법은 검색 엔진, 추천 시스템, 문서 분류 등에 널리 사용됩니다.

특히 비슷한 문서를 찾거나, 문서를 클러스터링할 때 효과적입니다. 최근에는 Word2Vec, BERT 같은 더 발전된 임베딩 기법이 많이 사용됩니다.

이런 기법들은 단어의 의미적 관계까지 포착합니다. 예를 들어 "왕 - 남자 + 여자 = 여왕" 같은 연산이 가능해집니다.

주의할 점은 임베딩 차원을 너무 낮게 설정하면 정보 손실이 크다는 것입니다. 적절한 차원은 데이터와 태스크에 따라 다르므로 실험을 통해 찾아야 합니다.

실전 팁

💡 - 텍스트 데이터에는 PCA 대신 TruncatedSVD를 사용하세요 (희소 행렬 지원)

  • 더 좋은 성능을 원하면 사전 학습된 Word2Vec이나 BERT 임베딩을 활용하세요

9. 피처 중요도 시각화

프로젝트가 마무리되어 가고 있었습니다. 팀장님이 김개발 씨에게 물었습니다.

"그래서 어떤 피처가 가장 중요한 거야? 경영진에게 설명해야 하는데." 김개발 씨는 당황했습니다.

모델은 만들었는데, 왜 그런 결과가 나왔는지 설명할 수 없었습니다.

피처 중요도 시각화는 모델이 어떤 피처를 중요하게 생각하는지 보여주는 기법입니다. 마치 의사가 진단 결과를 환자에게 설명하는 것과 같습니다.

좋은 모델은 성능뿐 아니라 해석 가능성도 중요합니다. 피처 중요도를 통해 비즈니스 인사이트를 도출할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np

# 데이터 로드 및 모델 학습
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X, y)

# 피처 중요도 추출 및 정렬
importances = model.feature_importances_
indices = np.argsort(importances)[::-1]

# 시각화
plt.figure(figsize=(10, 6))
plt.title('Feature Importances')
plt.bar(range(len(importances)), importances[indices])
plt.xticks(range(len(importances)),
           [feature_names[i] for i in indices], rotation=45)
plt.tight_layout()
plt.savefig('feature_importance.png')
print("피처 중요도 시각화 완료")

김개발 씨는 훌륭한 성능의 모델을 만들었습니다. 하지만 팀장님의 질문에 말문이 막혔습니다.

"이 모델이 고객 이탈을 예측할 때 뭘 보고 판단하는 거야?" 이것은 모델 해석 가능성의 문제입니다. 아무리 성능이 좋아도 왜 그런 결정을 내렸는지 설명할 수 없다면, 비즈니스에서 신뢰받기 어렵습니다.

박시니어 씨가 조언합니다. "트리 기반 모델은 피처 중요도를 자동으로 계산해줘요.

이걸 시각화하면 됩니다." 피처 중요도란 각 피처가 모델의 예측에 얼마나 기여했는지를 나타내는 수치입니다. 중요도가 높은 피처일수록 예측에 큰 영향을 미칩니다.

비유하자면, 피처 중요도는 마치 레시피에서 각 재료의 중요도와 같습니다. 김치찌개를 만들 때 김치와 돼지고기는 필수지만, 파는 없어도 어느 정도 맛이 납니다.

마찬가지로 모델도 어떤 피처는 필수이고, 어떤 피처는 있으면 좋은 정도입니다. 위 코드에서 feature_importances_ 속성은 랜덤 포레스트가 학습하면서 자동으로 계산합니다.

이 값은 각 피처가 분류 정확도 향상에 얼마나 기여했는지를 나타냅니다. 막대 그래프로 시각화하면 한눈에 어떤 피처가 중요한지 알 수 있습니다.

이 그래프를 경영진에게 보여주면 "이 모델은 주로 고객의 마지막 접속일과 구매 횟수를 보고 이탈을 예측합니다"라고 명확하게 설명할 수 있습니다. 실제 현업에서 피처 중요도는 여러 용도로 활용됩니다.

첫째, 비즈니스 인사이트 도출입니다. 중요한 피처가 무엇인지 알면 어디에 집중해야 하는지 알 수 있습니다.

둘째, 피처 선택입니다. 중요도가 낮은 피처를 제거하면 모델을 더 단순하게 만들 수 있습니다.

주의할 점은 피처 중요도의 해석입니다. 중요도가 높다고 해서 그 피처가 타겟과 인과관계가 있는 것은 아닙니다.

단지 상관관계가 있다는 것뿐입니다. 인과관계를 밝히려면 추가적인 분석이 필요합니다.

김개발 씨는 피처 중요도 그래프를 만들어 팀장님께 보여드렸습니다. "아, 그래서 마지막 접속일이 중요했구나!" 팀장님이 고개를 끄덕였습니다.

실전 팁

💡 - 피처 중요도는 상관관계일 뿐 인과관계가 아닙니다

  • SHAP 라이브러리를 사용하면 더 정교한 피처 중요도 분석이 가능합니다

10. 파이프라인 구축

드디어 마지막 단계입니다. 김개발 씨가 지금까지 배운 모든 기법을 적용하려고 하니 코드가 너무 복잡해졌습니다.

전처리, 피처 선택, 차원 축소, 모델 학습... 순서도 헷갈리고 새 데이터가 오면 같은 과정을 반복해야 합니다.

박시니어 씨가 마지막 비법을 전수합니다. "파이프라인으로 묶으면 깔끔해집니다."

파이프라인은 여러 전처리 단계와 모델을 하나로 연결하는 기법입니다. 마치 공장의 조립 라인처럼, 데이터가 순서대로 각 단계를 거쳐 최종 결과물이 됩니다.

파이프라인을 사용하면 코드가 깔끔해지고, 데이터 누수 방지에도 도움이 됩니다.

다음 코드를 살펴봅시다.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris

# 데이터 로드
X, y = load_iris(return_X_y=True)

# 파이프라인 구축: 표준화 -> PCA -> 분류
pipeline = Pipeline([
    ('scaler', StandardScaler()),      # 1단계: 표준화
    ('pca', PCA(n_components=2)),      # 2단계: 차원 축소
    ('classifier', RandomForestClassifier(random_state=42))  # 3단계: 분류
])

# 교차 검증으로 성능 평가
scores = cross_val_score(pipeline, X, y, cv=5)
print(f"교차 검증 점수: {scores}")
print(f"평균 정확도: {scores.mean():.2%} (+/- {scores.std()*2:.2%})")

# 전체 데이터로 학습
pipeline.fit(X, y)
print("파이프라인 학습 완료")

김개발 씨의 코드는 점점 스파게티처럼 엉켜갔습니다. StandardScaler 적용하고, SelectKBest 적용하고, PCA 적용하고, 모델 학습하고...

각 단계마다 fit과 transform을 따로 호출해야 했습니다. 더 큰 문제는 새 데이터가 올 때마다 같은 과정을 반복해야 한다는 것입니다.

실수로 순서를 바꾸거나 단계를 빠뜨리면 결과가 완전히 달라집니다. 박시니어 씨가 파이프라인을 소개합니다.

"여러 단계를 하나의 객체로 묶을 수 있어요. 마치 레고 블록처럼요." 파이프라인은 sklearn에서 제공하는 강력한 기능입니다.

여러 변환기(transformer)와 최종 추정기(estimator)를 순서대로 연결합니다. 비유하자면, 파이프라인은 공장의 조립 라인과 같습니다.

자동차 공장에서 철판이 들어가면 용접, 도장, 조립 단계를 거쳐 완성된 자동차가 나옵니다. 각 단계는 순서대로 진행되고, 한 단계의 출력이 다음 단계의 입력이 됩니다.

파이프라인도 똑같이 작동합니다. 위 코드에서 Pipeline 객체를 생성할 때 리스트로 각 단계를 전달합니다.

각 단계는 (이름, 변환기) 형태의 튜플입니다. 마지막 단계만 추정기(모델)가 될 수 있고, 나머지는 모두 transform 메서드를 가진 변환기여야 합니다.

파이프라인의 가장 큰 장점은 데이터 누수 방지입니다. 교차 검증을 할 때, 테스트 데이터의 정보가 학습에 사용되면 안 됩니다.

파이프라인 없이 전체 데이터로 StandardScaler를 먼저 학습시키면 테스트 데이터의 평균과 분산이 반영됩니다. 하지만 파이프라인을 사용하면 각 폴드에서 자동으로 올바른 순서로 처리됩니다.

실제 현업에서 파이프라인은 모델 배포에도 유용합니다. 학습된 파이프라인을 저장해두면, 새 데이터가 올 때 pipeline.predict(new_data) 한 줄로 전처리부터 예측까지 모두 처리됩니다.

주의할 점은 파이프라인의 각 단계가 이전 단계의 출력과 호환되어야 한다는 것입니다. 예를 들어 PCA는 밀집 행렬을 반환하므로, 그 뒤에 희소 행렬을 기대하는 변환기를 넣으면 오류가 발생합니다.

김개발 씨는 파이프라인으로 코드를 정리했습니다. 수십 줄이던 코드가 몇 줄로 줄었습니다.

박시니어 씨가 웃으며 말했습니다. "이제 진짜 개발자 같네요!"

실전 팁

💡 - GridSearchCV와 파이프라인을 함께 사용하면 전처리 파라미터까지 최적화할 수 있습니다

  • 파이프라인 저장에는 joblib.dump를 사용하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#FeatureSelection#DimensionalityReduction#PCA#MachineLearning#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.