by museonghwang

GMM(Gaussian Mixture Model)

|

GMM(Gaussian Mixture Model) 군집화군집화를 적용하고자 하는 데이터가 여러 개의 가우시안 분포(Gaussian Distribution)를 가진 데이터 집합들이 섞여서 생성된 것이라는 가정하에 군집화를 수행하는 방식입니다.

데이터를 여러 개의 가우시안 분포가 섞인 것으로 간주 하고, 섞인 데이터 분포에서 개별 유형의 가우시안 분포를 추출 합니다. 먼저 다음과 같이 세 개의 가우시안 분포 A, B, C를 가진 데이터 세트가 있다고 가정하겠습니다.

image


이 세 개의 정규 분포를 합치면 다음 형태가 될 것입니다.

image


군집화를 수행하려는 실제 데이터 세트의 데이터 분포도가 다음과 같다면 쉽게 이 데이터 세트가 정규분포 A, B, C가 합쳐서 된 데이터 분포도임을 알 수 있습니다.

image


전체 데이터 세트는 서로 다른 정규 분포 형태를 가진 여러 가지 확률 분포 곡선으로 구성될 수 있으며, 이러한 서로 다른 정규 분포에 기반해 군집화을 수행하는 것GMM 군집화 방식 입니다. 가령 1000개의 데이터 세트가 있다면 이를 구성하는 여러 개의 정규 분포 곡선을 추출하고, 개별 데이터가 이 중 어떤 정규 분포에 속하는지 결정하는 방식입니다.

image


이와 같은 방식은 GMM에서는 모수 추정 이라고 하는데, 모수 추정은 대표적으로 2가지를 추정하는 것 입니다.

  • 개별 정규 분포의 평균과 분산
  • 각 데이터가 어떤 정규 분포에 해당되는지의 확률


이러한 모수 추정을 위해 GMM은 EM(Expectation and Maximization) 방법을 적용 합니다. 사이킷런은 이러한 GMM의 EM 방식을 통한 모수 추정 군집화를 지원하기 위해 GaussianMixture 클래스를 지원합니다.


GMM을 이용한 붓꽃 데이터 세트 군집화

GMM확률 기반 군집화 이고 K-평균거리 기반 군집화 입니다. 이번에는 붓꽃 데이터 세트로 이 두 가지 방식을 이용해 군집화를 수행한 뒤 양쪽 방식을 비교해 보겠습니다.

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()
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']

# 보다 편리한 데이타 Handling을 위해 DataFrame으로 변환
irisDF = pd.DataFrame(data=iris.data, columns=feature_names)
irisDF['target'] = iris.target


GaussianMixture 객체의 가장 중요한 초기화 파라미터는 n_components 입니다. n_componentsgaussian mixture 의 모델의 총 개수 로, 군집의 개수를 정하는 데 중요한 역할을 수행합니다. n_components 를 3으로 설정하고 GaussianMixture 로 군집화를 수행하겠습니다.

from sklearn.mixture import GaussianMixture

gmm = GaussianMixture(
    n_components=3,
    random_state=0
).fit(iris.data)
gmm_cluster_labels = gmm.predict(iris.data)

# 클러스터링 결과를 irisDF 의 'gmm_cluster' 컬럼명으로 저장
irisDF['gmm_cluster'] = gmm_cluster_labels
irisDF['target'] = iris.target

# target 값에 따라서 gmm_cluster 값이 어떻게 매핑되었는지 확인. 
iris_result = irisDF.groupby(['target'])['gmm_cluster'].value_counts()
print(iris_result)
[output]
target  gmm_cluster
0       0              50
1       2              45
        1               5
2       1              50
Name: gmm_cluster, dtype: int64


Target 0은 cluster 0으로, Target 2는 cluster 1로 모두 잘 매핑됐습니다. Target 1만 cluster 2로 45개(90%), cluster 1로 5개(10%) 매핑됐습니다. 붓꽃 데이터 세트의 K-평균 군집화를 수행한 결과를 보겠습니다.

