비지도학습¶
비지도학습은 레이블 없이 자료에 숨은 구조를 발견한다. 대표적인 과제로 군집화, 차원축소, 이상치 탐지가 있다.
정의 1. 비지도학습¶
목표변수 \(y\) 없이 입력 특성 \(\{\mathbf{x}_i\}_{i=1}^n\)만 주어졌을 때, 비지도학습은 패턴, 집단, 또는 압축된 표현을 찾는다. 참값 레이블에 기반한 손실함수가 없으므로 평가는 도메인 전문성과 후속 활용도에 의존한다.
군집화는 비슷한 관측값을 묶는다. k-평균은 군집내 제곱합을 최소화하고, 계층적 군집화는 덴드로그램을 만들며, DBSCAN은 임의의 모양을 갖는 밀도 기반 군집을 찾는다.
k-평균이 해 주지 않는 것
"k-평균이 군집을 자동으로 찾아 준다"는 말은 세 가지를 숨긴다. 뒤의 연습문제가 각각을 수치로 확인한다.
- 군집 수 \(k\) 는 사람이 정한다. 알고리즘은 \(k\) 를 입력으로 받을 뿐 추정하지 않으며, 군집내 제곱합은 \(k\) 가 커지면 반드시 줄어들기만 하므로 그것만으로는 \(k\) 를 고를 수 없다(연습문제 5, 10).
- 지역해에 갇힌다. 목적함수는 볼록하지 않다. 배정·갱신을 되풀이하면 반드시 수렴하지만 수렴한 곳이 전역 최소라는 보장은 없어서, 초기값을 바꿔 여러 번 돌린 뒤 가장 좋은 것을 고른다.
- 군집의 모양을 가정한다. 유클리드 거리로 가장 가까운 중심에 배정하므로 경계가 늘 직선(초평면)이고, 사실상 구형이고 퍼짐이 비슷한 군집을 전제한다. 초승달이나 크기가 크게 다른 군집에서는 실패한다(연습문제 7). 축의 단위를 바꾸는 것만으로도 결과가 뒤집힌다(연습문제 8).
차원축소는 구조를 보존하면서 자료를 더 낮은 차원으로 사영한다. PCA는 분산이 최대가 되는 직교 방향을 찾고, t-SNE/UMAP은 시각화를 위한 비선형 임베딩을 제공한다. PCA는 군집화가 아니다. 자료를 집단으로 나누는 것이 아니라 분산을 최대한 보존하는 선형 사영을 찾을 뿐이며, PCA 그림에서 눈에 보이는 덩어리는 군집화의 결과가 아니라 보는 사람의 판단이다.
이상치 탐지는 기대되는 패턴에서 벗어난 점을 찾아낸다. 아이솔레이션 포레스트는 무작위 분할로 이상치를 고립시킨다.
비지도학습으로 오해받는 것
레이블이 없다고 해서 모두 비지도학습인 것은 아니다. 자기지도학습은 자료 자신에서 목표를 만들어 낸다 — 문장의 다음 낱말, 이미지의 가린 조각. 사람이 붙인 레이블이 없을 뿐, 목표가 만들어진 뒤의 학습은 입력–목표 쌍에 대한 지도학습이며 손실도 관측할 수 있다. 레이블이 붙은 적은 보기와 붙지 않은 많은 보기를 함께 쓰는 준지도학습도 마찬가지로 이 절의 바깥에 있다. 이 절이 다루는 것은 맞춰 볼 목표가 아예 없는 경우다.
금융에서의 응용: 분산투자를 위해 수익률 패턴으로 종목을 묶기, 상관된 위험 지표에서 주성분 요인 추출하기, 부정 거래 탐지하기.
보기 1. 비지도학습. 중심이 \((20,5)\), \((50,30)\), \((80,15)\)인 세 군집에서 각각 \(100\)개씩 뽑아 \(300\)개를 만든다. 흩어짐은 \(x\) 방향 \(8\), \(y\) 방향 \(4\)다. 정답 레이블은 알고리즘에게 주지 않고 \(K\)-평균을 \(k = 3\)으로 돌린다.
(1) 되찾은 중심이 참 중심에서 얼마나 떨어져 있어야 "맞는" 것인지, 그 자를 구하시오.
(2) 군집 크기가 \(100\)씩 나오지 않는다. 몇 개가 잘못 배정되었는지 세고, \(K\)-평균이 최소화하는 군집내 제곱합(WCSS)을 참 분할의 값과 견주시오.
풀이
(1) 해석적으로. 군집 중심의 추정값은 그 군집에 속한 점들의 평균이다. 각 군집이 \(100\)개이고 흩어짐이 \((\sigma_x, \sigma_y) = (8, 4)\)이므로
다. 참 중심에서 이 정도 안이면 맞는 것이다. 자가 없으면 \(19.1\)이 \(20\)에 가까운지 아닌지 말할 수 없다.
(2) 해석적으로. 세 중심이 서로 \(30\) 이상 떨어져 있고 흩어짐이 \(8\)이므로 군집은 잘 갈린다. 그래도 경계 근처의 몇 점은 이웃 중심이 더 가까워 잘못 붙는다. 군집 크기가 \(100\)에서 벗어나는 만큼이 그 흔적이다.
참 분할의 WCSS를 어림해 두면 견줄 자가 생긴다. 각 군집에서 \(\sum(x_i-\bar x)^2\)의 기댓값이 \((n-1)\sigma_x^2\)이므로
이다. \(K\)-평균이 찾은 WCSS는 이 값보다 작아야 한다. 참 분할도 하나의 후보일 뿐이고, \(K\)-평균은 WCSS를 더 줄이는 분할이 있으면 그쪽을 고르기 때문이다.
(1)(2) 수치적으로.
import numpy as np
np.random.seed(42)
# === 자료 생성: 군집 3개 ===
# 각 중심 주위에 100개씩 뿌려 총 300개를 만든다.
# 어느 점이 어느 군집에서 나왔는지는 우리가 알지만, **알고리즘에게는 주지 않는다.**
# 정답 레이블이 없다는 것이 비지도학습의 정의다.
centers = np.array([[20, 5], [50, 30], [80, 15]])
data = np.vstack([
np.random.normal(loc=c, scale=[8, 4], size=(100, 2))
for c in centers
])
# === K-평균 직접 구현 ===
k = 3
# 초기화: 자료점 중 k개를 무작위로 골라 첫 중심으로 삼는다.
# K-평균은 초기값에 민감해서, 실무에서는 여러 번 돌려 가장 좋은 것을 고른다.
centroids = data[np.random.choice(len(data), k, replace=False)]
for iteration in range(20):
# (1) 배정 단계: 각 점을 가장 가까운 중심에 붙인다.
# data[:, None]은 (300, 1, 2), centroids[None, :]은 (1, 3, 2) 모양이라
# 브로드캐스팅으로 (300, 3, 2)가 되고, axis=2로 노름을 내면
# dists[i, j] = i번 점과 j번 중심 사이의 거리가 된다.
dists = np.linalg.norm(data[:, None] - centroids[None, :], axis=2)
labels = dists.argmin(axis=1)
# (2) 갱신 단계: 각 군집에 속한 점들의 평균을 새 중심으로 삼는다.
new_centroids = np.array([data[labels == j].mean(axis=0) for j in range(k)])
# (3) 수렴 판정: 중심이 더 이상 움직이지 않으면 끝난다.
# 두 단계 모두 군집내 제곱합을 줄이기만 하므로 반드시 수렴한다.
# 다만 전역 최소가 아니라 국소 최소일 수 있다.
if np.allclose(centroids, new_centroids):
break
centroids = new_centroids
for j in range(k):
cluster = data[labels == j]
print(f"Cluster {j}: n={len(cluster)}, "
f"center=({cluster.mean(0)[0]:.1f}, {cluster.mean(0)[1]:.1f})")
print(f"Converged in {iteration + 1} iterations")
# --- (1)(2) 의 자로 재 본다 ---
true_label = np.repeat([0, 1, 2], 100)
wrong = int((labels != true_label).sum())
wcss = sum(((data[labels == j] - centroids[j]) ** 2).sum() for j in range(k))
print(f"중심의 표준오차: x = {8 / 10:.1f}, y = {4 / 10:.1f}")
for j in range(k):
d = data[labels == j].mean(0) - centers[j]
print(f" 군집 {j}: 참 중심에서 ({d[0]:+.2f}, {d[1]:+.2f}) "
f"= ({d[0] / 0.8:+.2f}, {d[1] / 0.4:+.2f}) 표준오차")
print(f"잘못 배정된 점: {wrong} / 300")
print(f"WCSS = {wcss:,.0f}, 참 분할의 기댓값 = 3*99*(64+16) = {3 * 99 * 80:,.0f}")
출력:
Cluster 0: n=101, center=(19.1, 5.3)
Cluster 1: n=95, center=(50.6, 30.5)
Cluster 2: n=104, center=(79.2, 14.9)
Converged in 8 iterations
중심의 표준오차: x = 0.8, y = 0.4
군집 0: 참 중심에서 (-0.88, +0.34) = (-1.09, +0.85) 표준오차
군집 1: 참 중심에서 (+0.61, +0.45) = (+0.77, +1.13) 표준오차
군집 2: 참 중심에서 (-0.82, -0.12) = (-1.03, -0.29) 표준오차
잘못 배정된 점: 5 / 300
WCSS = 22,006, 참 분할의 기댓값 = 3*99*(64+16) = 23,760
세 중심이 모두 맞는다. 참 중심에서의 거리가 \(x\) 방향 \(-1.09\), \(+0.77\), \(-1.03\) 표준오차, \(y\) 방향 \(+0.85\), \(+1.13\), \(-0.29\) 표준오차로 전부 \(1.2\) 안이다. (1)의 자가 없으면 \(19.1\)이 \(20\)에 가까운지 말할 수 없었을 것이다.
군집 크기가 \(101\), \(95\), \(104\)로 \(100\)에서 벗어난 것은 \(5\)개 점이 이웃 군집으로 넘어갔기 때문이다. \(300\)개 중 \(5\)개, 곧 \(1.7\%\)다. 세 중심이 \(30\) 넘게 떨어져 있고 흩어짐이 \(8\)이라 경계가 깨끗한데도 완벽하지는 않다.
WCSS는 \(22{,}006\)으로 참 분할의 기댓값 \(23{,}760\)보다 \(7\%\) 작다. 예상한 방향이다. \(K\)-평균은 WCSS를 최소화하므로 경계의 점들을 가까운 중심 쪽으로 옮겨 참 분할보다 더 낮은 값을 만들어 낸다. WCSS가 더 낮다는 것이 더 옳다는 뜻이 아니다. 이 사실이 다음 절에서 "\(k\)를 어떻게 고르는가"를 어렵게 만드는 바로 그 이유다.
정답이 없다는 것¶
보기 1은 \(k = 3\) 으로 두고 풀었다. 그 \(3\) 은 어디서 왔는가. 자료를 만들 때 중심을 셋 잡은 것이 우리이니 우리만 알았을 뿐, 점들만 건네받은 알고리즘에게는 알 길이 없는 수다. 같은 점구름을 \(k\) 만 바꿔 가며 나눠 보면 이 물음이 얼마나 곤란한지 드러난다.

