by museonghwang

K-평균 알고리즘

|

K-평균군집화(Clustering)에서 가장 일반적으로 사용되는 알고리즘 으로 군집 중심점(centroid)이라는 특정한 임의의 지점을 선택해 해당 중심에 가장 가까운 포인트들을 선택하는 군집화 기법 입니다.

군집 중심점 은 선택된 포인트의 평균 지점으로 이동하고 이동된 중심점에서 다시 가까운 포인트를 선택, 다시 중심점을 평균 지점으로 이동하는 프로세스를 반복적으로 수행합니다. 모든 데이터 포인트에서 더 이상 중심점의 이동이 없을 경우에 반복을 멈추고 해당 중심점에 속하는 데이터 포인트들을 군집화 하는 기법입니다.

image


  1. 먼저 중심을 구성하려는 군집화 개수만큼 임의의 위치에 가져다 놓습니다. 전체 데이터를 2개로 군집화하려면 2개의 중심을 임의의 위치에 가져다 놓습니다.
  2. 각 데이터는 가장 가까운 곳에 위치한 중심점에 소속됩니다. 위 그림에서는 A, B 데이터가 같은 중심점에 소속되며,C, E, F 데이터가 같은 중심점에 소속됩니다.
  3. 이렇게 소속이 결정되면 군집 중심점을 소속된 데이터의 평균 중심으로 이동합니다. 위 그림에서는 A, B 데이터 포인트의 평균 위치로 중심점이 이동했고, 다른 중심점 역시 C, E, F 데이터 포인트의 평균 위치로 이동했습니다.
  4. 각 데이터는 기존에 속한 중심점보다 더 가까운 중심점이 있다면 해당 중심점으로 다시소속을 변경합니다. 위 그림에서는 C 데이터가 기존의 중심점보다 더 가까운 중심점으로 변경됐습니다.
  5. 다시 중심을 소속된 데이터의 평균 중심으로 이동합니다. 위 그림에서는 데이터 C가 중심 소속이 변경되면서 두 개의 중심이 모두 이동합니다.
  6. 중심점을 이동했는데 데이터의 중심점 소속 변경이 없으면 군집화를 종료합니다. 그렇지 않다면 다시 4번 과정을거쳐서 소속을 변경하고 이 과정을 반복합니다.


K-평균의 장점

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

K-평균의 단점

  • 거리 기반 알고리즘으로 속성의 개수가 매우 많을 경우 군집화 정확도가 떨어짐
  • 반복을 수행하는데, 반복 횟수가 많을 경우 수행 시간이 매우 느려짐
  • 몇 개의 군집(cluster)을 선택해야 할지 가이드하기 어려움


사이킷런 KMeans 클래스

사이킷런 패키지는 K-평균을 구현하기 위해 KMeans 클래스를 제공합니다. KMeans 클래스는 다음과 같은 초기화 파라미터를 가지고 있습니다.

class sklearn.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 : 최대 반복 횟수, 이 횟수 이전에 모든 데이터의 중심점 이동이 없으면 종료


KMeans 는 사이킷런의 비지도학습 클래스와 마찬가지로 fit(데이터 세트) 또는 fit_transform(데이터세트) 메서드를 이용해 수행하면 됩니다. 이렇게 수행된 KMeans 객체는 군집화 수행이 완료돼 군집화와 관련된 주요 속성을 알 수가 있습니다. 다음은 이 주요 속성 정보입니다.

  • labels_ : 각 데이터 포인트가 속한 군집 중심점 레이블
  • cluster_centers_ : 각 군집 중심점 좌표(Shape는 [군집 개수, 피처 개수]).

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

붓꽃 데이터를 이용해 K-평균 군집화를 수행해 보겠습니다. 꽃받침(sepal), 꽃잎(petal)의 길이에 따라 각 데이터의 군집화가 어떻게 결정되는지 확인해 보고, 이를 분류 값과 비교해 보겠습니다.

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)

image


붓꽃 데이터 세트를 3개 그룹으로 군집화해 보겠습니다. 이를 위해 n_cluster 는 3, 초기 중심 설정 방식은 디폴트 값인 k-means++, 최대 반복 횟수 역시 디폴트 값인 max_iter=300 으로 설정한 KMeans 객체를 만들고, 여기에 fit() 를 수행하겠습니다.

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


fit() 을 수행해 irisDF 데이터에 대한 군집화 수행 결과가 kmeans 객체 변수로 반환됐습니다. kmeanslabels_ 속성값을 확인해 보면 irisDF 의 각 데이터가 어떤 중심에 속하는지를 알 수 있습니다. labels 속성값을 출력해 보겠습니다.

print(kmeans.labels_)
[output]
[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 2 2 2 0 2 2 2 2
 2 2 0 0 2 2 2 2 0 2 0 2 0 2 2 0 0 2 2 2 2 2 0 2 2 2 2 0 2 2 2 0 2 2 2 0 2
 2 0]


labels_ 의 값이 0, 1, 2 로 돼 있으며, 이는 각 레코드가 첫 번째 군집, 두 번째 군집, 세 번째 군집 에 속함을 의미합니다.

실제 붓꽃 품종 분류 값과 얼마나 차이가 나는지로 군집화가 효과적으로 됐는지, 실제 분류값인 target 과 군집화 분류값인 cluster 를 이용하여 확인해 보겠습니다.

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

image


sepal_length를 기준으로 분류 타깃이 0값인 데이터는 1번 군집으로 모두 잘 그루핑됐습니다. Target 1 값 데이터는 2개만 2번군집으로 그루핑됐고, 나머지 48개는 모두 0번 군집으로 그루핑됐습니다. 하지만 Target 2값 데이터는 0번 군집에 14개, 2번 군집에 36개로 분산돼 그루핑됐습니다.

