Python:3 维空间中的 DBSCAN

Posted

技术标签:

【中文标题】Python:3 维空间中的 DBSCAN【英文标题】:Python: DBSCAN in 3 dimensional space 【发布时间】:2014-12-02 11:18:04 【问题描述】:

我一直在寻找用于 3 维点的 DBSCAN 实现,但运气不佳。有谁知道我的图书馆可以处理这个问题或有这样做的经验吗?我假设 DBSCAN 算法可以处理 3 个维度,通过使 e 值是半径度量和通过欧几里德分离测量的点之间的距离。如果有人尝试过实现这一点并愿意分享,我们将不胜感激,谢谢。

【问题讨论】:

你试过sklearn吗?它有DBSCAN,也可以做三个维度。 @Anony-Mousse 我有,但它不起作用。除非我做错了什么。我通过 dbscan.fit(X) 给它一个 3 维坐标列表,它给了我一个错误:预期维度大小 2 而不是 3。否则,我知道你可以提供一个距离矩阵,在这种情况下它没有太多对我来说很有价值,我可以自己写一个 DBSCAN 算法。 两个选项 1. 自己实现 DBSCAN,使用 R 树库,只需一天或两个任务 2. 使用奇异值分解 【参考方案1】:

为什么不使用 PCA 将数据展平为二维并使用仅二维的 DBSCAN?似乎比尝试自定义构建其他东西更容易。

【讨论】:

【参考方案2】:

在使用第一个答案中提供的代码一段时间后,我得出结论认为它存在重大问题: 1)噪声点可以出现在后面的集群中。 2)它抛出了额外的集群,这些集群是先前构建的集群的子集,因为考虑到已访问和未探索的点导致集群少于 min_points 的问题,以及 3)某些点可能会出现在两个集群中——它们可以从两个集群到达,并且在此代码中甚至可能是其中一个集群的核心点。官方的 DBSCAN 算法将任何作为核心点的点放置在集群中,它是核心的一部分,但将只能从两个集群中到达的点放在第一个集群中可以到达的点。这使得这些点的聚类取决于数据中点的顺序,但所有点在输出数据中只出现一次 - 无论是在单个聚类中还是作为噪声。某些应用程序希望将这些可从两个集群访问的共享点放置在两个集群中,但核心点应该只出现在一个集群中。

所以这就是我想出的。它计算两点之间的间隔距离两次,不使用任何树,但它会立即消除没有近邻的点,并创建一个核心点列表,因此在构建核心时只需要考虑这些点。它利用集合进行包含测试请注意,此实现确实将共享点放置在它们可以访问的所有集群中

 class DBSCAN(object):
    def __init__(self, eps=0, min_points=2):
        self.eps = eps
        self.min_points = min_points
        self.noise = []
        self.clusters = []
        self.dp = []
        self.near_neighbours = []
        self.wp = set()
        self.proto_cores = set()

    def cluster(self, points):
        c = 0
        self.dp = points
        self.near_neighbours = self.nn(points)
        while self.proto_cores:
            near_points = set(self.proto_cores)
            for p in near_points:
                c += 1
                core = self.add_core(self.near_neighbours[p])
                complete_cluster = self.expand_cluster(core)
                self.clusters.append(["Cluster: %d" % c, complete_cluster])
                self.proto_cores -= core
                break
        for pt in self.dp:
            flag = True
            for c in self.clusters:
                if pt in c[1]:
                    flag = False
            if flag:
                self.noise.append(pt)

    # set up dictionary of near neighbours,create working_point and proto_core sets
    def nn(self, points):
        self.wp = set()
        self.proto_cores = set()
        i = -1
        near_neighbours = 
        for p in points:
            i += 1
            j = -1
            neighbours = []
            for q in points:
                j += 1
                distance = (((q[0] - p[0]) ** 2 + (q[1] - p[1]) ** 2
                             + (q[2] - p[2]) ** 2) ** 0.5)
                if distance <= self.eps:
                    neighbours.append(j)
            near_neighbours[i] = neighbours
            if len(near_neighbours[i]) > 1:
                self.wp |= i
            if len(near_neighbours[i]) >= self.min_points:
                self.proto_cores |= i
        return near_neighbours

    # add cluster core points
    def add_core(self, neighbours):
        core_points = set(neighbours)
        visited = set()
        while neighbours:
            points = set(neighbours)
            neighbours = set()
            for p in points:
                visited |= p
                if len(self.near_neighbours[p]) >= self.min_points:
                    core_points |= set(self.near_neighbours[p])
                    neighbours |= set(self.near_neighbours[p])
            neighbours -= visited
        return core_points

    # expand cluster to reachable points and rebuild actual point values
    def expand_cluster(self, core):
        core_points = set(core)
        full_cluster = []
        for p in core_points:
            core |= set(self.near_neighbours[p])
        for point_number in core:
            full_cluster.append(self.dp[point_number])
        return full_cluster

