K-Means 클러스터

학습의 종류에는 레이블 데이터의 포함 여부에 따라서 지도학습(Supervised Learning)과 비지도학습(Unsupervised Learning)으로 나눌 수 있습니다. 지도학습 알고리즘은 종속변수의 특징에 따라서 분류(Classification)와 회귀(Regression)로 나눌 수 있습니다.
반면 비지도 학습은 레이블 데이터가 없는 형태의 데이터로 클러스터링(Clustering)이 가장 대표적인 기법입니다. 학습 데이터의 Feature를 파악해서 특징을 추출하고 이를 통해서 공통점이 있는 데이터를 묶어주는 형태의 분석기법입니다.

예를 들어서 클러스터링 기법을 활용해서 고객의 구매특징에 따라서 고객군을 묶어 줄 수 있습니다. 그렇게 된다면 고객군을 대상으로하는 맞춤형 마케팅도 가능합니다. 이밖에도 네트워크 유해 트래픽 탐지, 영화나 TV 장면 분류, 권역 설정, 뉴스나 토픽 클러스터링 등 다양한 분야에 활용됩니다. 그리고 이러한 알고리즘은 단독으로 사용되기도 하지만 또 여러 다른 알고리즘과 결합되서 사용되기도 합니다.

이중에서 K-Means 알고리즘은 가장 유명한 클러스터링 알고리즘입니다. “K”는 분석 대상 데이터로부터 클러스터 할 수 있는 수를 의미합니다. 그리고 Means는 각 클러스터의 중심의 평균거리를 의미합니다. 종합해보면 각 클러스터의 중심(Centroid)을 기준으로 주변에 있는 가까운 데이터들을 하나로 묶어주는 과정이라고 할 수 있습니다.

K-Means 알고리즘은 아래와 같은 과정으로 수행됩니다.

  1. Centroid 설정
  2. 각 데이터들을 가까운 Centroid에 속한 그룹에 할당
  3. 2번 과정을 통해서 만들어진 클러스터의 Centroid를 새롭게 지정
  4. 2번,3번의 과정을 Centroid가 변하지 않을때까지 반복 수행

아래의 예제를 통해서 기본적인 컨셉을 알아보겠습니다. 먼저 필요한 라이브러리들을 임포트합니다.

from sklearn.datasets import make_blobs
import pandas as pd
import numpy as np
import math
import scipy as sp
import matplotlib.pyplot as plt

테스트용 데이터를 만들기 위해서 sklearn의 make_blobs() 함수를 사용합니다. 샘플 데이터는 [300 × 2] 행렬입니다. 데이터는 4개의 군집을 가지고 있습니다.

x, y = make_blobs(n_samples=300, centers=4, n_features=2)
df = pd.DataFrame(x, y, columns=['x','y']).reset_index(drop=True)

생성한 데이터에서 4개의 centroid 값을 임의로 추출해봅니다. 샘플 데이터의 그룹과 추출한 값을 붉은색 점으로 표시해보겠습니다. 그림1을 보니 4개의 군집을 이루는 데이터를 확인했습니다. 또 4개의 임의의 점을 표시한 부분을 보니 각 군집의 중앙값과는 상당히 거리가 멀어보입니다. 이제 이러한 학습데이터를 통해서 Clustering을 해보겠습니다.

centroids = df.sample(4)
plt.scatter(x[:,0], x[:,1])
plt.scatter(centroids['x'], centroids['y'], c='r')
그림1. 임의의 Centroid 설정

아래의 함수는 각 테스트 데이터와 4개의 점의 거리를 계산하는 함수입니다. 아래의 함수를 실행하면 [300 × 4]의 행렬이 나오게됩니다. 그 이유는 4개의 중앙값과 샘플 데이터의 거리를 측정하기 때문입니다. 이렇게 측정한 값에서 np.argmin() 함수를 실행하면 4개의 점 중에서 가장 가까운 점의 값을 리턴하게됩니다. 그 데이터를 cluster_num에 입력합니다.

