susinlee 님의 블로그

군집 본문

학습/머신러닝

군집

susinlee 2025. 1. 26. 23:10

목차

  1. 요약
  2. 클러스터링이란?
  3. K-Means 클러스터링
  4. PCA
  5. 실습

1. 요약

1) K-Means 란?

  • 데이터를 k개의 그룹(클러스터)으로 자동 분류하는 비지도 학습 알고리즘
  • 클러스터 중심(centroid)을 반복적으로 업데이트하여 최적의 군집을 찾음

2) K-Means의 주요 과정

  1. 초기 중심(centroid) 설정
  2. 각 데이터 포인트를 가장 가까운 중심에 할당
  3. 새롭게 할당된 데이터 기준으로 중심 재계산
  4. 중심이 더 이상 변하지 않을 때까지 반복

3) K 값 선택 방법

  • 엘보우 기법 (Elbow Method): WCSS(클러스터 내 거리 제곱합) 감소율이 꺾이는 지점 선택
  • 실루엣 점수 (Silhouette Score): 클러스터 내부 응집력과 외부 분리도를 평가하여 최적의 k 선택

4) K-Means의 장점 & 단점

  • 장점
    • 빠르고 효율적 O(n)
    • 해석이 쉬움 & 다양한 데이터에 적용 가능
  • 단점
    • 초기 중심 선택에 따라 성능 변화 (해결책: init='k-means++')
    • 이상치(outlier)에 민감
    • k값을 미리 정해야 함

5) PCA 적용 후 K-means (차원 축소 & 클러스터링)

  • 고차원 데이터를 PCA로 변환 후 K-Means 적용하면 성능 향상 가능
  • 노이즈 제거 및 시각화 용이

2. 클러스터링이란?

1) 클러스터링의 정의

  • 데이터를 그룹으로 나누는 비지도 학습(Unsupervised Learning)의 한 형태
  • 목적: 비슷한 특성을 가진 데이터 포인트를 같은 그룹으로 묶음

2) 활용 예

  • 고객 세분화(Customer Segmentation)
  • 이미지 분류(Image Classification)
  • 문서 군집화(Document Clustering)

3) 주요 알고리즘

  • K-Means 클러스터링
  • DBSCAN, HDBSCAN 등

3. K-Means 클러스터링

1) K-Means 클러스터링이란?

  • 데이터 포인트를 k개의 클러스터로 나누는 알고리즘

2) K-Means 알고리즘

  1. 클러스터의 개수 k를 설정
  2. 초기 클러스터 중심을 무작위 선택
  3. 각 데이터 포인터를 가장 가까운 클러스터 중심으로 할당
  4. 각 클러스터에 대해 중심을 재계산
  5. 클러스터 중심이 수렴하거나 최대 반복 횟수에 도달하면 종료

3) 매개변수 설명

  • n_cluster = 클러스터 개수(군집 개수)를 설정, 1 ~ N
  • init : 초기 중심점(centroid) 설정 방법을 지정, k-means++ / random / numpy 배열을 활용
  • n_init : 서로 다른 초기 중심점 설정을 몇 번 반복할지 지정
  • max_iter : 중심점이 수렴할 때까지 반복하는 최대 횟수 = K-Means의 최대 반복(iteration)

 

4) K-Means 알고리즘 시각화 

 

step 0. 세 개의 클러스터를 가지는 2D 데이터를 생성 

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)
X = np.vstack([
    np.random.normal(loc=[2, 2], scale=0.8, size=(50, 2)),
    np.random.normal(loc=[7, 7], scale=0.8, size=(50, 2)),
    np.random.normal(loc=[2, 7], scale=0.8, size=(50, 2))
])

plt.scatter(X[:, 0], X[:, 1])
plt.show()

 

 

 

step 1, 2. 클러스터 개수 k를 설정 & 초기 클러스터 중심 무작위 선택

# k값 설정 및 KMeans 초기화
k = 3
kmeans = KMeans(n_clusters=k, init='random', n_init=1, max_iter=1, random_state=42)

# 초기 클러스터 중심 시각화
kmeans.fit(X)
initial_centroids = kmeans.cluster_centers_

print("Step 1 - Initial Centroids:")
print(initial_centroids)

plt.figure(figsize=(6, 6))
plt.scatter(X[:, 0], X[:, 1], c='grey', s=50, label='Data Points')
plt.scatter(initial_centroids[:, 0], initial_centroids[:, 1], c='b', s=150, label='Initial Centroids', marker='X')
plt.title("Step 1: Initial Centroids")
plt.legend()
plt.grid()
plt.show()

 

 

 

