by museonghwang

군집 평가(Cluster Evaluation)

|

군집화 결과는 레이블과 비교해 군집화가 얼마나 효율적으로 됐는지 짐작할 수 있지만, 대부분의 군집화 데이터 세트는 이렇게 비교할 만한 타깃 레이블을 가지고 있지 않습니다. 또한 군집화는 분류(Classification)와 유사해 보일 수 있으나 성격이 많이 다릅니다. 데이터 내에 숨어 있는 별도의 그룹을 찾아서 의미를 부여하거나 동일한 분류 값에 속하더라도 그 안에서 더 세분화된 군집화를 추구하거나 서로 다른 분류 값의 데이터도 더 넓은 군집화 레벨화 등의 영역을 가지고 있습니다.

비지도학습의 특성상 어떠한 지표라도 정확하게 성능을 평가하기는 어렵습니다. 그럼에도 불구하고 군집화의 성능을 평가하는 대표적인 방법으로 실루엣 분석을 이용합니다.


실루엣 분석

군집화 평가 방법으로 실루엣 분석(silhouette analysis) 이 있습니다. 실루엣 분석은 각 군집 간의 거리가 얼마나 효율적으로 분리돼 있는지 를 나타냅니다. 효율적으로 잘 분리됐다는 것은 다른 군집과의 거리는 떨어져 있고 동일 군집끼리의 데이터는 서로 가깝게 잘 뭉쳐 있다는 의미 입니다. 군집화가 잘될수록 개별 군집은 비슷한 정도의 여유공간을 가지고 떨어져 있을 것입니다.

실루엣 분석은 실루엣 계수(silhouette coefficient)를 기반 으로 합니다. 실루엣 계수는 개별 데이터가 가지는 군집화 지표 입니다. 개별 데이터가 가지는 실루엣 계수해당 데이터가 같은 군집 내의 데이터와 얼마나 가깝게 군집화돼 있고, 다른 군집에 있는 데이터와는 얼마나 멀리 분리돼 있는지를 나타내는 지표입니다.

image


특정 데이터 포인트의 실루엣 계수 값 은 해당 데이터 포인트와 같은 군집 내에 있는 다른 데이터 포인트와의 거리를 평균한 값 a(i), 해당 데이터 포인트가 속하지 않은 군집 중 가장 가까운 군집과의 평균거리 b(i) 를 기반으로 계산됩니다. 두 군집 간의 거리가 얼마나 떨어져 있는가의 값은 b(i) - a(i) 이며, 이 값을 정규화하기 위해 MAX(a(i), b(i)) 값으로 나눕니다. 따라서 i 번째 데이터 포인트의 실루엣 계수값 s(i) 는 다음과 같이 정의합니다.

\[s(i)=\cfrac{b(i)-a(i)}{max(a(i), b(i))}\]


실루엣 계수-1 에서 1 사이의 값 을 가집니다.

  • 1에 가까움 : 근처의 군집과 더 멀리 떨어져 있다는 것
  • 0에 가까움 : 근처의 군집과 가까워진다는 것
  • 음수 : 아예 다른 군집에 데이터 포인트가 할당됐음을 뜻함.


사이킷런은 이러한 실루엣 분석을 위해 다음과 같은 메서드를 제공합니다.

sklearn.metrics.silhouette_samples(X, labels, metric='euclidean', **kwds)
  • 인자로 X feature 데이터 세트와 각 피처 데이터 세트가 속한 군집 레이블 값인 labels 데이터를 입력해주면 각 데이터 포인트의 실루엣 계수를 계산해 반환합니다.
sklearn.metrics.silhouette_score(X, labels, metric='euclidean', sample_size=None, **kwds)
  • 인자로 X feature 데이터 세트와 각 피처 데이터 세트가 속한 군집 레이블 값인 labels 데이터를 입력해주면 전체 데이터의 실루엣 계수 값을 평균해 반환합니다. 일반적으로 이 값이 높을수록 군집화가 어느정도 잘 됐다고 판단할 수 있습니다. 하지만 무조건 이 값이 높다고 해서 군집화가 잘 됐다고 판단할 수는 없습니다.