그리고 result라는 데이터셋을 리턴하게됩니다. 그러니까 result 데이터셋은 pandas dataframe의 자료형태를 가지고 있고 컬럼은 기존의 x, y외에 0,1,2,3 중에 하나의 값을 담고 있는 cluster라는 새로운 컬럼을 포함하고 있습니다.

def get_distance(center_df):
    # 각 데이터에 대하여, 각 중심점과의 유클리드 거리 계산
    distance = sp.spatial.distance.cdist(df, center_df, "euclidean")
    cluster_num = np.argmin(distance, axis=1)
    result = df.copy()
    result["cluster"] = np.array(cluster_num)
    return result

이 dataframe에서 cluster로 groupby한 후에 평균값을 계산하면 각 그룹의 x, y값 좌표를 리턴하게됩니다. 그 값을 아래의 scatter 그래프로 표시해보변 그림2와 같은 형태가 표시됩니다.

# cluster별로 묶어서 평균 계산
c = r.groupby("cluster").mean()
r = get_distance(c)
plt.scatter(r['x'], r['y'], c=r['cluster'])

그림2 그래프는 한눈에 봐도 clustering이 안돼보입니다. 이제 4가지 색의 군집의 중앙값을 구한 후에 centroid를 옮겨주고 다시 거리를 계산해봅니다. 이런 과정을 계속해보면 그림3, 그림4와 같이 점점 cluster별로 각기 다른 색으로 분류되는 것을 확인 할 수 있습니다.

그림2. 첫번째 수행
그림3. 두번째 수행
그림4. 3번째 수행

즉, centroid 값을 임의로 정해주고 그 포인트를 중심으로 clustering을 수행한 후에 clustering한 값을 중심으로 다시 centroid를 정하고 다시 clustering을 수행하는 작업을 더이상의 centroid 값이 변화가 없을 때까지 수행하면 군집이 형성되는 것이 바로 군집분석의 기본 알고리즘입니다.

sklearn K-Means 사용

sklearn에는 다른 머신러닝 알고리즘과 마찬가지로 비지도학습을 위한 클러스터링 알고리즘인 K-Means 알고리즘을 패키지 형태로 제공하고 있습니다. sklearn을 사용하면 방금 위에서 했던것과 같은 복잡한 작업을 대신해주기 때문에 편리하게 데이터 분석을 할 수 있습니다.

테스트 데이터로 그동안 사용했던 fitness.csv 데이터를 활용해서 테스트해보겠습니다. 데이터의 수가 많지 않아서 분류 결과가 아쉽게도 좋지 않지만 그래도 수행하는 방법에 대해서 가이드가 될 수 있을듯합니다. 테스트는 먼저 파일을 읽은 다음 해당 데이터셋은 레이블이 없기 때문에 테스트 삼아 임의로 레이블을 만들어보고 클러스터가 어떻게 예측했는지 비교해보겠습니다. 다시 말씀드리지만 정확성을 위해서 수행하는 부분은 아님을 알려드립니다.