이번에는 붓꽃 데이터 세트의 군집화를 시각화해 보겠습니다. 2차원 평면상에서 개별 데이터의 군집화을 시각적으로 표현하려고 합니다. 붓꽃 데이터 세트의 속성이 4개이므로 2차원 평면에 적합치 않아 PCA 를 이용해 4개의 속성을 2개로 차원 축소한 뒤에 X 좌표, Y 좌표로 개별 데이터를 표현하도록 하겠습니다.

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]
irisDF.head(3)

image


pca_x는 X 좌표 값, pca_y 는 Y 좌표 값을 나타냅니다. 각 군집별로 cluster 0 은 마커 ‘o’, cluster 1 은 마커 ‘s’, cluster 2 는 마커 ‘^’로 표현합니다.

# 군집 값이 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()

image


Cluster 1 을 나타내는 네모(‘s’)는 명확히 다른 군집과 잘 분리돼 있습니다. Cluster 0 을 나타내는 동그라미(‘o’)와 Cluster 2 를 나타내는 세모(‘^’)는 상당 수준 분리돼 있지만, 네모만큼 명확하게는 분리돼 있지 않음을 알 수 있습니다. Cluster 0과 1의 경우 속성의 위치 자체가 명확히 분리되기 어려운 부분이 존재합니다.


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

사이킷런은 다양한 유형의 군집화 알고리즘을 테스트해 보기 위한 간단한 데이터 생성기를 제공합니다. 대표적인 군집화용 데이터 생성기로 make_blobs() API가 있습니다. make_blobs() 은 여러 개의 클래스에 해당하는 데이터 세트를 만드는데, 하나의 클래스에 여러 개의 군집이 분포될 수 있게 데이터를 생성할 수 있으며, 개별 군집의 중심점과 표준 편차 제어 기능이 있고, 분류 용도로도 테스트 데이터 생성이 가능하다는 특징이 있습니다.

make_blobs() 의 간략한 사용법을 알아보면서 군집화를 위한 테스트 데이터 세트를 만드는 방법을 살펴보겠습니다. make_blobs() 를 호출하면 피처 데이터 세트와 타깃 데이터 세트가 튜플(Tuple)로 반환되며, 호출 파라미터는 다음과 같습니다.

  • n_samples : 생성할 총 데이터의 개수, 디폴트는 100개
  • n_features : 데이터의 피처 개수
  • centers : int 값으로 설정하면 군집의 개수를 나타냄. ndarray 형태로 표현할 경우 개별 군집 중심점의 좌표를 의미
  • cluster_std : 생성될 군집 데이터의 표준 편차를 의미, 군집별로 서로 다른 표준 편차를 가진 데이터 세트를 만들 때 사용


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

위 함수를 호출하면 총 200개의 레코드와 2개의 피처가 3개의 군집화 기반 분포도를 가진 피처 데이터 세트 X와 동시에 3개의 군집화 값을 가진 타깃 데이터 세트가 반환됩니다.

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)
[output]
(200, 2) (200,)
[0 1 2] [67 67 66]


피처 데이터 세트 X 는 200개의 레코드와 2개의 피처를 가지므로 shape(200, 2), 군집 타깃 데이터 세트인 yshape(200,), 그리고 3개의 cluster 의 값은 [0, 1, 2] 이며 각각 67, 67, 66개로 균일하게 구성돼 있습니다. 좀 더 데이터 가공을 편리하게 하기 위해서 피처의 이름을 피처의 이름은 ftr1, ftr2 로 변경하겠습니다.

import pandas as pd

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

image


이제 make_blob() 으로 만든 피처 데이터 세트가 어떠한 군집화 분포를 가지고 만들어졌는지 확인해보겠습니다. 타깃값 0, 1, 2에 따라 마커를 다르게 해서 산점도를 그려보면 다음과 같이 3개의 구분될수 있는 군집 영역으로 피처 데이터 세트가 만들어졌음을 알 수 있습니다.

target_list = np.unique(y)
# 각 타깃별 산점도의 마커 값.
markers=['o', 's', '^', 'P', 'D', 'H', 'x']
# 3개의 군집 영역으로 구분한 데이터 세트를 생성했으므로 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()

image


이번에는 이렇게 만들어진 데이터 세트에 KMeans 군집화를 수행한 뒤에 군집별로 시각화해 보겠습니다. 먼저 KMeans 객체에 fit_predict(X) 를 수행해 make_blobs() 의 피처 데이터 세트인 X 데이터를 군집화합니다.

# 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()

image


make_blobs() 의 타깃과 kmeans_label 은 군집 번호를 의미하므로 서로 다른 값으로 매핑될 수 있습니다.

print(clusterDF.groupby('target')['kmeans_label'].value_counts())
[output]
target  kmeans_label
0       0               66
        1                1
1       2               67
2       1               65
        2                1
Name: kmeans_label, dtype: int64


Target 0cluster label 0 으로, target 1label 2 로, target 2label 1 로 거의 대부분 잘 매핑됐습니다.

make_blobs()cluster_std 파라미터로 데이터의 분포도를 조절합니다. 다음 그림은 cluster_std 가 0.4, 0.8, 1.2, 1.6일 때의 데이터를 시각화한 것입니다. cluster_std 가 작을수록 군집 중심에 데이터가 모여 있으며, 클수록 데이터가 퍼져 있음을 알 수 있습니다.

image