좋은 군집화가 되려면 다음 기준 조건을 만족해야 합니다.

  1. 전체 실루엣 계수의 평균값, 즉 사이킷런의 silhouette_score() 값은 0 ~ 1 사이의 값을 가지며, 1에 가까울수록 좋습니다.
  2. 하지만 전체 실루엣 계수의 평균값과 더불어 개별 군집의 평균값의 편차가 크지 않아야 합니다. 즉, 개별 군집의 실루엣 계수 평균값이 전체 실루엣 계수의 평균값에서 크게 벗어나지 않는 것이 중요합니다. 만약 전체 실루엣 계수의 평균값은 높지만, 특정 군집의 실루엣 계수 평균값만 유난히 높고 다른 군집들의 실루엣 계수 평균값은 낮으면 좋은 군집화 조건이 아닙니다.


붓꽃 데이터 세트를 이용한 군집 평가

앞의 붓꽃 데이터 세트의 군집화 결과를 실루엣 분석으로 평가해 보겠습니다. 이를 위해 sklearn.metrics 모듈의 silhouette_samples()silhouette_score() 를 이용합니다.

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
%matplotlib inline
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
).fit(irisDF)
irisDF['cluster'] = kmeans.labels_

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

# irisDF에 실루엣 계수 컬럼 추가
irisDF['silhouette_coeff'] = score_samples

# 모든 데이터의 평균 실루엣 계수값을 구함. 
average_score = silhouette_score(iris.data, irisDF['cluster'])
print('붓꽃 데이터셋 Silhouette Analysis Score:{0:.3f}'.format(average_score))

irisDF.head(3)
[output]
silhouette_samples( ) return 값의 shape (150,)
붓꽃 데이터셋 Silhouette Analysis Score:0.553

image


붓꽃 데이터 세트의 평균 실루엣 계수 값은 약 0.553 입니다. irisDF의 맨 처음 3개 로우는 1번 군집에 해당하고 개별 실루엣 계수 값이 0.8529, 0.8154, 0.8293 일 정도로 1번 군집의 경우 평균적으로 약 0.8 정도의 높은 실루엣 계수 값을 나타냅니다. 하지만 1번 군집이 아닌 다른 군집의 경우 실루엣 계수값이 낮기 때문에 전체 평균 실루엣 계수 값이 0.553 정도가 되었습니다.

군집별 평균 실루엣 계수 값으로 확인해 보겠습니다.

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


1번 군집은 실루엣 계수 평균 값이 약 0.79인데 반해, 0번은 약 0.41, 2번은 0.45로 상대적으로 평균값이 1번에 비해 낮습니다.


군집별 평균 실루엣 계수의 시각화를 통한 군집 개수 최적화 방법

전체 데이터의 평균 실루엣 계수 값이 높다고 해서 반드시 최적의 군집 개수로 군집화가 잘 됐다고 볼 수는 없습니다. 특정 군집 내의 실루엣 계수값만 너무 높고, 다른 군집은 내부 데이터끼리의 거리가 너무 떨어져 있어 실루엣 계수값이 낮아져도 평균적으로 높은 값을 가질 수 있습니다. 개별 군집별로 적당히 분리된 거리를 유지하면서도 군집 내의 데이터가 서로 뭉쳐 있는 경우에 K-평균 의 적절한 군집 개수가 설정됐다고 판단할 수 있습니다.


여러 개의 군집 개수가 주어졌을 때 평균 실루엣 계수로 군집 개수를 최적화하는 방법을 알아보겠습니다. 사이킷런 문서 중 시각적으로 지원해주는 좋은 예제가 있습니다.

