콘텐츠로 이동

P4-18.1 차원 축소(dimensionality reduction)

Section ID: P4-18.1 Version: v2026.07.20

P4-17에서는 클러스터링(clustering)으로 어떤 묶음이 숨어 있는가를 물었습니다. 여기서는 다른 질문을 붙잡습니다.

특징(feature)이 너무 많아 구조를 읽기 어렵다면, 그 표현 자체를 더 적은 축으로 다시 만들 수는 없을까?

이 질문이 차원 축소(dimensionality reduction)의 출발점입니다. 차원 축소는 많은 특징을 그냥 버리는 기술이 아니라, 원래 표현이 너무 복잡할 때 더 읽기 쉬운 축으로 다시 표현하는 일에 가깝습니다.

이 절은 차원(dimension), PCA(principal component analysis), 그리고 왜 고유값(eigenvalue)과 고유벡터(eigenvector)가 여기서 나오나를 같은 장난감 데이터 장면으로 묶어 설명합니다. 뒤 절 P4-18.2에서는 이렇게 만든 그림을 어디까지 믿어야 하는지와 정보 손실을 이어서 닫습니다.

차원 축소(dimensionality reduction)에서 닫을 질문

이 절은 다음 질문에 답합니다.

  • 차원(dimension)은 머신러닝에서 무엇을 뜻하는가?
  • 왜 특징 수가 많으면 학습과 해석이 어려워질 수 있는가?
  • 차원 축소는 어떤 문제를 완화하려고 하는가?
  • PCA는 어떤 대표 직관을 보여 주는가?
  • 왜 분산(variance), 직교(orthogonal), 고유값, 고유벡터가 PCA 설명에 함께 나오는가?
  • kernel PCA와 Truncated SVD는 PCA와 어떤 감각 차이로 구분하면 좋은가?

이 절은 입문적으로 왜 차원을 줄이려 하는가PCA가 무엇을 하는 계산인가를 붙잡는 데 초점을 둡니다. 시각화 결과 해석, t-SNE·UMAP, 재구성 오차(reconstruction error), trustworthiness는 다음 절 P4-18.2에서 이어집니다.

차원 축소(dimensionality reduction)에서 남길 판단 기준

  • 차원 축소를 특징 공간을 더 적은 축으로 다시 표현하는 일로 설명할 수 있습니다.
  • PCA를 데이터가 크게 변하는 방향을 새 축으로 다시 잡는 방법으로 설명할 수 있습니다.
  • 첫 번째 성분과 두 번째 성분이 왜 서로 직교해야 하는지 말할 수 있습니다.
  • 고유벡터는 새 축의 방향, 고유값은 그 축이 설명하는 변동 크기로 읽을 수 있습니다.

왜 이 절이 필요한가

머신러닝에서는 특징 수가 빠르게 늘어납니다.

  • 고객 데이터에는 수십 개 수치 지표가 붙을 수 있습니다.
  • 문서 데이터에는 수천 개 단어 특징이 붙을 수 있습니다.
  • 이미지 데이터는 픽셀 수만큼 특징이 생깁니다.

이때 독자는 흔히 이렇게 생각합니다.

  • 특징이 많을수록 더 자세한 것 아닌가?
  • 그런데 왜 오히려 구조를 읽기 어려워지는가?

바로 여기서 차원 축소가 필요해집니다. 특징 수가 늘어날수록 정보는 많아지지만, 사람은 그 구조를 한 번에 붙잡기 어려워집니다. 그래서 차원 축소는 정보를 완전히 대체하기보다 지금 이 표현이 너무 복잡한가를 먼저 점검하고, 큰 흐름을 다시 읽을 축을 만드는 도구로 등장합니다.

하나의 장면으로 먼저 보기

이 절에서는 아래처럼 두 특징이 거의 함께 움직이는 작은 데이터를 계속 다시 봅니다.

샘플 월 방문 수 x1 평균 구매 금액 x2
A 2.0 2.1
B 3.0 3.2
C 4.0 3.9
D 5.0 5.1

