라일락 꽃이 피는 날

The Iris Dataset (3) - DBSCAN 본문

데이터 분석/실습

The Iris Dataset (3) - DBSCAN

eunki 2022. 1. 19. 21:04
728x90

DBSCAN 알고리즘

[DBSCAN 장점]
- K-means와 달리 최초 k(군집수)를 직접 할당하지 않음
- Density(밀도)에 따라서 군집을 나누기 때문에, 기하학적인 모양을 갖는 분포도 적용 가능
- Oulier 구분 가능

 

1. 비구형(nonspherical) 데이터 생성

from sklearn.datasets import make_moons
# DBSCAN 알고리즘을 적용시킬 비구형 분포 데이터 생성
moon_data, moon_labels = make_moons(n_samples=400, noise=0.1, random_state=42)
moon_data[:5]

 

 

 

# array 형태의 데이터를 Dataframe 형태로 변경
moon_data_df = pd.DataFrame(moon_data, columns=['x', 'y'])
moon_data_df['label'] = moon_labels
moon_data_df.head()

 

 

 

# scatter plot 그리기
fig = px.scatter(moon_data_df, x='x', y='y', color='label')
fig.update_layout(width=600, height=500)
fig.show()

 

 

 

2. DBSCAN 알고리즘 훈련

from sklearn.cluster import DBSCAN
# eps(epsilon): 기준점으로부터의 반경
# min_samples: 반경내 최소 data points 수
dbscan = DBSCAN(eps=0.2, min_samples=6)
dbscan.fit(moon_data)

# -1 로 할당된 데이터는 outlier
dbscan_label = dbscan.labels_
moon_data_df['dbscan_label'] = dbscan_label
print(set(dbscan_label))  # {-1, 0, 1}

 

 

 

3. DBSCAN 알고리즘 파라미터 비교

# plotly를 사용하여 시각화
fig = px.scatter(moon_data_df, x='x', y='y', color='dbscan_label')
fig.update_layout(width=600, height=500)
fig.show()

 

 

 

# eps 파라미터에 따른 clusters 차이 비교
for eps in [0.1, 0.2, 0.5]:
    dbscan = DBSCAN(eps=eps, min_samples=6).fit(moon_data)
    dbscan_label = dbscan.labels_
    print(f'eps:{eps} ->> label 수: {len(set(dbscan_label))}')
    moon_data_df['dbscan_label'] = dbscan_label

    fig = px.scatter(moon_data_df, x='x', y='y', color='dbscan_label')
    fig.update_layout(width=600, height=500)
    fig.show()

eps가 너무 작으면 불필요하게 많은 cluster가 생성된다.
eps가 너무 크면 모두 같은 cluster로 분류된다.

 

 

 

# min_samples 파라미터에 따른 clusters 차이 비교
for min_samples in [2, 6, 30]:
    dbscan = DBSCAN(eps=0.2, min_samples=min_samples).fit(moon_data)
    dbscan_label = dbscan.labels_
    print(f'min_samples:{min_samples} ->> label 수: {len(set(dbscan_label))}')
    moon_data_df['dbscan_label'] = dbscan_label

    fig = px.scatter(moon_data_df, x='x', y='y', color='dbscan_label')
    fig.update_layout(width=600, height=500)
    fig.show()

min_samples가 너무 크면 모두 같은 cluster로 분류된다.

 

 

 

4. DBSCAN과 K-means의 성능 비교

moon_dbscan = DBSCAN(eps=0.2, min_samples=6).fit(moon_data)
moon_data_df['dbscan_label'] = moon_dbscan.labels_
moon_data_df['dbscan_label'] = moon_data_df['dbscan_label'].astype(str)

moon_km = KMeans(n_clusters=2).fit(moon_data)
moon_data_df['kmeans_label'] = moon_km.labels_
moon_data_df['kmeans_label'] = moon_data_df['kmeans_label'].astype(str)

moon_data_df.head()

 

 

 

for label_case in ['dbscan_label', 'kmeans_label']:
    fig = px.scatter(moon_data_df, x='x', y='y', color=label_case)
    fig.update_layout(width=600, height=500, title=f'{label_case} 시각화')
    fig.show()

DBSCAN은 기하학적인 모양을 갖는 분포에도 적용 가능하다.
K-means는 구 형태의 분포에 최적화되어 있기 때문에 제대로 분류하지 못한다.

 

 

 

5. clustering 결과를 수치적으로 평가

