🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

K-Means 군집화 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 12. 6. · 13 Views

K-Means 군집화 완벽 가이드

비지도 학습의 대표 주자인 K-Means 군집화 알고리즘을 초급 개발자도 이해할 수 있도록 쉽게 설명합니다. 데이터를 자동으로 그룹화하는 원리부터 실무 활용까지 단계별로 알아봅니다.


목차

  1. K-Means의 기본 개념
  2. K-Means 알고리즘 동작 원리
  3. 최적의 K값 찾기 - 엘보우 방법
  4. 실루엣 점수로 군집 품질 평가하기
  5. 데이터 전처리 - 스케일링의 중요성
  6. K-Means++로 초기화 개선하기
  7. 실무 프로젝트 - 고객 세그멘테이션
  8. K-Means의 한계와 대안

1. K-Means의 기본 개념

데이터 분석팀에 새로 합류한 김개발 씨는 고객 데이터를 분류해달라는 요청을 받았습니다. 하지만 정답 라벨이 없는 데이터였습니다.

"라벨 없이 어떻게 분류하죠?" 선배 박시니어 씨가 미소 지으며 말했습니다. "K-Means를 써보세요."

K-Means는 정답이 없는 데이터를 K개의 그룹으로 자동 분류하는 알고리즘입니다. 마치 비슷한 색깔의 구슬을 바구니별로 나누는 것과 같습니다.

각 그룹의 중심점을 찾아 가장 가까운 데이터들을 모아주는 방식으로 동작합니다.

다음 코드를 살펴봅시다.

from sklearn.cluster import KMeans
import numpy as np

# 샘플 데이터 생성
data = np.array([[1, 2], [1.5, 1.8], [5, 8], [8, 8], [1, 0.6], [9, 11]])

# K-Means 모델 생성 (3개 그룹으로 분류)
kmeans = KMeans(n_clusters=3, random_state=42)

# 모델 학습 및 예측
kmeans.fit(data)
labels = kmeans.labels_

# 각 데이터가 속한 그룹 확인
print(f"클러스터 라벨: {labels}")
print(f"중심점: {kmeans.cluster_centers_}")

김개발 씨는 입사 2개월 차 데이터 분석가입니다. 오늘 팀장님으로부터 특별한 미션을 받았습니다.

회사의 고객 데이터를 분석해서 비슷한 성향의 고객끼리 그룹을 만들어달라는 것이었습니다. 문제는 이 데이터에 정답이 없다는 것이었습니다.

일반적인 머신러닝처럼 "이 고객은 VIP입니다"라고 알려주는 라벨이 전혀 없었습니다. 김개발 씨는 당황했습니다.

선배 박시니어 씨가 다가와 말했습니다. "걱정 마세요.

이럴 때 쓰는 게 비지도 학습이에요. 그중에서도 K-Means가 가장 기본적이고 강력하죠." 그렇다면 K-Means란 정확히 무엇일까요?

쉽게 비유하자면, K-Means는 마치 유치원 선생님이 아이들을 키 순서대로 줄 세우는 것과 비슷합니다. 선생님은 먼저 임의로 세 곳에 기준점을 정합니다.

그리고 각 아이를 가장 가까운 기준점 쪽으로 보냅니다. 그 다음 각 그룹의 평균 위치로 기준점을 옮기고, 다시 아이들을 재배치합니다.

이 과정을 반복하면 자연스럽게 비슷한 키의 아이들끼리 모이게 됩니다. K-Means가 없던 시절에는 어땠을까요?

분석가들은 일일이 데이터를 눈으로 보면서 수동으로 분류해야 했습니다. 데이터가 100개 정도면 가능하겠지만, 수만 개가 넘어가면 사실상 불가능했습니다.

더 큰 문제는 사람마다 분류 기준이 달라서 일관성이 없었다는 것입니다. 바로 이런 문제를 해결하기 위해 K-Means가 등장했습니다.

K-Means를 사용하면 컴퓨터가 자동으로 데이터의 패턴을 찾아 그룹화해줍니다. 수백만 개의 데이터도 빠르게 처리할 수 있습니다.

무엇보다 항상 같은 기준으로 일관되게 분류한다는 큰 장점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 **KMeans(n_clusters=3)**에서 n_clusters가 바로 K값입니다. 몇 개의 그룹으로 나눌지 지정하는 것입니다.

fit() 메서드를 호출하면 알고리즘이 학습을 시작합니다. labels_ 속성에서 각 데이터가 어떤 그룹에 속하는지 확인할 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 이커머스 회사에서 고객 세그멘테이션에 활용합니다.

