DBSCAN для кластеризации и обнаружения аномалий
Рассмотрим один из передовых методов кластеризации - DBSCAN. Для многих исследователей эффективность метода зачастую компенсируется сложностью его настройки, из-за чего предпочтение отдается другим алгоритмам. Давайте внесем ясность в вопрос и упростим использование DBSCAN.
Основные параметры алгоритма, которые меняются от задачи к задаче:
- ядерные (основные) - на расстоянии 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 едины.