본문 바로가기
IT & 코딩

데이터 분류 (클러스터링) 기법 : K-means clustering

by 에일라거 2020. 11. 29.

데이터 분류 (클러스터링) 기법이 뭐가 엄청 많은데, 이제부터 하나씩 공부해 가면서 정리 겸 블로그에 남기려고 합니다. 그 첫번째로 가장 기초적이고 가장 쉬운 K-means clustering부터 시작해 볼께요

 

 

K-means clustering 기법 자체에 대한 설명은 아래 위키피디아를 참조하면 됩니다.

 

 

k-평균 알고리즘 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. k-평균 알고리즘(K-means clustering algorithm)은 주어진 데이터를 k개의 클러스터로 묶는 알고리즘으로, 각 클러스터와 거리 차이의 분산을 최소화하는 방식으로 동작

ko.wikipedia.org

위 링크에서 뭐라뭐라 복잡하게 쓰여진 내용을 단순화하고 실제 코드로 어떻게 짰는지 공유하는 내용을 써보려고 해요.

 

지난 번에 공간 데이터를 다루는 글을 몇 개 쓰면서, 그 중에 하나가 공간 데이터의 시각화에 대한 글을 쓴 적이 있습니다. 시각화를 할 때도 데이터를 분류해서 시각적으로 (색으로) 표현하게 되는데 이때 데이터를 어떻게 분류할 것이냐.. 가 문제가 되었던 것이고, 이 때 Natural Break, 즉 사람 눈에 자연스럽게 보이는 구분 방법을 한 번 소개했었습니다.

 

 

공간 데이터의 이해 - 내추럴 브레이크 (Natural Breaks)

이제까지 공간 정보를 다루면서 가장 기초적인 부분들에 대해 내가 공부한 만치, 그리고 가능한 한 자세하게 설명하려고 했고 이제 여기까지 해서 크게 하나의 꼭지가 마무리될 것 같다. Natural B

guzene.tistory.com

뜬금없이 이 이야기가 왜 나오느냐면, 위 글에서도 소개했지만 내추럴 브레이크는 사실 K-means clustering 중 1차원 데이터의 분류와 동일하기 때문입니다. 바꿔 말하면, 내추럴 브레이크에서 데이터의 차원만 확장하게 되면 그게 K-means clustering이라는 겁니다.

 

이 알고리즘의 핵심은, 데이터를 여러 개의 그룹으로 분류한다고 할 때 그룹 간의 분산은 가장 크게 하고 그룹 내의 분산은 가장 작게 만드는 것입니다. 구체적으로 어떻게 하냐면

 

1. 클러스터 갯수와 각 클러스터의 중심값을 임의로 정함 (초기화) 
2. 각 데이터에서 가장 가까운 클러스터를 찾아 데이터를 배당
3. 배당 결과를 가지고 클러스터 중심점을 재계산
4. 2-3을 반복

 

에?

 

이거랑 분산이랑 뭔 상관이냐 싶겠지만, 2번과 3번이 바로 그 과정입니다. 각각의 데이터마다 가장 가까운 클러스터로 분류를 하고, 분류된 결과를 바탕으로 클러스터의 중심점을 옮겨갑니다. 그러면 데이터는 가만히 둔 채로 중심점만 슬금슬금 움직여서 분류가 완료되는 식입니다.

 

그래프로 한번 먼저 볼까요?

 

데이터셋

 

이런 데이터가 있다고 치죠. 일단 눈으로 먼저 분류를 시도해 봤을 때, 대충 세 뭉텅이로 구분이 되죠? 그리고 아 대충 이렇게 짜르면 될 거 같은데? 라는 감이 서죠? 그걸 컴퓨터가 할 수 있는지 보겠습니다.

 

도대체가 일을 이따위로밖에 못해?!?!?

먼저 아무렇게나 초기화를 합니다. 클러스터 갯수를 3개로 정하고 초기화를 했을 때 처음에는 이렇게 데이터가 배당이 됩니다. 딱 봐도 마음에 안듭니다.

 

호되게 혼쭐을 내주고 다시 한번 일을 시켜봅니다.

 

오... 드디어 일을 하기 시작하는군요! 역시 컴퓨터는 똑똑했습니다. 하지만 좀 더 잘할 수 있을 거 같아요. 조금 더 일을 시켜보겠습니다.

 

이제 진짜 뭔가 된 거 같아요. 시작부터 해서 몇 번 더 시켜본 결과는 아래와 같습니다.

 

슬금슬금

 

그래프에서 보이듯이, 이렇게 데이터는 가만히 두고 중심점이 슬금슬금 움직여서 찾아들어가죠? 다른 머신 러닝들이 그렇듯이 계산하고 업데이트하는 과정을 반복하여 데이터를 분류해 줍니다. 그럼 이제 실제 코드를 어떻게 구현했는지 보겠습니다.

 

파이썬 코드

코드는 파이썬으로 짰습니다. 아래를 보시죠.

 

#-*- coding:utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt


####################################
#
# Step 0 : 데이터 만들기

num_data = 300
dimension = 2
data = np.random.randn(num_data, dimension)
offsets = np.array([[5,2], [-1,5], [2,3], [5,5], [-4,0], [4,-3]])

for offset in offsets:
    data = np.append(data,np.random.randn(num_data, dimension)*1.2 + offset, axis=0)




####################################
#
# Step 1 : 초기화 - 클러스터의 갯수를 정하고, 각 클러스터의 중심점을 정함

num_cluster = 7

# 각 차원별 min/max가 구해짐. 2차원이라면 x축의 min/max와 y축의 min/max
# [[x_min, y_min], [x_max, y_max]] 이런 식
min_max = [np.min(data, axis=0), np.max(data, axis=0)]