구매 금액, 방문 빈도, 체류 시간 등을 기준으로 고객을 그룹화하면 각 그룹에 맞는 마케팅 전략을 세울 수 있습니다. 하지만 주의할 점도 있습니다.

K값을 몇으로 설정할지 미리 정해야 한다는 것입니다. 잘못된 K값을 선택하면 의미 없는 그룹이 만들어질 수 있습니다.

따라서 적절한 K값을 찾는 방법을 함께 알아두어야 합니다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 라벨 없이도 분류가 가능하군요!"

실전 팁

💡 - n_clusters 값은 데이터 특성에 맞게 신중하게 선택하세요

  • 데이터를 표준화하면 더 좋은 결과를 얻을 수 있습니다

2. K-Means 알고리즘 동작 원리

김개발 씨가 K-Means를 실행해보니 잘 동작했습니다. 하지만 궁금증이 생겼습니다.

"이게 내부적으로 어떻게 동작하는 거죠?" 박시니어 씨가 화이트보드 앞으로 김개발 씨를 데려갔습니다. "직접 그려가며 설명해드릴게요."

K-Means는 네 단계를 반복합니다. 먼저 K개의 중심점을 임의로 선택하고, 각 데이터를 가장 가까운 중심점에 할당합니다.

그 다음 각 그룹의 평균 위치로 중심점을 이동시키고, 중심점이 더 이상 변하지 않을 때까지 반복합니다.

다음 코드를 살펴봅시다.

import numpy as np

def kmeans_step_by_step(data, k, max_iters=100):
    # 1단계: 랜덤하게 K개의 중심점 초기화
    centroids = data[np.random.choice(len(data), k, replace=False)]

    for iteration in range(max_iters):
        # 2단계: 각 데이터를 가장 가까운 중심점에 할당
        distances = np.sqrt(((data - centroids[:, np.newaxis])**2).sum(axis=2))
        labels = np.argmin(distances, axis=0)

        # 3단계: 각 클러스터의 평균으로 중심점 업데이트
        new_centroids = np.array([data[labels == i].mean(axis=0) for i in range(k)])

        # 4단계: 중심점이 변하지 않으면 종료
        if np.allclose(centroids, new_centroids):
            break
        centroids = new_centroids

    return labels, centroids

김개발 씨는 코드가 동작하는 것만으로는 만족할 수 없었습니다. 내부 원리를 이해해야 진정한 개발자라고 생각했기 때문입니다.

박시니어 씨가 화이트보드에 점 몇 개를 찍었습니다. "자, 이 점들이 우리 데이터라고 생각해보세요.

K-Means가 어떻게 이걸 그룹으로 나누는지 단계별로 보여드릴게요." 첫 번째 단계는 중심점 초기화입니다. 마치 술래잡기에서 술래를 정하는 것처럼, 먼저 K개의 점을 임의로 선택합니다.

이 점들이 각 그룹의 초기 중심점이 됩니다. 처음에는 아무 데나 찍어도 괜찮습니다.

어차피 나중에 제자리를 찾아가게 됩니다. 두 번째 단계는 데이터 할당입니다.

모든 데이터를 살펴보면서 가장 가까운 중심점을 찾습니다. 마치 학생들이 가장 가까운 선생님에게 모이는 것과 같습니다.

이때 거리는 보통 유클리드 거리를 사용합니다. 우리가 일상에서 말하는 직선거리와 같은 개념입니다.

세 번째 단계는 중심점 이동입니다. 각 그룹에 할당된 데이터들의 평균 위치를 계산합니다.

그리고 중심점을 그 평균 위치로 옮깁니다. 마치 선생님이 학생들의 중앙으로 이동하는 것과 같습니다.

네 번째 단계는 수렴 확인입니다. 중심점이 이동했으니 다시 두 번째 단계로 돌아갑니다.

데이터를 다시 할당하고, 중심점을 다시 이동시킵니다. 이 과정을 반복하다 보면 어느 순간 중심점이 더 이상 움직이지 않게 됩니다.

이때 알고리즘이 수렴했다고 말하며, 학습이 완료됩니다. 위 코드에서 **np.argmin(distances, axis=0)**은 각 데이터에서 가장 가까운 중심점의 인덱스를 찾는 부분입니다.

