【问题标题】:Find number of times a set is a subset in a list of sets查找集合是集合列表中子集的次数
【发布时间】:2021-03-14 04:58:18
【问题描述】:

我试图解决的问题是在事务数据中找到每个项集的支持。

例如,

transactions = [
    'b c d',
    'a g' ,
    'a c d e',
    'e f h',
    'a b c g h',
    'd' , 
    'a e g h',
    'b c d',
    'a b f g h',
    'a c d g',
]

将有[2, 5, 1, 1, 1, 5, 1, 2, 1, 1]

所以基本上对于第二个事务a, g,它是其他事务的子集,例如'a g''a b c g h''a e g h''a b f g h''a c d g',因此计数为 5。

现在,最初,我使用 mlxtend 事务编码器将此数据集转换为一种 One Hot Encoded 事务。并使用了类似的东西

df.progress_apply(lambda x: (df.iloc[:, np.where(x==1)[0]].sum(1)==len(np.where(x==1)[0])).sum(), axis=1)

获取值。

这个想法就像用当前行的元素切片矩阵/df,然后跨行求和。与当前行的元素长度相同的情况是子集,因此计算它。

但是,这对于较小的数据集效果很好,然后当我遇到 kosarak 时,由于 OOM 错误,我无法获得密集的表示。所以,我切换回 countVectorizer 并生成一个稀疏表示,然后使用与前一个类似的逻辑。

现在的问题是,scipy sparse 在对稀疏进行求和时比在运行时间为密集时慢 4 倍

164 ms ± 22.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

即使使用集合来解决问题也没有太大改善。

到目前为止,这是我的方法,我相信它具有 O(n2) 复杂性。有没有更好的算法/包来加快速度。

感谢任何帮助。提前致谢。

【问题讨论】:

  • 时间以示例为例?
  • 所以我查看了kosarak数据集,我有一个问题:每行中交易的顺序重要吗? (例如 3, 5 等价于 5, 3)。根据您对集合的使用,我会说“不”是答案,但我不能确定。
  • 还有一个问题——有些行确实重复了。对重复的方法应该是什么? (完全有可能忽略它们,我不确定是否值得进行额外的比较来缓存/删除结果。)
  • 您能尝试分而治之的方法吗?按长度排序,计算重复项,只检查更长的字符串,记住结果(我的意思是,如果l9l11 的子集,那么如果l5l9 的子集,那么它也是一个子集l11)。

标签: python numpy scipy set apriori


【解决方案1】:

由于 2**26 远低于 32 位整数的整数限制,您可以这样做:

digitize = lambda x: np.in1d(list(string.ascii_lowercase), x.split()) @ 2 ** np.arange(26)

digitize 将字母字符串转换为每组字母的唯一按位整数。由于数据是按位计算的,所以可以用位运算来比较。

trans = np.array([digitize(t) for t in transactions])

Out[]: array([ 14,  65,  29, 176, 199,   8, 209,  14, 227,  77], dtype=int32)

(np.bitwise_and.outer(tr, tr) == tr).sum(0)  #bitwise definition of subset, summed over entries

Out[]: array([2, 5, 1, 1, 1, 5, 1, 2, 1, 1])

您可以轻松地创建一列trans,然后应用按位函数来获得所需的输出。应该通过不存储那些大的 onehot 来减少内存使用。

【讨论】:

  • facepalm 问题是np.bitwise_and.outer(tr, tr) 最终会变得很大。好吧,无论如何我都会离开它。
【解决方案2】:

我的小尝试

如果您当前的方法是每个循环约 164 毫秒,则此方法可回到 *8 效率。不幸的是,我不能声称任何天才,我担心它仍然太慢。我只是预先构建了所有集合,然后以最直接的方式运行 issubset 作为 @solid.py 。预先构建集合和简单地使用 for 循环而不是函数调用的差异是 6 倍。

一组检查的当前时间是~22ms +-2ms 或类似的东西。我一直在直接在 kosarak 数据集上进行测试,所以我希望只有一个具有这样名称的数据集。

我尝试了几种“更聪明”的方法来消除不合理的选项,不幸的是,所有这些方法最终都比这个“愚蠢”和直接的方法慢。

可能真正有用的方法很少:

  • 按大小对集合进行排序,然后仅计算具有 >= 长度的匹配项。无论如何,长度检查是.issubset 中的第一个。
    由于前约 30 000 个集合是一个事务,另一个约 35000 集合由两个事务组成,这可能意味着删除约 30% 的计算。也许更多,因为可以缓存少数事务集以进一步改进。

  • 这会导致缓存结果 - 至少是较短的结果。创建 1:{2:{}} 结构相当便宜,它允许您重用结果。即使在未排序的值上使用它也会导致~1,5ms 左右性能的提高。它并不多,但排序可能会更多。当集合变大时,也可以切断这种缓存(因此缓存结果的概率变小)。
    通常有几个事务重复数百甚至数千次。这将有助于减少它们,进一步减少 O(n^2) 中的 n 不幸的是我没有任何东西可以单独降低复杂性。

  • 扩展缓存 - 预先对集合进行排序和计数也可用于将每个集合替换为一个元组(集合,计数)。这将完全消除缓存 ^ 的需要,并消除大部分不必要的计算。

     import csv
     import time
    
     reader = csv.reader(open('kosarak.csv'), delimiter=' ')
     dataLines = []
     for line in reader:
         dataLines.append(set(map(int, line)))
    
     results = []
     count = 0
     totalTime = 0
     for line1 in dataLines:
         r1 = 0
         t1 = time.time_ns()
         for line2 in dataLines:
             if line1.issubset(line2):
                 r1 += 1
    
         t2 = time.time_ns()
         results.append(r1)
         totalTime += (t2 - t1) / 1000000
         count += 1
         if (count % 100) == 0:
             print("$$$$$$$$$$$$$")
             print(totalTime)
             print(totalTime / count)
             print(count)
    

【讨论】:

    【解决方案3】:

    如果可能的话,python 的集合算术通常相当不错,并且不涉及任何复杂的二值化逻辑,这可以说是更难阅读/理解。

    只是一个建议:

    transactions = [
        'b c d',
        'a g' ,
        'a c d e',
        'e f h',
        'a b c g h',
        'd', 
        'a e g h',
        'b c d',
        'a b f g h',
        'a c d g',
    ]
    transactions = list(map(lambda x: x.replace(' ', ''), transactions))
    print(transactions) # ['bcd', 'ag', 'acde', 'efh', 'abcgh', 'd', 'aegh', 'bcd', 'abfgh', 'acdg']
    
    transactions_set = list(map(set, transactions))
    counts = [sum(set(elem).issubset(s) for s in transactions_set) for elem in transactions]
    print(counts) # [2, 5, 1, 1, 1, 5, 1, 2, 1, 1]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-16
      • 2010-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-16
      相关资源
      最近更新 更多