step3. 데이터 포인트 할당

labels = kmeans.labels_

plt.figure(figsize=(6, 6))
for i in range(k):
    cluster_points = X[labels == i]
    plt.scatter(cluster_points[:, 0], cluster_points[:, 1], s=50, label=f'Cluter {i+1}')
plt.scatter(initial_centroids[:, 0], initial_centroids[:, 1], c='b', s=150, label='Initial Centroids', marker='X')
plt.title("Step 3: Assigning data points to the nearest centroid")
plt.legend()
plt.grid()
plt.show()

 

 

step 4. 각 클러스터에 대해 중심을 재계산

# 2. 데이터 포이트 할당 시각화
kmeans = KMeans(n_clusters=k, init=initial_centroids, max_iter=1, random_state=42)
kmeans.fit(X)
labels = kmeans.labels_
centroids2 = kmeans.cluster_centers_

plt.figure(figsize=(6, 6))
for i in range(k):
    cluster_points = X[labels == i]
    plt.scatter(cluster_points[:, 0], cluster_points[:, 1], s=50, label=f'Cluter {i+1}')
plt.scatter(centroids2[:, 0], centroids2[:, 1], c='b', s=150, label='Centroids', marker='X')
plt.title('Step 4: "Recalculate the centroid for each cluster"')
plt.legend()
plt.grid()
plt.show()

 

 

 

step 5. 클러스터 중심이 수렴하거나 최대 반복 횟수에 도달하면 종료

kmeans = KMeans(n_clusters=k, init=initial_centroids, max_iter=2, random_state=42)
kmeans.fit(X)
centroids3 = kmeans.cluster_centers_
new_labels = kmeans.labels_

print("Step 3 - Updated Centroids After Second Iteration:")
print(centroids3)

plt.figure(figsize=(6, 6))
for i in range(k):
    data_points = X[new_labels == i]
    plt.scatter(data_points[:, 0], data_points[:, 1], s=50, label=f'Cluster {i+1}')
plt.scatter(centroids3[:, 0], centroids3[:, 1], s=150, c='b', marker='X', label='New Centroids')
plt.title('Step 5: Terminate when centroids converge or max iterations reached.')
plt.legend()
plt.grid()
plt.show()

반복했으나 중심에 변화가 없으므로 종료

 

 

5) 클러스터링 결과 품질 측정 지표

  1. 엘보우 기법(Elbow method)
    • 클러스터의 중심 간의 거리를 측정한 후 제곱하여 모든 클러스터에 대한 제곱 오차를 합산한 산한 값을 활용
  2. 실루엣(Silhouette) 지표
    • 실루엣 점수 범위: -1 에서 +1
      • 1: 데이터 포인트가 완벽하게 적절한 클러스터에 속함
      • 0: 클러스터 경계에 위치
      • -1: 잘못된 클러스터에 할당됨
    • 실루엣 점수 계산
      • 실루엣 점수는 두 가지 거리 기반 계산을 사용
        1. a(i): 동일클러스터 내 다른 데이터 포인트까지의 평균 거리
        2. b(i): 가장 가까운 다른 클러스터 데이터 포인트까지의 평균 거리
      • 실루엣 지표 활용
        • 클러스터 개수 k를 선택할 때, 실루엣 점수를 참고
        • 실루엣 점수가 높을수록 클러스터링 품질이 좋음을 나타냄
      • 실루엣 점수 계산 공식

 

엘보우 기법 및 시각화 코드

# 엘보우 기법을 활용하여 최적의 클러스터 개수 찾기
wcss = []  # Within-cluster sum of squares (클러스터 내 제곱합)

# 여러 개의 k 값(클러스터 개수)을 실험
for i in range(1, 10):
    kmeans = KMeans(n_clusters=i, init='k-means++', n_init=10, max_iter=300, random_state=42)
    kmeans.fit(X)
    wcss.append(kmeans.inertia_)  # inertia는 클러스터 내 거리 제곱합

# 엘보우 그래프 시각화
plt.figure(figsize=(8, 5))
plt.plot(range(1, 10), wcss, marker='o', linestyle='-', color='b')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('WCSS (Within-Cluster Sum of Squares)')
plt.title('Elbow Method for Optimal k')
plt.grid()
plt.show()

 

 

실루엣 점수 및 시각화 코드

from sklearn.metrics import silhouette_score

