본문으로 건너뛰기

058 K-Means 클러스터링 상세

키워드: K-Means, kmeans

개요

K-Means는 가장 널리 사용되는 클러스터링 알고리즘입니다. 단순하면서도 효과적이며, 대용량 데이터에서도 빠르게 동작합니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: pycaret[full]>=3.0

K-Means 알고리즘 원리

  1. K개의 초기 중심점(centroid) 무작위 선택
  2. 각 데이터를 가장 가까운 중심점에 할당
  3. 각 클러스터의 평균으로 중심점 업데이트
  4. 중심점이 변하지 않을 때까지 2-3 반복
반복 1:          반복 2:          수렴 후:
○ x ○ x ○ x
● ● → ● ● → ● ●
○ ○ ○
x ● x ● x ●

● = 중심점 중심점 이동 중심점 고정
x = 데이터 데이터 재할당 최종 클러스터

PyCaret에서 K-Means

from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

# 058 K-Means 클러스터링
kmeans = create_model('kmeans')

# 058 클러스터 수 지정
kmeans_5 = create_model('kmeans', num_clusters=5)

K-Means++ 초기화

기본 K-Means의 초기화 문제를 개선:

from sklearn.cluster import KMeans
import numpy as np

# 058 일반 K-Means (random 초기화)
kmeans_random = KMeans(n_clusters=4, init='random', n_init=10, random_state=42)

# 058 K-Means++ (개선된 초기화, PyCaret 기본값)
kmeans_pp = KMeans(n_clusters=4, init='k-means++', n_init=10, random_state=42)

# 058 K-Means++가 더 안정적인 결과 제공

최적 K 찾기 - 엘보우 방법

from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

# 058 임시 모델로 엘보우 플롯
kmeans = create_model('kmeans', num_clusters=4)
plot_model(kmeans, plot='elbow')

수동으로 엘보우 분석:

from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

X = get_config('X')

inertias = []
K_range = range(1, 11)

for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X)
inertias.append(kmeans.inertia_)

plt.figure(figsize=(10, 6))
plt.plot(K_range, inertias, 'bo-', linewidth=2, markersize=8)
plt.xlabel('Number of Clusters (K)')
plt.ylabel('Inertia (Within-cluster sum of squares)')
plt.title('Elbow Method for Optimal K')
plt.grid(True, alpha=0.3)

# 058 엘보우 포인트 표시
plt.axvline(x=4, color='r', linestyle='--', label='Elbow Point')
plt.legend()
plt.savefig('kmeans_elbow.png', dpi=150)

최적 K 찾기 - 실루엣 분석

from pycaret.clustering import *
from pycaret.datasets import get_data
from sklearn.metrics import silhouette_score, silhouette_samples
import matplotlib.pyplot as plt
import numpy as np

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X')

# 058 실루엣 점수 계산
scores = []
for k in range(2, 11):
kmeans = create_model('kmeans', num_clusters=k)
clustered = assign_model(kmeans)
labels = clustered['Cluster'].values

score = silhouette_score(X, labels)
scores.append({'k': k, 'silhouette': score})

import pandas as pd
df = pd.DataFrame(scores)

plt.figure(figsize=(10, 6))
plt.bar(df['k'], df['silhouette'], color='steelblue')
plt.xlabel('Number of Clusters (K)')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score by K')
plt.xticks(df['k'])
plt.grid(True, alpha=0.3, axis='y')
plt.savefig('kmeans_silhouette.png', dpi=150)

print("K별 실루엣 점수:")
print(df)

실루엣 다이어그램

from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

kmeans = create_model('kmeans', num_clusters=4)

# 058 실루엣 다이어그램
plot_model(kmeans, plot='silhouette')

클러스터 특성 분석

from pycaret.clustering import *
from pycaret.datasets import get_data
import pandas as pd

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

kmeans = create_model('kmeans', num_clusters=4)
clustered_data = assign_model(kmeans)

# 058 클러스터별 통계
print("=== 클러스터별 프로파일 ===\n")

for cluster in sorted(clustered_data['Cluster'].unique()):
cluster_data = clustered_data[clustered_data['Cluster'] == cluster]
print(f"클러스터 {cluster} (n={len(cluster_data)}):")
print(cluster_data.describe().loc[['mean', 'std']])
print()

클러스터 중심점

from pycaret.clustering import *
from pycaret.datasets import get_data
import pandas as pd

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

kmeans = create_model('kmeans', num_clusters=4)