kmeans = KMeans(
    n_clusters=3,
    init='k-means++',
    max_iter=300,
    random_state=0
).fit(iris.data)
kmeans_cluster_labels = kmeans.predict(iris.data)

irisDF['kmeans_cluster'] = kmeans_cluster_labels
iris_result = irisDF.groupby(['target'])['kmeans_cluster'].value_counts()
print(iris_result)
[output]
target  kmeans_cluster
0       1                 50
1       0                 48
        2                  2
2       2                 36
        0                 14
Name: kmeans_cluster, dtype: int64


이는 어떤 알고리즘에 더 뛰어나다는 의미가 아니라 붓꽃 데이터 세트가 GMM 군집화에 더 효과적이라는 의미 입니다. K-평균은 평균 거리 중심으로 중심을 이동하면서 군집화를 수행하는 방식이므로 개별 군집 내의 데이터가 원형으로 흩어져 있는 경우에 매우 효과적으로 군집화가 수행될 수 있습니다.


GMM과 K-평균의 비교

KMeans원형의 범위에서 군집화를 수행 합니다. 데이터 세트가 원형의 범위를 가질수록 KMeans의 군집화 효율은 더욱 높아집니다.

다음은 make_blobs() 의 군집의 수를 3개로 하되, cluster_std 를 0.5로 설정해 군집 내의 데이터를 뭉치게 유도한 데이터 세트에 KMeans 를 적용한 결과입니다. 이렇게 cluster_std 를 작게 설정하면 데이터가 원형 형태로 분산될 수 있습니다. 결과를 보면 KMeans 로 효과적으로 군집화된 것을 알 수 있습니다.

image


KMeans 군집화는 개별 군집의 중심에서 원형의 범위로 데이터를 군집화 했습니다. 하지만 데이터가 원형의 범위로 퍼져 있지 않는 경우에는 어떨까요? KMeans 는 대표적으로 데이터가 길쭉한 타원형으로 늘어선 경우에 군집화를 잘 수행하지 못합니다.

다음에서 해당 데이터 세트를 make_blobs() 의 데이터를 변환해 만들어보겠습니다. 앞으로도 군집을 자주 시각화하므로 이를 위한 별도의 함수를 만들어 이용하겠습니다.

visualize_cluster_plot(
    clusterobj,
    dataframe,
    label_name,
    iscluster=True
)
  • clusterobj
    • 사이킷런의 군집 수행 객체
    • KMeans나 GaussianMixture의 fit()와 predict()로 군집화를 완료한 객체
    • 만약 군집화 결과 시각화가 아니고 make_blobs()로 생성한 데이터의 시각화일 경우 None 입력
  • dataframe
    • 피처 데이터 세트와 label 값을 가진 DataFrame
  • label_name
    • 군집화 결과 시각화일 경우 dataframe 내의 군집화 label 칼럼명
    • make_blobs() 결과 시각화일 경우는 dataframe 내의 target 칼럼명
  • iscenter
    • 사이킷런 Cluster 객체가 군집 중심 좌표를 제공하면 True, 그렇지 않으면 False


**visualize_cluster_plot()**
### 클러스터 결과를 담은 DataFrame과 사이킷런의 Cluster 객체등을 인자로 받아 클러스터링 결과를 시각화하는 함수  
def visualize_cluster_plot(clusterobj, dataframe, label_name, iscenter=True):
    if iscenter:
        centers = clusterobj.cluster_centers_
        
    unique_labels = np.unique(dataframe[label_name].values)
    markers=['o', 's', '^', 'x', '*']
    isNoise=False

    for label in unique_labels:
        label_cluster = dataframe[dataframe[label_name]==label]
        if label == -1:
            cluster_legend = 'Noise'
            isNoise=True
        else:
            cluster_legend = 'Cluster '+str(label)
        
        plt.scatter(x=label_cluster['ftr1'], y=label_cluster['ftr2'], s=70,\
                    edgecolor='k', marker=markers[label], label=cluster_legend)
        
        if iscenter:
            center_x_y = centers[label]
            plt.scatter(x=center_x_y[0], y=center_x_y[1], s=250, 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)
    
    if isNoise: legend_loc='upper center'
    else: legend_loc='upper right'
    
    plt.legend(loc=legend_loc)
    plt.show()