**np.allclose()**는 두 배열이 거의 같은지 확인하는 함수로, 중심점이 더 이상 변하지 않는지 판단합니다. 실무에서는 scikit-learn의 구현을 사용하면 되지만, 원리를 이해하고 있으면 문제가 발생했을 때 원인을 파악하기 훨씬 수월합니다.

주의할 점은 초기 중심점 선택에 따라 결과가 달라질 수 있다는 것입니다. 운이 나쁘면 최적이 아닌 결과에 수렴할 수 있습니다.

이를 지역 최적해 문제라고 합니다. 김개발 씨가 물었습니다.

"그럼 어떻게 해야 하나요?" 박시니어 씨가 대답했습니다. "여러 번 실행해서 가장 좋은 결과를 선택하면 돼요.

scikit-learn은 기본적으로 10번 실행합니다."

실전 팁

💡 - random_state를 설정하면 재현 가능한 결과를 얻을 수 있습니다

  • n_init 파라미터로 초기화 횟수를 조절할 수 있습니다

3. 최적의 K값 찾기 - 엘보우 방법

김개발 씨가 K-Means를 적용하려는데 막막했습니다. "K를 3으로 해야 할까요?

5로 해야 할까요?" 박시니어 씨가 말했습니다. "데이터에게 직접 물어보면 돼요.

엘보우 방법이라는 게 있거든요."

엘보우 방법은 K값에 따른 관성(Inertia) 변화를 그래프로 그려 최적의 K를 찾는 기법입니다. 관성은 각 데이터와 해당 중심점 사이 거리의 제곱합으로, 값이 작을수록 그룹이 잘 형성된 것입니다.

그래프에서 팔꿈치처럼 꺾이는 지점이 최적의 K입니다.

다음 코드를 살펴봅시다.

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

# 다양한 K값에 대해 관성 계산
inertias = []
K_range = range(1, 11)

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

# 엘보우 그래프 그리기
plt.figure(figsize=(8, 5))
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('K (클러스터 수)')
plt.ylabel('Inertia (관성)')
plt.title('엘보우 방법으로 최적의 K 찾기')
plt.show()

김개발 씨는 고민에 빠졌습니다. K-Means의 원리는 이해했는데, 정작 K를 몇으로 설정해야 할지 모르겠습니다.

2개로 나눌까요? 10개로 나눌까요?

박시니어 씨가 웃으며 말했습니다. "K값 선택은 모든 K-Means 사용자의 고민이에요.

하지만 걱정 마세요. 엘보우 방법이라는 아주 직관적인 기법이 있거든요." 엘보우 방법을 이해하려면 먼저 **관성(Inertia)**이라는 개념을 알아야 합니다.

관성은 각 데이터 포인트와 자신이 속한 클러스터 중심점 사이 거리의 제곱을 모두 더한 값입니다. 쉽게 말해 그룹 내 데이터들이 얼마나 뭉쳐있는지를 나타내는 지표입니다.

관성이 작을수록 데이터가 중심점 주변에 잘 모여있다는 뜻입니다. 그런데 재미있는 현상이 있습니다.

K값을 늘리면 관성은 무조건 줄어듭니다. 극단적으로 K를 데이터 개수만큼 설정하면 관성은 0이 됩니다.

각 데이터가 자기 자신만의 그룹을 갖게 되니까요. 하지만 이건 의미 없는 결과입니다.

그래서 적절한 지점을 찾아야 합니다. K값에 따른 관성을 그래프로 그리면 처음에는 급격히 감소하다가 어느 순간부터 완만해집니다.

이 그래프 모양이 마치 팔을 구부린 모양, 즉 팔꿈치(Elbow) 같아서 엘보우 방법이라고 부릅니다. 팔꿈치에 해당하는 지점이 바로 최적의 K입니다.

이 지점 이전에는 K를 늘릴 때마다 관성이 크게 줄어들어 효과가 좋습니다. 하지만 이 지점 이후에는 K를 늘려도 관성이 별로 줄지 않아 효과가 미미합니다.

비용 대비 효과가 가장 좋은 지점이 바로 팔꿈치입니다. 위 코드에서 kmeans.inertia_ 속성이 바로 관성 값을 반환합니다.

K를 1부터 10까지 바꿔가며 관성을 계산하고, 그래프로 시각화합니다. 주의할 점이 있습니다.

엘보우가 뚜렷하지 않은 경우도 있습니다. 데이터 특성에 따라 완만한 곡선만 나타날 수 있습니다.

이럴 때는 다른 방법을 함께 사용해야 합니다. 김개발 씨가 그래프를 보며 말했습니다.