점 \(600\) 개는 세 그림에서 모두 같고 알고리즘도 같다. 바꾼 것은 \(k\) 하나뿐인데, 어느 것도 틀려 보이지 않는다. \(k=2\) 는 멀찍이 떨어진 두 덩어리를 가르고, \(k=4\) 는 각 덩어리 안의 짜임새까지 나눈다. \(k=3\) 은 한쪽만 쪼갠 어정쩡한 답이지만 그것도 나름의 이야기가 된다. 지표는 판정을 내려 주지 않는다. 군집내 제곱합은 \(2180 \to 1463 \to 815\) 로 줄고 \(k=8\) 까지 가면 \(503\) 이 되는데, 줄어들기만 하므로 "좋아졌다"고 말할 근거가 되지 못한다(연습문제 5). 평균 실루엣은 \(0.767 \to 0.632 \to 0.527\) 로 \(k=2\) 에서 가장 크다.
그런데 이 자료는 정규분포 네 개에서 \(150\) 개씩 뽑아 만든 것이다. 실루엣이 고른 답은 \(2\) 이고 자료를 만든 구조는 \(4\) 다. 지표가 고장 난 것이 아니다. 두 뭉치씩 가까이 붙어 있으니 "덩어리 둘"도 이 점구름의 정직한 요약이며, 둘과 넷 중 무엇을 원하느냐는 자료 바깥의 질문이다. 고객을 두 유형으로 볼 것인지 네 유형으로 볼 것인지는 마케팅 예산이 정하는 것이지 점들이 정하는 것이 아니다.
지도학습이었다면 이 다툼은 시험자료가 끝냈을 것이다. 여기서는 시험자료를 아무리 크게 떼어 놓아도 소용이 없다. 떼어 놓은 점들에도 정답이 붙어 있지 않으니 맞춰 볼 대상이 없기 때문이다. 비지도학습에서 "맞았다"는 말은 자료만으로는 뜻을 갖지 못한다. 정의 1이 "평가는 도메인 전문성과 후속 활용도에 의존한다"고 적은 것은 겸손한 표현이 아니라 이 그림이 보여 주는 사실의 진술이다.
연습문제¶
연습문제 1. 2차원 자료점 \(\{(1,2), (1.5, 1.8), (5, 8), (8, 8), (1, 0.6), (9, 11)\}\)이 주어졌을 때, \(k=2\)이고 초기 중심이 \(\boldsymbol{\mu}_1 = (1, 2)\), \(\boldsymbol{\mu}_2 = (5, 8)\)인 k-평균 알고리즘을 한 번 반복하라. 군집 배정과 새 중심을 제시하라.
풀이
배정 단계(유클리드 거리로 각 점을 가장 가까운 중심에 배정):
| 점 | \(\boldsymbol{\mu}_1\)까지 거리 | \(\boldsymbol{\mu}_2\)까지 거리 | 군집 |
|---|---|---|---|
| \((1, 2)\) | 0 | 7.21 | 1 |
| \((1.5, 1.8)\) | 0.54 | 7.12 | 1 |
| \((5, 8)\) | 7.21 | 0 | 2 |
| \((8, 8)\) | 9.22 | 3.0 | 2 |
| \((1, 0.6)\) | 1.4 | 8.41 | 1 |
| \((9, 11)\) | 12.04 | 5.0 | 2 |
갱신 단계(새 중심 계산):
연습문제 2. PCA(주성분분석)가 왜 분산이 최대가 되는 방향을 찾는지 설명하라. PCA와 공분산행렬의 고유분해는 어떤 관계인가?
풀이
PCA는 사영된 자료의 분산을 최대화하는 방향 \(\mathbf{w}\)(단위벡터)를 찾는다: \(\max_{\lVert\mathbf{w}\rVert=1} \mathbf{w}^\top\mathbf{S}\mathbf{w}\), 여기서 \(\mathbf{S}\)는 표본 공분산행렬이다. 레일리 몫 이론에 따르면 이 최댓값은 \(\mathbf{w}\)가 \(\mathbf{S}\)의 최대 고윳값에 대응하는 고유벡터일 때 달성된다.
고유분해 \(\mathbf{S} = \mathbf{Q}\boldsymbol{\Lambda}\mathbf{Q}^\top\)는 모든 주성분을 직접 제공한다. \(\mathbf{Q}\)의 열은 주성분 방향이고 \(\boldsymbol{\Lambda}\)의 대각 성분은 각 성분이 설명하는 분산이다. 첫 번째 주성분이 가장 많은 분산을 포착하고, 두 번째 주성분은 첫 번째와 직교하면서 남은 분산을 가장 많이 포착하며, 이런 식으로 이어진다.
연습문제 3. 비지도학습 과제로서 군집화와 차원축소의 핵심 차이는 무엇인가? 둘을 함께 쓸 수 있는가?
풀이
군집화는 각 자료점을 이산적인 집단(군집 레이블)에 배정하여 자료를 비슷한 관측값의 부분집합으로 분할한다. 출력은 범주형 배정이다.
차원축소는 중요한 구조(분산, 거리, 이웃 관계)를 보존하면서 고차원 자료를 저차원 표현으로 사상한다. 출력은 연속적인 임베딩이다.
둘은 여러 방식으로 함께 쓸 수 있다. (1) 먼저 PCA로 차원을 줄인 뒤 축소된 공간에서 군집화한다(차원의 저주가 완화되어 흔히 더 효과적이다). (2) t-SNE나 UMAP으로 시각화한 뒤 눈으로 군집을 확인한다. (3) 먼저 군집화한 뒤 그 레이블로 차원축소 그림에 색을 입혀 해석한다.
연습문제 4. 어떤 자료가 관측값 1000개와 특성 500개로 이루어져 있다. k-평균을 바로 적용하면 왜 잘 작동하지 않을 수 있는지 설명하고, 차원축소가 어떻게 도움이 되는지 서술하라.
풀이
500차원에서 k-평균은 차원의 저주를 겪는다. 점들 사이의 유클리드 거리가 거의 같아져(모든 점이 대략 등거리가 되어) 군집을 구분하기 어려워진다. 또한 많은 특성이 잡음일 수 있어 정말 정보를 담은 특성의 신호를 희석한다.
차원축소는 다음과 같이 도움이 된다.
- 잡음 제거: PCA는 분산을 가장 많이 포착하는 상위 성분만 남기고, 군집 구조를 가리는 잡음 차원을 버린다.
- 거리 척도 개선: 저차원에서 유클리드 거리가 더 의미 있어지고 군집이 더 잘 분리된다.
- 계산 효율: 원 특성 500개보다 PCA 성분 10–50개에서 k-평균을 돌리는 것이 훨씬 빠르다.
전형적인 작업 흐름: 분산의 90–95%를 남기도록 PCA를 적용하고(흔히 20–50개 성분으로 축소된다) 축소된 자료에서 k-평균을 실행한다.
연습문제 5. 팔꿈치 방법은 군집내 제곱합(WCSS)을 \(k\)에 대해 그린 뒤 "꺾이는 지점"을 찾아 k-평균의 군집 수 \(k\)를 고르는 방법이다. 팔꿈치가 왜 항상 어떤 \(k\)에서 나타나며, 더 원칙적인 대안은 무엇인가?
풀이
WCSS는 \(k\)에 대해 단조 비증가다. 군집을 늘리면 군집 내 변동이 줄어들기만 한다. 그래프는 감쇠 곡선처럼 보이고, 어떤 변곡점이든 "팔꿈치" 후보가 된다. 선택이 주관적이어서 같은 그래프를 보고도 분석가마다 다른 팔꿈치를 고르는 일이 흔하다.
원칙적인 대안:
- 갭 통계량(Tibshirani, Walther, Hastie 2001): 관측 자료의 WCSS를 귀무 참조 분포(자료의 경계 상자 위 균등분포) 아래에서 기대되는 WCSS와 비교한다. 갭이 거의 최대가 되는 가장 작은 \(k\)를 고른다.
- 실루엣 계수: 각 점에 대해 응집도(자기 군집까지의 평균 거리)와 분리도(가장 가까운 다른 군집까지의 평균 거리)를 비교한다. \(k\)에 걸친 평균 실루엣이 더 뚜렷한 최적점을 준다.
- 모형 기반 군집화의 정보 기준(가우시안 혼합 모형의 BIC): \(k\)에 명시적으로 벌점을 부과한다.
이 방법들은 비지도 과제에 내재한 모호함을 줄여줄 뿐 없애지는 못한다. "올바른 군집 수"는 "평균"처럼 잘 정의된 모집단 모수가 아니다.
연습문제 6. 이상치 탐지는 일종의 단일 클래스 분류 문제로 볼 수 있다. 아이솔레이션 포레스트 알고리즘이 k-평균 기반 이상치 탐지기와 접근 방식에서 어떻게 다른지 서술하고, 각각이 선호되는 상황을 하나씩 제시하라.
풀이
k-평균 기반 이상치 탐지: 자료에 k-평균을 적합한 뒤, 어떤 점이 배정된 군집 중심까지의 거리가 임계값을 넘으면 이상치로 선언한다. 이 모형은 정상 자료를 소수의 밀집 영역으로 표현하며, 이상치는 어떤 영역에서도 멀리 떨어진 점이다.
아이솔레이션 포레스트(Liu, Ting, Zhou 2008): 무작위 특성을 무작위 임계값에서 반복적으로 분할하여 많은 무작위 이진 트리를 만든다. 이상치는 빨리 고립되는 경향이 있어, 잎에 홀로 남기까지 필요한 분할 수가 적다. 이상치 점수는 고립까지의 평균 경로 길이이며, 경로가 짧으면 이상치 신호다. "정상 군집"이라는 개념이 필요하지 않다.
k-평균이 선호될 때: 정상 자료가 소수의 뚜렷한 군집을 이룰 때(예: 표준 제품 주위에 몰려 있는 제조 수율). 이상치는 이 군집들에서 벗어난 것이다.
아이솔레이션 포레스트가 선호될 때: 정상 자료가 소수의 중심으로 분할되지 않는 복잡하고 다봉형일 수 있는 구조를 가질 때(예: 금융 거래). 아이솔레이션 포레스트는 고차원으로 잘 확장되고 \(k\)를 고를 필요가 없다. 중심까지의 거리가 아니라 이웃의 희소함으로 이상치를 찾아내기 때문이다.
연습문제 7. k-평균이 실패하는 자료를 만들고, 왜 실패하는지 설명하라. 어떤 대안이 통하는가?
풀이
import numpy as np
from sklearn.cluster import KMeans, DBSCAN
from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_moons
from sklearn.metrics import adjusted_rand_score
X, truth = make_moons(n_samples=1500, noise=0.06, random_state=0)
methods = [("k-평균", KMeans(2, n_init=10, random_state=0)),
("가우시안 혼합", GaussianMixture(2, random_state=0)),
("DBSCAN", DBSCAN(eps=0.2, min_samples=5))]
for name, m in methods:
labels = m.fit_predict(X)
print(f"{name:>14}: 조정 랜드 지수 {adjusted_rand_score(truth, labels):.4f}")
출력:
k-평균: 조정 랜드 지수 0.2549
가우시안 혼합: 조정 랜드 지수 0.5047
DBSCAN: 조정 랜드 지수 1.0000
조정 랜드 지수는 참 군집과 얼마나 일치하는지를 재며, \(1\)이 완벽, \(0\)이 무작위 수준이다.
| 방법 | ARI |
|---|---|
| k-평균 | \(0.255\) |
| 가우시안 혼합 | \(0.505\) |
| DBSCAN | \(\mathbf{1.000}\) |
왜 k-평균이 실패하는가. k-평균은 각 점을 가장 가까운 중심에 배정한다. 그러면 군집 경계가 반드시 중심들의 수직이등분선, 곧 직선이 된다(보로노이 분할). 초승달처럼 휘어진 군집은 어떤 중심을 잡아도 직선으로 갈라낼 수 없다.
더 근본적으로 k-평균은 군집이 구형이고 크기가 비슷하다고 암묵적으로 가정한다. 군집내 제곱합을 최소화하는 것이 등방성 정규분포 혼합의 최대가능도와 같기 때문이다.
대안이 각각 다른 가정을 푼다.
- 가우시안 혼합은 군집마다 다른 공분산을 허용해 타원형 군집을 다룬다. 여전히 볼록한 모양만 가능하므로 초승달에서는 절반만 성공한다.
- DBSCAN은 밀도가 이어진 영역을 하나의 군집으로 본다. 모양에 대한 가정이 없어 초승달을 완벽히 잡아낸다. 대신 밀도 매개변수
eps에 민감하고 군집마다 밀도가 크게 다르면 실패한다. - 스펙트럴 군집화는 유사도 그래프를 만들어 그 위에서 나누므로 역시 임의의 모양을 다룬다.
교훈은 지도학습과 같다. 방법마다 자료의 모양에 대한 가정이 들어 있고, 그 가정이 맞으면 잘 작동한다. 차이는 비지도학습에는 정답이 없어 가정이 맞는지 확인하기가 훨씬 어렵다는 것이다. 위에서 ARI를 계산할 수 있었던 것은 모의실험이라 참 레이블을 알고 있었기 때문이다. \(\square\)
연습문제 8. 연습문제 4에서 k-평균이 유클리드 거리에 기대고 있음을 보았다. 그 거리가 축의 단위에 얼마나 휘둘리는지 정량화하라. 자료의 정보를 하나도 바꾸지 않고 단위만 바꾸면 군집화 결과가 어떻게 달라지는가?
풀이
import numpy as np
from sklearn.cluster import KMeans
from sklearn.metrics import adjusted_rand_score
rng = np.random.default_rng(0)
A = rng.normal([0, 0], 1, (400, 2))
B = rng.normal([3, 0], 1, (400, 2)) # 군집은 첫째 축에서만 갈린다
Z = np.vstack([A, B])
truth = np.r_[np.zeros(400), np.ones(400)]
ari = lambda D: adjusted_rand_score(
truth, KMeans(2, n_init=10, random_state=0).fit_predict(D))
Z_scaled = Z.copy()
Z_scaled[:, 1] *= 100 # 군집과 무관한 축의 단위만 바꾼다
print(f"원래 척도 ARI {ari(Z):.4f}")
print(f"둘째 축 단위 x100 ARI {ari(Z_scaled):.4f} ← 완전히 무너진다")
print(f"표준화 후 ARI {ari((Z_scaled - Z_scaled.mean(0)) / Z_scaled.std(0)):.4f}")
출력:
원래 척도 ARI 0.7963
둘째 축 단위 x100 ARI -0.0007 ← 완전히 무너진다
표준화 후 ARI 0.7918
ARI가 \(0.796\)에서 \(-0.001\)로 떨어진다. 무작위 배정 수준이며, 자료의 정보는 하나도 바뀌지 않았는데 그렇다. 둘째 축을 미터에서 센티미터로 바꾼 것과 같은 일을 했을 뿐이다.
왜 그런가. k-평균은 유클리드 거리를 쓰고, 유클리드 거리는 축의 단위에 좌우된다. 둘째 축을 \(100\)배 하면 그 축의 분산이 \(10^4\)배가 되어 거리를 지배한다. 알고리즘은 정보가 있는 첫째 축을 사실상 무시하게 된다.
표준화가 되돌려 놓는다(\(0.792\)). 각 변수를 평균 \(0\), 표준편차 \(1\)로 맞추면 단위가 사라진다.
그런데 표준화가 언제나 옳은 것은 아니다.
| 상황 | 권장 |
|---|---|
| 변수들의 단위가 다르다 (키·몸무게·소득) | 표준화 |
| 변수들이 같은 단위이고 분산 차이가 의미 있다 | 표준화하지 않음 |
| 이상치가 많다 | 로버스트 척도화(중앙값·IQR) |
| 이미 비율·확률로 표현됨 | 대개 그대로 |
둘째 줄이 중요하다. 유전자 발현량처럼 모든 변수가 같은 단위라면, 변동이 큰 변수가 실제로 더 중요한 정보를 담고 있을 수 있다. 표준화는 그 정보를 지운다.
거리를 쓰는 모든 방법이 같은 문제를 갖는다. k-최근접이웃, 계층적 군집화, PCA, SVM의 RBF 커널이 모두 그렇다. 반면 트리 기반 방법은 각 변수를 따로 분할하므로 단조 변환에 불변이다. 전처리 선택이 곧 모형 가정이라는 점을 기억해야 한다. \(\square\)
연습문제 9. 연습문제 2를 실습으로 확장하라. PCA에서 몇 개의 성분을 남길지 어떻게 정하며, 척도와 상관 구조가 결과를 어떻게 바꾸는가?
풀이
import numpy as np
from sklearn.decomposition import PCA
rng = np.random.default_rng(1)
n = 2000
latent = rng.normal(0, 1, (n, 3)) # 실제 차원은 3
X = latent @ rng.normal(0, 1, (10, 3)).T + rng.normal(0, 0.3, (n, 10))
ratio = PCA().fit(X).explained_variance_ratio_
print("설명 분산 비율:", np.round(ratio, 4))
print("누적: ", np.round(np.cumsum(ratio), 4))
print(f"→ 성분 3개로 {np.cumsum(ratio)[2]:.4f} 설명\n")
X_bad = X.copy(); X_bad[:, 0] *= 1000 # 한 변수의 단위만 바꾼다
print(f"한 변수를 1000배 하면 첫 성분이 "
f"{PCA().fit(X_bad).explained_variance_ratio_[0]:.4f} 를 설명한다")
Xs = (X_bad - X_bad.mean(0)) / X_bad.std(0)
print(f"표준화 후: {np.round(PCA().fit(Xs).explained_variance_ratio_[:4], 4)}\n")
W = rng.normal(0, 1, (n, 10)) # 서로 무관한 10변수
print(f"무상관 자료: {np.round(PCA().fit(W).explained_variance_ratio_[:4], 4)} … 거의 균등")
출력:
설명 분산 비율: [0.5927 0.3184 0.0679 0.0033 0.0031 0.003 0.003 0.0029 0.0028 0.0028]
누적: [0.5927 0.9111 0.979 0.9823 0.9855 0.9885 0.9915 0.9944 0.9972 1. ]
→ 성분 3개로 0.9790 설명
한 변수를 1000배 하면 첫 성분이 1.0000 를 설명한다
표준화 후: [0.502 0.2692 0.1492 0.0337]
무상관 자료: [0.1115 0.1085 0.1064 0.102 ] … 거의 균등
세 가지를 읽어야 한다.
첫째, 성분 개수를 고르는 법. 설명 분산 비율이 \(0.59, 0.32, 0.07\) 뒤에 \(0.003\)으로 뚝 떨어진다. 이 절벽이 실제 차원 \(3\)을 정확히 가리킨다. 실무에서 쓰는 기준은 세 가지다.
- 누적 비율: \(90\%\)나 \(95\%\)를 넘는 최소 개수. 여기서는 \(3\)개로 \(97.9\%\).
- 스크리 그림의 팔꿈치: 고윳값이 급격히 꺾이는 지점.
- 카이저 기준: 상관행렬 PCA에서 고윳값 \(> 1\)인 성분. 표준화한 경우에만 뜻이 있다.
둘째, PCA도 척도에 민감하다. 한 변수의 단위만 \(1000\)배 하자 첫 성분이 분산의 \(100\%\)를 설명한다고 나온다. 그 성분은 사실상 그 변수 하나이며, PCA가 자료 구조가 아니라 단위를 발견한 셈이다.
이 때문에 실무에서는 대개 상관행렬 PCA(표준화 후 PCA)를 쓴다. 표준화하면 다시 \(0.50, 0.27, 0.15\)로 세 성분이 드러난다.
셋째, 상관이 없으면 PCA는 할 일이 없다. 무상관 \(10\)변수에서 설명 분산이 \(0.11, 0.11, 0.11, \ldots\)로 거의 균등하다. 어느 방향도 특별하지 않으므로 차원을 줄이면 정보를 그만큼 잃는다. PCA가 유용한 것은 변수들이 상관되어 있을 때뿐이다.
PCA는 변수 선택이 아니다
주성분은 모든 원래 변수의 선형결합이다. \(10\)차원을 \(3\)차원으로 줄여도 여전히 \(10\)개 변수를 전부 측정해야 한다. 측정 비용을 줄이려는 것이라면 PCA가 아니라 변수 선택이 필요하다.
그리고 주성분은 대개 해석되지 않는다. "첫 성분은 전반적 크기, 둘째는 모양 대비"처럼 읽히는 경우가 있지만 우연에 가깝고, 그런 해석을 강요하다 이야기를 지어내기 쉽다.
마지막으로 PCA는 분산이 곧 정보라고 가정한다. 분류에 유용한 방향이 분산이 작은 방향일 수도 있으며, 그럴 때 PCA로 전처리하면 오히려 신호를 버린다. 지도학습이 목적이라면 부분최소제곱(PLS) 이나 선형판별분석처럼 레이블을 함께 보는 방법이 낫다. \(\square\)
연습문제 10. 비지도학습의 근본적인 어려움은 정답이 없다는 것이다. 이것이 왜 위험한지 보여라. 군집화 알고리즘에 아무 구조도 없는 자료를 주면 어떻게 되는가?
풀이
import numpy as np
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
rng = np.random.default_rng(0)
noise = rng.uniform(0, 1, (600, 2)) # 아무 구조도 없다
real = np.vstack([rng.normal([0, 0], 0.5, (200, 2)), # 진짜 군집 3개
rng.normal([4, 0], 0.5, (200, 2)),
rng.normal([2, 4], 0.5, (200, 2))])
for label, D in [("균등 잡음", noise), ("진짜 군집 3개", real)]:
print(f"{label}")
for k in (2, 3, 4, 5):
m = KMeans(k, n_init=10, random_state=0).fit(D)
print(f" k={k}: WCSS {m.inertia_:>8.2f} 실루엣 {silhouette_score(D, m.labels_):.4f}")
print()
출력:
균등 잡음
k=2: WCSS 59.86 실루엣 0.3657
k=3: WCSS 38.65 실루엣 0.3775
k=4: WCSS 24.46 실루엣 0.4099
k=5: WCSS 20.06 실루엣 0.3914
진짜 군집 3개
k=2: WCSS 1860.37 실루엣 0.5613
k=3: WCSS 312.16 실루엣 0.7719
k=4: WCSS 267.61 실루엣 0.6332
k=5: WCSS 230.85 실루엣 0.4900
군집화는 언제나 군집을 돌려준다. 균등 잡음에서도 WCSS가 \(59.9 \to 20.1\)로 매끄럽게 줄어들고, 실루엣이 \(k=4\)에서 \(0.410\)으로 최댓값을 갖는다. 이 자료를 처음 보는 사람이라면 "군집이 네 개 있다"고 결론지을 수 있다.
연습문제 5의 팔꿈치가 왜 언제나 나타나는지도 여기서 보인다. WCSS는 \(k\)가 커지면 반드시 줄어들고 결국 \(0\)이 되므로, 감소가 완만해지는 지점은 구조가 없어도 생긴다.
구조가 있는지 어떻게 판별하는가. 실루엣의 절대적 크기를 비교하는 것이 첫걸음이다.
| 자료 | 최대 실루엣 |
|---|---|
| 균등 잡음 | \(0.410\) (\(k=4\)) |
| 진짜 군집 \(3\)개 | \(\mathbf{0.772}\) (\(k=3\)) |
진짜 군집이 있으면 실루엣이 훨씬 높고, 최댓값이 참값 \(k=3\)에 정확히 놓인다. WCSS도 \(k=2 \to 3\)에서 \(1860 \to 312\)로 극적으로 꺾이는데, 잡음 자료의 완만한 감소와 대조적이다.
더 원칙적인 방법들.
- 갭 통계량: 관측된 WCSS를 같은 범위의 균등 잡음에서 기대되는 WCSS와 비교한다. 위 실험을 형식화한 것으로, 귀무 참조 분포를 명시적으로 만든다는 점이 핵심이다.
- 안정성 기반 검증: 자료를 여러 번 부표집해 군집화하고 결과가 일관되는지 본다. 잡음에서 나온 군집은 표본이 바뀌면 흩어진다.
- 모형 기반 선택: 가우시안 혼합에 BIC를 적용하면 가능도와 복잡도를 함께 따져 \(k\)를 고를 수 있다.
- 외부 검증: 군집이 군집화에 쓰지 않은 변수와 연관되는지 확인한다. 가장 설득력 있는 증거다.
비지도학습의 진짜 위험
지도학습에서는 모형이 틀리면 시험 오차가 알려 준다. 비지도학습에는 그런 심판이 없다. 알고리즘은 언제나 답을 내놓고, 그 답이 발견인지 인공물인지 자료가 말해 주지 않는다.
그래서 비지도학습의 결과는 결론이 아니라 가설로 다루어야 한다. "고객을 네 유형으로 나누었다"는 발견이 아니라, 독립적인 증거로 확인해야 할 제안이다. 이것이 이 장 전체를 관통하는 주제 — 자료가 답해 주지 않는 것을 자료에 묻지 말라 — 의 마지막 사례다. \(\square\)
정리하며¶
비지도학습은 레이블 없이 자료의 구조를 찾는다. 세 가지 과제로 정리된다.
- 군집화. k-평균은 군집내 제곱합을 최소화하고, 계층적 군집화는 덴드로그램을 주며, DBSCAN은 밀도에 기반해 임의의 모양을 찾는다. 군집 개수를 무엇으로 정하느냐가 늘 남는 문제다. k-평균은 그 수를 입력으로 받을 뿐 찾아 주지 않으며, 지역해에 갇히고, 구형이고 퍼짐이 비슷한 군집을 전제한다.
- 차원축소. PCA는 분산이 최대가 되는 직교 방향을 찾고(0.3절 대칭행렬의 스펙트럼 정리가 그 근거다), t-SNE·UMAP은 시각화를 위한 비선형 임베딩을 준다. 분산을 보존하는 사영일 뿐 군집화가 아니다.
- 이상치 탐지. 기대되는 패턴에서 벗어난 점을 찾는다.
결정적인 차이는 평가다. 참값 레이블이 없으므로 지도학습처럼 손실을 관측할 수 없고, 결과의 좋고 나쁨은 도메인 지식과 후속 활용도로만 판단된다. 같은 자료에서 알고리즘이나 초모수를 바꾸면 전혀 다른 "구조"가 나오며, 그중 무엇이 실재인지 자료만으로는 답할 수 없다.
이 점이 비지도학습의 결과를 보고할 때 특히 조심해야 하는 이유다. 발견된 군집이 자연의 경계인지 알고리즘이 그은 선인지 구별하려면 별도의 근거가 필요하다.
다음 쪽 강화학습은 세 번째 갈래다. 레이블도 고정된 자료도 없이, 행동이 다음 자료를 만드는 순차적 의사결정 문제를 다룬다.