【问题标题】:Finding Intersection Between a Matrix and a Vector, by Row按行查找矩阵和向量之间的交集
【发布时间】:2019-03-15 04:51:23
【问题描述】:

考虑以下几点:

tmp1 = ['a', 'b', 'c', 'd', 'e']
tmp2 = ['f', 'g', 'h', 'b', 'd']
tmp3 = ['b', 'i', 'j', 'k', 'l']
matr = np.array([tmp1, tmp2, tmp3])

matr

产生一个矩阵:

array([['a', 'b', 'c', 'd', 'e'],
   ['f', 'g', 'h', 'b', 'd'],
   ['b', 'i', 'j', 'k', 'l']], 
  dtype='|S1')

现在,我想知道与向量相交的每一行中的值的总和。说吧,

vec = ['a', 'c', 'f', 'b']
[sum([y in vec for y in row]) for row in matr]

返回,

[3, 2, 1]

这是所需的输出。问题是我的“矩阵”实际上是 ≈ 1000000 x 2200,我有 6700 个向量可以比较。我在这里的解决方案太慢了,无法尝试。

如何改进我的工作?

值得注意的是,matr 内部的值来自一组约 30000 个值,而我拥有完整的一组。我已经考虑了解决方案,我对每个向量制作这 30000 个值的字典,并使用字典在整个矩阵中转换为真/假,然后按行求和。我不确定这是否会有所帮助。

【问题讨论】:

  • 在实际用例中所有元素都是单个字符吗?
  • 我这样描述的错。绝对不会接近 12-13 个字符。
  • 6700 vectors 的长度是否相同?这些向量的典型长度是多少?
  • 不幸的是他们不是。有些小到 50 左右,但都在 10 到 1000 之间。

标签: python numpy


【解决方案1】:

对于matrvec 作为数组,这里有一个np.searchsorted -

def count_in_rowwise(matr,vec):
    sidx = vec.argsort()
    idx = np.searchsorted(vec,matr,sorter=sidx)
    idx[idx==len(vec)] = 0
    return (vec[sidx[idx]] == matr).sum(1)

使用相对较小的vec,我们可以对其进行预排序并使用,为我们提供一个计算行数的替代方法,就像这样 -

def count_in_rowwise_v2(matr,vec,assume_sorted=False):
    if assume_sorted==1:
        sorted_vec = vec
    else:
        sorted_vec = np.sort(vec)
    idx = np.searchsorted(sorted_vec,matr)
    idx[idx==len(sorted_vec)] = 0
    return (sorted_vec[idx] == matr).sum(1)

上述解决方案适用于通用输入(数字或字符串等)。为了解决我们特定的字符串情况,我们可以通过使用 np.unique 将字符串转换为数字然后重新使用 count_in_rowwise/count_in_rowwise_v2 来进一步优化它,这将给我们提供第二种方法,就像这样 -

u,ids = np.unique(matr, return_inverse=True)
out = count_in_rowwise(ids.reshape(matr.shape),ids[np.searchsorted(u,vec)])