"아, K가 3인 지점에서 확 꺾이네요!" 박시니어 씨가 고개를 끄덕였습니다. "바로 그거예요.

이 데이터는 3개 그룹으로 나누는 게 최적이라는 뜻이에요."

실전 팁

💡 - 엘보우가 명확하지 않으면 실루엣 점수를 함께 활용하세요

  • 도메인 지식을 결합하면 더 좋은 K값을 선택할 수 있습니다

4. 실루엣 점수로 군집 품질 평가하기

김개발 씨가 엘보우 방법으로 K를 정했는데, 정말 잘 나눈 건지 확신이 없었습니다. "결과가 좋은지 나쁜지 어떻게 알 수 있죠?" 박시니어 씨가 대답했습니다.

"실루엣 점수를 확인해보세요. 마치 시험 점수처럼 군집의 품질을 수치로 알려줘요."

실루엣 점수는 군집화가 얼마나 잘 되었는지 평가하는 지표입니다. -1에서 1 사이의 값을 가지며, 1에 가까울수록 같은 그룹 내 데이터는 가깝고 다른 그룹과는 멀리 떨어져 있다는 뜻입니다.

0에 가까우면 경계가 모호하고, 음수면 잘못 분류된 데이터가 많다는 의미입니다.

다음 코드를 살펴봅시다.

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
import numpy as np

# K-Means 실행
kmeans = KMeans(n_clusters=3, random_state=42)
labels = kmeans.fit_predict(data)

# 전체 실루엣 점수 계산
score = silhouette_score(data, labels)
print(f"전체 실루엣 점수: {score:.3f}")

# 각 데이터의 실루엣 점수
sample_scores = silhouette_samples(data, labels)
for i, s in enumerate(sample_scores):
    print(f"데이터 {i}: 클러스터 {labels[i]}, 실루엣 점수 {s:.3f}")

엘보우 방법으로 K값을 정한 김개발 씨는 또 다른 의문이 생겼습니다. K를 3으로 정했는데, 이게 정말 좋은 결과인 걸까요?

객관적으로 평가할 방법이 필요했습니다. 박시니어 씨가 실루엣 점수를 소개했습니다.

"실루엣 점수는 마치 학교 성적표 같아요. 군집화가 얼마나 잘 되었는지 점수로 알려주거든요." 실루엣 점수의 원리를 이해해봅시다.

각 데이터 포인트에 대해 두 가지를 계산합니다. 첫째, 같은 클러스터 내 다른 데이터들과의 평균 거리를 계산합니다.

이를 a라고 부릅니다. 둘째, 가장 가까운 다른 클러스터의 데이터들과의 평균 거리를 계산합니다.

이를 b라고 부릅니다. 실루엣 점수는 **(b - a) / max(a, b)**로 계산됩니다.

이 공식이 의미하는 바를 생각해봅시다. b가 a보다 훨씬 크면 실루엣 점수는 1에 가까워집니다.

이는 같은 그룹 내에서는 가깝고, 다른 그룹과는 멀다는 뜻입니다. 완벽한 군집화입니다.

반대로 a가 b보다 크면 점수가 음수가 됩니다. 이는 같은 그룹보다 다른 그룹의 데이터가 더 가깝다는 뜻입니다.

잘못 분류된 것입니다. a와 b가 비슷하면 점수는 0에 가까워집니다.

경계가 모호해서 어느 그룹에 속해도 비슷하다는 의미입니다. 위 코드에서 **silhouette_score()**는 모든 데이터의 실루엣 점수 평균을 반환합니다.

전체적인 군집화 품질을 한눈에 볼 수 있습니다. **silhouette_samples()**는 각 데이터의 개별 점수를 반환합니다.

어떤 데이터가 잘못 분류되었는지 파악할 수 있습니다. 실무에서는 다양한 K값에 대해 실루엣 점수를 계산하고, 가장 높은 점수를 보이는 K를 선택하기도 합니다.

엘보우 방법과 함께 사용하면 더 신뢰도 높은 K값을 찾을 수 있습니다. 주의할 점은 실루엣 점수도 만능이 아니라는 것입니다.

데이터 분포에 따라 점수가 낮게 나와도 실제로는 의미 있는 군집화일 수 있습니다. 도메인 지식과 함께 해석해야 합니다.

김개발 씨가 실루엣 점수를 확인했습니다. "0.7이 나왔어요!

이 정도면 괜찮은 건가요?" 박시니어 씨가 웃었습니다. "아주 좋은 점수예요.