이 표는 방문 수가 높은 고객일수록 평균 구매 금액도 대체로 함께 올라간다는 흐름을 갖습니다. 아직은 축이 두 개뿐이라 눈으로도 읽을 수 있지만, 여기서 이미 PCA가 왜 필요한지의 핵심이 보입니다.

  1. 두 특징이 거의 같은 방향으로 움직입니다.
  2. 그러면 굳이 두 축을 따로 들고 있지 않아도 큰 흐름은 하나의 새 축으로 요약할 수 있습니다.
  3. 대신 두 축이 조금씩 어긋난 세부 차이는 약해질 수 있습니다.

즉, 차원 축소는 복잡한 표를 몇 개 축으로 눌러 담는 일이 아니라, 무엇을 큰 흐름으로 보고 무엇을 세부 차이로 남길지 정하는 일입니다.

차원(dimension)은 이 장면에서 무엇을 뜻하나

머신러닝 문맥에서 차원은 보통 한 샘플을 설명하는 축의 수, 즉 특징 수와 연결됩니다.

위 표에서 고객 한 명은 월 방문 수, 평균 구매 금액 두 값으로 표현되므로 2차원 데이터입니다. 여기에 최근 로그인 일수를 하나 더 붙이면 3차원, 반품 비율까지 붙이면 4차원이 됩니다.

이를 아주 단순하게 그리면 다음과 같습니다.

flowchart TB
  A["특징 1개<br/>축 1개"]
  B["특징 2개<br/>축 2개"]
  C["특징 3개<br/>축 3개"]
  D["특징이 많음<br/>직접 시각화가 어려움"]

  A --> B --> C --> D

차원이라는 말을 여기서는 수학 기호보다 데이터를 보는 좌표 축의 개수로 먼저 붙잡으면 충분합니다. 특징이 하나 늘 때마다, 데이터를 설명하는 축도 하나 늘어납니다.

왜 특징이 많아지면 어려워지는가

특징이 많아지면 표현력은 커질 수 있지만, 동시에 세 가지 어려움이 생깁니다.

  1. 사람이 구조를 상상하기 어려워집니다.
  2. 계산 비용이 커질 수 있습니다.
  3. 서로 겹치는 정보가 많아질 수 있습니다.

예를 들어 아래 값들은 완전히 독립적이지 않을 수 있습니다.

  • 월 구매 금액
  • 연 구매 금액
  • 구매 건수
  • 평균 주문 금액

이때 문제는 특징 수가 많다보다 정말 새로운 정보가 얼마나 있는가입니다. 서로 거의 같은 방향으로 움직이는 특징이 많다면, 원래 표는 길지만 실제로 읽어야 할 큰 흐름은 몇 개 안 될 수도 있습니다.

그래서 차원 축소는 단순한 계산 기법이 아니라, 지금 표현이 불필요하게 비대해졌는가를 다시 묻는 단계가 됩니다.

차원 축소는 무엇을 완화하려 하나

scikit-learn 사용자 가이드는 PCA를 다변량 데이터셋을 연속적인 직교 성분(orthogonal components)으로 분해하고, 가장 많은 분산을 설명하는 방향을 찾는 방법으로 설명합니다.

입문적으로는 차원 축소가 다음 문제를 완화하려는 시도로 읽힙니다.

현재 어려움 차원 축소가 주려는 도움
특징이 너무 많아 구조가 한눈에 안 보인다 더 적은 축으로 큰 흐름을 다시 본다
특징들이 서로 비슷하게 움직인다 겹치는 변동을 몇 개 성분으로 묶어 본다
시각화가 어렵다 2D, 3D 수준에서 거친 구조를 먼저 본다
후속 모델 입력이 너무 크다 더 작은 표현으로 바꾸어 다음 단계를 단순하게 만든다

이 장면을 흐름으로 줄이면 다음과 같습니다.

flowchart TB
  A["원래 특징이 많다"]
  B["직접 시각화가 어렵다"]
  C["겹침과 상관이 있을 수 있다"]
  D["더 적은 새 축을 만든다"]
  E["구조를 보거나 이후 모델에 사용한다"]

  A --> B
  A --> C
  B --> D
  C --> D
  D --> E

