본문 바로가기
IT & 코딩

데이터 분류 기법 : DBSCAN

by 에일라거 2020. 12. 3.

지난 글에 이어서 이번에 쓸 알고리즘은 DBSCAN 입니다. Density-Based Spatial Clustering of Applications with Noise 의 약자라고 하는데, 결론적으로 말해서 클러스터의 갯수를 정하지 않아도 된다라는 거 빼고는 엄청난... 뭐랄까 장점은 없어 보이네요

 

 

지난 글에서 클러스터링 기법 중에 가장 기초적인 K-means clustering을 소개했었습니다.

 

 

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

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

guzene.tistory.com

K-means 클러스터링의 아이디어가 너무나 간단하기 때문에, DBSCAN이라는 뭔가 약자를 이어붙인 것에서 굉장히 거창할 줄 알았는데, 그렇지는 않습니다. 

 

하...

 

경어체를 사용하려고 노력했었는데 뭔가 계속 어색하네요. 그냥 구어체랑 섞어서 막 할께요! 하던대로 해야지 이거 원

 

좋아 일단 위키 등으로 파악한 과정을 한 번 살펴보자 (어우 말투 편해)

 

1. 데이터들 중에 하나를 잡고 일정한 반경 안에 있는 포인트들은 같은 클러스터로 정의함

  1-1. 이때 두개 조건을 정해줘야 하는데, "일정한 반경" 즉 반경을 정해줘야 하고

  1-2. "있는 포인트들" 즉, 포인트의 최소 갯수를 정해줘야 한다.

2. 조건에 만족하지 못하면 지금 탐색했던 포인트는 클러스터를 이루지 못하고 노이즈로 빠짐

3. 이런 식으로 모든 포인트에 대해 클러스터에 해당되는지를 정함

 

K-means clustering은 오히려 스텝 바이 스텝으로 되어 있어서 명확했는데, 이건 코드로 살펴보는 게 나을 거 같은 느낌이 든다. 그런데, DBSCAN의 결정적인 단점 중에, "잘 작동하지 않는다" 라는 것이 있었는데, 그래서 이게 내가 코드를 잘못 짠 건지 원래 이런 건지 구분이 너무 애매해서... sklearn 라이브러리에서 제공하는 결과와 비교도 해보겠다.

 

먼저 코드를 스텝으로 살펴보자

 

필요한 라이브러리

# coding: utf-8

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

from sklearn.datasets import make_blobs
from sklearn.cluster import DBSCAN

이건 그냥 넘어갈게요... 

 

데이터셋 만들기

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],[6,8]])
# offsets = np.array([[6,8], [-6,-8], [-6,8], [6,-8]])

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

fig = plt.figure(figsize=[10,10])
plt.scatter(data.T[0],data.T[1], s=6)

plt.grid()
plt.show()

임의로 데이터셋을 만드는 코드. 가우시안 분포의 덩어리를 여기저기 오프셋을 주고 만들어준다. 위 코드로 생성된 데이터를 플랏해보면 아래와 같다.

 

딱 봐도 잘 안될 거 같은 분위기

솔직히 사람 눈에는 그래도 뭉터기가 좀 보이는데. 내가 만들어서 그런가? 아무튼 이걸로 진행해보자

 

클러스터링

eps = 1.5 # 하나의 클러스터라고 할 거리를 정함
min_samples = 5 # 최소한 이거 이상의 인자만 속하면 클러스터다!

cluster = np.zeros(len(data), dtype=np.int64)-1 # 클러스터 할당 초기화
index = np.arange(len(data))