from sklearn.datasets import make_blobs

# make_blobs() 로 300개의 데이터 셋, 3개의 cluster 셋, cluster_std=0.5 을 만듬. 
X, y = make_blobs(n_samples=300, n_features=2, centers=3, cluster_std=0.5, random_state=0)

# 길게 늘어난 타원형의 데이터 셋을 생성하기 위해 변환함. 
transformation = [[0.60834549, -0.63667341], [-0.40887718, 0.85253229]]
X_aniso = np.dot(X, transformation)

# feature 데이터 셋과 make_blobs( ) 의 y 결과 값을 DataFrame으로 저장
clusterDF = pd.DataFrame(data=X_aniso, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y

# 생성된 데이터 셋을 target 별로 다른 marker 로 표시하여 시각화 함. 
visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)

image


위와 같이 만들어진 데이터 세트에서는 KMeans 의 군집화 정확성이 떨어지게 됩니다. KMeans 가 위 데이터 세트를 어떻게 군집화하는지 확인해 보겠습니다.

# 3개의 Cluster 기반 Kmeans 를 X_aniso 데이터 셋에 적용 
kmeans = KMeans(3, random_state=0)
kmeans_label = kmeans.fit_predict(X_aniso)
clusterDF['kmeans_label'] = kmeans_label

visualize_cluster_plot(kmeans, clusterDF, 'kmeans_label',iscenter=True)

image


KMeans 로 군집화를 수행할 경우, 주로 원형 영역 위치로 개별 군집화 가 되면서 원하는 방향으로 구성되지 않음 을 알 수 있습니다. KMeans가 평균 거리 기반으로 군집화를 수행 하므로 같은 거리상 원형으로 군집을 구성하면서 위와 같이 길쭉한 방향으로 데이터가 밀접해 있을 경우에는 최적의 군집화가 어렵습니다. 이번에는 GMM 으로 군집화를 수행해 보겠습니다.

# 3개의 n_components기반 GMM을 X_aniso 데이터 셋에 적용 
gmm = GaussianMixture(n_components=3, random_state=0)
gmm_label = gmm.fit(X_aniso).predict(X_aniso)
clusterDF['gmm_label'] = gmm_label

# GaussianMixture는 cluster_centers_ 속성이 없으므로 iscenter를 False로 설정. 
visualize_cluster_plot(gmm, clusterDF, 'gmm_label',iscenter=False)

image


데이터가 분포된 방향에 따라 정확하게 군집화됐음 을 알 수 있습니다. make_blobs()target 값과 KMeans, GMM 의 군집 Label 값을 서로 비교해 위와 같은 데이터 세트에서 얼만큼의 군집화 효율 차이가 발생하는지 확인해 보겠습니다.

print('### KMeans Clustering ###')
print(clusterDF.groupby('target')['kmeans_label'].value_counts())
print('\n### Gaussian Mixture Clustering ###')
print(clusterDF.groupby('target')['gmm_label'].value_counts())
[output]
### KMeans Clustering ###
target  kmeans_label
0       2                73
        0                27
1       1               100
2       0                86
        2                14
Name: kmeans_label, dtype: int64

### Gaussian Mixture Clustering ###
target  gmm_label
0       2            100
1       1            100
2       0            100
Name: gmm_label, dtype: int64


KMeans 의 경우 군집 1번만 정확히 매핑됐지만, 나머지 군집의 경우 target 값과 어긋나는 경우가 발생하고 있습니다. 하지만 GMM 의 경우는 군집이 target 값과 잘 매핑돼 있습니다.

이처럼 GMM 의 경우는 KMeans보다 유연하게 다양한 데이터 세트에 잘 적용될 수 있다는 장점 이 있습니다. 하지만 군집화를 위한 수행 시간이 오래 걸린다는 단점이 있습니다.