군집 품질을 평가하는 실루엣 점수·DB 지수·갭 통계
실루엣 점수, Davies-Bouldin 지수, 갭 통계로 라벨 없는 데이터의 군집 품질과 최적 군집 수를 판단하는 방법을 정리합니다.
2026-08-14 · 최초 발행 2024-04-29
라벨 없는 군집 결과를 어떻게 비교할까
군집 분석에서는 정답 레이블이 없는 경우가 많다. 이때 군집 내부가 얼마나 밀집했는지, 군집끼리 얼마나 떨어져 있는지, 무작위 분포와 비교해도 구조가 남는지를 함께 봐야 한다.
실루엣 점수, Davies-Bouldin 지수, 갭 통계는 이런 판단에 쓰이는 내부 평가지표다. 각 지표가 포착하는 성질과 계산 비용이 다르므로, 하나의 점수에만 의존하기보다 탐색과 최종 검증의 역할을 나누는 편이 낫다.
응집도와 분리도를 읽는 실루엣 점수
실루엣 점수는 개별 데이터가 자기 군집에 얼마나 잘 속하는지와 가장 가까운 이웃 군집에서 얼마나 떨어져 있는지를 대비한다. 같은 군집의 응집도를 a(i), 가장 가까운 이웃 군집과의 분리도를 b(i)라고 할 때 식은 다음과 같다.
s(i) = (b(i) − a(i)) / max(a(i), b(i))
범위는 [-1, 1]이며 값이 클수록 좋은 군집 구조를 뜻한다. 구형·등방성 군집을 가정하는 경우에 강하지만, 고차원 또는 비구형 분포에서는 과소 군집 수를 선호하는 경향이 있다.
군집 간 균형을 보는 Davies-Bouldin 지수
Davies-Bouldin 지수는 각 군집의 퍼짐과 군집 중심 사이 거리를 이용해 군집 간 유사도를 평균화한다. 값이 작을수록 군집이 잘 분리된 것으로 본다.
KMeans 계열과의 정합성이 높고, 군집 수 증가에 민감하다. 계산량이 비교적 낮아 탐색 범위를 넓게 잡아야 할 때 유리하다.
참조 분포와 비교하는 갭 통계
갭 통계는 군집 내 분산 Wk를 참조 무작위 분포와 비교한다. 정의는 다음과 같다.
Gap(k) = E*[log Wk] − log Wk
k 선택에는 1-SE 규칙을 사용한다. 데이터 스케일과 분포가 지표에 미치는 영향을 줄이려는 목적이 있지만, 부트스트랩 반복 때문에 계산 비용이 크다. 참조 분포 가정이 데이터에 적합한지도 함께 확인해야 한다.
지표보다 먼저 정해야 할 조건
거리 또는 유사도 메트릭은 데이터 성격에 맞아야 한다. Euclidean을 기본으로 쓰되, 필요하면 코사인이나 마할라노비스 등을 선택한다. 표준화·정규화를 먼저 적용하면 메트릭의 안정성을 높일 수 있다.
실루엣 점수와 DB 지수는 임의의 군집 결과에 적용할 수 있는 내부 지표다. 다만 KMeans에서는 관성(inertia)과 정합되는 반면, 밀도 기반인 DBSCAN에서는 실루엣 점수 해석에 주의가 필요하다.
계산 특성도 다르다.
| 지표 | 계산 특성 | 확장성 관점 |
|---|---|---|
| Silhouette | 쌍거리 계산이 필요해 O(n^2) 경향 | 샘플링으로 근사 필요 |
| Davies-Bouldin | O(n + k^2) | 대규모 데이터에 유리 |
| Gap statistic | O(B × 클러스터링 비용) | 병렬화로 보완 |
시드를 고정하고 재시도(n_init)를 설정해야 결과를 재현하기 쉽다. 부트스트랩이나 서브샘플링으로 분산을 추정하면 선택 결과의 견고성도 확인할 수 있다.
운영 환경에서는 k-스윕 범위와 점수 개선 둔화에 따른 조기종료 기준을 미리 정한다. 빈 군집, 단일 포인트 군집, NaN/Inf 거리 값은 별도 방어 로직이 필요한 예외다.
군집 평가를 반복 가능한 흐름으로 만들기
빈 군집이 생기면 초기화를 다시 시도하거나 k를 줄인다. 거리 계산에 실패했다면 스케일링·정규화와 특이치 제거를 다시 검토한다. 갭 통계의 참조 분포가 맞지 않는 것으로 추정되면 PCA 투영 후 박스 샘플링을 적용하거나 대비 분포를 변경한다.
군집 방식에 따른 활용
KMeans 기반 제품 세분화에서는 실루엣 최대와 DB 최소를 함께 사용해 후보를 줄일 수 있다. 이후 갭 통계와 1-SE 규칙으로 최종 후보를 검증하면 과적합을 피하는 데 도움이 된다.
DBSCAN의 eps, min_samples를 조정할 때는 실루엣 점수로 과밀 또는 과소 군집 구간을 찾고, 노이즈 비율과 함께 eps 상·하한을 정한다.
계층 군집에서는 덴드로그램의 컷 높이 후보마다 DB 지수 최소화 기준을 적용할 수 있다. 데이터가 크다면 샘플링한 실루엣 점수로 근사 평가한다.
NLP나 추천 시스템의 임베딩 품질을 비교할 때는 동일한 k에서 지표 차이를 확인해 개선량을 판단한다. 고차원 코사인 거리 기반 실루엣 점수는 텍스트 토픽의 응집도를 진단하는 데 쓸 수 있다.
지표를 함께 볼 때의 차이
| 지표 | 최적화 방향 | 성능(계산비용) | 확장성 | 일관성 | 안정성 | 운영 편의 |
|---|---|---|---|---|---|---|
| Silhouette | 최대화 | O(n^2) 거리 + 군집 비용, 샘플링으로 경감 | 중 | 구형 군집에 강함, 비구형에 보수적 | 시드 영향 중간 | 구현 용이, 시각화 용이(프로파일) |
| Davies–Bouldin | 최소화 | O(n + k^2) | 높음 | 군집 수 증가에 민감, 분리·응집 균형 | 비교적 안정 | 대규모 탐색에 적합 |
| Gap statistic | 1-SE 규칙로 k 선택 | O(B × 군집 비용) | 병렬 필요 | 참조 분포 적합성에 좌우 | 표준오차로 신뢰구간 제공 | 최종 검증 지표로 권장 |
실행 가능한 평가 코드
데이터는 수치형이며 표준화를 권장한다. Python 3.10+, scikit-learn 1.4+, numpy 1.26+, scipy 1.11+ 환경을 전제로 한다.
# python 3.10+
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, davies_bouldin_score
from joblib import Parallel, delayed
rng = np.random.RandomState(42)
def gap_statistic(X, k_range, B=10, random_state=42, n_init=10, max_iter=300, n_jobs=-1):
X = np.asarray(X)
n, d = X.shape
# 박스 범위 참조 분포
mins = X.min(axis=0)
maxs = X.max(axis=0)
gaps, sk = [], []
wk = []
def kmeans_inertia(data, k):
km = KMeans(n_clusters=k, n_init=n_init, max_iter=max_iter, random_state=random_state)
km.fit(data)
return km.inertia_
for k in k_range:
# 실제 데이터 Wk
wk_real = kmeans_inertia(X, k)
wk.append(wk_real)
# 참조 분포 부트스트랩
def one_boot():
Z = rng.uniform(mins, maxs, size=(n, d))
return kmeans_inertia(Z, k)
wkb = Parallel(n_jobs=n_jobs)(delayed(one_boot)() for _ in range(B))
log_wkb = np.log(wkb)
gaps.append(np.mean(log_wkb) - np.log(wk_real))
sk.append(np.sqrt(1 + 1.0/B) * np.std(log_wkb, ddof=1))
return np.array(gaps), np.array(sk), np.array(wk)
# 데모 데이터
X, y = make_blobs(n_samples=3000, centers=4, cluster_std=0.75, random_state=42)
X = StandardScaler().fit_transform(X)
# k 탐색
k_values = range(2, 9)
# Silhouette, DB
sil_scores, db_scores = [], []
for k in k_values:
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(X)
# 샘플링으로 실루엣 근사(대규모 데이터 대응)
idx = np.random.choice(X.shape[0], size=min(5000, X.shape[0]), replace=False)
sil = silhouette_score(X[idx], labels[idx], metric='euclidean')
db = davies_bouldin_score(X, labels)
sil_scores.append(sil)
db_scores.append(db)
# Gap
gaps, sk, wk = gap_statistic(X, k_values, B=10, random_state=42, n_jobs=-1)
# 1-SE 규칙로 k 선택
def select_k_by_gap(k_values, gaps, sk):
for i in range(len(k_values) - 1):
if gaps[i] >= gaps[i + 1] - sk[i + 1]:
return k_values[i]
return k_values[-1]
k_gap = select_k_by_gap(list(k_values), gaps, sk)
print("k range:", list(k_values))
print("Silhouette:", [round(v, 4) for v in sil_scores], "best k =", k_values[int(np.argmax(sil_scores))])
print("Davies-Bouldin:", [round(v, 4) for v in db_scores], "best k =", k_values[int(np.argmin(db_scores))])
print("Gap:", [round(v, 4) for v in gaps], "selected k =", k_gap)
실루엣 점수는 n이 크면 5k10k 샘플을 무작위로 추출해 근사할 수 있다. DB 지수는 대량 탐색의 기본값으로 두고, 이상치 영향을 줄이기 위해 표준화를 적용한다. 갭 통계는 B를 1050으로 두고 병렬 처리하며, PCA 2~10차 투영 후 적용해 계산량을 완화할 수 있다.
탐색 비용과 선택 안정성을 함께 관리하기
DB 지수로 O(n + k^2) 범위에서 사전 후보를 축소하고, 최종 단계에서 O(B × 군집) 비용의 갭 통계 검증을 수행할 수 있다. 실루엣 점수는 샘플링을 적용하면 O(n^2)에서 O(m^2)로 바뀌며, m << n인 조건에서 실용적인 성능을 확보한다.
시드 고정과 n_init≥10 설정은 점수 분산을 줄이고, 갭 통계는 선택된 k의 표준오차를 제공한다. 서로 다른 가정을 둔 지표를 함께 사용하면 과소 또는 과대 군집화의 편향을 상쇄할 수 있다.
기존 파이프라인에는 DB와 실루엣 점수 로그부터 추가하고, 운영 데이터에서 갭 통계 검증으로 확장하는 방식이 가능하다. 거리 메트릭, 스케일링, 참조 분포 가정은 데이터 분포에 맞춰야 하며, 샘플링·병렬화·차원 축소를 함께 적용해 계산 비용과 안정성의 균형을 맞춘다.