K-평균 군집화는 데이터셋을 미리 정해진 K개의 군집으로 나누는 데 널리 사용되는 거리 기반 알고리즘입니다. 이 알고리즘의 목표는 각 데이터 포인트와 해당 군집 중심 간의 거리 제곱 합을 최소화하는 것입니다.
1. 핵심 원리
K-평균의 목표는 데이터를 K개의 군집으로 분할하여, 각 데이터 포인트가 가장 가까운 군집 중심에 속하도록 하는 것입니다. 군집 중심의 위치를 반복적으로 조정함으로써, K-평균은 군집 내부의 밀집도를 최적화하여 가능한 한 조밀하고 서로 분리된 군집을 형성합니다.
핵심 개념
- 군집(Cluster): K-평균은 군집 내 거리의 제곱 합을 최소화하여 데이터 포인트들이 군집 내에 뭉치도록 합니다. 군집은 특정 기준에서 "서로 유사한" 데이터 포인트들의 집합입니다. 예를 들어, 쇼핑몰 고객을 "학생", "직장인", "은퇴자" 세 군집으로 나눌 수 있습니다.
- 군집 중심(Centroid): 군집 중심은 해당 군집에 속한 모든 데이터 포인트의 평균값으로, 군집의 중심 위치를 나타냅니다.
- 할당 및 업데이트: K-평균은 반복적인 과정을 통해 군집 할당을 조정하고, 데이터 포인트와 중심 간의 거리를 최소화하여 점진적으로 수렴합니다.
2. 알고리즘 작동 방식
2.1 기본 절차
K-평균은 "가장 가까운 중심에 할당"하는 방식을 사용합니다:
- K개의 군집 중심을 무작위로 선택합니다 (초기 중심점).
- 각 데이터 포인트를 가장 가까운 군집 중심으로 할당합니다.
- 각 군집의 새로운 중심을 재계산합니다.
- 중심점이 더 이상 변하지 않거나 지정된 반복 횟수에 도달할 때까지 2단계와 3단계를 반복합니다.
2.2 상세 단계 (예시 포함)
K-평균 군집화 과정은 크게 할당(Assignment) 단계와 업데이트(Update) 단계로 나뉩니다. 다음은 상세 단계입니다:
- K 값 결정: 군집의 수 K를 설정합니다.
- 군집 중심 초기화: 데이터셋에서 K개의 데이터 포인트를 무작위로 선택하여 초기 군집 중심(centroids)으로 사용합니다.
- 할당 단계: 데이터셋의 각 데이터 포인트에 대해, 가장 가까운 군집 중심으로 할당합니다. 여기서 "거리"는 주로 유클리드 거리(Euclidean distance)를 사용합니다.
- 업데이트 단계: 현재의 군집 할당에 따라, 각 군집의 중심을 다시 계산합니다. 즉, 해당 군집 내 모든 데이터 포인트의 평균값을 새로운 군집 중심으로 사용합니다.
- 반복: 군집 중심이 더 이상 변하지 않거나(수렴) 최대 반복 횟수에 도달할 때까지 3단계와 4단계를 반복합니다.
의사 코드
function KMeans(데이터셋 X, 군집 수 K):
1. X에서 K개의 포인트를 무작위로 선택하여 초기 군집 중심 설정
2. 다음을 반복:
a. 각 포인트 P를 가장 가까운 군집 중심 C에 할당
b. 각 군집 C의 중심을 해당 군집 내 모든 포인트의 평균으로 업데이트
3. 군집 중심이 더 이상 변하지 않을 때까지 반복
4. 최종 군집 할당 및 군집 중심 반환
3. K-평균의 장단점
- 장점:
- 간결하고 효율적: 대규모 데이터셋에 적합하며, 처리 속도가 빠릅니다.
- 빠른 수렴: 적절한 초기 중심 선택 시 비교적 빠르게 수렴하는 경향이 있습니다.
- 단점:
- 초기 중심에 민감: 초기 군집 중심 선택에 따라 최종 결과가 달라질 수 있습니다.
- 구형 군집 가정: K-평균은 각 군집이 구형이고 크기가 비슷하다고 가정합니다. 따라서 비구형(예: 초승달 모양) 군집이나 크기 차이가 큰 군집을 잘 발견하지 못합니다.
- 이상치(Outlier)에 민감: 데이터의 이상치가 군집 중심 계산에 영향을 줄 수 있습니다.
- 지역 최적해: 전역 최적해(global optimum)를 보장하지 않으며, 지역 최적해(local optimum)에 도달할 수 있습니다.
- 데이터 유형 제한: 평균을 계산할 수 있는 데이터에만 적용 가능합니다.
4. 구현 예시
Scikit-learn 라이브러리 사용
from sklearn.cluster import KMeans
import numpy as np
# 예시 데이터 생성
X_sample = np.array([[1, 2], [1.5, 1.8], [5, 8], [8, 8], [1, 0.6], [9, 11]])
# KMeans 모델 초기화 및 학습 (군집 2개)
# random_state는 재현성을 위해 설정
kmeans_model = KMeans(n_clusters=2, random_state=42, n_init=10)
kmeans_model.fit(X_sample)
# 각 데이터 포인트의 군집 레이블
cluster_labels = kmeans_model.labels_
# 계산된 군집 중심
cluster_centers = kmeans_model.cluster_centers_
print("군집 레이블:", cluster_labels)
print("군집 중심:", cluster_centers)
직접 구현
import numpy as np
def init_centers_randomly(data, num_clusters):
# 데이터에서 무작위로 초기 중심점 선택
indices = np.random.choice(len(data), num_clusters, replace=False)
return data[indices]
def assign_to_closest_center(data, centers):
# 각 데이터 포인트와 모든 중심점 간의 거리 계산
# data shape: (n_samples, n_features), centers shape: (n_clusters, n_features)
# X[:, np.newaxis] -> (n_samples, 1, n_features)
# distances shape: (n_samples, n_clusters)
distances = np.linalg.norm(data[:, np.newaxis] - centers, axis=2)
# 각 데이터 포인트에 가장 가까운 중심점의 인덱스 반환
return np.argmin(distances, axis=1)
def recalculate_centers(data, labels, num_clusters):
new_centers = []
for i in range(num_clusters):
# 현재 군집에 속한 데이터 포인트들
points_in_cluster = data[labels == i]
if len(points_in_cluster) > 0:
# 해당 군집의 평균 계산
new_centers.append(points_in_cluster.mean(axis=0))
else:
# 군집에 포인트가 없으면 임의로 재설정 (이 예제에서는 단순화)
new_centers.append(data[np.random.choice(len(data))])
return np.array(new_centers)
def run_kmeans(data, num_clusters, max_iterations=100, tolerance=1e-4):
# 초기 중심점 설정
current_centers = init_centers_randomly(data, num_clusters)
for _ in range(max_iterations):
# 데이터 포인트를 가장 가까운 중심에 할당
point_labels = assign_to_closest_center(data, current_centers)
# 새로운 중심점 계산
next_centers = recalculate_centers(data, point_labels, num_clusters)
# 중심점 변화량 계산 및 수렴 여부 확인
center_shift = np.sum(np.abs(next_centers - current_centers))
if center_shift < tolerance:
break # 수렴 조건 만족
current_centers = next_centers # 중심점 업데이트
return point_labels, current_centers
# 예시 데이터 (위와 동일)
data_points = np.array([[1, 2], [1.5, 1.8], [5, 8], [8, 8], [1, 0.6], [9, 11]])
# K-means 실행
final_labels, final_centers = run_kmeans(data_points, num_clusters=2)
print("최종 군집 할당:", final_labels)
print("최종 군집 중심 위치:", final_centers)