# k-means 결과와 dbscan의 결과를 수치적으로 비교
def find_matching_cluster(cluster_case, actual_labels, cluster_labels):
    matched_cluster={}
    temp_labels = [i+100 for i in range(100)]
    actual_case = list(set(actual_labels))
    
    for i in cluster_case:
        if len(actual_case) > 0 :
            idx = cluster_labels == i
            new_label = scipy.stats.mode(actual_labels[idx])[0][0]
            print(actual_case, '-', new_label)
            
            if new_label in actual_case:
                actual_case.remove(new_label)   
            else:
                new_label = temp_labels[new_label]
                temp_labels.remove(new_label)
                # 매칭되는 실제 label명을 dict형태로 저장
                
            matched_cluster[i] = new_label      
        else:
            new_label = None
        print(f"훈련된 label명: {i} >> 가장 빈번한 실제 label명: {new_label}")
        
    return matched_cluster

 

 

 

# 훈련된 dbscan label을 데이터가 많은 순서로 정렬
dbscan_labels = moon_dbscan.labels_
dbscan_case_dict = dict((x, list(dbscan_labels).count(x)) for x in set(dbscan_labels))
dbscan_case_dict  # {-1: 3, 0: 198, 1: 199}

sorted_dbscan_case = sorted(dbscan_case_dict, key=dbscan_case_dict.get, reverse=True)
sorted_dbscan_case  # [1, 0, -1]

 

 

 

dbscan_perm_dict = find_matching_cluster(sorted_dbscan_case, moon_labels, dbscan_labels)
dbscan_perm_dict

 

 

 

# 훈련된 label명과 실제 label명이 매칭되는 경우 >> 새로 매칭된 label명으로 변경
# 훈련된 label명과 실제 label명이 매칭되지 않는 경우 >> 훈련된 label명 유지
dbscan_new_labels = [label if label not in dbscan_perm_dict else dbscan_perm_dict[label] for label in dbscan_labels]

print(np.array(dbscan_new_labels[:80]))
print(np.array(dbscan_labels[:80]))

 

 

 

kmean_labels = moon_km.labels_
km_case = list(set(kmean_labels))

kmean_perm_dict = find_matching_cluster(km_case, moon_labels, kmean_labels)
kmean_perm_dict

 

 

 

kmean_new_labels = [kmean_perm_dict[label] for label in kmean_labels]

 

 

 

# 훈련된 k-means의 정확도(accuracy) 계산
moon_kmeans_acc = accuracy_score(moon_labels, kmean_new_labels)
print(f"Accuracy score of K-means : {round(moon_kmeans_acc, 4)}")

# 훈련된 dbscan의 정확도(accuracy) 계산
moon_dbscan_acc = accuracy_score(moon_labels, dbscan_new_labels)
print(f"Accuracy score of DBSCAN : {round(moon_dbscan_acc, 4)}")

 

 

 

6. silhouette score 비교

[silhouette score 특징]
silhouette score 계산 방법의 특징 때문에,
실제 시각화에서는 dbscan의 군집이 합리적이게 보이나 silhouette score 점수는 낮게 나올 수 있음
(데이터 분포가 구형이 아닌 경우, 각 군집의 중심점이 다른 군집과 가까워질 수 있기 때문)

 

# 훈련된 k-means의 silhouette score 계산
km_sc_value = silhouette_score(np.array(moon_data), kmean_new_labels, metric='euclidean', sample_size=None, random_state=None)
print(f'Silhouette score of K-means: {round(km_sc_value,4)}')

# 훈련된 dbscan의 silhouette score 계산
dbscan_sc_value = silhouette_score(np.array(moon_data), dbscan_new_labels, metric='euclidean', sample_size=None, random_state=None)
print(f'Silhouette score of DBSCAN: {round(dbscan_sc_value,4)}')

 

 

 

7. Adjusted Rand Index (ARI) 비교

from sklearn.metrics import adjusted_rand_score
km_ari = adjusted_rand_score(moon_labels, kmean_new_labels)
print(f'Adjusted rand index (ARI) of K-means: {round(km_ari,4)}')

dbscan_ari = adjusted_rand_score(moon_labels, dbscan_new_labels)
print(f'Adjusted rand index (ARI) of DBSCAN: {round(dbscan_ari,4)}')

 

 

 

8. iris 데이터로 DBSCAN과 K-means의 성능 비교

train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.2, random_state=1)
iris_compare_df = train_x.copy()

# k-means 훈련
km_iris = KMeans(n_clusters=3).fit(iris_compare_df)
iris_compare_df["km_iris_label"] = km_iris.labels_
print(f"K-means label 종류: {list(set(km_iris.labels_))}")
# dbscan 훈련
dbscan_iris = DBSCAN(eps=0.05, min_samples=2).fit(iris_compare_df)
iris_compare_df["dbscan_iris_label"] = dbscan_iris.labels_
print(f"DBSCAN label 종류: {list(set(dbscan_iris.labels_))}")

 

 

 

