【问题标题】:Iterate over `True` entries of Boolean Numpy array遍历布尔 Numpy 数组的“True”条目
【发布时间】:2022-01-25 02:29:08
【问题描述】:

我希望每个索引 i 都有一个循环,其中数组 X布尔)是 True

有没有比在np.nditer 中包装np.nonzero 更高效/pythonic 的方法如下?

for i in np.nditer(np.nonzero(X), flags=['zerosize_ok']):
    myfunction(Y[i],Z2[Z[i]])

这里的问题是它迭代了两次而不是一次,并且占用了内存(首先,np.nonzero 遍历 X 并将其存储到一个大数组中,然后 np.nditer 遍历该数组)。

是否有一个命令(可以说有点类似于np.nditer)用于直接有效地迭代布尔数组的True 条目,而不首先用np.nonzero 明确列出它们? (遍历所有条目并使用 if 语句检查每个条目的效率可能低于 Numpy 提供的某些迭代器(如果存在)。)

【问题讨论】:

  • 让你的代码变慢的主要是 CPython 函数调用和循环中对 Numpy 数组的许多直接访问。删除第二个可以提高一点性能,但函数调用仍然是一个巨大的瓶颈。如果不更改函数本身(使用简单的装饰器或完全重写函数),就无法删除函数调用。你能描述/分享这个函数的作用吗?
  • @JérômeRichard 如果函数变得过于复杂,那么删除函数调用可能是不可能的,对吧?我在问np.nditer(np.nonzero(X), flags=['zerosize_ok']) 是否可以用更高效/pythonic 的东西代替;在当前的形式中,它迭代两次而不是一次,并占用内存(首先 np.nonzero 迭代通过 X 并将其存储到一个大数组,然后 np.nditer 迭代通过该数组)。
  • 在不知道您的输入和自定义函数的情况下很难说,但缓慢不太可能来自两次迭代。 np.nonzero 即使对于非常大的数组也非常快。
  • @root 这里的双重迭代显然不是问题。与迭代(约快 17 倍)和函数调用(约 25 倍)相比,np.nonzero(X) 调用非常快。这是因为 Numpy 进行本地调用,而 CPython 解释器非常慢。如果你想要一个快速的代码,你需要删除热路径中所有使用的纯 Python 代码。由于 Python(和解释器)的设计方式,可迭代对象也很慢。至于函数,Numpy 中有np.vectorize 之类的东西,但效率不高(由于前面提到:Numpy 无法超越 CPython 规则)。
  • @root 一旦矢量化被分开,因为该函数被假定为不可矢量化(手动)。剩下的唯一选择是绕过 CPython,例如使用嵌入式 JIT/AOT 编译器或不使用 CPython(请参阅 PyPy,它是一个 JIT)。但是,这种嵌入式编译器工具需要对函数进行修饰和/或限制为某些操作。至于 PyPy(或其他解释器),它们也几乎没有限制,并且如果通常不是免费的,则使用另一个解释器。我认为没有其他解决方案。

标签: python numpy iterable


【解决方案1】:

人们投了反对票,因为循环遍历 numpy 数组的条目是一个很大的问题。我们使用 numpy 是因为它速度快,并且单独处理每个元素而不是一次对整个数组进行操作,因此您可以获得 python 级别的性能而不是 numpy/c 性能。

想要通过给一个包含真假值的数组来排除值是很常见的,这被称为掩码。你可以通过索引到真假数组来做到这一点。要在 numpy 中执行此操作,请使用索引。例如。你做np.array([1,2,3])[np.array([True,False,True])]。它会给你np.array([1, 3])

所以基本上尝试以你可以做的方式安排事情

myfunction(Y[mask],Z2[Z[maks]]).

有几种技术可以做到这一点。一种方法是仅使用 numpy 函数来创建 myfunction,另一种方法是使用装饰器,如 numba.vectorizenumba.guvectorizenumba.njit 等等。

【讨论】:

  • 你的回答是基于myfunction可以被向量化的假设;我的问题是关于它不能的一般情况。如果存在这样的迭代器,我正在寻找一个迭代器,它可以直接有效地遍历True 值的索引X
  • @root 每个函数都可以向量化。这只是你是否愿意投入时间和精力的问题。我的观点是,如果你不进行矢量化,你就不会从 numpy 中获得良好的性能。有时这很好。但那就别谈效率了。
  • @root 但我相信您正在寻找的可能是 np.where(mask)[0]。它为您提供掩码为真的索引。
  • 一些函数(Python 意义上的“函数”一词)修改全局变量(或者它们的输入,如果它们是可变的)(或者它们的输出用于它们未来的输入),所以流程或第二次调用的结果可能取决于第一次调用的结果。它们不能被矢量化,对吧?
  • np.where(X)[0] 与我使用的 np.nonzero(X) 相比有什么优势?
【解决方案2】:

numpy.vectorise 与布尔选择器一起用作索引怎么样?

np.vectorise 接受一个函数并返回该函数的向量化版本,该函数接受数组。然后,您可以使用选择器在数组或其子集上运行该函数。

在 numpy 中,您可以使用 index of an array 使用数字列表进行子选择。

numpy.where 函数返回与某个值或函数匹配的数组的索引。

综合起来:

import numpy as np
import random

random.seed(0) # For repeatability

def myfunction(y, z, z2):
    return y*z2[z]

Y = np.array(range(100))  # Array of any length
Z = np.array(range(len(Y)))  # Must be the same length as Y
Z2 = np.array(range(len(Y))) # Must be constrained to indexes of X, Y and Z
X = [random.choice([True, False]) for toss in range(len(Y))] # Throw some coins

# X is now an array of True/False
# print( "\n".join(f"{n}: {v}" for n, v in enumerate(X)))
print(np.where(X)) # Where gets the indexes of True items

# Vectorise our function
vfunc = np.vectorize(myfunction, excluded={2}) # We exclude {2} as it's the Z2 array

# Do the work
res = vfunc(Y[np.where(X)],Z[np.where(X)],Z2)

# Check our work
for outpos, inpos in enumerate(np.where(X)[0]):
    assert myfunction(Y[inpos], Z[inpos], Z2) == res[outpos], f"Mismatch at in={inpos}, out={outpos}"

# Print the results
print(res)

【讨论】:

  • 你的答案是基于myfunction可以被向量化的假设;我的问题是关于它不能的一般情况。如果存在这样的迭代器,我正在寻找一个迭代器,它可以直接有效地遍历True 值的索引X
  • 您是说您的示例在您的应用程序中不是真正的用例。我的示例表明您可以在没有 Python 迭代器的情况下使用您的示例,与纯 numpy 相比,这很慢。请提供更准确的用例。
  • 我的问题是是否有一些 bool_iter 函数(用于迭代布尔数组的 True 条目的索引)使得 for i in np.nditer(np.nonzero(X), flags=['zerosize_ok']) (两次迭代:一次通过 np.nonzero 和一次由np.nditer) 可以替换为for i in bool_iter(X)(一次迭代)。在您的解决方案中,仍然有几个迭代(索引到 YZ,然后迭代结果)。 (并且由于Y[np.where(X)] 之类的数组会增加内存使用量,这可能对内存/运行时权衡有利也可能不利)。
猜你喜欢
  • 2023-03-16
  • 2011-08-01
  • 2015-11-08
  • 1970-01-01
  • 2018-11-06
  • 1970-01-01
  • 2020-11-29
  • 2012-07-04
  • 2021-12-12
相关资源
最近更新 更多