[ML] 군집화 - 실루엣 분석 (Silhouette Analysis)

강주형·2022년 7월 20일
0

실루엣 분석 개요

실루엣 분석: 각 군집 간의 거리가 얼마나 효율적으로 분리돼 있는지를 나타내는 것

다른 방법으로는 Elbow Method가 있음 (Inertia)

실루엣 계수 (Silhouette Coefficient)

  • 각각의 샘플마다 계산
  • aa: 나와 나와 같은 군집에 속한 샘플들 사이의 평균 거리
  • bb: 나와 나랑 그 다음으로 가까운 군집에 속한 샘플들 사이의 평균 거리
  • 1Si+1-1\leq S_i \leq +1 범위를 가짐

즉, aa가 낮고, bb가 높으면 잘 군집화 된 것!
-> 나와 나의 군집의 샘플들과는 가깝고, 다른 군집과는 멀기 때문

Si=biaimax(ai,bi)S_i = \frac{b_i-a_i}{max(a_i,\,b_i)}

실루엣 스코어 (Silhouette Score)

  • 전체 실루엣 계수의 평균
  • mean(Si)mean(S_i)
  • 높을수록 좋지만, 높다고 무조건 좋은 군집화라고 말하기는 어려움

시각화는 실습하면서 봐보자


실루엣 분석 실습

scikit-learn을 이용해서 실루엣 계수와 실루엣 스코어를 구해보고,
시각화를 통한 군집 수 최적화까지 해보자


실루엣 분석 실습 기본

scikit-learn에서 관련 클래스를 제공함

각 샘플의 실루엣 계수: sklearn.metric.silhouette_samples
실루엣 스코어 (실루엣 계수의 평균): sklearn.metric.silhouette_score

iris 데이터 사용
데이터프레임으로 만들고, K-Means 군집화 후 군집 결과 칼럼 추가

from sklearn.preprocessing import scale
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
# 실루엣 분석 metric 값을 구하기 위한 API 추가
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd


iris = load_iris()
feature_names = ['sepal_length','sepal_width','petal_length','petal_width']
irisDF = pd.DataFrame(data=iris.data, columns=feature_names)

# 군집화 수행
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0)
kmeans.fit(irisDF)

# 개별 데이터에 대한 군집 결과를 cluster 컬럼으로 DataFrame에 저장
irisDF['cluster'] = kmeans.labels_

irisDF.head(10)

각 샘플의 실루엣 계수를 구하고 칼럼으로 추가함
결과로 보이는 것처럼 1차원 ndarray로 반환

# iris 의 모든 개별 데이터에 실루엣 계수값을 구함
score_samples = silhouette_samples(iris.data, irisDF['cluster'])
print('silhouette_samples( ) return 값의 shape' , score_samples.shape)

# irisDF에 실루엣 계수 컬럼 추가
irisDF['silhouette_coeff'] = score_samples
silhouette_samples( ) return 값의 shape (150,)

실루엣 스코어도 확인해보자

print(np.mean(score_samples))
print(silhouette_score(iris.data, irisDF['cluster']))
0.5528190123564091
0.5528190123564091

보통 0.5가 넘으면 꽤 좋은 수치라고 함
각 군집별 실루엣 스코어도 확인

irisDF.groupby('cluster')['silhouette_coeff'].mean()
cluster
0    0.417320
1    0.798140
2    0.451105
Name: silhouette_coeff, dtype: float64

cluster 1이 setosa로 추정됨
0.79면 엄청 높은 수치인데, 이전에 해본 경험으로 보면
처음부터 군집이 잘 되어있었음


시각화를 통한 군집 개수 최적화

원본 코드 출처: https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html

시각화 코드 설명은 생략
군집별 실루엣 계수를 시각화해보자

### 여러개의 클러스터링 갯수를 List로 입력 받아 각각의 실루엣 계수를 면적으로 시각화한 함수 작성
def visualize_silhouette(cluster_lists, X_features): 
    
    from sklearn.datasets import make_blobs
    from sklearn.cluster import KMeans
    from sklearn.metrics import silhouette_samples, silhouette_score

    import matplotlib.pyplot as plt
    import matplotlib.cm as cm
    import math
    
    # 입력값으로 클러스터링 갯수들을 리스트로 받아서, 각 갯수별로 클러스터링을 적용하고 실루엣 개수를 구함
    n_cols = len(cluster_lists)
    
    # plt.subplots()으로 리스트에 기재된 클러스터링 수만큼의 sub figures를 가지는 axs 생성 
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)
    
    # 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 실루엣 개수 시각화
    for ind, n_cluster in enumerate(cluster_lists):
        
        # KMeans 클러스터링 수행하고, 실루엣 스코어와 개별 데이터의 실루엣 값 계산. 
        clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
        cluster_labels = clusterer.fit_predict(X_features)
        
        sil_avg = silhouette_score(X_features, cluster_labels)
        sil_values = silhouette_samples(X_features, cluster_labels)
        
        y_lower = 10
        axs[ind].set_title('Number of Cluster : '+ str(n_cluster)+'\n' \
                          'Silhouette Score :' + str(round(sil_avg,3)) )
        axs[ind].set_xlabel("The silhouette coefficient values")
        axs[ind].set_ylabel("Cluster label")
        axs[ind].set_xlim([-0.1, 1])
        axs[ind].set_ylim([0, len(X_features) + (n_cluster + 1) * 10])
        axs[ind].set_yticks([])  # Clear the yaxis labels / ticks
        axs[ind].set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1])
        
        # 클러스터링 갯수별로 fill_betweenx( )형태의 막대 그래프 표현. 
        for i in range(n_cluster):
            ith_cluster_sil_values = sil_values[cluster_labels==i]
            ith_cluster_sil_values.sort()
            
            size_cluster_i = ith_cluster_sil_values.shape[0]
            y_upper = y_lower + size_cluster_i
            
            color = cm.nipy_spectral(float(i) / n_cluster)
            axs[ind].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, \
                                facecolor=color, edgecolor=color, alpha=0.7)
            axs[ind].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
            y_lower = y_upper + 10
            
        axs[ind].axvline(x=sil_avg, color="red", linestyle="--")
# make_blobs 을 통해 clustering 을 위한 4개의 클러스터 중심의 500개 2차원 데이터 셋 생성  
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=500, n_features=2, centers=4, cluster_std=1, \
                  center_box=(-10.0, 10.0), shuffle=True, random_state=1)  

# cluster 개수를 2개, 3개, 4개, 5개 일때의 클러스터별 실루엣 계수 평균값을 시각화 
visualize_silhouette([ 2, 3, 4, 5], X)

  • 각 군집별 실루엣 스코어를 나타내고, 실루엣 계수를 시각화한 것임
  • 각 군집별 샘플의 실루엣 계수를 아래에서 위로 오름차순으로 쭉 이어서 만든 것
  • 실루엣 스코어만 보면 군집이 2일 때가 제일 좋지만, 보통 군집이 4일 때처럼 개별 군집의 실루엣 계수의 평균값의 편차가 작은 게 좋다.
  • 군집 수 최적화는 이런식으로 시각화를 주로 이용함!

iris 데이터로도 해보자

from sklearn.datasets import load_iris

iris=load_iris()
visualize_silhouette([ 2, 3, 4,5 ], iris.data)

그나마 군집이 3개일 때가 제일 괜찮을 것 같다.
원래 iris의 Label도 3종류였으니까..

profile
Statistics & Data Science

0개의 댓글