Spec-Zone.ru › scikit-learn

Примечание

Перейти к концу, чтобы загрузить полный пример кода. или запустить этот пример в браузере через 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)
True number of clusters: 3

Масштабная инвариантность

Стоит помнить, что, хотя 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])
Estimated number of clusters: 3 | scale=1, eps=0.3, Estimated number of clusters: 1 | scale=0.5, eps=0.3, Estimated number of clusters: 11 | scale=3, eps=0.3

Действительно, для сохранения тех же результатов нам нужно было бы масштабировать 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)
Estimated number of clusters: 3 | scale=3, eps=0.9

Хотя стандартизация данных (например, с помощью 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},
    )
Estimated number of clusters: 3 | scale=1, Estimated number of clusters: 3 | scale=0.5, Estimated number of clusters: 3 | scale=3

Кластеризация по нескольким масштабам

Однако 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)
True number of clusters: 4

Этот набор данных сложнее для 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])
Estimated number of clusters: 3 | eps=0.7, Estimated number of clusters: 14 | eps=0.3

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

hdb = HDBSCAN().fit(X)
plot(X, hdb.labels_, hdb.probabilities_)
Estimated number of clusters: 4

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])
Estimated number of clusters: 4 | min_cluster_size=5, Estimated number of clusters: 90 | min_cluster_size=3, Estimated number of clusters: 4 | min_cluster_size=25

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])
Estimated number of clusters: 4 | min_cluster_size=20, min_samples=5, Estimated number of clusters: 4 | min_cluster_size=20, min_samples=3, Estimated number of clusters: 4 | min_cluster_size=20, min_samples=25

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])
Estimated number of clusters: 3 | cut_distance=0.1, Estimated number of clusters: 3 | cut_distance=0.5, Estimated number of clusters: 1 | cut_distance=1.0

Полное время выполнения скрипта: (0 минут 14.099 секунд)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_hdbscan.ipynb

Download Python source code: plot_hdbscan.py

Download zipped: plot_hdbscan.zip

Примеры по теме

Демонстрация алгоритма кластеризации DBSCAN

Сравнение различных алгоритмов кластеризации на тестовых наборах данных

Основные моменты выпуска scikit-learn 1.3

Граница Джонсона-Линденстресса для встраивания с помощью случайных проекций

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API