给定距离内的点分组算法

Posted

技术标签:

【中文标题】给定距离内的点分组算法【英文标题】:Algorithm for grouping points in given distance 【发布时间】:2018-06-07 01:51:05 【问题描述】:

我目前正在寻找一种高效算法,该算法从三维空间中获取一组点并将它们分组为类(可能由列表表示)。如果一个点与该类中的一个或多个其他点接近,则它应该属于一个类。如果它们共享任何点,则两个类是相同的。 因为我正在处理大型数据集,所以我不想使用递归方法。此外,我尽量避免使用诸如具有 O(n^2) 性能的距离矩阵之类的东西。

我尝试在网上检查一些算法,但其中大多数并不适用于这个特定目的(例如 k-d 树或其他集群算法)。我曾想过将空间分成更小的部分,但这(可能)会导致结果不准确。

我试图自己写一些东西,但结果证明是有缺陷的。我会在距离之后对我的点进行排序并将距离附加为第四个坐标,然后重复以下代码段:

def grouping_presorted(lst, distance):
    positions = [0]
    x = []
    while positions:
        curr_el = lst[ positions[-1] ]
        nn_i = HasNeighbor(lst, distance, positions[-1])

        if nn_i is None:
            x.append(lst.pop(positions[-1]) )
            positions.pop(-1)
        else:
            positions.append(nn_i)
    return x

def HasNeighbor(lst,distance,index):
    i =  index+1
    while lst[i][3]- lst[index][3] < distance:
        dist = (lst[i][0]-lst[index][0])**2 + (lst[i][1]-lst[index][1])**2 + (lst[i][2]-lst[index][2])**2
        if dist < distance:
            return i
        i+=1
    return None

除了(可能很容易修复)溢出错误之外,链接点的逻辑还有一个更大的缺陷。如果您认为我的观点描述了空间中的线,那么该算法仅适用于严格指向原点外部的线,而不适用于圆形或类似结构。

是否有人知道为此预先编写的代码或知道我可以尝试什么?

提前致谢。

编辑:似乎我的拼写以及某些术语的混淆引发了一些误解。我希望这个(糟糕的)草图有所帮助。在这个例子中,我将我的参考距离标记为 d,并用红色圈出了我不希望最终得到的两个容器。

【问题讨论】:

另外,为什么不使用 bog-standard k-means 进行两个调整,即质心必须与现有点位于相同的坐标,并且类的成员必须位于设定的最大距离内质心? 此外,如果我理解正确并且您想要保证最佳结果(例如,没有随机起始类),则没有有效的算法(除非 P = NP)。 @timgeb 我不明白你的意思,这个问题在数学上被明确定义为等价关系。如果两个点的距离小于 d,则它们共享相同的等价类。这意味着每个点都属于一个类。所以不,类不能重叠,因为那样它们的所有点都属于同一个类。请注意,如果这种关系成立,我并没有说它们将属于同一类。它们都可能接近第三个点,因此属于同一类。 @timgeb no.. 给定“当两个点的距离小于 1 时,它们应该属于同一类”,这意味着 A 和 B 属于同一类,B 和 C 属于同一个类,所以类是 A,B,C。编辑:我从来没有说过一个类只包含两点。 @timgeb 我尽力澄清所有不确定性。如果您对如何重新表述问题有任何建设性的建议,我很乐意这样做。即使我措辞错误,到目前为止,您所说的只是“这种方式定义不明确”。编辑:我重新考虑,你是对的,它不满足等价关系的定义。我会以不同的方式再试一次:我想将坐标点一起放入容器中。给定一个容器,如果它接近一个或多个当前元素,我想将一个元素放入其中。 【参考方案1】:

你可以试试https://en.wikipedia.org/wiki/OPTICS_algorithm。当您首先索引点时(例如,使用 R-Tree),这应该可以在 O(n log n) 中实现。

编辑:

如果您已经知道您的 epsilon 以及集群中最少有多少点 (minpoints),那么 DBSCAN 可能是更好的选择。

【讨论】:

使用 epsilon = d 和 N=1 这个算法似乎完全符合我的要求。我不知道这些参数是否可以实现运行时,但我会仔细研究一下。谢谢! 关于您的编辑:是的,通过您的 OPTICS 建议,我已经找到了 DBSCAN,并且我现在想到了另一种可能的解决方案。我将尝试实现所有并重新调整以进行比较。非常感谢! 很高兴我能提供帮助。关于运行时:在 O(n log n) 中可以使用 R-Tree 索引 3d 点现在您基本上想要为每个执行 epsilon 范围查询(如果 epsilon 不是太大,则在 O (log n) 中)观点。所以你应该摆脱 O(n log n)【参考方案2】:

我最终做了什么

在听取了您的 cmets 的所有建议、来自 cs.stackexchange 的帮助并进行了一些研究之后,我能够写下两种不同的方法来解决这个问题。如果有人可能感兴趣,我决定在这里分享。同样,问题是编写一个程序,接收一组坐标元组并将它们分组到集群中。如果存在元素序列 x=x_1,..,y=x_N 使得 d(x_i,x_i+1) ,则两个点 x,y 属于同一个簇