# 수치로 비교
# k-means
iris_km_labels = km_iris.labels_  # km 훈련된 전체 lable
iris_km_case = list(set(iris_km_labels))  # km 훈련된 lable 종류

# label 매칭
iris_km_perm_dict = find_matching_cluster(iris_km_case, train_y, iris_km_labels)
iris_km_new_labels = [iris_km_perm_dict[label] for label in iris_km_labels]

# DataFrame에 컬럼 추가
iris_compare_df['new_km_iris_label'] = iris_km_new_labels

 

 

 

# dbscan
iris_dbscan_labels = dbscan_iris.labels_  # dbscan 훈련된 전체 lable

# labele 정렬
iris_dbscan_case_dict = dict((x, list(iris_dbscan_labels).count(x)) for x in set(iris_dbscan_labels))
sorted_iris_dbscan_case = sorted(iris_dbscan_case_dict, key=iris_dbscan_case_dict.get, reverse=True)
sorted_iris_dbscan_case  # [-1, 0]

# label 매칭
iris_dbscan_perm_dict = find_matching_cluster(sorted_iris_dbscan_case, train_y, iris_dbscan_labels)
iris_dbscan_new_labels = [label if label not in iris_dbscan_perm_dict else iris_dbscan_perm_dict[label] for label in iris_dbscan_labels]

# DataFrame에 컬럼 추가
iris_compare_df["new_dbscan_iris_label"] = iris_dbscan_new_labels

 

 

 

# k-means 정확도 계산
kmeans_train_acc = accuracy_score(train_y, iris_km_new_labels)
print(f"Accuracy score of K-means train set : {round(kmeans_train_acc, 4)}")

# dbscan 정확도 계산
dbscan_iris_acc = accuracy_score(train_y, iris_dbscan_new_labels)
print(f"Accuracy score of DBSCAN : {round(dbscan_iris_acc, 4)}")

 

 

 

# 시각화로 비교
fig = make_subplots(rows=1, cols=3, subplot_titles=('Actual', 'K-means cluster', 'DBSCAN cluster'))
# actual
fig.add_trace(
    go.Scatter(x=iris_compare_df['sepal_width'],
               y=iris_compare_df['sepal_length'],
               mode='markers',
               marker=dict(color=train_y),
               text=train_y
               ),
    row=1, col=1
)
# k-means
fig.add_trace(
    go.Scatter(x=iris_compare_df['sepal_width'],
               y=iris_compare_df['sepal_length'],
               mode='markers',
               marker=dict(color=iris_compare_df['new_km_iris_label']),
               text=iris_compare_df['new_km_iris_label']
               ),
    row=1, col=2
)
# dbscan
fig.add_trace(
    go.Scatter(x=iris_compare_df['sepal_width'],
               y=iris_compare_df['sepal_length'],
               mode='markers',
               marker=dict(color=iris_compare_df['new_dbscan_iris_label']),
               text=iris_compare_df['new_dbscan_iris_label']
               ),
    row=1, col=3
)
fig.update_layout(width=1000, height=600, showlegend=False)
fig.show()

 

 

 

fig = make_subplots(rows=1, cols=3, subplot_titles=('Actual', 'K-means cluster', 'DBSCAN cluster'))
# actual
fig.add_trace(
    go.Scatter(x=iris_compare_df['petal_width'],
               y=iris_compare_df['petal_length'],
               mode='markers',
               marker=dict(color=train_y),
               text=train_y
               ),
    row=1, col=1
)
# k-means
fig.add_trace(
    go.Scatter(x=iris_compare_df['petal_width'],
               y=iris_compare_df['petal_length'],
               mode='markers',
               marker=dict(color=iris_compare_df['new_km_iris_label']),
               text=iris_compare_df['new_km_iris_label']
               ),
    row=1, col=2
)
# dbscan
fig.add_trace(
    go.Scatter(x=iris_compare_df['petal_width'],
               y=iris_compare_df['petal_length'],
               mode='markers',
               marker=dict(color=iris_compare_df['new_dbscan_iris_label']),
               text=iris_compare_df['new_dbscan_iris_label']
               ),
    row=1, col=3
)
fig.update_layout(width=1000, height=600, showlegend=False)
fig.show()

항상 DBSCAN이 K-means보다 뛰어난 성능을 가지는 것은 아니다.
데이터의 형태와 특징에 따라 적절한 모델을 사용해야 한다.
728x90

'데이터 분석 > 실습' 카테고리의 다른 글

The Iris Dataset (4) - HDBSCAN  (0) 2022.01.20
The Iris Dataset (2) - Agglomerative  (0) 2022.01.19
The Iris Dataset (1) - K-Means  (0) 2022.01.18
COVID-19 data from John Hopkins University  (0) 2022.01.06
Video Game Sales with Ratings  (0) 2022.01.06