Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью 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 = (labels == k).nonzero()[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)

Инвариантность к масштабу
Стоит помнить, что, хотя для параметра eps в DBSCAN задано значение по умолчанию, на практике оно не является подходящим значением по умолчанию и его необходимо настраивать для конкретного используемого набора данных.
Для простого примера рассмотрим кластеризацию со значением 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 гораздо устойчивее: его можно рассматривать как алгоритм кластеризации для всех возможных значений eps с последующим выделением наилучших кластеров из всех возможных (см. Руководство пользователя). Одно из непосредственных преимуществ состоит в том, что HDBSCAN инвариантен к масштабу.
fig, axes = plt.subplots(3, 1, figsize=(10, 12))
hdb = HDBSCAN(copy=True)
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(copy=True).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(copy=True, **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(copy=True, **param).fit(X)
labels = hdb.labels_
plot(X, labels, hdb.probabilities_, param, ax=axes[i])

dbscan_clustering
Во время fit алгоритм HDBSCAN строит дерево одноцепочечной связи, которое кодирует кластеризацию всех точек для всех значений параметра eps алгоритма DBSCAN. Таким образом, мы можем эффективно строить графики и оценивать эти варианты кластеризации, не пересчитывая полностью промежуточные значения, такие как расстояния до базовых точек, взаимная достижимость и минимальное остовное дерево. Для этого достаточно указать cut_distance (эквивалент eps), с которым мы хотим выполнить кластеризацию.
PARAM = (
{"cut_distance": 0.1},
{"cut_distance": 0.5},
{"cut_distance": 1.0},
)
hdb = HDBSCAN(copy=True)
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 минут 12.572 секунды)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/cluster/plot_hdbscan.html