# 058 중심점 확인 (정규화된 스케일)
centroids = kmeans.cluster_centers_
feature_names = get_config('X').columns

centroid_df = pd.DataFrame(centroids, columns=feature_names)
centroid_df.index = [f'Cluster {i}' for i in range(len(centroids))]

print("클러스터 중심점 (정규화 스케일):")
print(centroid_df.round(3))

클러스터 시각화

from pycaret.clustering import *
from pycaret.datasets import get_data
import matplotlib.pyplot as plt

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

kmeans = create_model('kmeans', num_clusters=4)

# 2D PCA 기반 시각화
plot_model(kmeans, plot='cluster')

# 3D 시각화
plot_model(kmeans, plot='tsne')

K-Means의 한계

1. 구형 클러스터 가정

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_moons

# 058 비구형 데이터
X, _ = make_moons(n_samples=300, noise=0.05, random_state=42)

# 058 K-Means 적용 (잘 안됨)
kmeans = KMeans(n_clusters=2, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)

plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], c='gray', alpha=0.7)
plt.title('Original Data (Non-spherical)')

plt.subplot(1, 2, 2)
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.7)
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1],
c='red', marker='X', s=200)
plt.title('K-Means Result (Poor)')

plt.tight_layout()
plt.savefig('kmeans_limitation.png', dpi=150)

# 058 비구형 데이터에는 DBSCAN이나 Spectral Clustering 추천

2. K 값 미리 지정

# 058 K-Means는 K를 미리 지정해야 함
# 058 해결책: 엘보우, 실루엣 분석으로 최적 K 탐색

3. 이상치 민감

import numpy as np
from sklearn.cluster import KMeans

# 058 이상치가 있는 데이터
np.random.seed(42)
X_normal = np.random.randn(100, 2)
X_outliers = np.array([[10, 10], [-10, -10]]) # 이상치
X = np.vstack([X_normal, X_outliers])

# 058 K-Means는 이상치에 민감
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)

# 058 이상치가 중심점을 왜곡할 수 있음
print("중심점:", kmeans.cluster_centers_)

K-Means vs Mini-Batch K-Means

대용량 데이터에서:

from sklearn.cluster import KMeans, MiniBatchKMeans
import time
import numpy as np

# 058 대용량 데이터 시뮬레이션
np.random.seed(42)
X_large = np.random.randn(100000, 10)

# 058 일반 K-Means
start = time.time()
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
kmeans.fit(X_large)
print(f"K-Means: {time.time() - start:.2f}초")

# 058 Mini-Batch K-Means (더 빠름)
start = time.time()
mb_kmeans = MiniBatchKMeans(n_clusters=5, random_state=42, n_init=10, batch_size=1000)
mb_kmeans.fit(X_large)
print(f"Mini-Batch K-Means: {time.time() - start:.2f}초")

클러스터 안정성 평가

from pycaret.clustering import *
from pycaret.datasets import get_data
from sklearn.cluster import KMeans
from sklearn.metrics import adjusted_rand_score
import numpy as np

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X')

# 058 여러 번 실행하여 안정성 확인
n_runs = 10
labels_list = []

for i in range(n_runs):
kmeans = KMeans(n_clusters=4, random_state=i, n_init=10)
labels = kmeans.fit_predict(X)
labels_list.append(labels)

# 058 실행 간 ARI 비교
ari_scores = []
for i in range(n_runs):
for j in range(i+1, n_runs):
ari = adjusted_rand_score(labels_list[i], labels_list[j])
ari_scores.append(ari)

print(f"평균 ARI: {np.mean(ari_scores):.4f}")
print(f"표준편차: {np.std(ari_scores):.4f}")

# 058 ARI > 0.9면 안정적인 클러스터링

장단점

장점:

  • 간단하고 직관적
  • 빠른 수렴
  • 대용량 데이터에 적합 (Mini-Batch 사용 시)
  • 구현 쉬움

단점:

  • K를 미리 지정해야 함
  • 구형 클러스터만 잘 탐지
  • 이상치에 민감
  • 초기값에 따라 결과 달라질 수 있음

정리

  • K-Means는 가장 기본적인 클러스터링 알고리즘
  • K-Means++로 초기화 개선
  • 엘보우, 실루엣 분석으로 최적 K 결정
  • 비구형 데이터에는 한계
  • 대용량에는 Mini-Batch K-Means 고려

다음 글 예고

다음 글에서는 계층적 클러스터링 상세를 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #058