본문 바로가기

Euron/정리

[Week11] 07. 군집화(1)

01. K-평균 알고리즘 이해

 

K-평균(K-means)은 군집화(Clustering)에서 가장 일반적으로 사용되는 알고리즘이다.

군집 중심점(centroid)이라는 특정한 임의의 지점을 선택해 해당 중심에 가장 가까운 포인트들을 선택하는 기법이다.

선택된 포인트의 평균지점으로 이동 후 이를 중심점으로 선택한다. 모든 데이터 포인트에서 중심점의 이동이 없을 때까지 반복하고, 반복이 끝나면 해댕 중심점에 속하는 데이터 포인트들을 군집화한다.

 

 

장점

  • 일반적인 군집화에서 가장 많이 활용되는 알고리즘이다.
  • 알고리즘이 쉽고 간결하다.

단점

  • 거리 기반 알고리즘으로 속성의 개수가 많을 경우 군집화 정확도가 떨어진다. (이를 위해 PCA로 차원 감소를 적용해야 할 수도 있다.)
  • 반복을 수행하는데, 반복 횟수가 많을 경우 수행 시간이 매우 느려진다.
  • 몇 개의 군집(Cluster)을 선택해야 할지 가이드하기가 어렵다.

 

사이킷런 KMeans 클래스 소개

class skelarn.cluster.KMeans(n_clusters=8, init='k-means++', n_init=10, max_iter=300, tol=0.0001,
	precompute_distances='auto', verbose=0, random_state=None, copy_x=True,n+jobs=1, algorithm='auto')

 

주요 파라미터

  • n_clusters : 초기화 파라미터 중 가장 중요하며, 군집화할 개수, 즉 군집 중심점의 개수를 의미한다.
  • init : 초기 군집 중심점의 좌표를 성정할 방식을 말하며 보통 중심을 설정하지 않고 k-means++ 방식으로 설정한다.
  • max_iter : 최대 반복 횟수. 이 횟수 이전에 중심점 이동이 없으면 종료 한다.

주요 속성

  • labels_ : 각 데이터 포인터가 속한 군집 중심점 레이블
  • cluster_centers_: 각 군집 중심점 좌표(Shape는 [군집 개수, 피처 개수]). 이를 이용하면 군집 중심점 좌표가 어디인지 시각화할 수 있다.

K-평균을 이용한 붓꽃 데이터 세트 군집화

kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0)
kmeans.fit(irisDF)
print(kmeans.labels_)

K-Means 객체를 만들고 fit()을 수행해 irirsDF 데이터에 대한 군집화 수행 결과가 kmeans 객체 젼수로 반환됐다.

kmeans의 labes_ 속성값을 확인하여 각 데이터가 어떤 중심에 속하는지 확인할 수 있다.

 

실제와 얼마나 차이나는지 확인해 군집화가 효과적으로 됐는지 확인한다.

앞서 구한 labes_값을 cluster 칼럼으로 지정해 DF에 추가한 뒤 groupby 연산을 적용해 비교한다.

irisDF['target'] = iris.target
irisDF['cluster']=kmeans.labels_
iris_result = irisDF.groupby(['target','cluster'])['sepal_length'].count()
print(iris_result)

분류 타깃이 0값인 데이터는 1번 군집으로 잘 그루핑됐다.

그러나 Target 1값 데이터는 2개만 2번 군집으로 그루핑 됐고, 나머지 48개는 모두 0번 군집으로 그루핑됐다.

 

이번에는 데이터 세트의 군집화를 시각화한다.

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(iris.data)

irisDF['pca_x'] = pca_transformed[:,0]
irisDF['pca_y'] = pca_transformed[:,1]

# 군집 값이 0, 1, 2인 경우마다 별도의 인덱스로 추출
marker0_ind = irisDF[irisDF['cluster']==0].index
marker1_ind = irisDF[irisDF['cluster']==1].index
marker2_ind = irisDF[irisDF['cluster']==2].index