핵심은 원래 특징을 무작정 버린다가 아니라 새 축으로 다시 읽는다입니다.

PCA는 이 장면을 어떻게 다시 표현하나

PCA는 데이터가 실제로 많이 퍼진 방향을 새 축으로 다시 잡으려는 방법입니다. 위 장난감 데이터에서는 x1x2가 거의 함께 커지므로, 두 축을 각각 따로 보는 것보다 대각선 방향 하나가 더 중요한 흐름처럼 보입니다.

이 감각을 짧게 쓰면 다음과 같습니다.

  • 첫 번째 새 축: 데이터가 가장 많이 퍼진 방향
  • 두 번째 새 축: 첫 번째 축과 겹치지 않으면서 남은 변동을 설명하는 방향

비스듬한 타원 모양으로 점이 퍼져 있다고 생각하면, PCA는 원래 x축과 y축보다 타원의 긴 방향과 짧은 방향을 새 축으로 택하는 쪽에 가깝습니다.

flowchart TD
  A["원래 축"]
  B["데이터가 대각선으로 퍼짐"]
  C["더 잘 맞는 축으로 회전"]
  D["주성분 1이 더 많은 분산을 설명"]
  E["주성분 2가 남은 직교 분산을 설명"]

  A --> B --> C --> D --> E

이 장면에서 중요한 것은 축을 돌린다는 표현 자체보다, 데이터가 실제로 많이 변하는 방향을 표현의 중심으로 다시 세운다는 점입니다.

왜 분산(variance)과 직교(orthogonal)가 같이 나오나

PCA는 무엇이 더 크게 변하는가를 먼저 봅니다. 그래서 분산이 중요합니다. 위 데이터에서 x1x2는 거의 함께 움직이므로, 두 값을 더한 방향에서는 큰 변화가 보이고, 둘의 차이를 보는 방향에서는 상대적으로 작은 변화가 보입니다.

이 감각을 보기 쉽게 하려고 평균 중심화(centering)한 뒤 두 가지 임시 축을 같이 적어 보면 다음과 같습니다.

샘플 중심화한 x1 중심화한 x2 큰 흐름 축 비슷한 값 x1_c + x2_c 차이 축 비슷한 값 x1_c - x2_c
A -1.5 -1.48 -2.98 -0.02
B -0.5 -0.38 -0.88 -0.12
C 0.5 0.33 0.83 0.18
D 1.5 1.53 3.03 -0.03

여기서 독자가 읽어야 할 것은 두 가지입니다.

  1. x1_c + x2_c 쪽은 값 범위가 크므로 큰 변동을 잘 모읍니다.
  2. x1_c - x2_c 쪽은 값 범위가 작으므로 두 축이 어긋나는 작은 차이를 주로 담습니다.

이 분리가 흐름으로는 다음처럼 읽힙니다.

flowchart TD
  A["원래 특징<br/>x1, x2"]
  B["평균 중심화"]
  C["함께 변하는 흐름이 큼"]
  D["PC1 비슷한 축<br/>큰 결합 변동"]
  E["엇갈림은 작음"]
  F["PC2 비슷한 축<br/>남은 작은 차이"]

  A --> B
  B --> C --> D
  B --> E --> F

이제 왜 직교가 필요한지도 같이 보입니다. 첫 번째 성분이 이미 함께 커지고 함께 작아지는 방향을 설명했다면, 두 번째 성분은 그 설명을 그대로 반복하면 안 됩니다. 그래서 첫 번째 성분과 겹치지 않는 방향, 즉 직교하는 방향을 잡으려 합니다.

짧게 줄이면 다음 한 문장입니다.

두 번째 성분은 첫 번째 성분이 이미 설명한 변동을 다시 복사하지 않아야 한다.

고유값과 고유벡터는 여기서 왜 나오나

PCA를 수식으로 쓰기 시작하면 공분산 행렬(covariance matrix)의 고유벡터와 고유값이 바로 등장합니다. 이유는 단순합니다.

