Clustering 군집화

  • 분류와 유사해보일 수 있지만 성격이 다르다. 데이터 내에 숨어있는 별도의 그룹을 찾아서 의미를 부여하거나, 동일한 분류값에 속하더라도 그 안에서 더 세분화된 군집화를 추구하거나, 서로 다른 분류값의 데이터도 더 넓은 군집화 레벨화 등의 영역을 가진다.

1. K-Mean(K평균)

(거리기반 군집화)

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

  • 수행
    1. 군집 중심점을 임의의 위치에 놓는다. (일반적으로는 초기화 알고리즘으로 적합한 위치에 놓음)
    2. 각 데이터는 가장 가까운 곳에 위치한 중심점에 소속된다.
    3. 군집 중심점을 소속된 데이터의 평균 중심으로 이동
    4. 각 데이터는 기존에 속한 중심점보다 더 가까운 중심점이 있다면 해당 중심점으로 다시 소속을 변경
    5. 다시 중심을 소속된 데이터의 평균으로 이동
    6. 중심점을 이동했는데 데이터의 중심점 소속 변경이 없으면 군집화를 종료
  • 장점
    • 일반적으로 가장 많이 사용되는 알고리즘으로 쉽고 간결하다.
  • 단점
    • 속성의 개수가 많을 경우 군집화 정확도가 떨어진다. (PCA가 필요할 수도)
    • 반복이 많을수록 수행시간이 느려진다.
    • 몇 개의 군집을 선택해야 할지 가이드하기 어렵다.
    • 개별 군집 내의 데이터가 원형으로 흩어져 있는 경우에 효과적으로 군집화가 수행될 수 있지만,
      데이터가 길쭉한 타원형으로 늘어선 경우와 같을 때는 군집화를 잘 수행하지 못한다.
하이퍼 파라미터설명
n_clusters군집화할 개수(군집 중심점의 개수)
init초기에 군집 중심점의 좌표를 설정할 방식. 보통은 임의로 설정하지 않고 K-Means++ 방식으로 설정
  • 임의로 설정하고 싶으면 init=’random’
  • K-means++ 방식
    1. 데이터 포인트 중에서 무작위로 1개를 선택하여 중심점으로 지정
    2. 나머지 데이터 포인트들에 대해 첫 번째 중심점까지의 거리 계산
    3. 지정된 중심점으로부터 가장 멀리 있는 데이터 포인트를 다음 중심점으로 지정
    4. 중심점이 K개가 될 때까지 2, 3번 반복 |
    | max_iter | 최대 반복 횟수. 이 횟수 이전에 모든 데이터의 중심점 이동이 없으면 종료 |
  • 속성
    • labels_: 각 데이터 포인트가 속한 군집중심점 레이블
    • clustercenters: 각 군집 중심점 좌표(shape=[군집개수, 피처개수]). 이를 이용해 시각화 가능
  • 사용
    from sklearn.preprocessing import scale
    from sklearn.datasets import load_iris
    from sklearn.cluster import KMeans
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    %matplotlib inline
    
    iris = load_iris()
    # 보다 편리한 데이터 Handling을 위해 DataFrame으로 변환
    irisDF = pd.DataFrame(data=iris.data, columns=['sepal_length','sepal_width','petal_length','petal_width'])
    irisDF.head(3)
    
    # 개정판 소스 코드 수정(2019.12.24)
    kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0)
    kmeans.fit(irisDF)
    
    # irisDF['cluster']=kmeans.labels_ 개정 소스코드 변경(2019.12.24)
    irisDF['target'] = iris.target
    irisDF['cluster']=kmeans.labels_
    iris_result = irisDF.groupby(['target','cluster'])['sepal_length'].count()
    print(iris_result)
    
    # iris 4개의 속성을 2차원 평면에 그리기 위해 PCA로 2개로 차원 축소
    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]
    
    # cluster 값이 0, 1, 2 인 경우마다 별도의 Index로 추출
    marker0_ind = irisDF[irisDF['cluster']==0].index
    marker1_ind = irisDF[irisDF['cluster']==1].index
    marker2_ind = irisDF[irisDF['cluster']==2].index
    
    # cluster값 0, 1, 2에 해당하는 Index로 각 cluster 레벨의 pca_x, pca_y 값 추출. o, s, ^ 로 marker 표시
    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()

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

  • 사이킷런의 데이터 생성기: 여러 개의 클래스에 해당하는 데이터 세트를 만드는데, 하나의 클래스에 여러 개의 군집이 분포될 수 있게 데이터를 생성한다.
    • make_blobs(): 개별 군집의 중심점과 표준 편차 제어 기능이 추가되어 있다. 피처 데이터 세트, 타깃 데이터 세트가 튜플로 잔환

      파라미터설명
      n_samples디폴트 = 100
      생성할 총 데이터의 개수
      n_features데이터의 피처 개수
      centersint로 입력: 군집의 개수
      ndarray로 입력: 개별 군집 중심점의 좌표
      cluster_std생성될 군집 데이터의 표준편차

      float로 입력: 군집 내 데이터의 표준 편차
      [float, …]로 입력: 각 군집의 순서대로 각각의 표준편차가 만들어짐.
      ⇒ 군집별로 서로 다른 표준편차를 가진 데이터 세트를 만들 때 사용 |

    • make_classification(): 노이즈를 포함한 데이터를 만든다.

    • make_circle(), make_moon(): 중심기반의 군집화로 해결하기 어려운 데이터 세트를 만듦

      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)
      print(X.shape, y.shape)
      
      # y target 값의 분포를 확인
      unique, counts = np.unique(y, return_counts=True)
      print(unique,counts)
      
      > (200, 2) (200,)
      > [0 1 2] [67 67 66]
      
      # DataFrame에 적용
      import pandas as pd
      clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
      clusterDF['target'] = y
      
      target_list = np.unique(y)
      # 각 target별 scatter plot 의 marker 값들. 
      markers=['o', 's', '^', 'P','D','H','x']
      # 3개의 cluster 영역으로 구분한 데이터 셋을 생성했으므로 target_list는 [0,1,2]
      # target==0, target==1, target==2 로 scatter plot을 marker별로 생성. 
      for target in target_list:
          target_cluster = clusterDF[clusterDF['target']==target]
          plt.scatter(x=target_cluster['ftr1'], y=target_cluster['ftr2'], edgecolor='k', marker=markers[target] )
      
      plt.show()

  • KMeans 객체를 이용하여 X 데이터를 K-Means 클러스터링 수행 후, 시각화
    # 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()
    
    print(clusterDF.groupby('target')['kmeans_label'].value_counts())
    
    > target  kmeans_label
    > 0       0               66
    >         1                1
    > 1       2               67
    > 2       1               65
    >         2                1

