지금까지는 텍스트(고차원 희소 벡터)를 그대로 클러스터링했다.
[STUDY/ML(Machine Learning)] - [ML] K-means clustering
[ML] K-means clustering
1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(centroid)을 그 군집에 속한 데이터들의 평균(mean) 위치로 잡아가면서 군집을
blog.chaenii.me
[STUDY/ML(Machine Learning)] - [ML] 실루엣 스코어(Silhouette Score) - 군집화 평가 지표
[ML] 실루엣 스코어(Silhouette Score) - 군집화 평가 지표
1. Silhouette Score?[STUDY/ML(Machine Learning)] - [ML] K-means clustering [ML] K-means clustering1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(c
blog.chaenii.me
신경망 기반 클러스터링(SOM)으로 넘어가기 전에,
고차원 데이터를 다루기 쉬운 저차원으로 줄이는 SVD/차원축소를 먼저 알아야 한다.
1. SVD(특이값분해)?
KMeans, 임베딩 같은 글에서 데이터를 계속 "벡터"와 "행렬"로 다루는데,
행렬을 다루다 보면 "이 복잡한 행렬을 더 단순한 형태로 쪼개고 싶다"는 상황이 자주 온다.
그게 행렬분해(matrix decomposition)이다.
그리고 SVD(Singular Value Decomposition, 특이값분해)는 그중 가장 널리 쓰이는 방법이다.
SVD/차원축소에서 이걸 어떻게 응용하는지 다루기 전에, SVD 자체가 뭘 하는 건지부터 짚고 간다.
행렬 - 공간을 바꾸는 변환
벡터에 행렬을 곱하면 그 벡터는 다른 위치로 움직인다.
쉽게 예를 들어보자.
평면 위의 점 하나에 어떤 2×2 행렬을 곱하면 그 점은 회전하거나 늘어나거나 찌그러진 위치로 이동한다.
이런 식으로 평면 전체(원 모양으로 늘어선 점들이라고 해보자)에 같은 행렬을 곱하면
원은 보통 한쪽으로 길쭉하게 늘어난 타원이 되고, 방향도 돌아간다.

즉 행렬 = 공간을 회전시키거나 늘리거나 줄이는 변환이라고 보면 된다.
SVD는 "이 변환이 정확히 어떤 회전과 어떤 늘이기로 이루어져 있는지"를 분해해서 보여주는 도구다.
SVD의 핵심 아이디어: 회전 → 확대 → 회전
아무리 복잡해 보이는 행렬 변환이라도, 항상 다음 세 단계로 쪼갤 수 있다:
$$
X = U \Sigma V^T
$$
- $V^T$ (회전): 먼저 입력 공간을 한 번 회전시켜 "가장 많이 늘어나는 방향"이 좌표축에 딱 맞게 정렬되도록 만든다.
- $\Sigma$ (확대/축소)
- 그렇게 정렬된 축들을 따라 각각 다른 비율로 늘리거나 줄인다.
- 이 비율들이 대각선에 놓이는데, 이게 바로 특이값(singular value)이다. (큰 것부터 순서대로 나열됨)
- $U$ (다시 회전): 마지막으로 결과를 다시 한 번 회전시켜서 최종 위치로 옮긴다.
비유하면: 종이를 한 번 돌리고, 가로세로 비율을 다르게 늘렸다가, 다시 한 번 돌리는 것.
이 세 동작만으로 어떤 모양의 변형이든 표현할 수 있다는 게 SVD의 핵심 주장이다.
계산
대칭행렬(transpose해도 자기 자신과 같은 행렬)을 예로 들어보자.
$$
A = \begin{pmatrix} 3 & 1 \\ 1 & 3 \end{pmatrix}
$$
이 행렬의 고유값(eigenvalue)을 구하면 $\lambda = 4$ 또는 $\lambda = 2$가 나오고(특성방정식 $(3-\lambda)^2 - 1 = 0$을 풀면 됨),
각 고유값에 대응하는 고유벡터를 정규화하면:
$$
Q = \begin{pmatrix} \frac{1}{\sqrt2} & \frac{1}{\sqrt2} \\ \frac{1}{\sqrt2} & -\frac{1}{\sqrt2} \end{pmatrix}, \qquad \Sigma = \begin{pmatrix} 4 & 0 \\ 0 & 2 \end{pmatrix}
$$
대칭행렬인 경우 $U = V = Q$가 되어서, $A = Q \Sigma Q^T$로 분해된다.
실제로 $Q \Sigma Q^T$를 계산해보면 정확히 원래 $A$로 돌아온다.
즉 "45도 방향으로는 4배 늘리고, 그 수직 방향으로는 2배 늘리는 변환"이 바로 행렬 $A$의 정체였다는 뜻.
주의: 이 예시는 대칭행렬이라서 $U=V$로 단순해졌을 뿐, 일반적인 행렬(특히 정사각형이 아닌 행렬)에서는 $U$와 $V$가 서로 다른 행렬이다.
특이값이 크다?
특이값은 "그 방향으로 얼마나 많이 늘어나는가"를 나타낸다.
- 특이값이 큰 방향: 원본 데이터에서 변동이 가장 큰(=가장 중요한 정보를 담은) 방향
- 특이값이 작은 방향: 거의 안 늘어나는, 미세한 노이즈에 가까운 방향
이 성질 때문에 "특이값이 작은 방향은 버려도 정보 손실이 적다"는 아이디어가 나오고,
이게 바로 SVD를 이용한 차원축소의 출발점이다.
고유값분해와의 차이
- 대칭행렬의 고유값분해(eigendecomposition) - 정사각 행렬에서만 가능
- 반면 SVD - 행과 열의 개수가 달라도(m \times n, m \ne n이어도) 항상 존재
→ TF-IDF처럼 보통 정사각형이 아닌(문서 수 × 단어 종류 수) 행렬에도 SVD는 바로 적용할 수 있다.
→ 머신러닝 파이프라인에서 고유값분해 대신 SVD를 쓰는 실용적인 이유.
SVD 정리
- 행렬은 "공간을 회전·확대·축소하는 변환"으로 볼 수 있다
- SVD는 어떤 행렬이든 "회전($V^T$) → 축별로 다르게 늘이기($\Sigma$) → 다시 회전($U$)"의 세 단계로 쪼개는 분해
- 특이값(대각선 값)은 각 방향으로 얼마나 늘어나는지를 나타내고, 클수록 더 중요한 변동 방향
- 정사각 행렬에만 되는 고유값분해와 달리, SVD는 어떤 모양의 행렬에도 항상 존재한다
SVD가 뭘 하는지 알았다면, 이제 이걸 실제로 어떻게 써먹는지,
즉 고차원 데이터를 저차원으로 압축하는 데 어떻게 쓰이는지 알아보자.
문제 상황
TF-IDF로 텍스트를 벡터화하면 차원이 수천~수만이 된다 (단어 종류만큼). 이런 고차원 희소(sparse) 벡터는:
- 계산이 무겁다: 차원이 클수록 거리 계산, 행렬 연산이 느려짐
- 일부 알고리즘은 희소행렬을 직접 못 다룬다: 예를 들어 SOM은 내부적으로 dense(밀집) 벡터 연산을 전제로 짜여 있어서, 수천 차원 희소행렬을 그대로 넣으면 메모리·속도 문제가 생긴다
- "차원의 저주(curse of dimensionality)": 차원이 높아질수록 거리 개념 자체가 무의미해지는 경향이 있다 (모든 점이 서로 비슷하게 멀어짐)
해결책: 정보를 최대한 보존하면서 차원을 확 줄인다.
SVD의 핵심 아이디어
임의의 행렬 $X$ (크기 $m \times n$)는 다음과 같이 분해된다:
$$
X = U \Sigma V^T
$$
- $U$: $m \times m$ 직교행렬 (좌측 특이벡터)
- $\Sigma$: $m \times n$ 대각행렬, 대각선에 특이값(singular value)들이 큰 것부터 순서대로 나열
- $V^T$: $n \times n$ 직교행렬 (우측 특이벡터)
→ 핵심: 특이값이 큰 순서대로 정렬되어 있다.
특이값이 클수록 그 방향(성분)이 원본 데이터의 "주요한 변동"을 더 많이 설명한다.
작은 특이값에 대응하는 방향은 노이즈에 가까운 미세한 변동일 가능성이 높다.
TruncatedSVD: 상위 k개만 남기기
전체 $\Sigma$를 다 쓰지 않고, 가장 큰 특이값 $k$개만 남기고 나머지를 버린다:
$$
X \approx U_k \Sigma_k V_k^T
$$
이러면 원본을 $n$차원에서 $k$차원으로 압축한 근사치를 얻는다.
$k$를 원본보다 훨씬 작게 잡으면(예: 8000차원 → 100차원) 정보 손실은 있지만 대부분의 "중요한 변동"은 보존된 채로 차원이 확 줄어든다.
이게 "최선의" 근사라는 보장이 있는가?
에카르트-영 정리(Eckart–Young theorem)에 의하면, 상위 $k$개의 특이값만으로 만든 $U_k\Sigma_k V_k^T$는 원본 행렬 $X$와의 차이(프로베니우스 노름 기준 재구성 오차)를 최소화하는, 랭크(rank)가 $k$인 행렬 중에서 가장 가까운 행렬이다:
$$
U_k\Sigma_k V_k^T = \arg\min_{\text{rank}(B)=k} \|X - B\|_F
$$
즉 "차원을 $k$개로 줄인다"는 제약 안에서 임의로 잘 줄인 게 아니라, 수학적으로 증명 가능한 최적해라는 뜻이다.
KMeans처럼 반복해서 수렴시키는 방식이 아니라, 한 번의 행렬분해로 바로 최적해가 나오는 "닫힌 형태(closed-form) 해"라는 점이 KMeans와의 중요한 차이다.
scikit-learn의 TruncatedSVD(n_components=k)가 이 작업을 한다. PCA(주성분분석)와 매우 비슷한 개념인데, PCA는 데이터를 먼저 평균중심화(centering)하는 반면 TruncatedSVD는 희소행렬에 그대로 적용 가능하다는 실전적 차이가 있다 (평균중심화를 하면 희소행렬이 dense가 되어버려서 메모리 이점이 사라짐).
정보 손실의 트레이드오프
$k$를 어떻게 잡느냐에 따라 트레이드오프가 갈린다:
- $k$를 작게 잡으면: 계산은 빨라지지만 정보 손실이 커짐
- $k$를 크게 잡으면: 정보는 더 보존되지만 차원축소의 이점이 줄어듦
"얼마나 정보를 보존하는가"는 특이값의 제곱이 전체 분산에서 차지하는 비율로 정량화할 수 있다 — $i$번째 특이값을 $\sigma_i$라 하면, 상위 $k$개를 썼을 때의 누적 분산 설명비율은:
$$
\text{설명비율}(k) = \frac{\sum_{i=1}^{k}\sigma_i^2}{\sum_{i=1}^{n}\sigma_i^2}
$$
e.g) 이 값이 0.9가 되는 $k$ → 원본 정보의 90%를 유지하면서 차원을 $k$개로 줄임.\
실전에서는 이 비율을 그래프로 그려보고(누적되는 지점이 완만해지는 곳을 보고), 혹은 이후 단계(SOM)의 계산 비용과 성능을 보고 경험적·실험적으로 $k$를 정하는 경우가 많다.


왜 TF-IDF 다음에, SOM 전에 위치하는가
파이프라인 순서:
텍스트 → TF-IDF (수천차원 희소행렬) → TruncatedSVD (100차원 dense) → SOM 입력
KMeans는 희소행렬을 그대로 받아도 잘 작동하지만(scikit-learn의 KMeans는 sparse 입력을 지원),
SOM 라이브러리(MiniSom 등)는 dense 입력을 전제로 짜여 있는 경우가 많아서 SVD로 차원을 줄이고 dense로 변환하는 전처리가 필요하다.
정리
- SVD는 고차원 데이터를 정보 손실을 최소화하면서 저차원으로 압축하는 행렬분해 기법
- TruncatedSVD로 상위 $k$개 특이값만 남기면, 에카르트-영 정리에 의해 그게 랭크 $k$ 근사 중 수학적으로 최적
- $k$는 누적 분산 설명비율이나 스크리 플롯을 보고 경험적으로 정함
- TF-IDF(희소·고차원) → SVD(압축) → SOM(밀집 입력 필요) 파이프라인의 중간 단계 역할
GitHub 댓글