大集合图中常见子结构的引导挖掘

Posted

技术标签:

【中文标题】大集合图中常见子结构的引导挖掘【英文标题】:Guided mining of common substructures in large set of graphs 【发布时间】:2017-05-01 10:06:23 【问题描述】:

我有一大组 (>1000) 有向无环图,每个图都有一大组 (>1000) 顶点。顶点被标注,标注的基数很小(

我想识别(我的)在整组图表中频繁出现的子结构。

    子结构是至少两个具有特定标签的直接连接顶点的图。这样的子结构可以在一个或多个给定输入图中出现一次或多次。例如“一个[顶点标记为A,两个直接连接的子节点标记为B]在图U中出现两次,在图V中出现一次”。 我们正在寻找的子结构必须遵守一组预先给定的规则,这些规则过滤顶点的标签。例如:如果子图是一个包含标记为 A 的顶点的子结构,则该子结构是有趣的“一个标记为 A 的顶点具有至少一个标记为 B 的直接连接子节点,并且不是标记为 U 的顶点的直接连接兄弟或 V"。不符合这些规则的子结构可能会出现在输入图中,但对搜索不感兴趣。

我们正在寻找的输出是子结构列表及其在给定图中的(数量)外观

我试图调查一些事情并且(因为它似乎总是发生在我身上)问题是 NP 完全的。据我所知,gSpan 是解决这个问题的最常用算法。但是,如上所述,我不是在图中寻找 任何 常见的子结构,而只是那些遵守某些规则的子结构。应该可以使用它来减少搜索空间。

关于如何解决这个问题的任何见解?

更新:我可能应该补充一点,上述规则可以在一定程度上递归。例如,“一个标记为 A 的顶点具有至少两个标记为 B 的子节点,每个节点至少有一个标记为 A 的子节点”。最大递归深度在 1 到 10 之间。

更新二:指出我们不是在寻找已知或首选的子结构,而是挖掘它们。没有spoon针。

【问题讨论】:

可能的标签数量有限制吗? “频繁”子结构是否有特定的最小出现次数?此外,如果您希望针对任意一组预先给定的规则(似乎是这种情况)解决此问题,那么您不能真正依赖它来大幅减少搜索空间,至少是渐近的跨度> 标签少于30个; “频繁”的子结构至少出现两次。 一个顶点可以有多个边与标记相似的顶点。短 gSnap 参考:cs.ucsb.edu/~xyan/papers/gSpan-short.pdf 这是对子结构的唯一限制吗?示例子结构更加具体。示例中的 child 指的是直接子代吗? 我可以看到的一个问题是您的“过滤器”约束可能为空,在这种情况下您仍然需要解决 np-hard 问题。 【参考方案1】:

编辑:这是我要开始的:

    为相应节点构建一个包含 30x30 可能父/子组合的索引 与给定子结构的匹配项相交 手动检查更多条件

(原帖):

    找到一种方法为子结构构建哈希键 构建从子结构到相应节点的哈希映射 使用哈希图寻找候选,手动检查详细条件

【讨论】:

(1. 看起来,2. 占用大量内存。) @graybeard 例如,1. 可能只是“abb”的字符串哈希。但我忽略的是,您还需要插入“a”、“ab”和所有其他组合。因此,除非每个节点的边数受到限制,否则这可能会爆炸。 Re 2) 不过,包含大约一百万个条目的小倍数的哈希映射似乎是可以管理的?【参考方案2】:

他们以我阅读您的问题的方式,您可能想要下面的代码。 它在线性时间内找到 DAG 中的所有匹配子图。 它不支持过滤器,但是您可以在找到结果后检查结果,并手动过滤它们。 它还可能会发现某些部分折叠的图形。假设您正在寻找一棵树a((b|c)|(c|d)),那么它可能会找到一个子图,其中c 节点在两个子树之间共享。 同样,您可以检查输出并过滤掉这样的结果。 当然,只有在输出大小不太大的情况下才能进行这样的检查。为此,您必须对自己的图表进行一些实验。

from collections import namedtuple, defaultdict
Node = namedtuple('Node', ['label', 'children', 'id'])

# Simple tree patternA(B|AB)
pattern = Node('A', (Node('B', (), 1),
                     Node('A', (Node('B', (), 3),), 2)), 0)

