DBSCAN (Density-Based Spatial Clustering of Applications with Noise) #
DBSCAN은 밀도 기반 클러스터링 알고리즘이다. 데이터의 밀도에 따라 클러스터를 형성한다. 이 알고리즘은 군집의 형태가 불규칙하거나 크기가 다양할 때 효과적으로 동작하며, 노이즈 데이터(이상치)를 효과적으로 분리할 수 있다.
주요 개념 #
- 반경 (ε, Epsilon): 각 데이터 포인트 주변에서 이웃을 찾기 위한 최대 거리이다.
- 최소 샘플 수 (minPts): 클러스터를 형성하기 위해 필요한 최소 데이터 포인트 수이다.
- 핵심 포인트 (Core Point): 주변 반경 내에 최소 샘플 수 이상의 데이터 포인트가 존재하는 데이터 포인트이다.
- 경계 포인트 (Border Point): 핵심 포인트의 이웃이지만, 자체적으로는 최소 샘플 수를 만족하지 못하는 데이터 포인트이다.
- 노이즈 (Noise/Outlier): 어느 클러스터에도 속하지 않는 데이터 포인트이다.
알고리즘 동작 원리 #
상세 단계 #
- 이웃 탐색: 모든 데이터 포인트에 대해, 반경 ε 내에 있는 이웃 데이터 포인트의 수를 계산한다.
- 핵심 포인트 식별: 최소 샘플 수(minPts)보다 많은 이웃을 가진 포인트를 핵심 포인트로 식별한다.
- 클러스터 형성: 핵심 포인트를 중심으로 인접한 핵심 포인트와 경계 포인트를 확장하며 클러스터를 형성한다.
- 노이즈 분류: 어떤 클러스터로도 확장되지 않은 포인트는 노이즈로 분류한다.
의사코드 #
DBSCAN(D, ε, minPts):
C = 0 // 클러스터 번호
for each point p in D:
if p is visited:
continue
mark p as visited
NeighborPts = regionQuery(p, ε)
if |NeighborPts| < minPts:
mark p as NOISE
else:
C = C + 1
expandCluster(p, NeighborPts, C, ε, minPts)
expandCluster(p, NeighborPts, C, ε, minPts):
add p to cluster C
for each point p' in NeighborPts:
if p' is not visited:
mark p' as visited
NeighborPts' = regionQuery(p', ε)
if |NeighborPts'| >= minPts:
NeighborPts = NeighborPts joined with NeighborPts'
if p' is not yet member of any cluster:
add p' to cluster C
매개변수 선택 방법 #
ε (Epsilon) 값 선택 #
- k-distance 그래프 방법:
- 각 포인트에서 k번째 최근접 이웃까지의 거리를 계산
- k-distance를 오름차순으로 정렬하여 그래프를 그림
- 그래프에서 “무릎점(knee point)“을 찾아 ε 값으로 설정
# k-distance 그래프 예제
from sklearn.neighbors import NearestNeighbors
import matplotlib.pyplot as plt
def find_epsilon(X, k=4):
neigh = NearestNeighbors(n_neighbors=k)
nbrs = neigh.fit(X)
distances, indices = nbrs.kneighbors(X)
# k번째 이웃까지의 거리만 선택
k_distances = distances[:, k-1]
k_distances = np.sort(k_distances)
plt.plot(k_distances)
plt.ylabel('k-NN distance')
plt.xlabel('Data Points sorted by distance')
plt.title('k-distance Graph')
plt.show()
return k_distances
minPts 값 선택 #
- 일반적인 규칙:
minPts = 2 × 차원수
- 최소값: 차원수 + 1
- 2D 데이터: 보통 4개 이상 권장
- 고차원 데이터: 더 큰 값 필요
Python 구현 예제 #
scikit-learn 사용 #
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
# 샘플 데이터 생성
X, _ = make_blobs(n_samples=300, centers=4, n_features=2,
random_state=42, cluster_std=0.60)
# DBSCAN 적용
dbscan = DBSCAN(eps=0.3, min_samples=10)
cluster_labels = dbscan.fit_predict(X)
# 결과 시각화
plt.figure(figsize=(10, 8))
unique_labels = set(cluster_labels)
colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels)))
for k, col in zip(unique_labels, colors):
if k == -1:
# 노이즈 포인트는 검은색으로 표시
col = 'black'
marker = 'x'
else:
marker = 'o'
class_member_mask = (cluster_labels == k)
xy = X[class_member_mask]
plt.plot(xy[:, 0], xy[:, 1], marker, markerfacecolor=col,
markeredgecolor='k', markersize=6)
plt.title('DBSCAN Clustering')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.show()
print(f"발견된 클러스터 수: {len(set(cluster_labels)) - (1 if -1 in cluster_labels else 0)}")
print(f"노이즈 포인트 수: {list(cluster_labels).count(-1)}")
직접 구현 #
import numpy as np
from scipy.spatial.distance import cdist
class DBSCANCustom:
def __init__(self, eps=0.5, min_samples=5):
self.eps = eps
self.min_samples = min_samples
def fit_predict(self, X):
n_points = len(X)
labels = np.full(n_points, -1) # -1은 노이즈를 의미
cluster_id = 0
# 모든 포인트 간의 거리 계산
distances = cdist(X, X)
for i in range(n_points):
if labels[i] != -1: # 이미 처리된 포인트
continue
# 이웃 찾기
neighbors = self._find_neighbors(distances, i)
if len(neighbors) < self.min_samples:
labels[i] = -1 # 노이즈로 분류
else:
# 새 클러스터 시작
labels = self._expand_cluster(X, distances, labels, i,
neighbors, cluster_id)
cluster_id += 1
return labels
def _find_neighbors(self, distances, point_idx):
return np.where(distances[point_idx] <= self.eps)[0]
def _expand_cluster(self, X, distances, labels, point_idx,
neighbors, cluster_id):
labels[point_idx] = cluster_id
i = 0
while i < len(neighbors):
neighbor_idx = neighbors[i]
if labels[neighbor_idx] == -1: # 노이즈였던 경우
labels[neighbor_idx] = cluster_id
elif labels[neighbor_idx] == -1: # 미분류 상태
labels[neighbor_idx] = cluster_id
# 새로운 이웃들 찾기
new_neighbors = self._find_neighbors(distances, neighbor_idx)
if len(new_neighbors) >= self.min_samples:
neighbors = np.concatenate([neighbors, new_neighbors])
i += 1
return labels
다른 클러스터링 알고리즘과의 비교 #
특성 | DBSCAN | K-Means | Hierarchical |
---|---|---|---|
클러스터 수 사전 지정 | 불필요 | 필요 | 불필요 |
클러스터 모양 | 임의 형태 | 구형 | 임의 형태 |
노이즈 처리 | 우수 | 미흡 | 미흡 |
시간 복잡도 | O(n log n) | O(nkt) | O(n³) |
밀도 변화 | 어려움 | 불가능 | 가능 |
실제 응용 분야 #
1. 이미지 분할 #
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
import cv2
def image_segmentation_dbscan(image_path):
# 이미지 로드 및 전처리
img = cv2.imread(image_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 픽셀 데이터 reshape
pixel_data = img.reshape(-1, 3)
# 정규화
scaler = StandardScaler()
pixel_data_scaled = scaler.fit_transform(pixel_data)
# DBSCAN 적용
dbscan = DBSCAN(eps=0.3, min_samples=50)
labels = dbscan.fit_predict(pixel_data_scaled)
# 결과 이미지 생성
segmented_img = labels.reshape(img.shape[:2])
return segmented_img, labels
2. 고객 세그멘테이션 #
def customer_segmentation(customer_data):
# 특성 선택 및 정규화
features = ['annual_spending', 'frequency', 'recency']
X = customer_data[features]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# DBSCAN 적용
dbscan = DBSCAN(eps=0.5, min_samples=5)
clusters = dbscan.fit_predict(X_scaled)
# 결과 분석
customer_data['cluster'] = clusters
return customer_data
3. 이상 탐지 #
def anomaly_detection(sensor_data):
# DBSCAN을 이용한 이상 탐지
dbscan = DBSCAN(eps=0.3, min_samples=10)
labels = dbscan.fit_predict(sensor_data)
# 노이즈로 분류된 포인트들을 이상값으로 간주
anomalies = sensor_data[labels == -1]
return anomalies, labels
성능 평가 지표 #
1. 실루엣 계수 (Silhouette Score) #
from sklearn.metrics import silhouette_score
def evaluate_dbscan(X, labels):
# 노이즈 포인트 제외하고 계산
mask = labels != -1
if len(set(labels[mask])) > 1:
score = silhouette_score(X[mask], labels[mask])
return score
else:
return -1 # 클러스터가 1개 이하인 경우
2. 조정 랜드 지수 (Adjusted Rand Index) #
from sklearn.metrics import adjusted_rand_score
def compare_clustering(true_labels, pred_labels):
return adjusted_rand_score(true_labels, pred_labels)
장점과 단점 #
장점 #
- 임의 형태의 클러스터: 클러스터의 모양이 불규칙해도 효과적으로 동작한다.
- 노이즈 처리: 노이즈나 이상치를 효과적으로 분리할 수 있다.
- 클러스터 수 자동 결정: 사전에 클러스터 수를 지정할 필요가 없다.
- 강건성: 이상값에 대해 강건하다.
단점 #
- 매개변수 민감성: 데이터의 밀도가 크게 차이나는 경우, 적절한 ε 값을 찾기 어렵다.
- 고차원 문제: 고차원 데이터에서는 성능이 저하될 수 있다 (차원의 저주).
- 밀도 변화: 밀도가 크게 다른 클러스터들을 동시에 찾기 어렵다.
- 메모리 사용량: 모든 포인트 간의 거리를 계산해야 하므로 메모리 사용량이 클 수 있다.
변형 알고리즘 #
HDBSCAN (Hierarchical DBSCAN) #
- 계층적 밀도 기반 클러스터링
- 다양한 밀도의 클러스터를 효과적으로 처리
- 더 안정적인 클러스터 추출
import hdbscan
def hdbscan_clustering(X):
clusterer = hdbscan.HDBSCAN(min_cluster_size=10)
cluster_labels = clusterer.fit_predict(X)
return cluster_labels
OPTICS (Ordering Points To Identify Clustering Structure) #
- DBSCAN의 확장 버전
- 클러스터 추출을 위한 순서 정보 제공
- 다양한 밀도 레벨에서 클러스터 분석 가능
실습 과제 #
- 매개변수 최적화: k-distance 그래프를 이용하여 최적의 ε 값을 찾아보세요.
- 실제 데이터 적용: 실제 데이터셋(예: Iris, Wine)에 DBSCAN을 적용하고 결과를 분석해보세요.
- 성능 비교: DBSCAN과 K-Means의 성능을 다양한 데이터셋에서 비교해보세요.
- 이상 탐지: DBSCAN을 이용한 이상 탐지 시스템을 구현해보세요.
DBSCAN은 데이터의 밀도 기반 특성을 살리고 노이즈를 효과적으로 다루고자 할 때 매우 유용한 알고리즘이다. 특히 클러스터의 형태가 불규칙하거나 이상값이 많은 실제 데이터에서 뛰어난 성능을 보인다.