DBSCAN: 通过固定欧几里得度量,minPts = 2 和分组距离 epsilon = r。 scikit-learn 提供了这个算法的一个很好的实现。该任务的最小代码 sn-p 将是:

from sklearn.cluster import DBSCAN
from sklearn.datasets.samples_generator import make_blobs
import networkx as nx
import scipy.spatial as sp

def cluster(data, epsilon,N): #DBSCAN, euclidean distance
    db     = DBSCAN(eps=epsilon, min_samples=N).fit(data)
    labels = db.labels_ #labels of the found clusters
    n_clusters = len(set(labels)) - (1 if -1 in labels else 0) #number of clusters
    clusters   = [data[labels == i] for i in range(n_clusters)] #list of clusters
    return clusters, n_clusters

centers = [[1, 1,1], [-1, -1,1], [1, -1,1]]
X,_ = make_blobs(n_samples=N, centers=centers, cluster_std=0.4,
                            random_state=0)
cluster(X,epsilon,N)

在我的机器上,N=20000 对于这种具有 epsilon = 0.1 的 epsilon 的集群变化只需要 290ms,所以这看起来确实快给我。


图组件: 可以把这个问题想象成:坐标定义了一个图的节点,如果两个节点的距离小于epsilon/r,则两个节点是相邻的。然后给出一个集群作为该图的连接组件。起初我在实现这个图时遇到了问题,但是有很多方法可以编写线性时间算法来做到这一点。然而,对我来说,最简单和最快的方法是使用 scipy.spatial 的 cKDTree 数据结构和相应的 query_pairs() 方法,它返回给定距离内的点的索引元组列表。例如,可以这样写:

class IGraph:
    def __init__(self, nodelst=[], radius = 1):
        self.igraph = nx.Graph()
        self.radii  = radius
        self.nodelst = nodelst #nodelst is array of coordinate tuples, graph contains indices as nodes
        self.__make_edges__()

    def __make_edges__(self):
        self.igraph.add_edges_from( sp.cKDTree(self.nodelst).query_pairs(r=self.radii) )

    def get_conn_comp(self):
        ind = [list(x) for x in nx.connected_components(self.igraph) if len(x)>1]
        return [self.nodelst[indlist] for indlist in ind]


def graph_cluster(data, epsilon):
    graph = IGraph(nodelst = data, radius = epsilon)
    clusters = graph.get_conn_comp() 
    return clusters, len(clusters)

对于上面提到的同一个数据集,这个方法需要 420ms 来找到连接的组件。但是,对于较小的集群,例如N=700,这个sn-p跑得更快。它似乎在寻找更小的集群(即被赋予更小的 epsilon 值)方面也有优势,而在另一个方向上则有很大的劣势(当然,所有这些都在这个特定的数据集上)。我认为,根据给定的情况,这两种方法都值得考虑。

希望这对某人有用。

编辑: 从理论上讲,DBSCAN 在正确实现时具有 O(n log n) 的计算复杂度(根据***...),同时构建图以及查找其连接的组件以线性方式运行时间。不过,我不确定这些语句对于给定的实现有多好。

【讨论】:

【参考方案3】:

采用路径查找算法,例如 Dijkstra 或 A*,或者采用图的广度优先或深度优先搜索。从一组未访问点中的任何点开始,然后继续执行您选择的任何算法,但需要注意的是,一个点被认为只连接到其距离小于阈值的所有点。当你完成一个类时(即当你无法发现更多新节点时),从一组未访问的节点中选择任何一个节点并重复。

【讨论】:

感谢您的提示。但是建立图表,即检查“..仅连接到其距离小于阈值的所有点”。不需要我做N!这样的检查?这就是为什么我最初认为我可以通过预先排序列表来限制自己只检查子集(我可以在创建我的观点时这样做) @Banana 您的积分是如何存储的?将它们存储在图表中是否可行? @Banana 您说您想避免使用距离矩阵,但是您能否设置一个布尔值矩阵,该矩阵在您运行此算法之前预先计算,其中 true 值表示两个点具有距离不大于阈值(否则为假)?另外,您的点坐标是整数还是浮点数? 我在 for 循环中以 [x,y,z] 列表的形式从函数(对我来说是一个黑盒)中获取点。目前,我将它们插入到列表中,以便列表就地排序。将它们存储到图表中可能是可能的,但我不确定如何有效地进行此操作。 @Banana 我浏览了几篇关于算法的文章,但我看不出你怎么能比 O(n^2) 做得更好。我建议问cs.stackexchange.com

以上是关于给定距离内的点分组算法的主要内容,如果未能解决你的问题,请参考以下文章

从分类预测到聚类算法,有何不同?

基于度量/密度的聚类/分组

Python:如何将给定距离内的点组合在一起?

如何从单个表中对多行进行分组并获取给定范围内的所有记录

Java,Mysql-根据一个给定经纬度的点,进行附近500米地点查询–合理利用算法

给定数百万个点,找到位于线上或距线 0.2 毫米距离范围内的点 [关闭]