# 실루엣 지수를 저장할 리스트
silhouette_scores = []

# k=2부터 9까지 실험 (k=1은 실루엣 점수 계산 불가)
for i in range(2, 10):
    kmeans = KMeans(n_clusters=i, init='k-means++', n_init=10, max_iter=300, random_state=42)
    kmeans.fit(X)
    labels = kmeans.labels_
    silhouette_avg = silhouette_score(X, labels)  # 실루엣 점수 계산
    silhouette_scores.append(silhouette_avg)

# 실루엣 점수 시각화
plt.figure(figsize=(8, 5))
plt.plot(range(2, 10), silhouette_scores, marker='o', linestyle='-', color='g')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score for Optimal k')
plt.grid()
plt.show()

 

 

두 방법의 결과를 함께 분석하여 최적의 k를 선택

 

 

6) 데이터 스케일링

  • K-Means에서 데이터 스케일링이 필요한 이유
    • K-Means는 유클리드 거리를 사용하여 데이터 포인트와 클러스터 중심 간의 거리를 계산
    • 데이터의 스케일(특성 값의 범위)이 다를 경우, 값의 범위가 큰 특성이 클러스터링 결과에 과도하게 영향을 미침
  • 데이터 스케일링 방법
    • 표준화(Standardization)
      • 데이터의 평균을 0으로, 표준편차를 1로 변환
      • 표준화는 데이터가 정규 분포를 따르거나 특성의 단위가 다를 때 유용
    • 정규화
      • 데이터의 값을 0~1 사이로 변환
      • 정규화는 데이터가 정규 분포를 따르지 않거나 특성의 범위를 제한하려는 경우에 유용
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler

scaler_standard = StandardScaler()
sclaer_minmax = MinMaxScaler()

X_scaled_s = scaler_standard.fit_transform(X)
X_scaled_m = sclaer_minmax.fit_transform(X)

fig, axes = plt.subplots(1, 3, figsize=(18, 4))
axes[0].scatter(X[:, 0], X[:, 1])
axes[0].set_title("Original data")
axes[1].scatter(X_scaled_s[:, 0], X_scaled_s[:, 1])
axes[1].set_title("Standard Scaler")
axes[2].scatter(X_scaled_m[:, 0], X_scaled_m[:, 1])
axes[2].set_title('MinMax Scaler')
plt.show()

 


4. PCA

1) PCA 란?

  • 데이터의 주요 분산 방향을 찾는 차원 축소 기법
  • 고차원의 데이터를 저차원으로 변환하면서 중요한 정보를 유지

 

2) PCA 목적

  • 데이터 시각화
  • 데이터의 중복 제거
  • 계산 효율성 향상
  • 활용
    • feature engineering 과정에서 차원을 줄이면서 과적합(overfitting) 위험 감소 & 모델 학습 속도 증가시킬 수 있음
    • 회귀분석에서 다중공선성 (multicollinearity)이 있는 설명 변수를 직접 제거하지 않아도 됨
    • 데이터가 고차원(3D 이상)일 때, PCA를 활용하여 2D 또는 3D로 변환하여 시각화 가능
  • PCA 작동 원리
    • 공분산 행렬 계산
    • 공분산 행렬에서 고유값과 고유벡터 계산
    • 고유값: 각 축(주성분)이 설명하는 데이터 분산의 크기
    • 고유벡터: 각 주성분의 방향
    • 고유값이 큰 순서대로 정렬하여 주요한 주성분을 선택

 

5. 실습

  • penguins 데이터셋을 활용해 K-Means와 PCA 실습

1) 필요 라이브러리 불러오기 및 데이터 셋 로드하기

# 필요한 라이브러리 불러오기
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score


# 데이터셋 로드
df = sns.load_dataset('penguins')
df

 

 

2) 시각화

from sklearn.preprocessing import LabelEncoder

df.dropna(inplace=True)
X = df.select_dtypes(include=['number']).to_numpy() # 4차원 데이터
y = df.species # 실제 레이블

# 레이블 인코딩
label_encoder = LabelEncoder()
y_numeric = label_encoder.fit_transform(y)

# 산점도 시각화 (두 번째, 네 번째 피처만 사용)
plt.figure(figsize=(6, 6))
plt.scatter(X[:, 1], X[:, 3], c=y_numeric, cmap='viridis', s=50, edgecolor='k')
plt.xlabel(df.columns[3])
plt.ylabel(df.columns[5])
plt.title('Penguins Dataset (2 Features)')
plt.grid()
plt.show()

 

 