분산이 큰 방향을 계산으로 찾으려면, 어떤 방향 벡터가 그 퍼짐을 가장 잘 설명하는지 찾아야 하기 때문이다.

입문적으로는 다음 네 단계만 잡으면 충분합니다.

  1. 데이터를 평균 중심화한다.
  2. 각 축이 함께 얼마나 움직이는지 공분산 행렬을 만든다.
  3. 그 공분산 구조에서 변동을 가장 잘 설명하는 방향을 찾는다.
  4. 그 방향을 고유벡터, 그 방향이 설명하는 변동 크기를 고유값으로 읽는다.

공식은 보통 다음처럼 적습니다.

\[ \Sigma v = \lambda v \]

여기서:

  • \(\Sigma\): 공분산 행렬
  • \(v\): 새 축 후보 방향
  • \(\lambda\): 그 방향이 설명하는 변동 크기

이 식은 어떤 방향 \(v\)를 공분산 구조에 넣었을 때, 방향은 그대로 유지되면서 크기만 \(\lambda\)배로 바뀌는가를 보는 식입니다. 방향은 그대로인데 크기만 바뀐다면, 그 방향은 데이터가 실제로 퍼져 있는 고유한 축 후보로 읽을 수 있습니다.

위 장난감 데이터에서는 두 특징이 거의 함께 움직인다는 구조 때문에, [1, 1]에 가까운 방향이 첫 번째 고유벡터 후보처럼 읽히고, [1, -1]에 가까운 방향은 그와 직교하는 두 번째 축 후보처럼 읽힙니다.

이 연결을 도식으로 줄이면 다음과 같습니다.

flowchart TD
  A["공분산 행렬"]
  B["같은 방향으로 유지되는 축을 찾음"]
  C["고유벡터<br/>새 축의 방향"]
  D["고유값<br/>그 축이 설명하는 분산"]

  A --> B
  B --> C
  B --> D
PCA에서 보고 싶은 것 수학적으로 대응되는 것
가장 중요한 첫 새 축 가장 큰 고유값에 대응하는 고유벡터
그다음 새 축 다음 고유값에 대응하는 고유벡터
축마다 얼마나 큰 변동을 설명하는가 각 고유값의 크기

여기서 끝까지 유도를 외우는 것이 목표는 아닙니다. 중요한 것은 고유벡터가 새 축의 방향이고, 고유값이 그 축의 설명력 크기라는 연결이 실제 데이터 장면과 붙는가입니다.

PCA, kernel PCA, Truncated SVD는 어떤 감각으로 갈라지나

차원 축소가 모두 같은 종류의 질문을 푸는 것은 아닙니다.

방법 먼저 붙잡을 감각 언제 더 자연스럽게 떠오르는가
PCA 선형(linear) 새 축으로 큰 분산 방향을 요약한다 수치 특징의 큰 전반적 변동을 요약하고 싶을 때
kernel PCA 원래 공간에서 직선 축만으로 잘 안 보이는 구조를 다른 공간에서 펼쳐 보려 한다 곡선형, 비선형 구조를 더 잘 보고 싶을 때
Truncated SVD 큰 행렬을 몇 개 주요 성분으로 압축한다 희소 행렬, 텍스트-단어 행렬 같은 큰 행렬을 다룰 때

짧게 줄이면:

  • PCA: 직선적인 새 축으로 다시 본다
  • kernel PCA: 비선형 구조도 더 잘 펼쳐 보려 한다
  • Truncated SVD: 큰 행렬을 몇 개 성분으로 압축한다

현재 절에서는 이름을 외우기보다 선형 재표현, 비선형 재표현, 행렬 압축이라는 세 감각을 구분해 두는 편이 더 중요합니다.

차원을 줄이면 무엇이 좋아지고 무엇이 사라지나

차원 축소는 늘 교환(trade-off)을 만듭니다.