# 군집 값 0, 1, 2에 해당하는 인덱스로 각 군집 레벨의 pca_x, pca_y 값 추출. o, s, ^ 로 마커 표시
plt.scatter(x=irisDF.loc[marker0_ind, 'pca_x'], y=irisDF.loc[marker0_ind, 'pca_y'], marker='o')
plt.scatter(x=irisDF.loc[marker1_ind, 'pca_x'], y=irisDF.loc[marker1_ind, 'pca_y'], marker='s')
plt.scatter(x=irisDF.loc[marker2_ind, 'pca_x'], y=irisDF.loc[marker2_ind, 'pca_y'], marker='^')

plt.xlabel('PCA 1')
plt.ylabel('PCA 2')
plt.title('3 Clusters Visualization by 2 PCA Components')
plt.show()

동그라미와 세모가 상당 수준 분리되어 있지만, 네모만큼 명확하게 분리되어 있지 않다.

 Cluster 0과 1은 속성의 위치 자체가 명확히 분리되기 어렵다.

 

군집화 알고리즘 테스트를 위한 데이터 생성

군집화 알고리즘을 테스트하기 위한 데이터 생성기 make_blobs()와 make_classification() API가 있다.

두 API 모두 여러 개의 클래스에 해당하는 데이터 세트를 만드는데, 하나의 클래스에 여러 개의 군집이 분포될 수 있게 데이터를 생성할 수 있다.

make_blobs()는 개별 군집의 중심점과 표준 편차 제어 기능이 추가돼 있고, make_classfication()은 노이즈를 포함한 데이터를 만드는 데 유용하게 사용될 수 있다.

이와에 중심 기반의 군집화로 해결하기 어려운 데이터 세트를 만드는 make_circle(), make_moon() API가 있다.

 

make_blobs()의 호출 파라미터

  • n_samples : 생성할 총 데이터의 개수. default=100
  • n_features : 데이터의 피처 개수. 시각화를 목표로 할 경우 보통 x, y좌표 2개로 설정한다.
  • centers : int 값으로 설정하면 순집의 개수, ndarray로 설정하면 개별 군집 중심점의 좌표를 의미한다.
  • cluster_std : 생성될 군집데이터의 표준 편차 float값 혹은 배열로 표현된다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
%matplotlib inline

X, y = make_blobs(n_samples=200, n_features=2, centers=3, cluster_std=0.8, random_state=0)

import pandas as pd

clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y
clusterDF.head(3)

이렇게 만들어진 데이터 세트에 KMeans 군비화를 수행한 뒤 군집별로 시각화한다.

make_blobs()의 피처 데이터 세트 X데이터를 군집화 한 후 DF의 'kmeans_label' 칼럼으로 저장한다.

그리고 KMeans 객체의 cluster_centers_ 속성은 개별 군집의 중심 위치 좌표를 나타내기 위해 사용한다.

# KMeans 객체를 이용하여 X 데이터를 K-Means 클러스터링 수행 
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=200, random_state=0)
cluster_labels = kmeans.fit_predict(X)
clusterDF['kmeans_label']  = cluster_labels

#cluster_centers_ 는 개별 클러스터의 중심 위치 좌표 시각화를 위해 추출
centers = kmeans.cluster_centers_
unique_labels = np.unique(cluster_labels)
markers=['o', 's', '^', 'P','D','H','x']

# 군집된 label 유형별로 iteration 하면서 marker 별로 scatter plot 수행. 
for label in unique_labels:
    label_cluster = clusterDF[clusterDF['kmeans_label']==label]
    center_x_y = centers[label]
    plt.scatter(x=label_cluster['ftr1'], y=label_cluster['ftr2'], edgecolor='k', 
                marker=markers[label] )
    
    # 군집별 중심 위치 좌표 시각화 
    plt.scatter(x=center_x_y[0], y=center_x_y[1], s=200, color='white',
                alpha=0.9, edgecolor='k', marker=markers[label])
    plt.scatter(x=center_x_y[0], y=center_x_y[1], s=70, color='k', edgecolor='k', 
                marker='$%d$' % label)