보통 0.5 이상이면 괜찮고, 0.7 이상이면 훌륭한 거예요."

실전 팁

💡 - 실루엣 점수 0.5 이상이면 합리적인 군집화로 볼 수 있습니다

  • 음수 점수를 가진 데이터는 재검토가 필요합니다

5. 데이터 전처리 - 스케일링의 중요성

김개발 씨가 고객 데이터에 K-Means를 적용했는데 이상한 결과가 나왔습니다. 나이와 연소득 데이터를 함께 사용했는데, 연소득만 영향을 미치는 것 같았습니다.

박시니어 씨가 데이터를 보더니 말했습니다. "스케일링을 안 하셨네요.

K-Means에서 가장 흔한 실수예요."

K-Means는 거리 기반 알고리즘이라 데이터의 스케일에 민감합니다. 연소득이 수천만 원 단위이고 나이가 수십 단위라면, 거리 계산에서 연소득이 압도적으로 큰 영향을 미칩니다.

따라서 모든 특성을 비슷한 범위로 맞춰주는 스케일링이 필수입니다.

다음 코드를 살펴봅시다.

from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import numpy as np

# 스케일이 다른 원본 데이터 (나이, 연소득)
raw_data = np.array([
    [25, 35000000], [30, 42000000], [35, 58000000],
    [40, 75000000], [28, 38000000], [55, 95000000]
])

# StandardScaler로 표준화 (평균 0, 표준편차 1)
scaler = StandardScaler()
scaled_data = scaler.fit_transform(raw_data)

print("스케일링 전:", raw_data[0])
print("스케일링 후:", scaled_data[0])

# 스케일링된 데이터로 K-Means 실행
kmeans = KMeans(n_clusters=2, random_state=42)
labels = kmeans.fit_predict(scaled_data)

김개발 씨는 고객 세그멘테이션 프로젝트를 진행 중이었습니다. 고객의 나이와 연소득을 기반으로 그룹을 나누려고 했습니다.

그런데 결과가 이상했습니다. 나이가 25세든 55세든 상관없이 연소득이 비슷한 사람들만 같은 그룹으로 묶였습니다.

박시니어 씨가 데이터를 확인하더니 바로 문제를 찾았습니다. "나이는 25에서 55 사이인데, 연소득은 3500만에서 9500만 사이네요.

스케일 차이가 엄청나잖아요." 왜 이게 문제가 될까요? K-Means는 유클리드 거리를 사용합니다.

두 점 사이의 거리를 계산할 때 각 차원의 차이를 제곱해서 더합니다. 나이 차이 30의 제곱은 900입니다.

하지만 연소득 차이 6000만의 제곱은 3,600,000,000,000,000입니다. 나이의 영향은 사실상 무시되는 것입니다.

마치 키와 몸무게로 사람을 분류하는데, 키는 미터 단위(1.7m)이고 몸무게는 그램 단위(70,000g)라면 어떨까요? 키의 차이는 거의 무시되고 몸무게만으로 분류될 것입니다.

이 문제를 해결하는 것이 스케일링입니다. 가장 많이 사용하는 방법은 StandardScaler입니다.

각 특성의 평균을 0으로, 표준편차를 1로 만들어줍니다. 이렇게 하면 모든 특성이 비슷한 범위의 값을 갖게 됩니다.

또 다른 방법으로 MinMaxScaler가 있습니다. 모든 값을 0과 1 사이로 변환합니다.

이상치에 민감하지만 해석이 직관적입니다. 위 코드에서 **scaler.fit_transform()**은 두 가지 일을 합니다.

먼저 데이터의 평균과 표준편차를 계산하고(fit), 그 값으로 데이터를 변환합니다(transform). 변환된 데이터는 평균이 0이고 표준편차가 1인 분포를 갖게 됩니다.

실무에서 중요한 점이 있습니다. 학습 데이터와 새로운 데이터에 같은 스케일러를 적용해야 합니다.

학습할 때 fit_transform()을 사용하고, 새 데이터에는 transform()만 사용해야 합니다. 그렇지 않으면 스케일이 달라져서 엉뚱한 결과가 나옵니다.

김개발 씨가 스케일링을 적용한 후 다시 K-Means를 실행했습니다. 이번에는 나이와 연소득이 균형 있게 반영된 합리적인 결과가 나왔습니다.

"스케일링 하나로 이렇게 달라지네요!"

실전 팁

