by museonghwang

DBSCAN

|

DBSCAN 개요

DBSCAN(Density Based Spatial Clustering of Applications with Noise)데이터의 분포가 기하학적으로 복잡한 데이터 세트에도 효과적인 군집화가 가능 합니다. 다음과 같이 내부의 원 모양과 외부의 원 모양 형태의 분포를 가진 데이터 세트를 군집화 한다고 가정할 때 K-평균, 평균 이동, GMM으로는 효과적인 군집화를 수행하기가 어렵습니다.

DBSCAN특정 공간 내에 데이터 밀도 차이를 기반 알고리즘 으로 하고 있어서 복잡한 기하학적 분포도를 가진 데이터 세트에 대해서도 군집화를 잘 수행 합니다.

image


DBSCAN을 구성하는 가장 중요한 두 가지 파라미터입실론(epsilon)으로 표기하는 주변 영역 과 이 입실론 주변 영역에 포함되는 최소 데이터의 개수 min points 입니다.

  • 입실론 주변 영역(epsilon) : 개별 데이터를 중심으로 입실론 반경을 가지는 원형의 영역
  • 최소 데이터 개수(min points) : 개별 데이터의 입실론 주변 영역에 포함되는 타 데이터의 개수


입실론 주변 영역 내에 포함되는 최소 데이터 개수를 충족시키는가 아닌가에 따라 데이터 포인트를 다음과 같이 정의합니다.

  • 핵심 포인트(Core Point) : 주변 영역 내에 최소 데이터 개수 이상의 타 데이터를 가지고 있을 경우 해당 데이터를 핵심 포인트라고 합니다.
  • 이웃 포인트(Neighbor Point) : 주변 영역 내에 위치한 타 데이터를 이웃 포인트라고 합니다.
  • 경계 포인트(Border Point) : 주변 영역 내에 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않지만 핵심 포인트를이웃 포인트로 가지고 있는 데이터를 경계 포인트라고 합니다.
  • 잡음 포인트(Noise Point) : 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않으며, 핵심 포인트도 이웃 포인트로 가지고 있지 않는 데이터를 잡음 포인트라고 합니다.


다음 그림과 같이 P1에서 P12까지 12개의 데이터 세트에 대해서 DBSCAN 군집화를 적용하면서 주요 개념을 설명하겠습니다. 특정 입실론 반경 내에 포함될 최소 데이터 세트를 6개로(자기 자신의 데이터를 포함) 가정 하겠습니다.

image


P1 데이터를 기준으로 입실론 반경 내에 포함된 데이터가 7개(자신은 P1, 이웃 데이터 P2, P6, P7, P8, P9, P11)로 최소 데이터 5개 이상을 만족하므로 P1 데이터는 핵심 포인트(Core Point) 입니다.

image


다음으로 P2 데이터 포인트를 살펴보겠습니다. P2 역시 반경 내에 6개의 데이터(자신은 P2, 이웃 데이터 P1, P3, P4, P9, P10)를 가지고 있으므로 핵심 포인트 입니다.

image


핵심 포인트 P1의 이웃 데이터 포인트 P2 역시 핵심 포인트일 경우 P1에서 P2로 연결해 직접 접근이 가능 합니다.

image


특정 핵심 포인트에서 직접 접근이 가능한 다른 핵심 포인트를 서로 연결하면서 군집화를 구성 합니다. 이러한 방식으로 점차적으로 군집(Cluster) 영역을 확장해 나가는 것이 DBSCAN 군집화 방식 입니다.

image


P3 데이터의 경우 반경 내에 포함되는 이웃 데이터는 P2, P4로 2개 이므로 군집으로 구분할 수 있는 핵심 포인트가 될 수 없습니다. 하지만 이웃 데이터 중에 핵심 포인트인 P2를 가지고 있습니다. 이처럼 자신은 핵심 포인트가 아니지만, 이웃 데이터로 핵심 포인트를 가지고 있는 데이터경계 포인트(Border Point) 라고 합니다. 경계 포인트는 군집의 외곽을 형성합니다.

image


다음 그림의 P5와 같이 반경 내에 최소 데이터를 가지고 있지도 않고, 핵심 포인트 또한 이웃 데이터로 가지고 있지 않는 데이터잡음 포인트(Noise Point) 라고 합니다.

image