3. 군집 평가 Cluster Evaluation - 실루엣 분석 silhouette analysis

: 대부분의 군집화 데이터 세트는 타깃 레이블을 가지고 있지 않다.
: 그래서 비지도 학습의 특성상 정확한 성능 평가는 어렵지만 군집화의 성능을 평가하는 방법으로는 실루엣 분석이 있다.

  • 실루엣 분석: 각 군집 간의 거리가 얼마나 효율적으로 분리되어 있는지 나타냄

    • 효율적 분리 ⇒ 다른 군집과는 떨어져 있고, 동일 군집끼리의 데이터는 서로 가깝게 잘 뭉쳐 있는것.
    • 군집화가 잘 될수록 개별 군집은 비슷한 정도의 여유공간을 가지고 떨어져 있다.
  • 실루엣 계수(silhouette coefficient) : 개별 데이터가 가지는 군집화 지표

    • 해당 데이터가 같은 군집 내의 데이터와 얼마나 가깝게 군집화 돼있고,
      다른 군집에 있는 데이터와는 얼마나 멀리 분리되어 있는지 나타내는 지표

    • aij: i번째 데이터에서 [자신이 속한 클러스터 내]의 [다른 데이터 포인트]까지의 거리

    • a(i): i번째 데이터에서 [자신이 속한 클러스터 내]의 [다른 데이터 포인트]들의 [평균] 거리 ⇒ a(1) = avg(a12, a13…)

    • b(i): i번째 데이터에서 [가장 가까운 타 클러스터 내]의 [다른 데이터 포인트]들의 [평균] 거리 ⇒ b(1) = avg(b14, b15…)

      실루엣계수S(i)=b(i)a(i)max(a(i),b(i))실루엣 계수S \> (i)={b(i)-a(i)\over max(a(i), b(i))}
    • 실루엣 계수는 -1~1 사이의 값을 가짐

      • 1로 가까울 수록, 근처의 군집과 더 멀리 떨여지 있다는 것
        • b(i)b(i)가 압도적으로 크면 → b(i)a(i)b(i)\frac{b(i) - a(i)}{b(i)}1a(i)b(i)1\frac{1- \frac{a(i)}{b(i)}}{1}10.001\frac{1 - 0.00…}{1} → 1에 가까워짐
      • 0에 가까울 수록, 근처의 군집과 가까워진다는 것
        • b(i)a(i)=0b(i) - a(i) = 0b(i)=a(i)b(i) = a(i) : 클러스터내 거리랑, 타 클러스트내 거리랑 차이가 없다는 거니깐
        • 값이면 다른 군집에 데이터 포인트가 할당되었다는 것
          • b(i)<a(i)b(i) < a(i) → 클러스터내 거리가 타 클러스트내 거리보다 크다 → 다른 군집 데이터가 할당됐다고 볼 수 있음
  • 사이킷런의 실루엣 분석 메소드

    • silhouette_sample(X, labels, metric='euclidean', **kwds)
      • 인자로 X_feature 데이터 세트, 군집 레이블 값(labels) ⇒ 각 데이터의 실루엣 계수를 계산하여 반환
    • silhouette_score(X, labels, metric='euclidean', sample_size=None, **kwds)
      • 인자로 X feature 데이터 세트, 군집 레이블 값(labels) ⇒ 전체 데이터의 실루엣계수 값을 평균하여 반환
      • np.mean(silhouette_samples()) 랑 같음
      • 일반적으로 이 값이 높을수록 군집화가 어느정도 잘 됐다고 판단할 수 있지만, 무조건 그런건 아니다.
    • 사용
      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)
      print(np.mean(silhouette_samples(iris.data, irisDF['cluster'])))
      print(silhouette_score(iris.data, irisDF['cluster']))
      
      > silhouette_samples( ) return 값의 shape (150,)
      > 0.5528190123564095
      > 0.5528190123564095
      
      # irisDF에 실루엣 계수 컬럼 추가
      irisDF['silhouette_coeff'] = score_samples
      
      # 모든 데이터의 평균 실루엣 계수값을 구함. 
      average_score = silhouette_score(iris.data, irisDF['cluster'])
      print('붓꽃 데이터셋 Silhouette Analysis Score:{0:.3f}'.format(average_score))
      
      > 붓꽃 데이터셋 Silhouette Analysis Score:0.553
      
      # 군집별 평균 실루엣 계수
      print(irisDF.groupby('cluster')['silhouette_coeff'].mean())
      
      > cluster
      > 0    0.417320
      > 1    0.798140
      > 2    0.451105
      > Name: silhouette_coeff, dtype: float64

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

  • 전체 데이터의 평균 실루엣 계수 값이 높다고 해서, 반드시 최적의 군집 개수로 군집화가 잘 됐다고 볼 수 없음
    • 특정 군집만 실루엣 계수가 엄청 높고 나머지 군집들은 낮아도, 평균 실루엣 계수 자체는 높게 나올 수 있기 때문
  • 따라서, 좋은 군집의 조건으로
    1. 전체 실루엣 계수의 평균값(silhouette_score())은 0~1 사이의 값을 가지며, 1에 가까울 수록 좋다.
    2. 하지만 전체 실루엣 계수의 평균값과 더불어, 개별 군집의 평균값의 편차가 크지 않아야 한다.
    3. 즉, 개별 군집의 실루엣 계수 평균값이 전체 실루엣 계수 평균값에서 크게 벗어나지 않는 것이 중요하다.
  • visualize_silhouette( [군집 갯수 list], X_feature )을 통한 실루엣 시각화 분석
    ### 여러개의 클러스터링 갯수를 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, 10), nrows=2, 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)
            centers = clusterer.cluster_centers_
            
            sil_avg = silhouette_score(X_features, cluster_labels)
            sil_values = silhouette_samples(X_features, cluster_labels)
            
            y_lower = 10
            axs[0,ind].set_title('Number of Cluster : '+ str(n_cluster)+'\n' \
                              'Silhouette Score :' + str(round(sil_avg,3)) )
            axs[0,ind].set_xlabel("The silhouette coefficient values")
            axs[0,ind].set_ylabel("Cluster label")
            axs[0,ind].set_xlim([-0.1, 1])
            axs[0,ind].set_ylim([0, len(X_features) + (n_cluster + 1) * 10])
            axs[0,ind].set_yticks([])  # Clear the yaxis labels / ticks
            axs[0,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[0,ind].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, \
                                    facecolor=color, edgecolor=color, alpha=0.7)
                axs[0,ind].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
                y_lower = y_upper + 10
    
                # 클러스터링된 데이터 시각화
                axs[1,ind].scatter(X_features[:, 0], X_features[:, 1], marker='.', s=30, lw=0, alpha=0.7, \
                    c=cluster_labels)
                axs[1,ind].set_title("Clustered data")
                axs[1,ind].set_xlabel("Feature space for the 1st feature")
                axs[1,ind].set_ylabel("Feature space for the 2nd feature")  
    
            # 군집별 중심 위치 좌표 시각화 
            unique_labels = np.unique(cluster_labels)
            for label in unique_labels:
                center_x_y = centers[label]
                axs[1,ind].scatter(x=center_x_y[0], y=center_x_y[1], s=70, color='k', edgecolor='k', 
                            marker='$%d$' % label)
                
            axs[0,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)
  • iris 데이터로 실루엣 시각화 분석
    from sklearn.datasets import load_iris
    
    iris=load_iris()
    visualize_silhouette([ 2, 3, 4,5 ], iris.data)
  • 단점
    • (직관적으로 이해하기 쉽지만) 각 데이터별로 다른 데이터와의 거리를 반복적으로 계산해야 하므로, 데이터 양이 늘어나면 수행시간이 크게 늘어난다.
    • 또한 메모리 부족 등의 에러가 발생하기 쉬우며, 이 경우 군집별로 임의의 데이터를 샘플링 해 실루엣 계수를 평가하는 방안을 고민해야 한다.
profile
mios의 데이터 놀이터 | Instagram@data.decision (하단 홈 아이콘 버튼)

0개의 댓글