💡 - K-Means 적용 전 반드시 스케일링을 수행하세요

  • 새 데이터 예측 시에는 학습 때 사용한 스케일러로 transform()만 적용하세요

6. K-Means++로 초기화 개선하기

김개발 씨가 K-Means를 여러 번 실행해봤는데, 실행할 때마다 결과가 조금씩 달랐습니다. "왜 매번 다른 결과가 나오죠?" 박시니어 씨가 설명했습니다.

"초기 중심점을 랜덤하게 선택해서 그래요. K-Means++를 사용하면 이 문제를 완화할 수 있어요."

기본 K-Means는 초기 중심점을 완전히 랜덤하게 선택해서 결과가 불안정합니다. **K-Means++**는 첫 번째 중심점만 랜덤하게 선택하고, 나머지는 기존 중심점과 멀리 떨어진 점을 우선 선택합니다.

이렇게 하면 더 빠르게 수렴하고 더 좋은 결과를 얻을 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.cluster import KMeans
import numpy as np

# 테스트용 데이터
np.random.seed(42)
data = np.vstack([
    np.random.randn(50, 2) + [0, 0],
    np.random.randn(50, 2) + [5, 5],
    np.random.randn(50, 2) + [10, 0]
])

# 기본 랜덤 초기화 (비추천)
kmeans_random = KMeans(n_clusters=3, init='random', n_init=1, random_state=42)
kmeans_random.fit(data)
print(f"랜덤 초기화 관성: {kmeans_random.inertia_:.2f}")

# K-Means++ 초기화 (추천, 기본값)
kmeans_plus = KMeans(n_clusters=3, init='k-means++', random_state=42)
kmeans_plus.fit(data)
print(f"K-Means++ 관성: {kmeans_plus.inertia_:.2f}")

김개발 씨는 같은 데이터에 K-Means를 실행했는데 결과가 매번 달라서 당황했습니다. 어떤 때는 깔끔하게 세 그룹으로 나뉘고, 어떤 때는 이상하게 나뉘었습니다.

박시니어 씨가 원인을 설명했습니다. "K-Means의 결과는 초기 중심점에 크게 좌우돼요.

운 나쁘게 초기 중심점이 설정되면 지역 최적해에 빠지게 됩니다." 지역 최적해란 무엇일까요? 마치 등산으로 비유하면, 에베레스트 정상에 가려고 했는데 동네 뒷산 정상에서 멈춘 것과 같습니다.

주변보다는 높지만 진짜 최고점은 아닙니다. K-Means도 마찬가지로 더 좋은 해가 있는데 찾지 못하고 멈출 수 있습니다.

이 문제를 해결하기 위해 **K-Means++**가 등장했습니다. K-Means++의 핵심 아이디어는 간단합니다.

초기 중심점들이 서로 멀리 떨어지도록 선택하는 것입니다. 첫 번째 중심점은 랜덤하게 선택합니다.

두 번째 중심점은 첫 번째와 멀리 떨어진 점이 선택될 확률을 높입니다. 세 번째는 기존 두 중심점과 멀리 떨어진 점을 우선합니다.

구체적으로, 각 데이터 포인트가 선택될 확률은 가장 가까운 기존 중심점까지의 거리의 제곱에 비례합니다. 멀리 있을수록 선택될 가능성이 높아지는 것입니다.

이렇게 하면 초기 중심점들이 데이터 공간에 고르게 퍼지게 됩니다. 결과적으로 더 빠르게 수렴하고, 지역 최적해에 빠질 확률도 줄어듭니다.

다행히 scikit-learn의 KMeans는 기본적으로 **init='k-means++'**를 사용합니다. 별도로 설정하지 않아도 K-Means++가 적용됩니다.

위 코드에서 **init='random'**은 기본 랜덤 초기화를, **init='k-means++'**는 K-Means++ 초기화를 사용합니다. 관성 값을 비교하면 K-Means++가 더 낮은 관성, 즉 더 좋은 결과를 보여주는 것을 확인할 수 있습니다.

추가로 n_init 파라미터도 중요합니다. 이는 다른 초기 중심점으로 몇 번 실행할지를 정합니다.

기본값은 10으로, 10번 실행해서 가장 좋은 결과를 반환합니다. 김개발 씨가 고개를 끄덕였습니다.

"scikit-learn이 알아서 해주는군요. 그래도 원리를 알고 있으니까 마음이 놓이네요."

실전 팁

💡 - scikit-learn은 기본적으로 K-Means++를 사용하므로 별도 설정이 필요 없습니다

  • n_init을 높이면 더 안정적인 결과를 얻지만 시간이 오래 걸립니다

