HDBSCAN聚类算法演示#

在本演示中,我们将从推广cluster.DBSCAN算法的角度,研究cluster.HDBSCAN。我们将比较这两种算法在特定数据集上的表现。最后,我们将评估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没有DBSCAN的eps参数,但它仍然有一些超参数,例如min_cluster_sizemin_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构建一个单链接树,该树编码了所有点在DBSCANeps参数所有值上的聚类结果。因此,我们可以高效地绘制和评估这些聚类结果,而无需完全重新计算中间值,例如核心距离、互达距离和最小生成树。我们只需要指定想要聚类的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 分钟 15.245 秒)

相关示例

DBSCAN 聚类算法演示

DBSCAN 聚类算法演示

比较玩具数据集上的不同聚类算法

比较玩具数据集上的不同聚类算法

scikit-learn 1.3 发行亮点

scikit-learn 1.3 发行亮点

使用随机投影嵌入的Johnson-Lindenstrauss界限

使用随机投影嵌入的Johnson-Lindenstrauss界限

由 Sphinx-Gallery 生成的图库