첫 번째 경우는 다음 그림과 같이 주어진 데이터에 대해서 군집의 개수 2개를 정했을 때, 평균 실루엣 계수, 즉 silhouette_score는 약 0.704로 매우 높게 나타났습니다. 다음 그림에서 왼쪽 부분은 개별 군집에 속하는 데이터의 실루엣 계수를 2차원으로 나타낸 것입니다. X축은 실루엣 계수 값이고, Y축은 개별 군집과 이에 속하는 데이터입니다. 개별 군집은 Y축에 숫자 값으로 0, 1로 표시돼 있으며 이에 해당하는 데이터는 Y축 높이로 추측할 수 있습니다. 그리고 점선으로 표시된 선은 전체 평균 실루엣 계수값을 나타냅니다. 이로 판단해 볼 때 1번 군집의 모든 데이터는 평균 실루엣 계수값 이상이지만, 2번 군집의 경우는 평균보다 적은 데이터 값이 매우 많습니다.

오른쪽에 있는 그림으로 그 이유를 보충해서 설명할 수 있습니다. 1번 군집의 경우는 0번 군집과 멀리 떨어져 있고, 내부 데이터끼리도 잘 뭉쳐 있습니다. 하지만 0번 군집의 경우는 내부 데이터끼리 많이 떨어져 있는 모습입니다.

image


다음 그림은 군집 개수가 3개일 경우입니다. 전체 데이터의 평균 실루엣 계수 값은 약 0.588입니다. 1번, 2번 군집의 경우 평균보다 높은 실루엣 계수 값을 가지고 있지만, 0번의 경우 모두 평균보다 낮습니다. 오른쪽 그림을 보면 0번의 경우 내부 데이터 간의 거리도 멀지만, 2번 군집과도 가깝게 위치하고있기 때문입니다.

image


다음으로 군집이 4개인 경우를 보겠습니다. 이때의 평균 실루엣 계수 값은 약 0.65 입니다. 왼쪽 그림에서 보듯이 개별 군집의 평균 실루엣 계수값이 비교적 균일하게 위치하고 있습니다. 1번 군집의 경우 모든 데이터가 평균보다 높은 계수 값을 가지고 있으며, 0번, 2번의 경우는 절반 이상이 평균보다 높은 계수 값을, 3번 군집의 경우만 약 1/3 정도가 평균보다 높은 계수값을 가지고 있습니다. 군집이 2개인 경우보다는 평균 실루엣 계수값이 작지만 4개인 경우가 가장 이상적인 군집화 개수로 판단할 수 있습니다.

image


군집별 평균 실루엣 계수 값을 구하는 부분을 시각화해 보겠습니다. visualize_silhouette() 함수는 군집 개수를 변화시키면서 K-평균 군집을 수행했을 때 개별 군집별 평균 실루엣 계수 값을 시각화해서 군집의 개수를 정하는 데 도움을 줍니다.

make_blobs() 함수를 통해 4개 군집 중심의 500개 2차원 데이터 세트를 만들고 이를 K-평균 으로 군집화할 때 2개, 3개, 4개, 5개 중 최적의 군집 개수를 시각화로 알아보겠습니다.

**visualize_silhouette()**
### 여러개의 클러스터링 갯수를 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)

image


앞에서 소개한 바와 마찬가지로 4개의 군집일 때 가장 최적이 됨을 알 수 있습니다. 이번에는 붓꽃 데이터를 이용해 K-평균 수행 시 최적의 군집 개수를 알아보겠습니다.

from sklearn.datasets import load_iris

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

image


붓꽃 데이터를 K-평균으로 군집화할 경우에는 군집 개수를 2개로 하는 것이 가장 좋아 보입니다. 3개의 경우 평균 실루엣 계수 값도 2개보다 작을뿐더러 1번 군집과 다른 0번, 2번 군집과의 실루엣 계수의 편차가 큽니다. 4개, 5개의 경우도 마찬가지입니다.

실루엣 계수를 통한 K-평균 군집 평가 방법은 직관적으로 이해하기 쉽지만, 단점으로 각 데이터별로 다른 데이터와의 거리를 반복적으로 계산해야 하므로 데이터양이 늘어나면 수행 시간이 크게 늘어납니다.