7. 실무 프로젝트 - 고객 세그멘테이션

드디어 김개발 씨가 실제 프로젝트에 K-Means를 적용할 시간이 됐습니다. 마케팅팀에서 고객을 그룹으로 나눠달라는 요청이 왔습니다.

박시니어 씨가 말했습니다. "지금까지 배운 모든 것을 종합해서 적용해봅시다."

실무에서 K-Means를 적용할 때는 데이터 전처리, 최적 K 탐색, 모델 학습, 결과 해석까지 체계적인 파이프라인이 필요합니다. 특히 결과를 비즈니스 관점에서 해석하고 각 군집의 특성을 파악하는 것이 중요합니다.

다음 코드를 살펴봅시다.

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

# 고객 데이터 로드 (예시)
customers = pd.DataFrame({
    'annual_income': [15, 16, 17, 18, 19, 39, 40, 41, 76, 77, 78, 79],
    'spending_score': [39, 81, 6, 77, 40, 76, 94, 3, 56, 88, 17, 95]
})

# 1. 데이터 스케일링
scaler = StandardScaler()
scaled_data = scaler.fit_transform(customers)

# 2. K-Means 적용
kmeans = KMeans(n_clusters=3, random_state=42)
customers['cluster'] = kmeans.fit_predict(scaled_data)

# 3. 각 군집의 특성 분석
cluster_summary = customers.groupby('cluster').mean()
print("군집별 평균 특성:")
print(cluster_summary)

드디어 실전입니다. 마케팅팀장이 김개발 씨를 불렀습니다.

"우리 고객들을 몇 개 그룹으로 나눠서 각 그룹에 맞는 마케팅 전략을 세우고 싶어요. 도와줄 수 있나요?" 김개발 씨는 자신 있게 대답했습니다.

"K-Means로 해결할 수 있습니다!" 박시니어 씨가 옆에서 조언했습니다. "실무 프로젝트는 단순히 코드만 돌리는 게 아니에요.

체계적인 과정을 따라야 합니다." 첫 번째 단계는 데이터 이해입니다. 어떤 데이터를 사용할지 정해야 합니다.

이번 프로젝트에서는 고객의 연간 소득과 쇼핑 점수를 사용하기로 했습니다. 쇼핑 점수는 고객의 구매 빈도와 금액을 종합한 지표입니다.

두 번째 단계는 데이터 전처리입니다. 앞서 배운 것처럼 K-Means는 스케일에 민감합니다.

StandardScaler로 모든 특성을 표준화합니다. 결측치가 있다면 처리하고, 이상치도 확인해야 합니다.

세 번째 단계는 최적 K 탐색입니다. 엘보우 방법과 실루엣 점수를 활용해 적절한 K값을 찾습니다.

이번 데이터에서는 K=3이 가장 적절했습니다. 네 번째 단계는 모델 학습입니다.

KMeans 객체를 생성하고 fit_predict()로 학습과 예측을 동시에 수행합니다. 결과로 각 고객이 어떤 군집에 속하는지 라벨이 반환됩니다.

다섯 번째 단계가 가장 중요합니다. 바로 결과 해석입니다.

각 군집의 평균 특성을 계산해서 어떤 고객들이 모였는지 파악합니다. 예를 들어 군집 0은 소득은 낮지만 쇼핑 점수가 높은 고객입니다.

가성비를 중시하는 열정적인 쇼핑객일 수 있습니다. 군집 1은 소득이 높고 쇼핑 점수도 높은 VIP 고객입니다.

이런 해석을 바탕으로 마케팅 전략을 세울 수 있습니다. 군집 0에게는 할인 쿠폰을, 군집 1에게는 프리미엄 서비스를 제안할 수 있습니다.

데이터에 기반한 의사결정이 가능해지는 것입니다. 김개발 씨가 마케팅팀에 결과를 발표했습니다.

팀장이 감탄했습니다. "이렇게 깔끔하게 고객을 나눠주다니!

이제 각 그룹에 맞는 캠페인을 기획할 수 있겠어요." 박시니어 씨가 마지막으로 덧붙였습니다. "군집화는 시작일 뿐이에요.

시간이 지나면 고객 성향도 변하니까 주기적으로 다시 분석해야 합니다."

실전 팁

💡 - 군집 결과에 의미 있는 이름을 붙이면 비즈니스팀과 소통이 쉬워집니다

  • 결과를 시각화해서 발표하면 더 효과적으로 전달할 수 있습니다