dataset = pd.read_csv('./fitness.csv')
dataset.loc[ dataset['age'] < 40, 'ACODE']= 0
dataset.loc[ (dataset['age'] >= 40) & (dataset['age'] <50), 'ACODE']= 1
dataset.loc[ (dataset['age'] >= 50) & (dataset['age'] <60), 'ACODE']= 2
dataset['ACODE'] = dataset['ACODE'].astype('int32')
dataset.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 31 entries, 0 to 30
Data columns (total 8 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   age       31 non-null     int64  
 1   weight    31 non-null     float64
 2   oxygen    31 non-null     float64
 3   runtime   31 non-null     float64
 4   runpulse  31 non-null     int64  
 5   rstpulse  31 non-null     int64  
 6   maxpulse  31 non-null     int64  
 7   ACODE     31 non-null     int32  
dtypes: float64(3), int32(1), int64(4)
memory usage: 1.9 KB

sklearn 패키지에서 KMeans 패키지를 임포트합니다. 사용할 때에 culster가 우리는 3개로 알고 있기 때문에 n_cluster=3으로 설정해줍니다. 해당 알고리즘에서 가장 중요한 부분이 centroid를 초기에 어떻게 설정할것인가에 의한 것인데 init을 정해주지 않는다면 기본적으로 k-means++ 방법을 사용합니다. n_init는 30회 중앙값을 각기 다른 포인트로 설정해주고 그중에 가장 best 값을 활용합니다. 이 외에도 다양한 파라메터가 존재하니 공식 홈페이지를 살펴보시는 것을 추천합니다.

https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html

from sklearn.cluster import KMeans 
kmeans = KMeans(n_clusters=3, n_init=30,)
kmeans.fit(dataset[dataset.columns[:-1]]) # ACODE 제외
dataset['ACODE2'] = kmeans.labels_

예측결과를 ACODE2라는 컬럼에 데이터를 추가해봅니다.

(dataset['ACODE'].values == dataset['ACODE2'].values).sum()/len(dataset)
# 0.3225806451612903

예제로 사용했던 데이터의 경우는 사전에 3개의 클래스를 알고 있었지만 비지도 학습은 이에 대한 정보가 주어지지 않기 때문에 어떻게 군집을 만드는 것이 가장 좋은 케이스인지 알 수 없는 경우가 대부분입니다. 그럴 경우 아래와 같은 방식으로 num_cluster를 체크해볼 필요가 있습니다. 그림5의 경우는 num_cluster를 정하기가 어렵습니다. 그 이유는 데이터가 군집하기 어려운 형태로 분산되어 있기 때문입니다. 굳이 한다면 4,5정도가 좋을 듯합니다.

반면 그림6은 클러스터의 갯수가 명확합니다. 3개 정도가 가장 좋은 케이스라고 여집니다. 이것 역시 데이터에 따라서 차이가 있기 때문에 사전에 확인을 해보는 것도 좋은 방법입니다.

num_cluster = range(1,10)
inertia_ = []

for c in num_cluster:
    model = KMeans(n_clusters=c)
    model.fit(dataset[dataset.columns[:-2]])
    inertia_.append(model.inertia_)

# Plot ks vs inertias
plt.plot(num_cluster, inertia_, '-o')
plt.xlabel('number of clusters, k')
plt.ylabel('inertia_')
plt.xticks(num_cluster)
plt.show()
그림 5 num_cluster
그림 6. num_cluster

그림 6은 health.csv 데이터셋의 주성분분석으로 나타낸 그림입니다. 확인 결과 클러스터링에는 적절치 않은 데이터로 확인되네요. 아마도 데이터의 수가 많지 않기 때문이라고 생각됩니다.

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
dim2 = pca.fit_transform(dataset[dataset.columns[:-2]])
plt.scatter(dim2[:,0], dim2[:,1], c=dataset['ACODE'].values )
그림 6. health cluster

위와 같이 k-Means가 항상 좋은 결과를 낼 수 있는 것은 아닙니다.
각각의 데이터가 잘 모여져있다면 좋은 결과를 내지만 그렇지 않고 그림6과 같이 군집이 약할 경우는 좋은 결과를 얻을 수 없습니다.

그림 7
그림 8

K-Means가 좋은 결과를 얻지 못하는 경우는 그림7과 같은 형태의 데이터일 경우도 좋은 성능을 발휘할 수 없습니다. K-Means는 클러스터의 방향성을 고려하지 않고 무조건 거리가 가까운 데이터를 클러스터로 묶어주기 때문에 위와 같은 형태의 데이터는 잘 반영하지 못합니다. 그림에서와 같이 클러스터를 3개로 분류했지만 한눈에 보기에도 좋은 분류가 아님을 확인할 수 있습니다.

이와 마찬가지로 그림8도 역시 좋은 결과를 얻을 수 없습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다