# Generate random DAG
import random
labels = 'ABCDE'
dag = []
for _ in range(1000):
    label = random.choice(labels)
    children = tuple(random.sample(dag, min(len(dag)//2, 10)))
    dag.append(Node(label, children, len(dag)))

# Helper
def subtrees(pattern):
    yield pattern
    for sub in pattern.children:
        yield from subtrees(sub)

colors = defaultdict(list)
# Iterate the nodes in topologically sorted order
for node in dag:
    # Check if the node is the head of some sub-pattern
    for sub in subtrees(pattern):
        if node.label == sub.label \
                and all(any(sc.id in colors[nc.id]
                    for nc in node.children) for sc in sub.children):
            # If so, color the node with that sub-pattern's color
            colors[node.id].append(sub.id)

matches = [node for node in dag if pattern.id in colors[node.id]]
print('Found  matches.'.format(len(matches)))

我相信这与 Stefan Haustein 的想法非常相似。

【讨论】:

【参考方案3】:

我在我的回答中假设您正在尝试最小化运行时间,并且不想花费过多的时间编写代码来执行此操作。一开始我在学习编写高效算法时遇到的一件事情是,有时多次通过可能会更有效率。在这种情况下,我想说,从根本上说,您希望有两次通过:

首先,创建一个过滤器,允许您忽略大多数(如果不是全部)非重复模式。为此:

    分配两个位数组(并在执行此操作时考虑缓存大小)。第一个将是一个简单的布隆过滤器。第二个是重复的布隆过滤器。 在第一次遍历结构时,为每个可索引结构计算一个哈希值。在您的第一个布隆过滤器中选择适当的位并设置它。如果该位已设置,则还要在重复的布隆过滤器中设置相应的位。

在您的第二次通过时,您将需要执行实际确认匹配的“更重”过程。为了做到这一点:

    再次扫描图并记录与第一遍生成的重复布隆过滤器匹配的所有结构。 将匹配项放在哈希表中(理想情况下使用与计算的哈希不同的位) 当检测到重复信息时,将该信息存储在您想收集的任何地方。

该算法将在大型数据集上运行得非常快,因为它会显着降低适当缓存级别的压力。您还可以进行几项增强,以使其在不同情况下表现更好。

    为了提高多线程系统的性能,第一步并行化实际上是安全的。为此,请给每个线程(或集群中的计算机)一张图。每个人都应该计算自己的两个绽放的副本。然后这些花朵可以组合成最终的花朵。归约函数就是 (present, duplicate) = (present1 OR present2, duplicate1 OR duplicate2 OR (present1 AND present2))。这一步非常快。 并行化第二步也是完全安全的,但必须稍作修改。为此,您将从第一步中获取重复的布隆过滤器,并在第二步中将其用作过滤器,与之前相同。但是,您无法轻松完成最终比较。您必须改为将潜在的重复项放在哈希桶中。然后,在将每个数据分片写入其自己的潜在重复哈希表列表后,将数据按哈希桶划分,并在第三步中找到重复项。每个哈希桶(来自第二步中的任何输出)都必须由同一个工作人员处理。 如果您有大量要索引的结构,您可以通过递归应用上述算法来提高性能。调整是您使用上述算法的输出的每个匹配类别作为递归传递的输入。例如,如果您在算法的第一次运行中仅索引最多包含 5 个项目的结构,则可以在递归时获取每组重复的子图并仅在这些子图上运行算法。显然,只有在数据集非常大的情况下才需要这样做。 如果图表非常大,为了提高布隆过滤器的有效性,您可以考虑的另一个调整是迭代算法。例如,在第一遍中,您可能只考虑将具有第一个标签的子图作为子图的基础。这将减少您的布隆过滤器所需的大小和/或允许您在第一次通过时过滤掉更多的子图。

调整上述内容的几点注意事项:

    考虑缓存大小。例如,在 Intel Haswell 芯片上,每个内核在 L1 缓存中具有 32K,在 L2 缓存中具有 256K。每个缓存行将包含 512 位,因此如果填充 1% 的布隆过滤器,大部分缓存行都会被触及。根据算法其他部分的速度以及其他东西使用这些缓存的速度,您可以安全地创建一个布隆过滤器,该过滤器最多具有大约 512 * 1024 个条目(在超线程系统上,每个过滤器每位 8 个条目 = 128k,即是你得到多少 L2)并且仍然维护 L2 缓存中的大部分数据集和 L1 中真正活跃的东西。对于较小的数据集,请降低此数字,因为将其变大是没有意义的。如果您只在功能不少于 1% 的情况下将其标记为潜在重复,那完全没问题。 再一次,并行化仅在您拥有大量数据的情况下才真正有用。我假设你可能会。如果你做并行化,你应该考虑几何。在每台计算机上放置部分数据集将使用此算法。然后,您可以在每台计算机上运行每次迭代(在变体 #4 中)。如果您拥有庞大的数据集,可以避免将所有数据传输到所有计算机。

无论如何,总结一下运行时复杂性,我会说它确实取决于。许多人忽略了这样一个事实,即增加工作数据集将导致内存访问的成本并非全部相等。从根本上说,上述算法虽然性能很高,但如果调整得当,将在小数据集上运行得非常快,但它确实适用于更大的数据集,因为它允许以高效的方式将工作数据集保持在任何缓存级别是合适的(L1、L2、L3、RAM、本地磁盘、本地网络等)算法的复杂性将取决于数据,但我不相信可以创建更快的算法。我确实遗漏了您如何表示子图,并且有工作要做以实现最佳算法,但在不了解更多信息的情况下,我无法确定存储该信息的最佳方式。

算法不能比我介绍的算法运行得更快的原因是,第一遍运行所需的工作量要比第二遍少得多,因为它不需要分支,而且要做的工作量更少位运算。因此,我们可以说它对我们正在做的整体工作几乎没有任何帮助。第二阶段也尽可能高效。您必须(除非有一种方法可以用一组有限的位完美地描述每种可能性,我将在后面解释)实际比较每个图形特征并将信息写入某处。唯一的变量是检查您是否需要这样做的工作量。检查一下您可以将错误率任意调整为 0% 的位置就可以了。

对于较小的数据集,两次传递对您有利的原因是您可能在较小的内存量中拥有更多的绽放基数。但是对于非常小的数据集,您不妨只使用第二步而忽略第一步。但是,您至少需要为每个散列目标存储一个指针。这意味着您需要为相同级别的过滤写入 32 或 64 倍的数据。对于足够小的数据集,这无关紧要,因为读取是读取,写入是写入,但对于较大的数据集,这可以让您在保持给定级别的缓存中完成相同级别的过滤。在您必须跨多台计算机或多线程工作的情况下,此算法中提供的机制将更加高效,因为可以更快地组合数据并且可以交换更多关于潜在匹配的信息。

现在,最后,正如我所提到的,如果您在每次迭代中检查的功能数量进一步减少,您可能会稍微好一些。例如,如果您只检查 32 个可能的标签以及每次遍历中具有特定标签的子代数(并且限制为 1024),您可以用 15 位完美地表示这一点。如果将计数限制为 255,则可以使用 32K 完美地存储此信息。为了在您的情况下实现这一点,您需要使用我上面提到的迭代、递归和分片策略,然后您还需要跟踪源图和其他一些信息。老实说,我怀疑这是否能正常工作,除非在非常有限的情况下,但为了完整起见,我将其包括在内。

无论如何,这是我在 Stack Overflow 上的第一个答案,所以不要对我太苛刻。我希望这会有所帮助!

【讨论】:

我不知道有谁从一个规范的答案开始。你做得很好。【参考方案4】:

你的问题:

你有 - 一组图表和一组规则(我们称规则为子结构模式)。

您想要 - 图表集中每个子结构的出现次数。


由于图是 DAG,因此在子结构搜索中您不会陷入循环。

简单的解决伪代码是:

for each graph G                            //Sub-problem 4
    for each node N                         //Sub-problem 3
        for each substructure pattern P     //Sub-problem 2
            if N's structure is like P      //Sub-problem 1
                PatternCountMap.Get(G).Get(P)++;
            
        
    

在每个地方我都标出了需要处理的子问题。

如果你不了解 Map-Reduce,我的解决方案对你来说并不完全清楚。让我知道是否是这种情况。一般来说,Map-Reduce 代码总是可以以一般的编程方式运行,只是对于大数据需要更长的时间。


子问题 1

这个问题可以简单写成:

给定一个“根”节点并给定一个模式 P,以该节点为根表示的树是否遵循给定的模式?

这个问题是可以解决的。只需从“根”开始沿着图表向下移动,看看是否遵循模式。如果是,则增加其在PatternCountMap 中的计数,否则继续下一个模式,看看“根”是否遵循下一个模式。

PatternCountMap 是一个 HashMap>,它将 Graphs 映射到另一个 HashMap,后者将 Patterns 映射到它们的频率。因此,如果 P 在图 G1 和 G2 中分别出现 12 次和 17 次,则 PatternCountMap.Get(G1)。在算法运行结束时,Get(P) 将为 12,PatternCountMap.Get(G2).Get(P) 将为 17。

有用提示:由于您不想递归太深,请使用迭代解决方案。如果必须执行 DFS,请使用堆栈执行迭代 DFS。迭代 DFS 算法非常简单。


子问题 2

这里我们只是循环遍历每个模式(或规则)。这里没有魔法。对于每个规则,我们查看图 G 的节点 N 是否遵循规则。

有用提示:预处理规则。例如,如果遵循一个规则,看看其他哪些规则是绝对不能遵循的,可以跳过它们。或者,如果遵循一种模式意味着也可以遵循另一种模式,那么看看第二条规则是否可以缩小,因为作为第一条规则的一部分已经完成了检查。


子问题 3 & 4

这两个又是简单的循环,就像子问题 2。但是这里可以应用一个想法。这就是 Map-Reduce(虽然 [1]Map-Reduce 并非 100% 符合这些问题)。

您有来自众多不同图表的众多节点。只要你能识别出节点所属的图,如果某个特定的节点遵循某种模式,你可以发出<N_G, P>,这意味着图G中的节点N遵循模式(又名规则)P。

地图输出可以收集在可以用值填充PatternCountMap 的reducer 中。其中大部分由 Map-Reduce 框架本身处理,因此很多事情都会自动为您处理。


创建PatternCountMap 后,您可以计算每个图表中每个有用模式的计数这就是您想要的。


[1]Map-Reduce 适用于可以在商品硬件上解决的问题。如果您正在挖掘的规则很复杂,那么商品硬件可能不是您想要在其上运行算法的硬件。

【讨论】:

以上是关于大集合图中常见子结构的引导挖掘的主要内容,如果未能解决你的问题,请参考以下文章

Java-大集合拆分为指定大小的小集合

Mongo突然忽略大集合中的索引

虚拟机无法上网/连接失败常见原因大集合

笔试题大集合

Java大集合求交集的方法比较

数据结构与算法-大集合