【讨论】:

如何运行这个脚本?我应该在我的笔记本中运行这个类的什么示例代码?【参考方案3】:

您可以将 sklearn 用于 DBSCAN。这是一些对我有用的代码-

from sklearn.cluster import DBSCAN
import numpy as np
data = np.random.rand(500,3)

db = DBSCAN(eps=0.12, min_samples=1).fit(data)
labels = db.labels_
from collections import Counter
Counter(labels)

我得到的输出是-

Counter(1: 342, 10: 30, 31: 13, 13: 11, 30: 10, 24: 5, 29: 5, 2: 4, 18: 4,
19: 4, 28: 4, 49: 4, 3: 3, 17: 3, 23: 3, 32: 3, 7: 2, 9: 2, 12: 2, 14: 2, 15: 2,
16: 2, 20: 2, 21: 2, 26: 2, 39: 2, 41: 2, 46: 2, 0: 1, 4: 1, 5: 1, 6: 1, 8: 1, 11:
1, 22: 1, 25: 1, 27: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 40: 1, 42: 1,
43: 1, 44: 1, 45: 1, 47: 1, 48: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1)

因此,聚类识别了 55 个聚类,每个聚类中的点数计数如上所示。

【讨论】:

这个练习有什么意义? OP 要求提供 3 维数据。 @SergeyBushmanov 它是 3d 数据,它有 500 个样本,每个样本有 3 个坐标【参考方案4】:

所以这就是我想出的,我知道这不是最有效的实现,但它确实有效;例如区域查询,它是算法的主要时间消耗者,它不止一次地计算两点之间的距离,而不是仅仅存储它以供以后使用。

class DBSCAN(object):

def __init__(self, eps=0, min_points=2):
    self.eps = eps
    self.min_points = min_points
    self.visited = []
    self.noise = []
    self.clusters = []
    self.dp = []

def cluster(self, data_points):
    self.visited = []
    self.dp = data_points
    c = 0
    for point in data_points:
        if point not in self.visited:
            self.visited.append(point)
            neighbours = self.region_query(point)
            if len(neighbours) < self.min_points:
                self.noise.append(point)
            else:
                c += 1
                self.expand_cluster(c, neighbours)

def expand_cluster(self, cluster_number, p_neighbours):
    cluster = ("Cluster: %d" % cluster_number, [])
    self.clusters.append(cluster)
    new_points = p_neighbours
    while new_points:
        new_points = self.pool(cluster, new_points)

def region_query(self, p):
    result = []
    for d in self.dp:
        distance = (((d[0] - p[0])**2 + (d[1] - p[1])**2 + (d[2] - p[2])**2)**0.5)
        if distance <= self.eps:
            result.append(d)
    return result

def pool(self, cluster, p_neighbours):
    new_neighbours = []
    for n in p_neighbours:
        if n not in self.visited:
            self.visited.append(n)
            n_neighbours = self.region_query(n)
            if len(n_neighbours) >= self.min_points:
                new_neighbours = self.unexplored(p_neighbours, n_neighbours)
        for c in self.clusters:
            if n not in c[1] and n not in cluster[1]:
                cluster[1].append(n)
    return new_neighbours

@staticmethod
def unexplored(x, y):
    z = []
    for p in y:
        if p not in x:
            z.append(p)
    return z

【讨论】:

为什么要使用自定义代码,因为您可以简单地计算二维距离矩阵并在其上执行DBSCAN

以上是关于Python:3 维空间中的 DBSCAN的主要内容,如果未能解决你的问题,请参考以下文章

具有噪声的基于密度的空间聚类算法--DBSCAN

scikit-learn 中的 DBSCAN(仅使用指标)

常用聚类算法 DBSCAN算法

R语言实现密度聚类

DBSCAN聚类算法原理总结

DBSCAN聚类算法