【讨论】:

  • 虽然这看起来很有趣,但我怀疑它是否会更快...我明天会进行基准测试,然后肯定会回复您。
  • @JohnRouhana 有什么特别的理由持怀疑态度吗?并且比原来的 loopy 更快?
  • 这似乎是更多的步骤,所以我很惊讶它更快。我是一个生物信息学的人,而不是纯粹的计算机科学,所以我并不真正理解“为什么”它比其他建议的方法快得多。但它是。我针对 50 个向量在 1000x2200 矩阵上运行基准测试。从我的方法到你的方法,平均时间从 ~4.9s/vector 下降到 ~0.088s/vector。这大约是 55 倍的加速 - 太棒了。
  • @JohnRouhana 很高兴得到反馈!好吧,我指望searchsorted,因为它非常高效,只是需要更多的工作来满足它的排序要求。此外,如果您对这 6700 个向量使用相同的 matr,我建议您使用最后的建议:np.unique(.. 预先计算 u,ids 一次并在那些 6700 @ 上重新使用它987654335@'s,只为每个新的vec 使用count_in_rowwise(ids.reshape(matr.shape),ids[np.searchsorted(u,vec)])
  • 是的——这正是我最终要做的。运行 1,000,000x2200 矩阵似乎仍然不方便;根据我的预测,对所有向量执行此操作仍需要大约 7 天。话虽如此,您的解决方案已将 100,000x2200 缩短到大约 18 小时,这是完全可以接受的。在大多数情况下,我不需要运行整整一百万。
【解决方案2】:

您可以使用设置交集来加快速度。这是一个比较:

您目前的列表推导解决方案:

%%timeit
print([sum([y in vec for y in row]) for row in matr])
#Output
[3,2,1]
20 µs ± 1.9 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

在列表理解中设置交集的建议解决方案:

%%timeit
print([len(set(row).intersection(vec)) for row in matr])
#Output:
[3,2,1]
17.8 µs ± 1.46 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

如果vec也是一个集合,我们会得到更好的效率:

%%timeit
vec = {'a', 'c', 'f', 'b'}
print([len(set(row).intersection(vec)) for row in matr])
#Output:
[3, 2, 1]
16.6 µs ± 1.99 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

【讨论】:

  • 如果矩阵和向量首先由集合组成,则此解决方案的效率会显着高于其他解决方案。
  • 我可以将矩阵和向量调整为集合而不是列表;每行的所有值都是唯一的,并且向量具有唯一的值。这是有希望的。明天我将用更大的矩阵和向量进行基准测试,看看这是否成立。
  • 我使用此方法对我的原始方法使用更大的 1000x2200 矩阵和 50 个向量进行基准测试。在实践中,我没有看到拥有列表 vec 和设置 vec 之间的实质性区别。我通常看到每个向量的差异为 0.0001 秒,而设置的 vec 始终更快。这种方法比我所拥有的方法有了很大的改进;我观察到上述矩阵大小的速度提高了 20-25 倍。不过,其他一些方法的基准测试速度更快。
  • 您也可以考虑在混合中添加线程或多处理,并对所有方法执行基准测试。这应该会给你最好的结果。
【解决方案3】:

这是一个简单易读的解决方案,np.isin() (docs):

np.sum(np.isin(matr, vec), axis=1)

作为奖励,如果您想获得矩阵的哪些元素在向量中,您可以只使用 np.isin() 而无需求和:

>>> np.isin(matr, vec)
array([[ True,  True,  True, False, False],
       [ True, False, False,  True, False],
       [ True, False, False, False, False]])

这说明了为什么沿行求和会产生所需的输出。

【讨论】:

  • 这将是我尝试过的,但事实证明系统管理员正在运行 Numpy 1.11。这是在 1.13 中出现的。我明天要给他发个错误,让我们更新,然后我会用其他解决方案进行基准测试,看看我们是怎么做的。
  • @JohnRouhana 啊,嘘!如果您确实受限于不超过特定 numpy 版本的解决方案,那么我建议编辑 OP 并添加具有该约束的标签(供未来的读者使用)。
  • 我绝对听到了。不过,我很高兴这个潜在的解决方案就在这里,我正在努力获取 numpy 的更新,以便我可以看到这个方法如何与其他方法相匹配。如果我无法获得这方面的基准,我将更新 OP。
  • 我得到了这个工作;根据我的基准,这是建议的第二好的方法。
【解决方案4】:

让我们看看您当前算法的速度。根据 python wiki,检查一个项目是否在像 y in vec 这样的数组中是 O(n),这意味着最坏的情况,它必须遍历 vec 中的每个元素。由于您正在检查矩阵的每个元素,因此您的操作总数为numRows * numCols * vecLen,即O(n^3)

一种更快的方法是为vec 构造一个字典以优化查找,因为字典是O(1) 而不是O(n),这意味着无论 vec 有多长,它们都可以执行您的签入操作:

vecDict = dict([(x, 1) for x in vec])

因此,您的新时间复杂度为 (numRows * numCols) + vecLen,即 O(n^2),我认为这是您获取数据的最快速度。

[sum([y in vecDict for y in row]) for row in matr]

【讨论】:

  • 绝对有效,这是我在看到其他解决方案之前正在考虑的解决方案。虽然这与我最初的方法相比有了很大的改进(与我的方法相比,在使用 1000x2200 矩阵时,我观察到了 10-15 倍的加速),但 Divakar 和 amanb 的方法似乎要快得多。
  • 嘿,如果它更快,那就去吧。我不喜欢 python 的 numpy 库。可能是即使他们在做更多的操作,他们是在本地做的,所以它可能会更优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-22
相关资源
最近更新 更多