Scikit-learn:使用DBSCAN预测新数据点

49

我正在使用Scikit-Learn(Python 2.7)中的DBSCAN对一些数据进行聚类:

from sklearn.cluster import DBSCAN
dbscan = DBSCAN(random_state=0)
dbscan.fit(X)

然而,我发现除了 "fit_predict" 之外,没有内置的函数可以将新数据点 Y 分配到原始数据 X 中识别出的聚类中。K均值法有一个 "predict" 函数,但我希望能够像 DBSCAN 一样做同样的事情。就像这样:

dbscan.predict(X, Y)

我希望从X中推断密度,但返回值(聚类分配/标签)仅适用于Y。据我所知,R提供了这种能力,因此我认为Python也以某种方式提供此功能。我只是似乎找不到任何相关的文档。

此外,我尝试搜索了一些关于为什么不能使用DBSCAN对新数据进行标记的原因,但我没有找到任何合理的解释。

9个回答

35

虽然Anony-Mousse提出了一些好的观点(聚类确实不等同于分类),但我认为指派新的数据点也很有用。 *

根据关于DBSCAN的原始论文以及robertlaytons在github.com/scikit-learn上的想法,建议通过核心要点并将其指派给与您新数据点距离小于eps的第一个核心点所在的簇。 这样至少可以保证您的点是被分配的簇的边界点,根据聚类使用的定义来判断。(请注意,您的点可能被视为噪声而未被分配到任何簇中)

我已经快速实现了这个方案:

import numpy as np
import scipy as sp

def dbscan_predict(dbscan_model, X_new, metric=sp.spatial.distance.cosine):
    # Result is noise by default
    y_new = np.ones(shape=len(X_new), dtype=int)*-1 

    # Iterate all input samples for a label
    for j, x_new in enumerate(X_new):
        # Find a core sample closer than EPS
        for i, x_core in enumerate(dbscan_model.components_): 
            if metric(x_new, x_core) < dbscan_model.eps:
                # Assign label of x_core to x_new
                y_new[j] = dbscan_model.labels_[dbscan_model.core_sample_indices_[i]]
                break

    return y_new

聚类得到的标签(dbscan_model = DBSCAN(...).fit(X))和同一模型在同样数据上得到的标签(dbscan_predict(dbscan_model, X))有时会不同。我不确定这是某个地方的错误还是随机性结果。

编辑:我认为上述不同预测结果的问题可能源于边界点可以接近多个簇的可能性。如果您测试并找到答案,请更新。通过每次洗牌核心点或选择最近的而不是第一个核心点来解决歧义。

*) 现在的情况是:我想评估从我的数据子集中获得的聚类是否适用于其他子集或者仅仅是一个特例。如果它具有普遍性,则支持聚类及先前应用的预处理步骤的有效性。


2
聚合聚类能否预测新的数据点? - javac
可能是可以的,但我认为上述问题至少同样重要。在上述情况中,我利用了DBSCAN具有接近性的概念。如果我没记错,Aglo. Clustering没有这个功能,因此您必须引入一个新的功能,例如受K-NN启发的功能。我建议真正注意@anony-mousse的答案。 - kidmose
1
从sklearn的用户指南中:即使核心样本始终分配到相同的簇中,但这些簇的标签将取决于在数据中遇到这些样本的顺序。其次,更重要的是,非核心样本分配的簇可能会因数据顺序而异。 - sandyp

25

聚类不是分类。

聚类没有标签。如果您想将其塞入预测思维模式(这并不是最好的想法),那么本质上它是没有学习的预测。因为聚类没有可用于训练的带标签数据。它必须根据所见到的内容为数据编制新标签。但是您不能在单个实例上执行此操作,只能进行“批量预测”。

但是scipys DBSCAN有些问题:

random_state:numpy.RandomState,可选:

用于初始化中心点的发生器。默认为numpy.random。

DBSCAN不会“初始化中心点”,因为DBSCAN中没有中心点。

