susinlee 님의 블로그
군집 본문
목차
- 요약
- 클러스터링이란?
- K-Means 클러스터링
- PCA
- 실습
1. 요약
1) K-Means 란?
- 데이터를 k개의 그룹(클러스터)으로 자동 분류하는 비지도 학습 알고리즘
- 클러스터 중심(centroid)을 반복적으로 업데이트하여 최적의 군집을 찾음
2) K-Means의 주요 과정
- 초기 중심(centroid) 설정
- 각 데이터 포인트를 가장 가까운 중심에 할당
- 새롭게 할당된 데이터 기준으로 중심 재계산
- 중심이 더 이상 변하지 않을 때까지 반복
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 알고리즘
- 클러스터의 개수 k를 설정
- 초기 클러스터 중심을 무작위 선택
- 각 데이터 포인터를 가장 가까운 클러스터 중심으로 할당
- 각 클러스터에 대해 중심을 재계산
- 클러스터 중심이 수렴하거나 최대 반복 횟수에 도달하면 종료
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) 클러스터링 결과 품질 측정 지표
- 엘보우 기법(Elbow method)
- 클러스터의 중심 간의 거리를 측정한 후 제곱하여 모든 클러스터에 대한 제곱 오차를 합산한 산한 값을 활용
- 실루엣(Silhouette) 지표
- 실루엣 점수 범위: -1 에서 +1
- 1: 데이터 포인트가 완벽하게 적절한 클러스터에 속함
- 0: 클러스터 경계에 위치
- -1: 잘못된 클러스터에 할당됨
- 실루엣 점수 계산
- 실루엣 점수는 두 가지 거리 기반 계산을 사용
- a(i): 동일클러스터 내 다른 데이터 포인트까지의 평균 거리
- b(i): 가장 가까운 다른 클러스터 데이터 포인트까지의 평균 거리
- 실루엣 지표 활용
- 클러스터 개수 k를 선택할 때, 실루엣 점수를 참고
- 실루엣 점수가 높을수록 클러스터링 품질이 좋음을 나타냄
- 실루엣 점수 계산 공식
- 실루엣 점수는 두 가지 거리 기반 계산을 사용
- 실루엣 점수 범위: -1 에서 +1
엘보우 기법 및 시각화 코드
# 엘보우 기법을 활용하여 최적의 클러스터 개수 찾기
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 사이로 변환
- 정규화는 데이터가 정규 분포를 따르지 않거나 특성의 범위를 제한하려는 경우에 유용
- 표준화(Standardization)
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가 제일 낮은 성적을 보였다.