머신러닝 기초 (15) - 차원축소: PCA
특성이 많으면 왜 곤란한지, PCA가 분산을 최대한 보존하는 새 축을 어떻게 찾는지, explained_variance_ratio_로 각 축이 설명하는 분산 비율을 어떻게 읽는지를 breast_cancer 데이터의 2차원 투영으로 정리한 15편입니다.
머신러닝 기초 시리즈의 15편입니다. 14편의 군집화에 이어, 차원축소를 다룹니다.
14편의 군집화도 정답 없이 데이터의 구조를 찾는 작업이었습니다. 이번 차원축소도 같은 비지도학습이지만 목표가 다릅니다. 군집화가 표본을 묶는다면, 차원축소는 특성의 수를 줄입니다. 특성 30개짜리 데이터를 2개나 3개짜리로 바꾸되, 원래 데이터가 담고 있던 정보를 최대한 지키는 것이 목표입니다.
이 편은 왜 차원을 줄이는지를 먼저 세우고, 가장 널리 쓰는 방법인 PCA(주성분 분석)가 무엇을 하는지를 정리합니다. 그다음 load_breast_cancer의 30개 특성을 2개로 압축해, 각 축이 원래 정보를 얼마나 담는지 직접 확인합니다.
왜 차원을 줄이나
특성의 수를 차원이라고 부릅니다. 특성이 30개면 30차원, 100개면 100차원입니다. 차원을 줄이려는 이유는 크게 셋입니다.
- 시각화: 사람은 2차원이나 3차원까지만 그림으로 볼 수 있습니다. 30차원 데이터의 전체 모양은 눈으로 확인할 방법이 없습니다. 30차원을 2차원으로 눌러 그리면, 표본들이 어떻게 뭉치고 갈라지는지를 한 장의 그림으로 볼 수 있습니다.
- 노이즈 제거: 특성 중에는 실제 신호는 거의 없이 잡음만 담은 것들이 섞여 있습니다. 정보가 많은 축만 남기고 나머지를 버리면, 잡음이 함께 걸러지기도 합니다.
- 계산량 감소: 특성이 적으면 모델 학습이 빠르고, 저장 공간도 덜 듭니다. 뒤에서 보듯 30개 특성의 정보 대부분을 10개 안쪽으로 담을 수 있다면, 이후 학습을 더 가볍게 할 수 있습니다.
여기에 하나 더, 차원이 지나치게 높으면 학습 자체가 어려워지는 문제가 있습니다. 차원이 늘어날수록 같은 수의 표본이 공간에 더 성기게 흩어집니다. 공간의 부피는 차원에 따라 기하급수로 커지는데 표본 수는 그대로이기 때문입니다. 그 결과 표본들 사이의 거리가 서로 비슷해져 “가깝다”와 “멀다”의 구분이 흐려지고, 같은 성능을 내려면 필요한 데이터가 폭발적으로 늘어납니다. 이 현상을 차원의 저주라고 부릅니다. 차원축소는 이 저주를 완화하는 한 가지 방법이기도 합니다.
PCA: 분산을 최대한 보존하는 축
차원을 줄이는 가장 단순한 방법은 특성 몇 개를 그냥 버리는 것입니다. 하지만 버린 특성에 정보가 있었다면 그만큼 손해입니다. PCA는 특성을 버리는 대신, 특성들을 조합한 새 축을 만듭니다.
기준은 분산입니다. 어떤 방향으로 데이터를 바라볼 때 값들이 가장 넓게 퍼지는지, 즉 분산이 가장 큰 방향을 첫 번째 축으로 잡습니다. 이 축을 첫 번째 주성분이라고 부릅니다. 그다음, 첫 축과 겹치지 않는(수직인) 방향 중에서 다시 분산이 가장 큰 방향을 두 번째 주성분으로 잡습니다. 이런 식으로 분산이 큰 순서대로 축을 뽑아 갑니다.
분산을 기준으로 삼는 이유는, 분산이 큰 방향에 데이터를 구분 짓는 정보가 많다고 보기 때문입니다. 모든 표본이 거의 같은 값을 갖는 방향(분산이 작은 방향)은 버려도 정보 손실이 적습니다. 그래서 앞쪽 주성분 몇 개만 남기고 뒤를 버리면, 정보를 최대한 지키면서 차원을 줄일 수 있습니다.
각 주성분이 원래 데이터의 분산을 얼마나 설명하는지는 explained_variance_ratio_로 확인합니다. 첫 주성분이 0.44라면 전체 분산의 44%를 그 축 하나가 담고 있다는 뜻입니다. 이 값을 앞에서부터 더해 가면, 주성분 몇 개로 원래 정보의 몇 퍼센트를 지킬 수 있는지 알 수 있습니다.
표준화가 먼저다
PCA는 분산이 큰 방향을 찾습니다. 그런데 분산의 크기는 특성의 단위에 좌우됩니다. 12편에서 봤듯 load_breast_cancer의 특성들은 스케일이 제각각입니다. 면적(mean area)은 수백에서 수천 단위이고, 매끄러움(mean smoothness)은 0.1 언저리입니다.
이 상태로 PCA를 돌리면, 값의 범위가 큰 면적 특성이 분산도 압도적으로 크게 나옵니다. PCA는 그 방향을 첫 주성분으로 잡아 버립니다. 실제로 정보가 많아서가 아니라 단지 단위가 커서 뽑힌 축입니다. 그래서 PCA 전에는 표준화가 사실상 필수입니다. StandardScaler로 각 특성을 평균 0, 표준편차 1로 맞추면, 모든 특성이 같은 기준 위에서 분산을 겨루게 됩니다.
트리 기반 모델은 스케일링이 필요 없었지만(12편), PCA는 다릅니다. PCA는 특성 사이의 분산을 직접 비교해 축을 정하므로, 표준화를 건너뛰면 스케일이 큰 특성이 주성분을 지배합니다.
코드: 30차원을 2차원으로
load_breast_cancer의 30개 특성을 표준화한 뒤 2개의 주성분으로 투영합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
X, y = load_breast_cancer(return_X_y=True) # 표본 569, 특성 30
# PCA 전 표준화: 특성마다 스케일이 제각각이므로 먼저 맞춘다
X_scaled = StandardScaler().fit_transform(X)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled) # (569, 30) 을 (569, 2) 로 압축
print(X_pca.shape) # (569, 2)
print(pca.explained_variance_ratio_.round(3)) # [0.443 0.19 ]
print(pca.explained_variance_ratio_.sum().round(3)) # 0.632
첫 주성분이 전체 분산의 44.3%, 두 번째가 19.0%를 설명합니다. 두 축을 합치면 63.2%입니다. 30개였던 특성을 2개로 줄였는데도 원래 정보의 6할 이상이 남아 있다는 뜻입니다. 이제 이 2차원 좌표로 데이터를 그림으로 그릴 수 있습니다.
1
2
3
4
5
6
import matplotlib.pyplot as plt
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap="coolwarm", s=15)
plt.xlabel("PC1") # 첫 번째 주성분
plt.ylabel("PC2") # 두 번째 주성분
plt.show()
이 그림에서 두 종류(악성, 양성)의 표본이 좌우로 상당히 갈라져 보입니다. 라벨(y)은 색을 칠하는 데만 썼을 뿐, PCA는 라벨을 전혀 보지 않고 좌표를 잡았습니다. 그런데도 클래스가 갈라진다는 것은, 첫 주성분 방향에 두 클래스를 구분하는 정보가 많이 담겨 있다는 뜻입니다.
두 축으로 63.2%를 담았다면, 몇 개를 더 쓰면 얼마나 회복될까요. n_components를 지정하지 않으면 모든 주성분이 나옵니다. 설명분산을 앞에서부터 누적해 봅니다.
1
2
3
4
5
6
7
import numpy as np
pca_full = PCA().fit(X_scaled)
cum = np.cumsum(pca_full.explained_variance_ratio_)
print((cum >= 0.90).argmax() + 1) # 7 (누적 90% 를 넘는 최소 주성분 수)
print((cum >= 0.95).argmax() + 1) # 10 (누적 95% 를 넘는 최소 주성분 수)
주성분 7개면 원래 분산의 90%, 10개면 95%를 담습니다. 30개 특성의 정보 대부분이 10개 축 안에 들어간다는 뜻입니다. 시각화가 목적이면 2개, 계산량을 줄이는 것이 목적이면 이렇게 누적 비율을 보고 필요한 개수를 정하면 됩니다. 흔히 90%나 95% 같은 기준을 두고 그 선을 넘는 최소 개수를 고릅니다.
한계: 선형 축소이고 해석이 어렵다
PCA는 두 가지 한계가 있습니다.
첫째, PCA가 만드는 축은 원래 특성들의 선형 결합입니다. 즉 특성들을 곧게 더하고 빼서 만든 방향입니다. 데이터가 곡면이나 나선처럼 휘어진 구조로 놓여 있으면, 곧은 축으로는 그 구조를 제대로 펴지 못합니다. 이런 비선형 구조를 담아 시각화하려면 t-SNE나 UMAP 같은 비선형 차원축소를 씁니다.
둘째, 주성분은 해석하기 어렵습니다. 첫 주성분은 “면적 0.2배 더하기 반지름 0.18배 더하기 매끄러움 0.15배 빼기 …” 같은 30개 특성의 혼합입니다. 원래 특성 하나가 아니라 여러 특성이 뒤섞인 값이라, “이 축이 무엇을 뜻하는가”를 한마디로 말하기 어렵습니다. 시각화나 압축에는 문제가 없지만, 어떤 특성이 중요한지를 설명해야 하는 상황에는 맞지 않습니다.
지도학습의 전처리로도 쓴다
PCA는 그 자체로 데이터를 들여다보는 도구이기도 하지만, 지도학습의 전처리 단계로도 자주 들어갑니다. 특성이 아주 많을 때 PCA로 먼저 차원을 줄인 다음 모델을 학습시키면, 학습이 빨라지고 노이즈가 걸러져 과대적합이 줄기도 합니다. 특성을 압축하는 전처리인 셈입니다.
이때 주의할 점은 13편에서 다룬 데이터 누수입니다. PCA의 축은 데이터로부터 학습됩니다. 그러니 표준화와 마찬가지로, PCA도 훈련 데이터로만 fit하고 그 축을 검증, 테스트 데이터에 transform으로 적용해야 합니다. 이 순서를 지키려면 Pipeline으로 StandardScaler, PCA, 모델을 묶는 것이 가장 안전합니다. 파이프라인은 교차검증의 매 분할마다 훈련 몫으로만 fit을 다시 하므로, 검증 데이터의 정보가 새어들지 않습니다.
정리
| 개념 | 한 줄 요약 |
|---|---|
| 차원축소 | 정보를 최대한 지키며 특성 수를 줄이는 비지도학습 |
| 줄이는 이유 | 시각화, 노이즈 제거, 계산량 감소 |
| 차원의 저주 | 차원이 커지면 표본이 성겨져 거리 구분이 흐려짐 |
| PCA | 분산이 큰 순서로 새 축(주성분)을 뽑아 앞쪽만 남김 |
| explained_variance_ratio_ | 각 주성분이 설명하는 분산 비율. 누적으로 보존율 확인 |
| 표준화 | PCA 전 필수. 안 하면 스케일 큰 특성이 주성분을 지배 |
| 한계 | 선형 축소라 휜 구조에 약하고, 주성분 해석이 어려움 |
| 비선형 대안 | t-SNE, UMAP |
| 전처리 활용 | 특성 압축용. 누수 방지 위해 Pipeline으로 train에만 fit |