얻는 것 잃을 수 있는 것
더 읽기 쉬운 표현 원래 특징별 세부 차이
더 쉬운 시각화 작은 국소 변동
더 작은 후속 입력 축별 직접 해석성
중복 정보 압축 특정 특징의 업무 의미

따라서 차원 축소 뒤에는 항상 같은 질문으로 돌아와야 합니다.

지금 단순해진 표현이, 내가 풀려는 문제에는 충분한가?

사례 및 예시

사례 1. 수십 개 고객 지표를 그대로 들고는 흐름이 안 보일 때

고객 분석 팀이 방문 수, 구매 금액, 최근 접속, 반품 비율, 카테고리 선호도처럼 수십 개 지표를 한꺼번에 보고 있다고 해 보겠습니다. 원래 표는 풍부하지만, 서로 비슷하게 움직이는 지표도 많아서 한눈에 큰 구조를 읽기 어렵습니다. 이때 차원 축소는 지표를 몇 개 새 축으로 다시 묶어 활동성, 구매 규모, 최근성처럼 큰 흐름을 먼저 보게 도와줍니다. 그래서 팀은 원래 표에서 안 보이던 구조를 먼저 읽고, 이후 군집화나 후속 모델 입력을 더 단순하게 준비할 수 있습니다.

flowchart TD
  A["고객 특징이 많다"]
  B["표 전체를 읽기 어렵다"]
  C["몇 개의 주성분으로 압축"]
  D["활동성 같은 축"]
  E["지출 규모 축"]
  F["최근성 같은 축"]
  G["구조를 더 쉽게 본다"]
  H["군집화나 다운스트림 모델에 재사용"]

  A --> B --> C
  C --> D
  C --> E
  C --> F
  D --> G
  E --> G
  F --> G
  G --> H

이 장면을 review 메모로 적으면 다음처럼 남길 수 있습니다.

원래 표현에서 막히던 점 새 축에서 먼저 보인 것 바로 다음 검토
지표가 많아 전체 구조가 한눈에 안 들어왔다 활동성, 구매 규모처럼 큰 흐름이 더 또렷해졌다 이 축이 실제 고객 차이를 설명하는지 원래 특징으로 다시 확인
비슷해 보이는 지표가 많았다 겹치는 변동이 몇 개 성분으로 압축됐다 압축 때문에 사라진 세부 차이가 중요한지 점검

연습 및 예제

이번 예제는 앞에서 본 네 샘플을 그대로 써서, 실제 PCA로 2차원 데이터를 1차원으로 줄이면 무엇이 남고 무엇이 사라지는가를 확인하는 장난감 실습입니다.

  • 문제 상황: 두 특징이 거의 함께 움직일 때, 실제로 1개의 주성분만 남겨도 큰 흐름이 유지되는지 보고 싶다
  • 입력(input): 방문 수와 평균 구매 금액 두 특징
  • 기대 출력(output): 1차원 주성분 점수, 설명 분산 비율, 복원값
  • 확인할 개념:
  • PCA는 원래 특징을 1개의 새 축으로 투영할 수 있다
  • 1개 성분만 남겨도 큰 변동은 유지될 수 있지만, 복원값은 원본과 완전히 같지 않을 수 있다
# PCA로 2차원 특징을 1차원 주성분으로 줄이고 복원했을 때 무엇이 남는지 확인하는 예제입니다.
import numpy as np
from sklearn.decomposition import PCA

sample_ids = np.array(["A", "B", "C", "D"])
X = np.array([
    [2.0, 2.1],
    [3.0, 3.2],
    [4.0, 3.9],
    [5.0, 5.1],
])

pca = PCA(n_components=1)
X_reduced = pca.fit_transform(X)
X_restored = pca.inverse_transform(X_reduced)

print("principal axis:", np.round(pca.components_, 3))
print("explained variance ratio:", np.round(pca.explained_variance_ratio_, 3))

for idx, reduced, restored in zip(sample_ids, X_reduced, X_restored):
    print(
        idx,
        "reduced =", np.round(reduced, 3),
        "restored =", np.round(restored, 3),
    )

실행 결과는 다음과 같습니다.