几乎唯一一个可以将新点分配给旧簇的聚类算法是k-means(及其许多变体)。因为它使用前一次迭代的聚类中心执行“1NN分类”,然后更新中心点。但大多数算法不像k-means那样工作,所以您无法复制这种方法。

如果要对新点进行分类,则最好在聚类结果上训练分类器。

可能R版本正在做的是使用1NN分类器进行预测;也许还使用额外的规则,如果它们的1NN距离大于epsilon,则将点分配给噪声标签,可能仅使用核心点。也可能没有。

获取DBSCAN论文,我记得它没有讨论“预测”。


17
Scikit-learn的k-means聚类有一个“预测”的方法:predict(X): 预测X中每个样本所属的最近簇。,这通常是在聚类上下文中使用“预测”的意图。 - Sid
2
@Sid 除了 k-means 算法之外,“closest” 才有意义,并且会与聚类标签保持一致。但是在 DBSCAN 中,这不会产生与 fit_predict 相同的标签,即它将不一致。 - Has QUIT--Anony-Mousse

7

这里是稍微不同且更高效的实现方式。而且,不是选择在eps半径内最优的第一个核心点,而是选择距离样本最近的核心点。

def dbscan_predict(model, X):

    nr_samples = X.shape[0]

    y_new = np.ones(shape=nr_samples, dtype=int) * -1

    for i in range(nr_samples):
        diff = model.components_ - X[i, :]  # NumPy broadcasting

        dist = np.linalg.norm(diff, axis=1)  # Euclidean distance

        shortest_dist_idx = np.argmin(dist)

        if dist[shortest_dist_idx] < model.eps:
            y_new[i] = model.labels_[model.core_sample_indices_[shortest_dist_idx]]

    return y_new

很棒的函数!唯一要改动的是,假设X是一个pandas.DataFrame: diff = model.components_ - list(X.iloc[i, :]) - Keity

4

这个问题已经有很好的回答了,HDBSCAN 或许是你需要尝试的。它提供了一个approximate_predict()方法,可能正符合你的需求。


1
添加一个详细的解释,说明这将如何解决问题。 - Arghya Sadhu

4
虽然它不是完全相同的算法,但你可以使用sklearn HDBSCAN对新点进行近似预测。请参见这里
它的工作方式如下:
clusterer = hdbscan.HDBSCAN(min_cluster_size=15, prediction_data=True).fit(data)
test_labels, strengths = hdbscan.approximate_predict(clusterer, test_points)

这可能会产生与DBSCAN完全不同的结果。我使用DBSCAN得到了一些非常好的结果,但是我无法标记新点——不幸的是,HDBSCAN没有产生相同质量的结果(或者至少我无法做到)。 - CutePoison

2

首先,让我们了解一些关于DBSCAN密度聚类的基础知识,下图总结了基本概念。

enter image description here 接下来,让我们创建一个2D样本数据集,用DBSCAN进行聚类。以下图展示了数据集的外观。

import numpy as np
import matplotlib.pylab as plt
from sklearn.cluster import DBSCAN

X_train = np.array([[60,36], [100,36], [100,70], [60,70],
    [140,55], [135,90], [180,65], [240,40],
    [160,140], [190,140], [220,130], [280,150], 
    [200,170], [185, 170]])
plt.scatter(X_train[:,0], X_train[:,1], s=200)
plt.show()

在这里输入图片描述

现在让我们使用scikit-learn的DBSCAN实现进行聚类:

eps = 45
min_samples = 4
db = DBSCAN(eps=eps, min_samples=min_samples).fit(X_train)
labels = db.labels_
labels
# [ 0,  0,  0,  0,  0,  0,  0, -1,  1,  1,  1, -1,  1,  1]
db.core_sample_indices_
# [ 1,  2,  4,  9, 12, 13]

从上面的结果可以看出:

  • 算法找到了6个核心点
  • 找到了2个簇(标签为0、1)和一些离群点(噪声点)。

让我们使用以下代码片段来可视化这些簇:

def dist(a, b):
    return np.sqrt(np.sum((a - b)**2))