3) K-Means 클러스터링 적용 & 최적 클러스터 개수 찾기

 

  • 엘보우 기법 시각화
# K-Means 클러스터링 적용 & 최적 클러스터 개수 찾기

wcss = [] # 클러스터 내 거리 제곱합 저장 리스트
silhouette_scores = [] # 실루엣 점수 저장 리스트

for k in range(2, 10): # k=1은 실루엣 점수 계산 불가능
    kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, max_iter=300, random_state=42)
    kmeans.fit(X)
    wcss.append(kmeans.inertia_) # WCSS 저장
    silhouette_avg = silhouette_score(X, kmeans.labels_) # 실루엣 점수 계산
    silhouette_scores.append(silhouette_avg)

# 엘보우 기법 시각화
plt.figure(figsize=(8, 5))
plt.plot(range(2, 10), wcss, marker='o', linestyle='-', color='b')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('WCSS (Within-Cluster Sum of Sqaures)')
plt.title('Elbow Method for Optimal k')
plt.grid()
plt.show()

 

 

 

  • 실루엣 점수 시각화
# 실루엣 점수 시각화
plt.figure(figsize=(8, 5))
plt.plot(range(2, 10), silhouette_scores, marker='o', linestyle='-', color='g')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('Silhouette Scores')
plt.title('Silhouette Score for Optimal k')
plt.grid()
plt.show()

 

 

# 최적 k 값 선택 (k=3으로 설정, penguins 데이터셋은 3개의 클래스로 구성됨)
optimal_k = 3

# 최적 k 값으로 K-Means 적용
kmeans = KMeans(n_clusters=optimal_k, init='k-means++', n_init=10, max_iter=300, random_state=42)
kmeans.fit(X)
labels_kmeans = kmeans.labels_

# 클러스터링 결과 시각화 (두 번째, 네 번째 피처 기준)
plt.figure(figsize=(6, 6))
plt.scatter(X[:, 1], X[:, 3], c=labels_kmeans, cmap='viridis', s=50, edgecolors='k')
plt.xlabel(df.columns[3])
plt.ylabel(df.columns[5])
plt.title('K-Means Clustering (Original Data)')
plt.grid()
plt.show()

 

body_mass_g 특성의 범위가 훨씬 크기 때문에 클러스터링 과정에서 이 특성이 대부분의 영향을 미치게 됨

 

 

4) 데이터 스케일링 (StandardScaler 혹은 MinMaxScaler 활용)

from sklearn.preprocessing import StandardScaler

# 데이터 스케일링
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# K-Means 클러스터링 적용 & 최적 클러스터 개수 찾기
wcss = [] # 클러스터 내 거리 제곱합 저장 리스트
silhouette_scores = [] # 실루엣 점수 저장 리스트

for k in range(2, 10): # k=1은 실루엣 점수 계산 불가능
    kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, max_iter=300, random_state=42)
    kmeans.fit(X_scaled)
    wcss.append(kmeans.inertia_) # WCSS 저장
    silhouette_avg = silhouette_score(X_scaled, kmeans.labels_) # 실루엣 점수 계산
    silhouette_scores.append(silhouette_avg)

# 엘보우 기법 시각화
plt.figure(figsize=(8, 5))
plt.plot(range(2, 10), wcss, marker='o', linestyle='-', color='b')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('WCSS (Within-Cluster Sum of Sqaures)')
plt.title('Elbow Method for Optimal k')
plt.grid()
plt.show()

# 실루엣 점수 시각화
plt.figure(figsize=(8, 5))
plt.plot(range(2, 10), silhouette_scores, marker='o', linestyle='-', color='g')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('Silhouette Scores')
plt.title('Silhouette Score for Optimal k')
plt.grid()
plt.show()

# 최적 k 값 선택 (k=3으로 설정, penguins 데이터셋은 3개의 클래스로 구성됨)
optimal_k = 3

# 최적 k 값으로 K-Means 적용
kmeans = KMeans(n_clusters=optimal_k, init='k-means++', n_init=10, max_iter=300, random_state=42)
kmeans.fit(X_scaled)
labels_kmeans = kmeans.labels_

# 클러스터링 결과 시각화 (두 번째, 네 번째 피처 기준)
plt.figure(figsize=(6, 6))
plt.scatter(X_scaled[:, 1], X_scaled[:, 3], c=labels_kmeans, cmap='viridis', s=50, edgecolors='k')
plt.xlabel(df.columns[3])
plt.ylabel(df.columns[5])
plt.title('K-Means Clustering (Original Data)')
plt.grid()
plt.show()

 

 

5) 데이터 스케일링 및  PCA 적용 (4차원 → 2차원)

from sklearn.preprocessing import MinMaxScaler

# 데이터 스케일링 및 PCA 변환
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

# PCA 변환
pca = PCA(n_components=2)
X_scaled_pca = pca.fit_transform(X_scaled)

# 변환된 데이터 시각화
plt.figure(figsize=(6, 6))
plt.scatter(X_scaled_pca[:, 0], X_scaled_pca[:, 1], c=y_numeric, cmap='viridis', s=50, edgecolor='k')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('PCA Transformed Data (2D)')
plt.grid()
plt.show()

 

 

6) PCA 적용 후 K-Means 재적용

# PCA 적용 후 K-Means 재적용

# 최적 k 값으로 PCA 데이터에 K-Means 적용
kmeans_pca = KMeans(n_clusters=optimal_k, init='k-means++', n_init=10, max_iter=30, random_state=42)
kmeans_pca.fit(X_scaled_pca)
labels_pca = kmeans_pca.labels_

# PCA 데이터 클러스터링 시각화
plt.figure(figsize=(6, 6))
plt.scatter(X_scaled_pca[:, 0], X_scaled_pca[:, 1], c=labels_pca, cmap='viridis', s=50, edgecolor='k')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('K-Means Clustering After PCA')
plt.grid()
plt.show()

 

 

7) 성능 비교 (실루엣 점수 비교)

# 성능 비교 (실루엣 점수 비교)
silhouette_original = silhouette_score(X, labels_kmeans)
silhouette_scaled = silhouette_score(X_scaled, labels_scaled)
silhouette_pca = silhouette_score(X_scaled_pca, labels_pca)

print(f'Silhouette Score (Original Data): {silhouette_original:.4f}')
print(f'Silhouette Score (Scaled Data): {silhouette_scaled:.4f}')
print(f'Silhouette Score (After PCA): {silhouette_pca:.4f}')

# 결과 비교
plt.figure(figsize=(6, 4))
plt.bar(['Original Data', 'Scaled Data', 'PCA Transformed'], [silhouette_original, silhouette_scaled, silhouette_pca], color=['red', 'blue', 'green'])
plt.xlabel('Data Type')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score Comparison')
plt.ylim(0, 1)
plt.grid()
plt.show()

 

실제 레이블을 잘 반영한 것은 Scaled Data와 PCA Transformed 이지만 Original Data가 가장 높은 실루엣 점수를 보인다.

 

8) 다른 지표로 평가해보기

from sklearn.metrics import davies_bouldin_score, calinski_harabasz_score

# ----------------------------------------
# 1. 해결 방법 3: Davies-Bouldin Index 사용
# ----------------------------------------

dbi_score1 = davies_bouldin_score(X, labels_kmeans)
dbi_score2 = davies_bouldin_score(X, labels_scaled)
dbi_score3 = davies_bouldin_score(X, labels_pca)
print(f"Davies-Bouldin Index (Original Data): {dbi_score1:.4f}")
print(f"Davies-Bouldin Index (Scaled Data): {dbi_score2:.4f}")
print(f"Davies-Bouldin Index (PCA): {dbi_score3:.4f}\n")

# ----------------------------------------
# 2. 해결 방법 4: Calinski-Harabasz Index 사용
# ----------------------------------------

ch_score1 = calinski_harabasz_score(X, labels_kmeans)
ch_score2 = calinski_harabasz_score(X, labels_scaled)
ch_score3 = calinski_harabasz_score(X, labels_pca)
print(f"Calinski-Harabasz Index (Original Data): {ch_score1:.4f}")
print(f"Calinski-Harabasz Index (Scaled Data): {ch_score2:.4f}")
print(f"Calinski-Harabasz Index (PCA): {ch_score3:.4f}")
Davies-Bouldin Index (Original Data): 0.5148
Davies-Bouldin Index (Scaled Data): 1.6658
Davies-Bouldin Index (PCA): 0.9928

Calinski-Harabasz Index (Original Data): 1088.8789
Calinski-Harabasz Index (Scaled Data): 378.5255
Calinski-Harabasz Index (PCA): 461.1709

 

다른 지표에서는 Original Data가 제일 낮은 성적을 보였다.

'학습 > 머신러닝' 카테고리의 다른 글

분류  (0) 2025.01.29