plt.show()

make_blobs()는 cluster_std 파라미터로 데이터의 분포도를 조절한다.

cluster_std가 작을수록 군집 중심에 데이터가 모여 있으며, 클수록 데이터가 퍼져있다.

 

 


02. 군집 평가(Cluster Evaluation)

대부분의 군집화 데이터 세트는 비교할 만한 타깃 레이블을 가지고 있지 않다.

군집화는 분류와 다르게 별도의 그룹을 찾아 의미를 부여하거나 더 세분화된 군집화를 추구하는 등의 영역을 가진다.

비지도학습의 특성상 정확히 성능을 평가하기는 어렵지만, 실루엣 분석을 이용해 평가한다.

 

실루엣 분석(silhouette analysis)의 개요

실루엣 분석은 각 군집 간의 거리가 얼마나 효율적으로 분리돼 있는지를 나타낸다.

다른 군집과의 거리는 떨어져 있고 동일 군집 끼리의 데이터는 서로 가깝게 뭉쳐있을 때 효율적으로 잘 분리됐다고 한다.

개별 데이터가 가지는 군집화 지표인 실루엣 계수(silhouette coeffiecient)를 기반으로 실루엣 분석을 실시한다.

 

같은 군집 내 데이터 포인트 간의 거리를 평균한 값을 a(i), 가장 가까운 군집과의 평균거리를 b(i)라 할 때, 

실루엣 계수 s(i) = (b(i) - a(i))/(MAX((a(i), b(i))라 정의한다.

실루엣 계수는 -1에서 1 사이의 값을 가지며, 1로 가까워질수록 근처의 근접과 더 멀리 떨어져 있다는 것이다.

-값은 아예 다른 군집에 데이터 포인트가 할당됐음을 뜻한다.

 

좋은 군집화를 위해 실루엣 계수의 평균값이 1에 가깝고, 개별 군집의 평균값의 편차가 크지 않아야 한다.

 

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

sklearn.metrics 모듈의 silhouette_sample()와 silhoette_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
import numpy as np
import pandas as pd

%matplotlib inline

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)

 

1번 군집의 경우 약 0.8 정도의 높은 실루엣 계수값이나, 다른 군집의 계수 값이 낮아 전체 데이터 세트의 평균 실루엣 계수 값은 약 0.553이 나왔다.

 

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

위와 같이 특정 군집 내의 실루엣 계수 값만 높고, 다른 군집은 낮아져도 평균적으로 높은 값을 가질 수 있다.

 

군집화 개수를 조절 후 비교하여 최적 군집화 개수를 조절할 수 있다.

아래 그림에서 점선은 전체 평균 실루엣 계수 값이며, y축 높이로 개별 실루엣 계수값을 추측할 수 있다.

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

make_blobs()를 이용해 만든 데이터 세트 결과, 군집이 4개일 때 가장 최적이 됨을 알 수 있다.

 

from sklearn.datasets import load_iris

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

붓꽃 데이터 세트를 이용해 K-평균을 수행할 대의 최적 군집 개수는 2개임을 알 ㅅ 있다.

 

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

 


# 회고

 

양이 길어질까봐 두개로 나누었다.

기학 수업에서는 clustering을 먼저하고 차원축소를 진행했는데, 책에서 소개하는 것처럼 K-means의 경우 속성의 개수를 줄여야 하는 경우가 있어 차원축소를 먼저 진행하는게 좋을 것 같다.

전에 AI 강의 듣다가 본 자료인데, 생각나서 보니까 정말 많이 했다!

개인적으로는 랭킹/추천이 기대된당ㅎㅎ 정말 많이 사용되는 분야기도 하고, 웹서비스 만들 때 기획팀에서 정말정말 많이 원하던 기능이였다.