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 = (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)
True number of clusters: 3

Инвариантность к масштабу

Стоит помнить, что, хотя для параметра 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])
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 гораздо устойчивее: его можно рассматривать как алгоритм кластеризации для всех возможных значений 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},
    )
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(copy=True).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(copy=True, **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(copy=True, **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 строит дерево одноцепочечной связи, которое кодирует кластеризацию всех точек для всех значений параметра 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])
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 минут 12.572 секунды)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_hdbscan.ipynb

Download Python source code: plot_hdbscan.py

Download zipped: plot_hdbscan.zip

Связанные примеры

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

Основные нововведения scikit-learn 1.3

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

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

© 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

Spec-Zone.ru

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