Примечание
Перейти к концу, чтобы загрузить полный пример кода. или запустить этот пример в браузере через JupyterLite или Binder
Демонстрация алгоритма кластеризации HDBSCAN
В этой демонстрации мы рассмотрим cluster.HDBSCAN с точки зрения обобщения алгоритма cluster.DBSCAN. Мы сравним оба алгоритма на конкретных наборах данных. Наконец, мы оценим чувствительность HDBSCAN к определённым гиперпараметрам.
Сначала мы определим несколько служебных функций для удобства.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
import matplotlib.pyplot as plt
import numpy as np
from sklearn.cluster import DBSCAN, HDBSCAN
from sklearn.datasets import make_blobs
def plot(X, labels, probabilities=None, parameters=None, ground_truth=False, ax=None):
if ax is None:
_, ax = plt.subplots(figsize=(10, 4))
labels = labels if labels is not None else np.ones(X.shape[0])
probabilities = probabilities if probabilities is not None else np.ones(X.shape[0])
# Black removed and is used for noise instead.
unique_labels = set(labels)
colors = [plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))]
# The probability of a point belonging to its labeled cluster determines
# the size of its marker
proba_map = {idx: probabilities[idx] for idx in range(len(labels))}
for k, col in zip(unique_labels, colors):
if k == -1:
# Black used for noise.
col = [0, 0, 0, 1]
class_index = np.where(labels == k)[0]
for ci in class_index:
ax.plot(
X[ci, 0],
X[ci, 1],
"x" if k == -1 else "o",
markerfacecolor=tuple(col),
markeredgecolor="k",
markersize=4 if k == -1 else 1 + 5 * proba_map[ci],
)
n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0)
preamble = "True" if ground_truth else "Estimated"
title = f"{preamble} number of clusters: {n_clusters_}"
if parameters is not None:
parameters_str = ", ".join(f"{k}={v}" for k, v in parameters.items())
title += f" | {parameters_str}"
ax.set_title(title)
plt.tight_layout()
Генерация тестовых данных
Одним из важнейших преимуществ HDBSCAN перед DBSCAN является его встроенная устойчивость. Это особенно заметно на неоднородных смесях данных. Как и DBSCAN, он может моделировать произвольные формы и распределения, однако в отличие от DBSCAN, он не требует указания произвольного и чувствительного eps гиперпараметра.
Например, ниже мы сгенерируем набор данных из смеси трёх двумерных и изотропных гауссовых распределений.
centers = [[1, 1], [-1, -1], [1.5, -1.5]]
X, labels_true = make_blobs(
n_samples=750, centers=centers, cluster_std=[0.4, 0.1, 0.75], random_state=0
)
plot(X, labels=labels_true, ground_truth=True)

Масштабная инвариантность
Стоит помнить, что, хотя DBSCAN предоставляет значение по умолчанию для eps параметра, у него нет правильного значения по умолчанию, и его необходимо настраивать для конкретного используемого набора данных.
В качестве простой демонстрации рассмотрим кластеризацию для eps значения, настроенного для одного набора данных, и кластеризацию, полученную с тем же значением, но применённую к масштабированным версиям набора данных.
fig, axes = plt.subplots(3, 1, figsize=(10, 12))
dbs = DBSCAN(eps=0.3)
for idx, scale in enumerate([1, 0.5, 3]):
dbs.fit(X * scale)
plot(X * scale, dbs.labels_, parameters={"scale": scale, "eps": 0.3}, ax=axes[idx])

Действительно, для сохранения тех же результатов нам нужно было бы масштабировать eps на тот же коэффициент.
fig, axis = plt.subplots(1, 1, figsize=(12, 5))
dbs = DBSCAN(eps=0.9).fit(3 * X)
plot(3 * X, dbs.labels_, parameters={"scale": 3, "eps": 0.9}, ax=axis)

Хотя стандартизация данных (например, с помощью sklearn.preprocessing.StandardScaler) помогает смягчить эту проблему, необходимо проявлять крайнюю осторожность при выборе подходящего значения для eps.
HDBSCAN намного более устойчив в этом смысле: HDBSCAN можно рассматривать как кластеризацию по всем возможным значениям eps и извлечение лучших кластеров из всех возможных кластеров (см. Руководство пользователя). Одним из немедленных преимуществ является то, что HDBSCAN масштабно-инвариантен.
fig, axes = plt.subplots(3, 1, figsize=(10, 12))
hdb = HDBSCAN()
for idx, scale in enumerate([1, 0.5, 3]):
hdb.fit(X * scale)
plot(
X * scale,
hdb.labels_,
hdb.probabilities_,
ax=axes[idx],
parameters={"scale": scale},
)

Кластеризация по нескольким масштабам
Однако HDBSCAN — это больше, чем просто масштабная инвариантность — он способен на кластеризацию по нескольким масштабам, что учитывает кластеры с различной плотностью. Традиционный DBSCAN предполагает, что любые потенциальные кластеры имеют однородную плотность. HDBSCAN не имеет таких ограничений. Для демонстрации этого мы рассмотрим следующий набор данных
centers = [[-0.85, -0.85], [-0.85, 0.85], [3, 3], [3, -3]]
X, labels_true = make_blobs(
n_samples=750, centers=centers, cluster_std=[0.2, 0.35, 1.35, 1.35], random_state=0
)
plot(X, labels=labels_true, ground_truth=True)