# 범위가 0~10 이고 클러스터가 2개라 하면
# 일단 0~5/5~10 으로 나눈 다음에,
# 2.5, 7.5 를 클러스터의 초기 중심값으로 하려는 것
quantile = (min_max[1] - min_max[0])/num_cluster/2

# 클러스터 초기 중심값을 구함
clst_mu = np.array([quantile*(2*i+1) + min_max[0] for i in range(num_cluster)])
print(f'각 클러스터별 평균 : {clst_mu}')




####################################
#
# Step 2 : 각 데이터에서 가장 가까운 클러스터를 찾아 데이터를 배당

distance = (data - clst_mu.reshape(num_cluster,1,dimension))
distance = distance*distance
distance = distance.sum(axis=2).T
cluster = np.where(distance==distance.min(axis=1).reshape((len(data),1)))[1]




####################################
#
# Step 3 : 배당 결과를 가지고 클러스터 중심점을 재계산

clst_mu_old = clst_mu.copy()
for i in range(num_cluster):
    clst_mu[i] = np.mean(data[np.where(cluster==i)[0]], axis=0)

# 특정 그룹에 속하는 인자가 하나도 없을 경우 np.mean의 결과로 nan을 뱉어냄
# 그럴 경우 이전 평균을 그대로 갖다 쓰는 것으로 코드 만듬
clst_mu = clst_mu_old*np.isnan(clst_mu) + np.nan_to_num(clst_mu)
print(f'\n* 각 클러스터별 평균\n{clst_mu}')




####################################
#
# Step 4 : 계속 반복하는 코드 (Step.2 & Step.3)

maxEpoch = 100

for epoch in range(maxEpoch):
    distance = (data - clst_mu.reshape(num_cluster,1,dimension))
    distance = distance*distance
    distance = distance.sum(axis=2).T
    cluster = np.where(distance==distance.min(axis=1).reshape((len(data),1)))[1]
    
    clst_mu_old = clst_mu.copy()
    for i in range(num_cluster):
        clst_mu[i] = np.mean(data[np.where(cluster==i)[0]], axis=0)

    clst_mu = clst_mu_old*np.isnan(clst_mu) + np.nan_to_num(clst_mu)    





####################################
#
# Step 5 : 시각화

clustered = []

for i in range(num_cluster):
    clustered.append(data[np.where(cluster==i)[0]])

    
fig = plt.figure(figsize=[6,6])

for clustered_data in clustered:
    plt.scatter(clustered_data.T[0],clustered_data.T[1], s=6)
plt.scatter(clst_mu.T[0],clst_mu.T[1], c='black', s=50)

plt.grid()
plt.show()

스텝별로 코드를 짜 두었습니다. 사실 차원이 높아지면 초기화를 저렇게 하면 안되는데... 일단 그냥 샘플이니까?

 

실제 코드는 아래 파일을 다운받으시면 됩니다.

 

k_means_clustering.py
0.00MB

 

알고리즘의 한계는?

분류가 힘든 데이터의 형태가 존재합니다

방식 자체가 그룹 내의 분산을 가장 줄이는 방식으로 동작하기 때문에, 인터넷 여기저기 한계점이 있지만 모양이 이상한 데이터에 대해서는 분류가 잘 안됩니다. 웃는 얼굴 예제 같은 게 많거든요.

 

출처 : https://marlabskochi.github.io/ml-blog/site/clustering.html

 

왼쪽 두개는 이 알고리즘으로는 절대 분류가 안됩니다. 그래서 이를 보완하기 위한 다른 알고리즘들이 많이 나왔습니다. 여기서 드는 의문은, 2차원 데이터야 그냥 눈으로 확인하고 하면 되지만 차원이 올라가게 되면 데이터의 형태를 눈으로 확인할 수가 없고, 그러면 어떤 알고리즘이 최적인지 어떻게 찾을 것인가? 라는 질문입니다. 이건 앞으로 공부해가면서 한 번 찾아보려고 해요.

 

분류할 갯수를 미리 정해줘야 합니다

그리고 이 알고리즘의 특징이 미리 클러스터의 갯수를 정해줘야 한다는 것으로... 클러스터 갯수를 엉뚱하게 지정했을 때는 엉뚱한 결과가 나올 수 있습니다. 아래 그래프를 볼께요

 

 

이런 데이터가 있다고 하겠습니다. 그러면 이건 몇개의 그룹으로 분류해야 할까요? 대충 봤을 때 한...5개? 6개? 눈으로 딱 봤을 때는 감이 잘 안옵니다. 실제로 한 번 분류를 해 보면 아래와 같습니다.

 

대충 뭘 선택해도 말이 될 거 같은 느낌

 

아... 3개 그룹은 좀 아닌 거 같고, 4개? 5개? 아니면 6개? 뭘 골라도 대충 다 말은 돼 보이는데....

 

여기서부터 이제 기술이라기보다는 의사결정의 문제인 거 같아 보이네요. 그렇다면 과연 이 분류는 제대로 된 것인가? 분석하는 사람의 의도에 따라 다른 결과를 뱉어낼 수도 있는 게 아닌가? 라는 숙제가 남는 거 같습니다.

 


 

여러 한계점에도 불구하고 K-means clustering은 일단은 쉽고, 그 원리가 명쾌하기 때문에 빠르게 결과를 보고 싶을 때 잘 쓰이는 거 같습니다. 가성비가 좋은 느낌이랄까.... 다음 시간에는 다른 종류의 클러스터링 기법을 들고 찾아오겠습니다. 씨유!

댓글