【问题标题】:How to vectorize this operation如何向量化这个操作
【发布时间】:2020-01-10 19:35:09
【问题描述】:

假设我有两个列表(总是相同的长度):

l0 = [0, 4, 4, 4, 0, 0, 0, 8, 8, 0] 
l1 = [0, 1, 1, 1, 0, 0, 0, 8, 8, 8]

在逐元素比较这些列表时,我需要应用以下交集和联合规则:

# union and intersect
uni = [0]*len(l0)
intersec = [0]*len(l0)
for i in range(len(l0)):
    if l0[i] == l1[i]:
        uni[i] = l0[i]
        intersec[i] = l0[i]
    else:
        intersec[i] = 0  
        if l0[i] == 0:
            uni[i] = l1[i]
        elif l1[i] == 0:
            uni[i] = l0[i]
        else:
            uni[i] = [l0[i], l1[i]]

因此,所需的输出是:

uni: [0, [4, 1], [4, 1], [4, 1], 0, 0, 0, 8, 8, 8] 
intersec: [0, 0, 0, 0, 0, 0, 0, 8, 8, 0]

虽然这可行,但我需要使用数百个非常大的列表(每个列表包含数千个元素)来执行此操作,因此我正在寻找一种对其进行矢量化的方法。我尝试使用np.where 和各种屏蔽策略,但效果并不好。任何建议都将受到欢迎。

* 编辑 *

关于

uni: [0, [4, 1], [4, 1], [4, 1], 0, 0, 0, 8, 8, 8]

对

uni: [0, [4, 1], [4, 1], [4, 1], 0, 0, 0, 8, 8, [0, 8]]

在我的脑海中,我仍在与 8 与 [0, 8] 作斗争。列表源自系统注释中的 BIO 标记(请参阅IOB labeling of text chunks),其中每个列表元素是文档中的字符索引,而 vakue 是分配的枚举标签。 0 表示表示没有注释的标签(即,用于确定混淆矩阵中的否定);而非零元素表示为该字符分配的枚举标签。由于我忽略了真正的否定,我想我可以说 8 相当于 [0, 8]。至于这是否简化了事情,我还不确定。

* 编辑 2 *

我使用[0, 8] 来保持简单,并使intersection 和union 的定义与集合论保持一致。

【问题讨论】:

  • 矢量化交点很简单。np.where(a0==l1,a0,0) where a0 = np.array(l0).向量化你的uni 会很困难,因为输出不是一个有效的numpy 数组。它可能是,但它的dtype 将是object,从而抵消了库中的大多数矢量化收益
  • uni 中的列表和标量的混合很好地表明了完全“矢量化”的解决方案是不可能的。如果解决方案具有不同长度的列表(或数组),情况也是如此。
  • 你能定义nowhere fast吗?您的解决方案现在拥有数百个大型列表的速度有多快?
  • 呃,无处可逃的意思:我没办法!在速度方面没有任何意义。我有十字路口,但工会是症结所在。

标签: python numpy vectorization


【解决方案1】:

我不会将它们称为“交集”和“并集”,因为这些操作在集合上具有明确定义的含义,而您要执行的操作都不是。

但是,做你想做的事:

l0 = [0, 4, 4, 4, 0, 0, 0, 8, 8, 0]
l1 = [0, 1, 1, 1, 0, 0, 0, 8, 8, 8]

values = [
    (x
     if x == y else 0,
     0
     if x == y == 0
     else x if y == 0
     else y if x == 0
     else [x, y]) 
    for x, y in zip(l0, l1)
]

result_a, result_b = map(list, zip(*values))

print(result_a)
print(result_b)

这对于数千甚至数百万个元素来说已经绰绰有余了,因为操作非常基础。当然,如果我们谈论的是数十亿,你可能还是想看看 numpy。

【讨论】:

  • 并不是说解决方案对于元组而不是列表来说更简单一些,但是该示例要求列表。
  • 我接受了你的回答,因为它确实很简单,并且很好地概括了我的要求。我还没有做任何速度测试。
  • 不错!与我之前的代码块相比,为了修改我的“联合”和“交集”,我得到:old union: 10.6 µs ± 44.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)。 old intersection:10.9 µs ± 82 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)。 new union: 656 ns ± 9.45 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)。 new intersection: 653 ns ± 11.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  • @horcle_buzz 显然你没有打开最终的 map 对象
  • 这是怎么回事(在包含更多元素的列表上运行):17.1 ns ± 0.276 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)20.5 ns ± 0.456 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
【解决方案2】:

并集的半矢量化解决方案和交集的完全解决方案:

import numpy as np

l0 = np.array(l0)
l1 = np.array(l1)
intersec = np.zeros(l0.shape[0])
intersec_idx = np.where(l0==l1)
intersec[intersec_idx] = l0[intersec_idx]
intersec = intersec.astype(int).tolist()

union = np.zeros(l0.shape[0])
union_idx = np.where(l0==l1)
union[union_idx] = l0[union_idx]
no_union_idx = np.where(l0!=l1)
union = union.astype(int).tolist()
for idx in no_union_idx[0]:
    union[idx] = [l0[idx], l1[idx]]

和输出:

>>> intersection
[0, 0, 0, 0, 0, 0, 0, 8, 8, 0]
>>> union  
[0, [4, 1], [4, 1], [4, 1], 0, 0, 0, 8, 8, [0, 8]]

注意:我认为您最初的联合解决方案不正确。查看最后的输出 8 vs [0,8]

【讨论】:

  • 包含 OP 的代码没有害处,尤其是在您更正结果或计时时。如果OP提供了好的minimal reproducible example我经常把代码和数据复制到自己的session中,然后自己开发解决方案。这让我可以比较结果和时间。
  • 我仍在与8 和[0, 8] 作斗争。列表源自系统注释中的 BIO 标记,其中每个列表元素都是文档中的字符索引。 0 表示表示没有注释的标签(即,用于确定混淆矩阵中的否定);而非零元素表示为该字符分配的枚举标签。由于我忽略了真正的否定,我想我可以说8 等同于[0, 8]。至于这是否简化了事情,我还不确定。
  • 我喜欢这个答案,但在决定接受哪一个之前,我需要对真实数据进行一些基准测试。 CPU 时间似乎是最大的瓶颈。
  • 唯一让我紧张的是,数组使用了名称union,特别是因为有一个同名的方法。这就是我选择uni的原因。
猜你喜欢
  • 2021-09-02
  • 1970-01-01
  • 2013-12-28
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
相关资源
最近更新 更多