DBSCAN 은 이처럼 입실론 주변 영역의 최소 데이터 개수를 포함하는 밀도 기준을 충족시키는 데이터인 핵심 포인트를 연결하면서 군집화를 구성하는 방식 입니다.

사이킷런은 DBSCAN 클래스를 통해 DBSCAN 알고리즘을 지원합니다. DBSCAN 클래스는 다음과 같은 주요한 초기화 파라미터를 가지고 있습니다.

  • eps
    • 입실론 주변 영역의 반경을 의미
  • min_samples
    • 핵심 포인트가 되기 위해 입실론 주변 영역 내에 포함돼야 할 데이터의 최소 개수를 의미
    • 자신의 데이터를 포함(min points + 1)


DBSCAN 적용하기 - 붓꽃 데이터 세트

DBSCAN 클래스를 이용해 붓꽃 데이터 세트를 군집화하겠습니다. eps=0.6, min_samples=8 로 하겠습니다. 일반적으로 eps 값으로는 1 이하의 값을 설정합니다.

from sklearn.datasets import load_iris

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
from sklearn.cluster import DBSCAN

dbscan = DBSCAN(
    eps=0.6,
    min_samples=8,
    metric='euclidean'
)
dbscan_labels = dbscan.fit_predict(iris.data)

irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target

iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)
[output]
target  dbscan_cluster
0        0                49
        -1                 1
1        1                46
        -1                 4
2        1                42
        -1                 8
Name: dbscan_cluster, dtype: int64


먼저 dbscan_cluster 값을 살펴보면 0과 1 외에 특이하게 -1이 군집 레이블로 있는 것 을 알 수 있습니다. 군집 레이블이 -1 인 것은 노이즈에 속하는 군집을 의미 합니다. 따라서 위 붓꽃 데이터 세트는 DBSCAN에서 0과 1 두 개의 군집으로 군집화됐습니다. Target 값의 유형이 3가지인데, 군집이 2개가 됐다고 군집화 효율이 떨어진다는 의미는 아니며, 특히 붓꽃 데이터 세트는 군집을 3개로 하는 것보다는 2개로 하는 것이 군집화의 효율로서 더 좋은 면이있습니다.

DBSCAN은 군집의 개수를 알고리즘에 따라 자동으로 지정 하므로 DBSCAN에서 군집의 개수를 지정하는 것은 무의미 하다고 할 수 있습니다.


DBSCAN 으로 군집화 데이터 세트를 2차원 평면에서 표현하기 위해 PCA 를 이용해 2개의 피처로 압축 변환한 뒤, visualize_cluster_plot() 함수를 이용해 시각화해 보겠습니다.

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.decomposition import PCA

# 2차원으로 시각화하기 위해 PCA n_componets=2로 피처 데이터 세트 변환
pca = PCA(n_components=2, random_state=0)
pca_transformed = pca.fit_transform(iris.data)

# visualize_cluster_plot( ) 함수는 ftr1, ftr2 컬럼을 좌표에 표현하므로 PCA 변환값을 해당 컬럼으로 생성
irisDF['ftr1'] = pca_transformed[:,0]
irisDF['ftr2'] = pca_transformed[:,1]

visualize_cluster_plot(dbscan, irisDF, 'dbscan_cluster', iscenter=False)

image


별표로 표현된 값은 모두 노이즈로, PCA로 2차원으로 표현하면 이상치인 노이즈 데이터가 명확히 드러납니다. DBSCAN을 적용할 때는 특정 군집 개수로 군집을 강제하지 않는 것이 좋습니다. DBSCAN 알고리즘에 적절한 eps와 min_samples 파라미터를 통해 최적의 군집을 찾는 게 중요합니다.

  • eps의 값을 크게 설정
    • 반경이 커져 포함하는 데이터가 많아지므로 노이즈 데이터 개수가 작아짐
  • min_samples를 크게 설정
    • 주어진 반경 내에서 더 많은 데이터를 포함시켜야 하므로 노이즈 데이터 개수가 커지게 됨
    • 데이터 밀도가 더 커져야 하는데, 매우 촘촘한 데이터 분포가 아닌 경우 노이즈로 인식하기 때문


eps를 기존의 0.6에서 0.8로 증가시키면 노이즈 데이터 수가 줄어듭니다.

from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps=0.8, min_samples=8, metric='euclidean')
dbscan_labels = dbscan.fit_predict(iris.data)

irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target

iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)