8. K-Means의 한계와 대안

김개발 씨가 다른 프로젝트에서 K-Means를 적용했는데 결과가 이상했습니다. 데이터가 원형이 아니라 초승달 모양으로 분포되어 있었는데, K-Means가 제대로 나누지 못했습니다.

박시니어 씨가 말했습니다. "K-Means도 만능은 아니에요.

한계를 알고 대안도 알아둬야 해요."

K-Means는 구형(원형) 클러스터를 가정하기 때문에 복잡한 모양의 데이터에는 적합하지 않습니다. 또한 K값을 미리 지정해야 하고, 이상치에 민감하며, 밀도가 다른 군집을 잘 구분하지 못합니다.

이런 경우 DBSCAN, 계층적 군집화 등의 대안을 고려해야 합니다.

다음 코드를 살펴봅시다.

from sklearn.cluster import KMeans, DBSCAN
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt

# 초승달 모양 데이터 생성
X, y = make_moons(n_samples=200, noise=0.05, random_state=42)

# K-Means 시도 (잘 안 됨)
kmeans = KMeans(n_clusters=2, random_state=42)
kmeans_labels = kmeans.fit_predict(X)

# DBSCAN 시도 (잘 됨)
dbscan = DBSCAN(eps=0.2, min_samples=5)
dbscan_labels = dbscan.fit_predict(X)

print(f"K-Means 고유 라벨: {set(kmeans_labels)}")
print(f"DBSCAN 고유 라벨: {set(dbscan_labels)}")
# DBSCAN의 -1은 노이즈(이상치)를 의미

모든 도구에는 한계가 있습니다. K-Means도 마찬가지입니다.

김개발 씨가 그 한계를 직접 경험했습니다. 새 프로젝트의 데이터를 시각화해보니 두 개의 초승달이 맞물린 모양이었습니다.

K-Means를 적용했더니 엉뚱하게 가로로 잘라버렸습니다. 사람 눈에는 분명히 두 그룹인데 말입니다.

왜 이런 일이 생겼을까요? K-Means의 첫 번째 한계는 구형 클러스터 가정입니다.

K-Means는 중심점으로부터의 거리로 그룹을 나눕니다. 따라서 원형에 가까운 클러스터는 잘 찾지만, 길쭉하거나 구불구불한 모양은 제대로 구분하지 못합니다.

두 번째 한계는 K값을 미리 알아야 한다는 것입니다. 엘보우 방법이나 실루엣 점수로 추정할 수 있지만, 정확한 값을 알기 어려울 때도 있습니다.

세 번째 한계는 이상치에 민감하다는 것입니다. 극단적인 값이 있으면 중심점이 그쪽으로 끌려가서 전체 결과가 왜곡될 수 있습니다.

네 번째 한계는 밀도 차이를 무시한다는 것입니다. 어떤 그룹은 빽빽하고 어떤 그룹은 느슨해도, K-Means는 단순히 거리만 보고 나눕니다.

이런 한계를 극복하기 위한 대안들이 있습니다. DBSCAN은 밀도 기반 군집화 알고리즘입니다.

가까이 모여있는 점들을 하나의 군집으로 인식합니다. K값을 미리 정할 필요도 없고, 복잡한 모양도 잘 찾습니다.

게다가 이상치를 자동으로 탐지합니다. 계층적 군집화는 데이터를 트리 구조로 표현합니다.

가장 가까운 데이터부터 차례로 합쳐나가면서 군집을 형성합니다. 덴드로그램이라는 시각화로 다양한 수준의 군집을 확인할 수 있습니다.

**가우시안 혼합 모델(GMM)**은 K-Means의 확률적 버전입니다. 각 데이터가 어떤 군집에 속할 확률을 계산합니다.

타원형 클러스터도 잘 찾습니다. 위 코드에서 **make_moons()**는 초승달 모양의 테스트 데이터를 생성합니다.

K-Means는 이를 제대로 나누지 못하지만, DBSCAN은 잘 구분합니다. 박시니어 씨가 정리했습니다.

"도구는 상황에 맞게 선택해야 해요. K-Means가 안 맞으면 다른 알고리즘을 시도해보세요."

실전 팁

💡 - 데이터를 먼저 시각화해서 모양을 확인하세요

  • 한 가지 알고리즘에 집착하지 말고 여러 방법을 비교해보세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#K-Means#Clustering#MachineLearning#ScikitLearn#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.