머신러닝 - 비지도학습(덴드로그램, K-Means)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


비지도 학습?

보유한 데이터에 타겟값(y)가 없는 경우

  1. 계층적 군집화 > 덴드로그램
  2. 비계층적 군집화 > K-Means, DBSCAN 등을 사용

비지도 학습은 서비스 단에서는 사용 불가능 & 서비스를 이해할 때만 사용 가능

높은 정확도를 가지기 힘들기 때문

덴드로그램

모델을 구동 후 사후적으로 군집을 나눌 수 있음

위 그림에서 내가 2000을 기준으로 군집을 나눈다면, 위에서 군집은 총 4개로 나뉘게 됨! (2000을 기준으로 가로 직선을 그어보면 됨)

하지만 이는 연산이 많아질 수 있고, 초반에 잘못 분류되면 오분류의 가능성이 있다는 단점이 존재함.

K-Means

  1. 초반에 센트로이드 설정을 해주고,
  2. 센트로이드를 중심으로 거리를 계산함.
  3. 센트로이드가 거리를 최소화하는 방향으로 이동함
  4. 다시한번 나머지 애들끼리 이동하고
  5. 센트로이드가 거리 계산해서 이동.. 반복..
  • 센트로이드 중심으로 이동하다보니, 연산이 덴드로그램보다는 적다.
  • 그러나 군집이 몇개가 될지 알수가 없어서, 초반 센트로이드를 설정해야하는 것이 단점이다.
  • 센트로이드가 적정 군집보다 크면 안좋으니까 > 왜곡된다고 보면 된다.

라이브러리 불러오기

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.datasets import load_iris
from sklearn.cluster import KMeans

from sklearn.decomposition import PCA

데이터 불러오기

iris = load_iris()
X = iris.data

모델 정의

# n_clusters: 데이터의 센트로이드를 설정 
# 센트로이드: 군집의 중심 > 그 중심을 내가 정해주는 것 > 몇개의 군집으로 설정할 것인지? 
kmeans = KMeans(n_clusters=3, random_state=42)

모델 훈련

kmeans.fit(X)
cluster = kmeans.fit_predict(X)

시각화 > 주성분 분석(PCA)

labels = kmeans.labels_

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

centroids = pca.transform(kmeans.cluster_centers_)

plt.figure(figsize=(10, 7))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=labels, cmap='viridis', marker='o', edgecolor='k', s=50)
plt.scatter(centroids[:, 0], centroids[:, 1], c='red', marker='X', s=200, label='Centroids')
plt.title('K-Means Clustering on Iris Dataset')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.legend()
plt.show()

K-Means의 약점은 초반 센트로이드를 선택해야하는 것에 있는데, 이를 보완한 라이브러리로는

  1. 엘보우
  2. 실루엣 계수 가 있다. (적정 군집을 구하는 방법임)
  • 엘보우: for문 돌면서 거리합을 구함, (클러스터 갯수가 증가했을떄 합이 최소화 되는 방향으로)
    • 갯수마다의 증감 차이를 봐야하며 어느순간 덜 줄어드는 때를 봐줘야 함 > 최적의 군집을 찾는게 중요
    • k가 증가하면 SSE(에러의 합)이 줄어드는 것을 보는 것으로 최적 k는 SSE 감소율이 급격히 줄어드는 팔꿈치 지점임
    • 센트로이드와 점들의 거리를 에러 > 이 에러의 합을 줄이는게 목표
# 다양한 K값에 대해 성능 측정
inertias = []
K_range = range(1, 4)

# for문 돌면서 거리합을 구함 
for k in K_range:
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X)
    inertias.append(kmeans.inertia_)

# 엘보우 그래프 그리기
plt.figure(figsize=(8, 5))
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('클러스터 개수 (K)')
plt.ylabel('Inertia (클러스터 내 거리의 합)')
plt.title('📈 최적의 K 찾기: Elbow Method')
plt.grid(True, alpha=0.3)
plt.show()

# 팔꿈치 부분이 최적의 K!

1개였을떄의 거리합과 2개였을때의 거리합이 확연히 줄어드는 것을 볼 수 있다.

  • 실루엣: for문 돌면서 predict, predict에서 실루엣 한 것을 클러스터에 얼마나 잘 속해있는 지를 측정함
    • 하나의 군집을 클러스터라고 하는데, 같은 클러스터 내 센트로이드와 다른 점들과의 거리를 재고 가장 가까운 다른 클러스터 까지의 평균거리를 가지고 계산식으로 실루엣 계수를 정함
    • 실루엣 계수가 크면 적절(1에 가까우면)
    • 음수는 이상치가 많이 껴있을 경우로 잘못된 클러스터에 할당된 것
    • 0에 가까울수록 클러스터 경계에 위치한 것
from sklearn.metrics import silhouette_score

# 실루엣 점수 계산
silhouette_scores = []
K_range = range(2, 4)  # 최소 2개 그룹 필요

for k in K_range:
    kmeans = KMeans(n_clusters=k, random_state=42)
    labels = kmeans.fit_predict(X)
    score = silhouette_score(X, labels)
    silhouette_scores.append(score)
    print(f"K={k}: 실루엣 점수 = {score:.3f}")

# 가장 높은 점수가 최적의 K!
best_k = K_range[np.argmax(silhouette_scores)]
print(f"\n🎯 최적의 K: {best_k}")

실습해보기

data = {
    '연간구매금액': [20, 25, 30, 100, 120, 110, 200, 250, 220],
    '연간방문횟수': [5, 8, 6, 20, 25, 22, 40, 45, 38]
}
df = pd.DataFrame(data)

데이터 스케일링

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X = scaler.fit_transform(df)

K-Means

kmeans = KMeans(n_clusters=3, random_state=42)
# kmeans.fit(X)

# cluster_labels = kmeans.labels_
cluster_labels = kmeans.fit_predict(X)

결과 확인

df['고객그룹'] = cluster_labels

시각화

plt.figure(figsize=(10, 6))
colors = ['red', 'blue', 'green']
for i in range(3):
    mask = cluster_labels == i
    plt.scatter(df[mask]['연간구매금액'], df[mask]['연간방문횟수'],
               c=colors[i], label=f'그룹 {i+1}', s=100)

# 중심점 표시
centers_original = scaler.inverse_transform(kmeans.cluster_centers_)
plt.scatter(centers_original[:, 0], centers_original[:, 1],
           c='black', marker='x', s=200, linewidths=3, label='중심점')

plt.xlabel('연간 구매금액 (만원)')
plt.ylabel('연간 방문횟수')
plt.title(' 고객 세분화 결과')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()