visualize_cluster_plot(dbscan, irisDF, 'dbscan_cluster', iscenter=False)
[output]
target  dbscan_cluster
0        0                50
1        1                50
2        1                47
        -1                 3
Name: dbscan_cluster, dtype: int64

image


노이즈 군집인 -1이 3개밖에 없습니다. 기존에 eps 가 0.6일 때 노이즈로 분류된 데이터 세트는 eps 반경이 커지면서 Cluster 1에 소속됐습니다. 이번에는 eps 를 기존 0.6으로 유지하고 min_samples 를 16으로 늘려보겠습니다.

dbscan = DBSCAN(eps=0.6, min_samples=16, metric='euclidean')
dbscan_labels = dbscan.fit_predict(iris.data)

irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target

iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)

visualize_cluster_plot(dbscan, irisDF, 'dbscan_cluster', iscenter=False)
[output]
target  dbscan_cluster
0        0                48
        -1                 2
1        1                44
        -1                 6
2        1                36
        -1                14
Name: dbscan_cluster, dtype: int64

image


노이즈 데이터가 기존보다 많이 증가함을 알 수 있습니다.


DBSCAN 적용하기 - make_circles() 데이터 세트

이번에는 복잡한 기하학적 분포를 가지는 데이터 세트에서 DBSCAN 과 타 알고리즘을 비교해 보겠습니다. 먼저 make_circles() 함수를 이용해 내부 원과 외부 원 형태로 돼 있는 2차원 데이터 세트를 만들어 보겠습니다.

  • make_circles()
    • 오직 2개의 피처만을 생성하므로 별도의 피처 개수를 지정할 필요가 없음
    • noise : 노이즈 데이터 세트의 비율
    • factor : 외부 원과 내부 원의 scale 비율


from sklearn.datasets import make_circles

X, y = make_circles(
    n_samples=1000,
    shuffle=True,
    noise=0.05,
    random_state=0,
    factor=0.5
)
clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y

visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)

image


make_circles() 는 내부 원과 외부 원으로 구분되는 데이터 세트를 생성함을 알 수 있습니다. DBSCAN 이 이 데이터 세트를 군집화한 결과를 보기 전에, 먼저 K-평균GMM 은 어떻게 이 데이터 세트를 군집화하는지 확인해 보겠습니다. 먼저 K-평균 으로 make_circles() 데이터 세트를 군집화해 보겠습니다.

# KMeans로 make_circles( ) 데이터 셋을 클러스터링 수행. 
from sklearn.cluster import KMeans

kmeans = KMeans(
    n_clusters=2,
    max_iter=1000,
    random_state=0
)
kmeans_labels = kmeans.fit_predict(X)
clusterDF['kmeans_cluster'] = kmeans_labels

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

image


위, 아래 군집 중심을 기반으로 위와 아래 절반으로 군집화됐습니다. 거리 기반 군집화로는 위와 같이 데이터가 특정한 형태로 지속해서 이어지는 부분을 찾아내기 어렵습니다.

다음으로는 GMM을 적용해 보겠습니다.

# GMM으로 make_circles( ) 데이터 셋을 클러스터링 수행. 
from sklearn.mixture import GaussianMixture

gmm = GaussianMixture(
    n_components=2,
    random_state=0
)
gmm_label = gmm.fit(X).predict(X)
clusterDF['gmm_cluster'] = gmm_label

visualize_cluster_plot(gmm, clusterDF, 'gmm_cluster', iscenter=False)

image


GMM 은 일렬로 늘어선 데이터 세트에서는 효과적으로 군집화 적용이 가능했으나, 내부와 외부의 원형으로 구성된 더 복잡한 형태의 데이터 세트에서는 군집화가 원하는 방향으로 되지 않았습니다. 이제 DBSCAN으로 군집화를 적용해 보겠습니다.

# DBSCAN으로 make_circles( ) 데이터 셋을 클러스터링 수행. 
from sklearn.cluster import DBSCAN

dbscan = DBSCAN(
    eps=0.2,
    min_samples=10,
    metric='euclidean'
)
dbscan_labels = dbscan.fit_predict(X)
clusterDF['dbscan_cluster'] = dbscan_labels

visualize_cluster_plot(dbscan, clusterDF, 'dbscan_cluster', iscenter=False)

image


DBSCAN 으로 군집화를 적용해 원하는 방향으로 정확히 군집화가 됐음을 알 수 있습니다.