如今,有许多可用于聚类的算法。DBSCAN(基于密度的噪声应用空间聚类)就是其中一种聚类算法。它可以根据密度对数据点进行聚类,即将样本较多的区域归为一组。这使得它在噪声条件下进行聚类时特别有用。正如我们将要看到的,除了聚类之外,DBSCAN还能够检测噪声点,这些噪声点可以根据需要从数据集中丢弃。
在本文中,我们将详细探讨DBSCAN。首先,我们将通过一个包含一些噪声样本的两个数据团的示例用例来介绍聚类。然后,我们将介绍DBSCAN聚类,包括其概念(核心点、直接可达点、可达点和离群点/噪声)和算法(通过逐步解释)。最后,我们将使用Python和Scikit-learn实现DBSCAN聚类算法。这使我们既能理解算法,又能应用它。
什么是聚类?
DBSCAN是一种聚类算法,属于无监督学习算法类。但什么是聚类呢?让我们先看一下定义:
聚类分析或聚类是将一组对象分组的任务,使得同一组(称为簇)中的对象在某种意义上彼此更相似,而与其他组(簇)中的对象不太相似。
它允许我们根据特定组内样本的共享特征从数据集中选择组。
这很有趣,因为仅举一个例子,我们可以使用聚类来生成带标签的数据集(例如,如果我们没有类别,则选择类别)以创建预测模型。此外,正如我们将在本文中看到的,聚类还可以用于检测噪声样本,这些样本可能在训练监督学习模型之前被移除。
介绍DBSCAN聚类
DBSCAN是一种对数据集进行聚类分析的算法。
在我们开始使用Scikit-learn实现DBSCAN之前,让我们先深入了解一下算法本身。如上所述,DBSCAN代表基于密度的噪声应用空间聚类,这对于一个相对简单的算法来说是一个相当复杂的名字。但我们可以将其分解,以便直观地理解它的工作原理。基于密度意味着它会聚焦到密度较大的区域,换句话说,就是大量样本紧密聚集在一起的地方。由于簇是密集的,因此这种对密度的关注是有益的。
空间聚类意味着它通过在特征空间中执行操作来进行聚类。换句话说,虽然一些聚类技术通过在点之间发送消息来工作,但DBSCAN在空间中进行距离测量,以确定哪些样本属于同一簇。聚类不言而喻,而“带噪声的应用”意味着该技术可以用于噪声数据集。我们接下来将看到为什么会这样,因为我们现在将查看DBSCAN的基本概念:核心点、直接可达点、可达点和离群点。
DBSCAN的概念
在我们开始探讨这些概念之前,必须先生成一个假设的数据集。这里是数据集。假设我们处理的是一个二维特征空间,其中的样本可以表示为点(即(X_1, X_2)坐标)。它可能看起来像这样:
在执行DBSCAN算法时,在运行算法之前必须提供两个参数。第一个是epsilon值,或称为ε。这个值表示某个点周围的距离,可以想象成以该点为中心、直径为ε的圆。需要注意的是,每个点的epsilon值都是相同的,但下面我们只画一个点的圆以示说明。
第二个是最小样本数。这个数表示在epsilon范围内(即圆内)应该包含的最小样本数(包括该点本身),以便将该点视为核心点。我们现在来看看这些参数是什么。
核心点
假设我们有一个ε值,并将最小点数设置为3。现在我们来查看数据集中的两个点。左边是较高的点,而右边是中间的一个点。
如果一个点p在距离ε范围内至少有minPts个点(包括p本身),则p是一个核心点。
换句话说,在我们的例子中,如果一个圆内至少有3个点(包括它自己),那么这个点就是一个核心点。显然,我们正在查看的两个点都是所谓的核心点。
核心点的伟大之处在于它们很可能是簇的一部分,因为它们位于其他点的附近。这就是为什么它们在DBSCAN算法中如此重要的原因。
如果数据集更大(例如,因为我们放大了某个特定区域),并且检查了另一个点,我们可能会得出结论,它不是核心点。下面的例子说明了原因:在该点的ε范围内(包括它自己)只有两个点。由于minPts = 3,且2 < 3,因此这不是一个核心点。
直接可达点
如果一个点不是核心点,我们必须确定它是否是直接可达的。
如果一个点q距离核心点p的距离在ε以内,则称点q是从p直接可达的。只有从核心点出发,才称点是直接可达的。
在上面的例子中,我们看到我们查看的额外点不是核心点。但它是否是直接可达的呢?
似乎是这样的:
这意味着它是直接可达的。
可达点
DBSCAN中的另一个概念是可达点:
如果存在一条路径p_1, …, p_n,其中p_1 = p且p_n = q,且每个p_{i+1}都是从p_i直接可达的,则称点q是从p可达的。注意,这意味着初始点和路径上的所有点都必须是核心点,q可能是个例外。
如果我们可以通过路径上的点(即路径上的核心点)直接可达的点画出一条到达某个点的路径,那么这些点就是从某个点可达的。在我们的例子中,B是从A可达的,我们仅展示了到达B的一条路径。
离群点
如果一个点不能从任何其他点到达,则称为离群点:
所有不能从任何其他点到达的点都是离群点或噪声点。
换句话说,如果我们不能从核心点画出一条到达另一个点的路径(即,如果它既不是直接可达的,也不是从特定点可达的),那么它就被认为是离群点。这正是DBSCAN在聚类与离群点检测方面如此出色的原因:它能够原生地标识离群点。
为epsilon选择合适的值
在DBSCAN中,选择ϵ的值至关重要,因为它直接影响生成的簇和噪声点。ϵ的选择决定了用于识别同一簇内点的邻域半径。
没有一种适用于所有情况的ϵ值,但有几种方法可以帮助你选择。以下是一种已被证明效果良好的方法/算法。在其他博客或相关主题的书籍中,你还可以找到其他几种方法。
k-距离图(肘部法)
选择ϵ的最广泛使用的方法之一是基于k-距离图。
选择minPts的值。这通常选择为数据的维度加一(即,对于2D数据,可以尝试4;对于3D数据,尝试5-6)。
对于数据集中的每个点,计算其到第k个最近邻的距离(其中k=minPts−1)。
将这些距离按升序排序并绘制出来。
在图中寻找肘部点。肘部表示距离开始迅速增加的点,这表明点正在从密集区域过渡到稀疏区域。肘部对应的距离值是ϵ的一个良好候选值。
为什么这种方法有效:属于密集区域的点与其邻居的距离相对较小,随着密度下降(即,移动到稀疏区域),邻居之间的距离增加。“肘部”是密度过渡发生的地方。
示例:如果k-距离图在0.5处显示一个明显的弯曲,那么ϵ=0.5可能是一个不错的选择。
如何将一切整合在一起:DBSCAN的伪代码
现在我们已经了解了DBSCAN的所有概念,即“是什么”,接下来我们可以深入探讨“如何做”。换句话说,是时候看看DBSCAN是如何工作的了。
通过逐个簇地搜索簇,我们可以缓慢但肯定地构建一个簇,并且不一定会得到太多实际上是同一簇一部分的簇指示。当然,这是我们可以通过设置ϵ和minPts来控制的,并且取决于数据集(首先需要进行你自己的探索性数据分析)。此外,将点标记为噪声意味着在聚类完成后,我们可以简单地显示和计数仍标记为噪声的点,并可能从数据集中删除它们。
如果对于某个ϵ,minPts的值=4,那么结果将是:许多核心点,一些不是核心点但直接从核心点可达因此是簇的一部分的点,以及一些不可达因此是离群点的点。换句话说,我们在这里有一个簇,包括绿色和红色点,其中两个蓝色点是离群点。
使用Scikit-learn执行DBSCAN聚类
好了,现在你应该对DBSCAN算法的工作原理以及如何用于聚类有了相当的了解。让我们通过将知识转化为代码,编写一个能够对一些数据进行聚类的脚本。为此,我们将使用Scikit-learn,因为它在其sklearn.cluster API中提供了DBSCAN,并且Python是当今机器学习工程的实际标准语言。
让我们打开一个文本编辑器,并创建一个名为dbscan.py的文件。
添加导入语句
首先,我们添加导入语句:
from sklearn.datasets import make_blobs
from sklearn.cluster import DBSCAN
import numpy as np
import matplotlib.pyplot as plt
生成数据集
为了生成数据集,我们将做两件事:指定一些配置选项,并在调用make_blobs时使用它们。请注意,我们还会指定epsilon和min_samples,它们稍后将用于聚类操作。
# Configuration options
num_samples_total = 1000
cluster_centers = [(3,3), (7,7)]
num_classes = len(cluster_centers)
epsilon = 1
.0
min_samples = 13
# Generate data
X, y = make_blobs(n_samples = num_samples_total, centers = cluster_centers, n_features = num_classes, center_box=(0, 1), cluster_std = 0.5)
这些簇看起来如下(在你的情况下,它们看起来会略有不同,因为它们是随机生成的)。
为了确保可重复性,建议在运行脚本后仅保存一次数据,方法是取消注释.save(...)行,这样你就可以始终从clusters.npy文件中加载相同的数据。不过,这并不是必须的。
np.save('./clusters.npy', X)
X = np.load('./clusters.npy')
初始化DBSCAN并计算簇
现在我们可以初始化DBSCAN并计算簇。
# Compute DBSCAN
db = DBSCAN(eps=epsilon, min_samples=min_samples).fit(X)
labels = db.labels_
在我们的例子中,由于我们选择了ε = 1.0和minPts = 13,打印出的簇数量和噪声样本数量为2个簇和0个噪声样本。在你的情况下,结果可能会不同。调整epsilon值(即增大圆的半径)或最小样本数(取决于簇的密度)将会产生不同的结果!
no_clusters = len(np.unique(labels) )
no_noise = np.sum(np.array(labels) == -1, axis=0)
print('Estimated no. of clusters: %d' % no_clusters)
print('Estimated no. of noise points: %d' % no_noise)
(结果:)
Estimated no. of clusters: 2
Estimated no. of noise points: 0
绘制聚类数据
最后,我们可以为训练数据生成散点图。由于我们有两个簇,因此使用一个简单的lambda函数来选择一种颜色或另一种颜色。如果你有多个簇,可以很容易地通过字典方法来泛化这个lambda函数。
# Generate scatter plot for training data
colors = list(map(lambda x: '#3b4cc0' if x == 1 else '#b40426', labels))
plt.scatter(X[:,0], X[:,1], c=colors, marker="o", picker=True)
plt.title('Two clusters with data')
plt.xlabel('Axis X[0]')
plt.ylabel('Axis X[1]')
plt.show()
最终结果确实如预期的那样是两个簇:
完整模型代码
以下是完整代码,供那些希望立即使用的人参考:
from sklearn.datasets import make_blobs
from sklearn.cluster import DBSCAN
import numpy as np
import matplotlib.pyplot as plt
# Configuration options
num_samples_total = 1000
cluster_centers = [(3,3), (7,7)]
num_classes = len(cluster_centers)
epsilon = 1.0
min_samples = 13
# Generate data
X, y = make_blobs(n_samples = num_samples_total, centers = cluster_centers, n_features = num_classes, center_box=(0, 1), cluster_std = 0.5)
np.save('./clusters.npy', X)
X = np.load('./clusters.npy')
# Compute DBSCAN
db = DBSCAN(eps=epsilon, min_samples=min_samples).fit(X)
labels = db.labels_
no_clusters = len(np.unique(labels) )
no_noise = np.sum(np.array(labels) == -1, axis=0)
print('Estimated no. of clusters: %d' % no_clusters)
print('Estimated no. of noise points: %d' % no_noise)
# Generate scatter plot for training data
colors = list(map(lambda x: '#3b4cc0' if x == 1 else '#b40426', labels))
plt.scatter(X[:,0], X[:,1], c=colors, marker="o", picker=True)
plt.title('Two clusters with data')
plt.xlabel('Axis X[0]')
plt.ylabel('Axis X[1]')
plt.show()
在聚类后从数据集中去除噪声
如果我们调整ε的值并将其设置为0.3,我们会得到不同的结果:
Estimated no. of clusters: 3
Estimated no. of noise points: 50
特别是,该算法现在能够检测到噪声样本,如下图所示。然而,在执行DBSCAN后去除噪声样本很容易——只需要额外的四行代码。这是因为DBSCAN将噪声样本的标签设置为-1;这是它“将标签标记为噪声”的方式。
在生成散点图之前添加这几行代码,可以看到被标记为噪声的样本会从数据集中移除。
因此,我们也可以将DBSCAN用作噪声去除算法,例如,在应用基于SVM的分类之前,以找到更好的决策边界。
# Remove the noise
range_max = len(X)
X = np.array([X[i] for i in range(0, range_max) if labels[i] != -1])
labels = np.array([labels[i] for i in range(0, range_max) if labels[i] != -1])
# Generate scatter plot for training data
colors = list(map(lambda x: '#000000' if x == -1 else '#b40426', labels))
plt.scatter(X[:,0], X[:,1], c=colors, marker="o", picker=True)
plt.title(f'Noise removed')
plt.xlabel('Axis X[0]')
plt.ylabel('Axis X[1]')
plt.show()
总结
在本文中,我们从多个角度探讨了DBSCAN聚类。首先,我们讨论了聚类分析或一般意义上的聚类:它是什么?它用于什么?
然后,我们介绍了DBSCAN,它代表基于密度的带有噪声的空间聚类应用,是一种广泛使用的聚类算法。我们先从算法和概念构建块入手。我们看到,如果一个点至少有minPts个点在小于ε的距离内,那么它就被称为核心点。这个圆内的所有点都是直接可达的。如果我们可以通过其他核心点从一个点构建一条路径到另一个非直接可达的点,那么这个点最终被认为是可达的。所有不可达的点都被认为是离群点或噪声。
算法本身其实很简单。从一个点开始,它尝试通过将其ε邻域(即该点的直接可达点)分组来构建一个簇。如果没有这样的点,则将其标记为噪声。如果有一些这样的点,则将这些点的直接可达点添加进来,依此类推,直到簇无法再扩展为止。然后,它选择另一个未访问的点并执行相同的步骤,直到所有点都被访问过。这样,我们就知道了簇和噪声点。
在了解了构建块和算法的概念工作原理后,我们继续使用Scikit-learn提供了DBSCAN的Python实现。我们看到,仅用几行Python代码,我们就能够生成一个数据集,对其应用DBSCAN聚类,可视化簇,甚至去除噪声点。后者使我们的数据集更加干净,而不会牺牲簇中的大部分核心信息。