for i in range(len(data)):
    cluster_max = np.max(cluster, axis=0) # 클러스터의 최대값
    
    diff = data - data[index[i]]
    dist = np.sum(diff*diff, axis=1)
    dist = np.sqrt(dist)
    
    cluster_now = cluster[index[i]] # 현재 데이터의 클러스터를 구함

    # 거리가 지정한 값 이내에 있는 점들의 클러스터를 구하고
    # 이의 최대값을 구함. 즉, 클러스터가 -1이 아니라 다른 것으로 할당된 적이 있는가를 확인
    cluster_next = np.max(cluster[np.where(dist < eps)[0]], axis=0)
    
    # 반경 안에 최소 샘플 이상이 속할 때만 뭔가를 할 수 있음
    if np.sum(dist < eps) >= min_samples:
        # 주변 점부터 탐색할 수 있도록 인덱스 순서를 바꿈
        index = np.append(np.where(dist < eps)[0], np.where(dist >= eps)[0], axis=0) 
        
        if cluster_next == -1: # 주변 점들 모두가 클러스터를 할당받은 적이 없을 때
            # 거리가 일정 이하인 점들에게 이제까지 할당한 적 없는 클러스터를 할당
            cluster[np.where(dist < eps)[0]] = cluster_max + 1 
            searched[np.where(dist < eps)[0]] = 1
        else: # 누군가 할당을 받았을 때
            # 거리가 일정 이하인 점들에게 어딘가의 점이 할당된 클러스터를 할당
            cluster[np.where(dist < eps)[0]] = cluster_next 

# 클러스터에서 중복을 제거하고 할당된 클러스터가 뭐가 있는지 구함
cluster_number = list(set(list(cluster.tolist()))) 

어느 정도 학습을 반복하면서 진행했던 K-means clustering과는 조금 방식이 다르다. 루프를 돌긴 하지만 그냥 점점마다 진행하는 것으로... 주석을 최대한 자세히 달아놨는데, 일단 한번 결과를 볼까?

 

잘 된 건가....

 

아니 솔직히 이게 거의 비슷한 데이터라도, 데이터를 재생성하면 (재생성하는 방식이 randn 이다 보니) 클러스터링이 완전히 달라진다. 똑같은 조건으로 재생성해볼까?

 

이게 말이 되냐고...

 

코드를 잘못 짠건가... 싶어서 이번엔 sklearn으로 돌려보자

 

클러스터링 두번째 : sklearn

eps = 0.5 # 하나의 클러스터라고 할 거리를 정함
min_samples = 5 # 최소한 이거 이상의 인자만 속하면 클러스터다!

dbscan = DBSCAN(eps=eps, min_samples=min_samples)
cluster = dbscan.fit_predict(data)

cluster_number = list(set(list(cluster.tolist())))

코드는 초간단. 역시 라이브러리는 라이브러리!! (요새 누가 날코딩하나?)

 

이게 뭐죠

 

장난하는 것도 아니고 ㅋㅋㅋㅋㅋ 이게 뭐냐고...

 

아니 DBSCAN이라는 게 이렇게 형편없는 거였어?? 라는 생각이 들어서, 이번엔 하기 좋은 데이터를 한번 띄워봤다

 

누가 봐도 잘 될 거 같은 데이터

 

이거도 못하면 바보지 그냥 못쓰는 거다!! 라고 생각하고 해본 결과는 아래와 같다.

 

이건 잘 돼야지

 

이 결과는 날코딩한 거나 sklearn 쓴 거나 비슷하다. 아니 뭐... 당연히 그렇겠지요

 

그래서 장점이 뭐야?

아니 그래서 대체 장점이 뭐냐... 라고 생각했을 때, 솔직히 k-means clustering은 위의 데이터처럼 구분하기 좀 어려운 것도 어느 정도 구분을 잘 해준다. 단지 최대의 단점이 클러스터의 갯수를 정해줘야 한다는 건데 DBSCAN의 장점은 바로 그 지점 아니겠어?

 

즉, 다닥다닥 붙어있는 데이터만 아니라면, 상당히 뭐랄까... 아 적당히 알아서 해 주네 라는 느낌으로 쓸 수 있다는 거? 아 근데 생각보다... 실망스러워서 ㅋㅋㅋㅋ 구글링해보면 진짜 거침없이 클러스터링 해대는 느낌이었는데 말입니다.

 

어쨌든 이걸로 DBSCAN도 독파! 그래도 대충 어느 경우에 써야 하는지는 알겠다. 다음에는 Expectation-Maximization 관련된 EM-GMM 알고리즘을 들고 오겠어요

댓글