【问题标题】:scikit-learn: Predicting new points with DBSCANscikit-learn:使用 DBSCAN 预测新点
【发布时间】:2015-03-05 13:00:43
【问题描述】:

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

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

但是,我发现没有内置函数(除了“fit_predict”)可以将新数据点 Y 分配给原始数据 X 中标识的集群。K-means 方法具有“预测”功能,但我希望能够对 DBSCAN 做同样的事情。像这样的:

dbscan.predict(X, Y)

因此可以从 X 推断出密度,但返回值(集群分配/标签)仅适用于 Y。据我所知,此功能在 R 中可用,因此我假设它在 Python 中也以某种方式可用.我似乎找不到任何文档。

另外,我尝试寻找 DBSCAN 不能用于标记新数据的原因,但我没有找到任何理由。

【问题讨论】:

    标签: machine-learning scikit-learn cluster-analysis data-mining dbscan


    【解决方案1】:

    虽然 Anony-Mousse 有一些优点(聚类确实不是分类),但我认为分配新点的能力是有用的。 *

    基于DBSCAN 上的原始论文和github.com/scikit-learn 上的robertlayton 的想法,我建议遍历核心点并将您的新点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))有时会有所不同。我不太确定这是某个地方的错误还是随机性的结果。

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

    *) 手头的案例:我想评估从我的数据子集获得的集群是否对其他子集有意义,或者只是一种特殊情况。 如果它是泛化的,则它支持集群的有效性和应用的早期预处理步骤。

    【讨论】:

    • 是否可以通过凝聚聚类预测新数据点?
    • 可能是的,但我认为上述担忧至少是相关的。在上面的例子中,我利用了 DBSCAN 有一个接近的概念。 IIRC Aglo。集群没有,所以你必须引入一个新的,例如受 K-NN 启发的一个。我建议真的关注@anony-mousse 的回答。
    • 来自 sklearn 的用户指南:even though the core samples will always be assigned to the same clusters, the labels of those clusters will depend on the order in which those samples are encountered in the data. Second and more importantly, the clusters to which non-core samples are assigned can differ depending on the data order.
    【解决方案2】:

    聚类不是分类。

    聚类未标记。如果您想将其挤入预测思维模式(这不是最好的想法),那么它本质上是无需学习即可预测。因为没有可用于聚类的标记训练数据。它必须根据所看到的内容为数据制作新的标签。但是你不能在单个实例上这样做,你只能“批量预测”。

    但是 scipys DBSCAN 有问题:

    random_state:numpy.RandomState,可选:

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

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

    only 可以将新点分配给旧集群的集群算法几乎是 k-means(及其许多变体)。因为它使用先前的迭代聚类中心执行“1NN 分类”,然后更新中心。但是大多数算法不像 k-means 那样工作,所以你不能复制它。

    如果要对新点进行分类,最好根据聚类结果训练分类器。

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

    获取 DBSCAN 论文,它没有讨论“预测”IIRC。

    【讨论】:

    • Scikit-learn 的 k-means 聚类有一种“预测”方法:predict(X): Predict the closest cluster each sample in X belongs to.,这通常是人们打算在聚类上下文中对“预测”进行的操作。
    • @Sid 除了 only for k-means "closest" 有任何意义,并且将与集群标签一致。使用 DBSCAN,这不会产生与fit_predict 相同的标签,即不一致。
    【解决方案3】:

    这里有一个稍微不同且更高效的实现。另外,不是取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
    

    【讨论】:

      【解决方案4】:

      虽然它不是完全相同的算法,但您可以使用 sklearn HDBSCAN 对新点进行近似预测。见here

      它是这样工作的:

      clusterer = hdbscan.HDBSCAN(min_cluster_size=15, prediction_data=True).fit(data)
      test_labels, strengths = hdbscan.approximate_predict(clusterer, test_points)
      

      【讨论】:

        【解决方案5】:

        此处已针对此问题发布了很好的答案。我的建议是试试HDBSCAN。它提供了一个approximate_predict() 方法,这可能是您需要的。

        【讨论】:

        • 添加详细说明如何解决问题
        【解决方案6】:

        我们先尝试了解一下DBSCAN基于密度的聚类的几个基本知识,下图总结了基本概念。

        让我们首先创建一个将使用 DBSCAN 进行聚类的示例 2D 数据集。下图显示了数据集的外观。

        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)和几个异常值(噪声点)。

        让我们使用以下代码 sn-p 可视化集群:

        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 中的点为绿色
        • 异常点为黑色
        • 核心点标有“*”。
        • 如果两个点在 ϵ-nbd 范围内,则它们由一条边连接。

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

        • 为了使新点x属于一个簇,它必须是从簇中的一个核心点直接密度可达的。

        • 我们将计算离集群最近的核心点,如果它在距离x的ε距离内,我们将返回核心点的标签,否则返回点x将被声明为噪声点(异常值)。

        • 请注意,这与训练算法不同,因为我们不再允许任何点成为新的核心点(即核心点的数量是固定的)。

        • 接下来的代码sn -p根据上面的思路实现predict()函数

          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() 函数标记几个新的测试点。

        【讨论】:

          【解决方案7】:
          DBSCAN.fit_predict(X, y=None, sample_weight=None)
          

          https://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html阅读更多信息

          【讨论】:

            猜你喜欢
            • 2013-04-29
            • 2021-11-02
            • 2016-01-01
            • 2015-02-19
            • 2023-03-09
            • 2015-12-19
            • 2018-01-09
            • 2015-03-11
            • 2014-09-11
            相关资源
            最近更新 更多