Этот набор данных сложнее для DBSCAN из-за различной плотности и пространственного разделения:
- Если
epsслишком велико, то мы рискуем ошибочно объединить два плотных кластера в один, поскольку их взаимная достижимость будет расширять кластеры. - Если
epsслишком мало, то мы рискуем раздробить более разреженные кластеры на множество ложных кластеров.
Не говоря уже о том, что это требует ручного подбора значений eps до тех пор, пока мы не найдём компромисс, который нас устраивает.
fig, axes = plt.subplots(2, 1, figsize=(10, 8))
params = {"eps": 0.7}
dbs = DBSCAN(**params).fit(X)
plot(X, dbs.labels_, parameters=params, ax=axes[0])
params = {"eps": 0.3}
dbs = DBSCAN(**params).fit(X)
plot(X, dbs.labels_, parameters=params, ax=axes[1])

Для правильной кластеризации двух плотных кластеров нам потребовалось бы меньшее значение epsilon, однако при eps=0.3 мы уже дробим разреженные кластеры, что только усугубилось бы по мере уменьшения epsilon. Действительно, кажется, что DBSCAN не способен одновременно разделить два плотных кластера и предотвратить дробление разреженных кластеров. Давайте сравним с HDBSCAN.
hdb = HDBSCAN().fit(X) plot(X, hdb.labels_, hdb.probabilities_)

HDBSCAN способен адаптироваться к многомасштабной структуре набора данных без необходимости настройки параметров. Хотя любой достаточно интересный набор данных потребует настройки, этот случай демонстрирует, что HDBSCAN может давать качественно лучшие классы кластеризации без вмешательства пользователя, что недоступно с помощью DBSCAN.
Устойчивость к гиперпараметрам
В конечном итоге настройка будет важным этапом в любом реальном применении, поэтому давайте рассмотрим некоторые из наиболее важных гиперпараметров для HDBSCAN. Хотя HDBSCAN освобожден от eps параметра DBSCAN, у него всё же есть некоторые гиперпараметры, такие как min_cluster_size и min_samples, которые настраивают его результаты в отношении плотности. Однако мы увидим, что HDBSCAN относительно устойчив к различным примерам реального мира благодаря этим параметрам, чёткое значение которых помогает при их настройке.
min_cluster_size
min_cluster_size — это минимальное количество образцов в группе для того, чтобы группа считалась кластером.
Кластеры, меньшие, чем кластеры этого размера, будут оставлены как шум. Значение по умолчанию равно 5. Этот параметр, как правило, настраивается на большие значения по мере необходимости. Меньшие значения, вероятно, приведут к результатам с меньшим количеством точек, помеченных как шум. Однако значения, которые слишком малы, приведут к тому, что будут отобраны и предпочтены ложные подкластеры. Более большие значения, как правило, более устойчивы к шумным наборам данных, например, кластерам с высокой дисперсией и существенным перекрытием.
PARAM = ({"min_cluster_size": 5}, {"min_cluster_size": 3}, {"min_cluster_size": 25})
fig, axes = plt.subplots(3, 1, figsize=(10, 12))
for i, param in enumerate(PARAM):
hdb = HDBSCAN(**param).fit(X)
labels = hdb.labels_
plot(X, labels, hdb.probabilities_, param, ax=axes[i])

min_samples
min_samples — это количество образцов в окрестности для того, чтобы точка считалась ядром, включая саму точку. min_samples по умолчанию равно min_cluster_size. Аналогично min_cluster_size, более высокие значения min_samples увеличивают устойчивость модели к шуму, но рискуют игнорировать или отбрасывать потенциально допустимые, но небольшие кластеры. min_samples лучше настроить после нахождения хорошего значения для min_cluster_size.
PARAM = (
{"min_cluster_size": 20, "min_samples": 5},
{"min_cluster_size": 20, "min_samples": 3},
{"min_cluster_size": 20, "min_samples": 25},
)
fig, axes = plt.subplots(3, 1, figsize=(10, 12))
for i, param in enumerate(PARAM):
hdb = HDBSCAN(**param).fit(X)
labels = hdb.labels_
plot(X, labels, hdb.probabilities_, param, ax=axes[i])

dbscan_clustering
Во время fit, HDBSCAN строит дерево одиночной связи, которое кодирует кластеризацию всех точек по всем значениям DBSCAN’s eps параметр. Таким образом, мы можем эффективно отображать и оценивать эти кластеризации без полного перерасчёта промежуточных значений, таких как расстояния до ядра, взаимная достижимость и минимальное остовное дерево. Всё, что нам нужно сделать, это указать cut_distance (эквивалентное eps значение), с которым мы хотим выполнить кластеризацию.
PARAM = (
{"cut_distance": 0.1},
{"cut_distance": 0.5},
{"cut_distance": 1.0},
)
hdb = HDBSCAN()
hdb.fit(X)
fig, axes = plt.subplots(len(PARAM), 1, figsize=(10, 12))
for i, param in enumerate(PARAM):
labels = hdb.dbscan_clustering(**param)
plot(X, labels, hdb.probabilities_, param, ax=axes[i])

Полное время выполнения скрипта: (0 минут 14.099 секунд)
Примеры по теме
© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/cluster/plot_hdbscan.html