colors = ['r', 'g', 'b', 'k']
for i in range(len(X_train)):
    plt.scatter(X_train[i,0], X_train[i,1], 
                s=300, color=colors[labels[i]], 
                marker=('*' if i in db.core_sample_indices_ else 'o'))
                                                            
    for j in range(i+1, len(X_train)):
        if dist(X_train[i], X_train[j])  < eps:
            plt.plot([X_train[i,0], X_train[j,0]], [X_train[i,1], X_train[j,1]], '-', color=colors[labels[i]])
            
plt.title('Clustering with DBSCAN', size=15)
plt.show()
  • 聚类0中的点被标记为红色。
  • 聚类1中的点被标记为绿色。
  • 异常值被标记为黑色。
  • 核心点用*标记。
  • 如果两个点在ϵ邻域内,则它们之间连接一条边。

enter image description here

最后,让我们实现predict()方法来预测新数据点的聚类。实现基于以下内容:

  • in order that the new point x belongs to a cluster, it must be directly density reachable from a core point in the cluster.

  • We shall compute the nearest core point to the cluster, if it's within ϵ distance from x, we shall return the label of the core point, otherwise the point x will be declared a noise point (outlier).

  • Notice that this differs from the training algorithm, since we no longer allow any more point to become a new core point (i.e., number of core points are fixed).

  • the next code snippet implements the predict() function based on the above idea

    def predict(db, x):
      dists = np.sqrt(np.sum((db.components_ - x)**2, axis=1))
      i = np.argmin(dists)
      return db.labels_[db.core_sample_indices_[i]] if dists[i] < db.eps else -1
    
    X_test = np.array([[100, 100], [160, 160], [60, 130]])
    for i in range(len(X_test)):
       print('test point: {}, predicted label: {}'.format(X_test[i], 
                                                   predict(db, X_test[i])))
    # test point: [100 100], predicted label: 0
    # test point: [160 160], predicted label: 1
    # test point: [ 60 130], predicted label: -1
    
下面的动画展示了如何使用上面定义的predict()函数标记一些新的测试点。

enter image description here


2

非参数聚类模型,如DBSCAN、谱聚类和层次聚类,不能直接预测新点的标签,因为它们是非参数的。这意味着我们无法获得一组参数,并根据这些参数预测新点的标签。

相比之下,K-means和高斯混合模型是参数聚类模型。

神经网络和索引聚类(CNNI)模型是另一个参数聚类模型。

要在分析DBSCAN后预测新点的标签,可以按照其他答案的建议,使用聚类结果训练监督模型,然后使用监督模型预测标签。或者只需使用KNN分类器。

最小-最大-跳跃距离提供了一种预测新点标签的替代方法。

https://doi.org/10.31219/osf.io/fbxrz

为了预测新数据点的标签,我们只需将该点的最小-最大-跳跃距离与每个聚类中心(One-SCOM)进行比较。这种机制类似于K-means。

以下是使用最小-最大-跳跃距离来预测三个玩具数据集中10,000个新数据点标签的示例。

预测新数据点的标签

下面是一个例子,说明在聚类不明显时,使用KNN和最小-最大-跳跃距离来预测新数据点标签的区别


1
K-means算法不进行预测,它只是尝试最佳地放置K个聚类。sklearn.cluster.KMeans.predict比较每个聚类的欧几里得距离和新实例,并用最接近的聚类标记它。
DBSCAN没有聚类中心,但每个聚类都有一个或多个“核心实例”。
因此,在使用DBSCAN后的一些预测选项是:
- 制作一个函数来选择与新实例距离最近的核心聚类,然后将该实例标记为该“最近核心实例”所属的聚类。 - 使用未标记的输入数据到DBSCAN模型+从sklearn.cluster.DBSCAN.fit.labels_获取的聚类标签,将其作为标签训练监督式机器学习模型,然后提供新实例以预测它们所属的聚类。
--另外--
为了避免混淆(因为我看到一些答案说了一些错误的事情),DBSCAN确实是非参数的,但这并不是sklearn实现没有predict函数的原因。
作为反例,k-最近邻是非参数的,并且能够进行预测。

-5

这个很合适。小心! - Daniel Böckenhoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接