машинное обучение
November 1, 2023

DBSCAN для кластеризации и обнаружения аномалий

Рассмотрим один из передовых методов кластеризации - DBSCAN. Для многих исследователей эффективность метода зачастую компенсируется сложностью его настройки, из-за чего предпочтение отдается другим алгоритмам. Давайте внесем ясность в вопрос и упростим использование DBSCAN.

Основные параметры алгоритма, которые меняются от задачи к задаче:

  • eps - расстояние;
  • min_samples - количество точек.

Они определяют 3 вида точек:

  • ядерные (основные) - на расстоянии eps находится не менее min_samples точек;
  • граничные - не ядерные, но на eps расстоянии от них есть ядерные;
  • выбросы - остальные точки.

Далее формируются отдельные кластера для каждой группы достижимых на расстоянии eps ядерных точек (возможно, одной). Граничным точкам соответствует кластер ближайшей (либо для экономии времени первой найденной в ее окрестности) ядерной точки.

min_samples обычно задают исходя из минимального желаемого размера кластера. А для оценки eps разумно получить статистические показатели, а также гистограмму расстояний.

Для демонстрационных целей создадим набор из трех кластеров точек (о том как подробнее читай тут):

from sklearn.datasets import make_blobs
import pandas as pd

X, y, centers = make_blobs(n_samples=1000, centers=3, n_features=2, center_box=[2,100], 
                           return_centers=True, random_state=0)
X = pd.DataFrame(X, columns=['feat1', 'feat2'])
X.head()

Для оценки расстояний можно вывести их квантили, вряд ли в качестве eps нам будет интересно значение более 10-20% квантили (если только не хотите получить очень малое число кластеров):

from sklearn.metrics.pairwise import cosine_distances

dists = pd.Series(cosine_distances(X, X).flatten())
dists.describe(percentiles=[0.01, 0.05])

Также информативна гистограмма расстояний:

dists.hist()

Еще удобно вывести график изменения средних расстояний от каждой точки до min_samples ближайших.

Отмечу, что при большом размере датасета, все эти статистики лучше выводить для случайной подвыборки данных:

from sklearn.neighbors import NearestNeighbors

min_samples = 10
knn = NearestNeighbors(n_neighbors=min_samples, metric='cosine')
knn.fit(X)

dists, inds = knn.kneighbors(X)
dists_ar = dists[:, 1:min_samples].mean(axis=1)
dists_ar.sort()
import seaborn as sns

sns.scatterplot(x=range(len(dists_ar)), y = dists_ar)

eps разумно выбрать в точке изгиба графика.

Теперь обучим модель:

from sklearn.cluster import DBSCAN

clust = DBSCAN(n_jobs=-1, min_samples=min_samples, eps=1e-5, metric='cosine')
clust.fit(X)

Вот как кластеризованы данные:

sns.scatterplot(data=X, x='feat1', y='feat2', hue=clust.labels_)

Розовые точки - выбросы:

(clust.labels_==-1).sum()

Как можно заметить, алгоритм помечает выбросы "-1", то есть может использоваться не только для кластеризации, но и для обнаружения аномалий. В целом касательно параметров алгоритма и его особенностей можно сделать вывод, что для eps слишком маленьких большая часть данных не будет кластеризована, а для больших кластеры будут сливаться.

Также отмечу, что DBSCAN не сможет хорошо кластеризовать наборы данных с большой разницей в плотности, так как параметры eps и min_samples едины.