1
2
3
4
5
6
principal axis: [[0.707 0.707]]
explained variance ratio: [0.996]
A reduced = [-2.112] restored = [2.007 2.086]
B reduced = [-0.662] restored = [3.032 3.111]
C reduced = [0.52] restored = [3.868 3.947]
D reduced = [2.254] restored = [5.093 5.172]

이 결과에서 읽어야 할 것은 다음입니다.

  1. principal axis가 거의 [0.707, 0.707]이므로, 두 특징이 비슷한 비중으로 함께 움직이는 방향이 첫 번째 주성분으로 잡혔습니다.
  2. explained variance ratio0.996이므로, 1개의 성분만으로도 전체 변동의 대부분이 유지됩니다.
  3. restored 값은 원래 값과 매우 비슷하지만 완전히 같지는 않으므로, 차원을 줄이면 큰 흐름은 남겨도 세부 차이는 조금씩 사라질 수 있음을 보여 줍니다.

값 하나 바꿔 보기: 함께 움직이지 않으면 복원 오차가 커진다

이번에는 마지막 샘플의 두 번째 특징만 크게 낮춰, 두 특징이 같은 흐름으로 움직이지 않는 경우를 일부러 만듭니다.

# 두 특징이 함께 움직이지 않는 샘플을 넣었을 때 PCA 복원 오차가 어떻게 커지는지 보는 예제입니다.
import numpy as np
from sklearn.decomposition import PCA

sample_ids = np.array(["A", "B", "C", "D"])
X = np.array([
    [2.0, 2.1],
    [3.0, 3.2],
    [4.0, 3.9],
    [5.0, 2.5],
])

pca = PCA(n_components=1)
X_reduced = pca.fit_transform(X)
X_restored = pca.inverse_transform(X_reduced)

row_errors = np.sum((X - X_restored) ** 2, axis=1)

print("principal axis:", np.round(pca.components_, 3))
print("explained variance ratio:", np.round(pca.explained_variance_ratio_, 3))

for idx, reduced, restored, err in zip(sample_ids, X_reduced, X_restored, row_errors):
    print(
        idx,
        "reduced =", np.round(reduced, 3),
        "restored =", np.round(restored, 3),
        "row_error =", round(float(err), 3),
    )
1
2
3
4
5
6
principal axis: [[0.894 0.449]]
explained variance ratio: [0.904]
A reduced = [-1.937] restored = [2.075 1.959] row_error = 0.027
B reduced = [-0.555] restored = [3.311 2.578] row_error = 0.413
C reduced = [0.54] restored = [4.29  3.067] row_error = 0.736
D reduced = [1.952] restored = [5.554 3.698] row_error = 1.727

이번에는 D의 row_error가 가장 크게 나옵니다. 즉, 1개의 주성분만으로는 D의 엇갈린 패턴을 잘 복원하지 못한다는 뜻입니다. 이 장면은 차원 축소가 큰 흐름은 남겨도, 모든 샘플의 세부 차이를 똑같이 잘 보존하지는 않는다는 점을 Python 예제로 직접 보여 줍니다.

체크리스트

  • 차원이 데이터를 설명하는 축의 수, 즉 특징 수와 연결된다는 점을 설명할 수 있는가?
  • 지금 필요한 것이 묶음 찾기보다 표현 다시 만들기에 더 가까운가?
  • 차원 축소를 특징을 지우는 기술보다 표현을 다시 만드는 기술에 가깝다고 설명할 수 있는가?
  • 첫 번째 성분이 큰 흐름을, 두 번째 성분이 남은 차이를 담는다는 점을 설명할 수 있는가?
  • 고유벡터를 새 축의 방향, 고유값을 그 축의 설명력 크기로 연결해 말할 수 있는가?
  • 차원을 줄이면 읽기 쉬워지지만 세부 차이 일부는 약해질 수 있다는 점을 이해했는가?
  • 차원 축소 결과가 편리해 보여도, 원래 특징에서 사라진 차이가 없는지 다시 볼 준비가 되어 있는가?

출처와 참고 자료