Numpy Argwhere 的低效率

use*_*410 5 python numpy

我正在学习有关类似的方法argwhere,并nonzero在NumPy的。numpy.nonzero(x)该函数似乎返回一个一维ndarray对象元组,因此该函数的输出可用于索引。

我还没有准备好C源代码,nonzero因为我不知道如何找到它。但是,我想该nonzero函数将构造一个mbyndim ndarray对象(对于某些m取决于输入的对象),该对象将保存a. 为了验证这个猜测是正确的,我尝试了:

import numpy as np
from numpy.random import Generator, PCG64
rg = Generator(PCG64())
x = rg.integers(0,2,(10000,10000))
y = np.nonzero(x)
print(y[0].base is y[1].base)
z = y[0].base
print(type(z),z.shape)
print(np.array_equal(z[:,0].reshape(-1),y[0]))
print(np.array_equal(z[:,1].reshape(-1),y[1]))
Run Code Online (Sandbox Code Playgroud)

输出:

True
<class 'numpy.ndarray'> (50005149, 2)
True
True
Run Code Online (Sandbox Code Playgroud)

我对上述内容的解释是该nonzero函数确实构造了一个mndim大小排列的数组。

还有一个np.argwhere(x)功能。与 不同np.nonzero,它将返回一个mndim大小排列的数组,而不是一个元组。Reason 表明argwherenonzero几乎相同,除了nonzero以略有不同的格式返回输出。令我惊讶的是,argwhere似乎实现如下(根据:https : //numpy.org/doc/stable/reference/generated/numpy.argwhere.html):

True
<class 'numpy.ndarray'> (50005149, 2)
True
True
Run Code Online (Sandbox Code Playgroud)

因为该nonzero函数返回一个元组,所以似乎transpose操作 inargwhere将无缘无故地需要一个额外的副本。一个计时器的快速实验表明这个副本确实发生了。

问题有人 可以解释为什么以argwhere这种方式实现该功能吗?例如,替代方案

# nonzero does not behave well on 0d, so promote to 1d
    if np.ndim(a) == 0:
        a = shape_base.atleast_1d(a)
        # then remove the added dimension
        return argwhere(a)[:,:0]
    return transpose(nonzero(a))
Run Code Online (Sandbox Code Playgroud)

似乎已经更好了,因为:

x = rg.integers(0,2,(10000,10000))
t_0 = time.perf_counter()
y = np.argwhere(x)
t_1 = time.perf_counter()
z = faster_argwhere(x)
t_2 = time.perf_counter()
print('elapsed time for argwhere:' + str(t_1-t_0) + ", and for other method:" + str(t_2-t_1))
print(np.array_equal(y,z))
Run Code Online (Sandbox Code Playgroud)

产量:

elapsed time for argwhere:2.175326200000086, and for other method:1.7338391999999203
True
Run Code Online (Sandbox Code Playgroud)

hpa*_*ulj 2

前段时间我看的时候nonzero,发现它执行了两次传递。第一个np.count_nonzero(在 c-api 版本中)确定返回大小,第二个用于实际获取索引。我没有注意它是分配n独立数组还是只分配一个 2d 。但现在你提到了,数组是常见二维数组的视图:

\n
In [464]: x = np.arange(12).reshape(3,4)  \nIn [468]: idx = np.nonzero(x%3==0)                                                                   \nIn [469]: idx                                                                                        \nOut[469]: (array([0, 0, 1, 2]), array([0, 3, 2, 1]))\nIn [470]: idx[0].__array_interface__                                                                 \nOut[470]: \n{'data': (54070800, False),\n 'strides': (16,),\n  ...}\nIn [471]: idx[1].__array_interface__                                                                 \nOut[471]: \n{'data': (54070808, False),\n 'strides': (16,),\n ....}\n
Run Code Online (Sandbox Code Playgroud)\n

数据指针8更大,并且 strides16与此一致。

\n

对于 3d 数组:

\n
In [472]: x = np.arange(24).reshape(2,3,4)                                                           \nIn [473]: idx = np.nonzero(x%3==0)                                                                   \nIn [474]: idx[0].__array_interface__                                                                 \nOut[474]: \n{'data': (54163904, False),\n 'strides': (24,),\nIn [475]: idx[1].__array_interface__                                                                 \nOut[475]: \n{'data': (54163912, False),\n 'strides': (24,),\nIn [476]: idx[2].__array_interface__                                                                 \nOut[476]: \n{'data': (54163920, False),\n 'strides': (24,),\n
Run Code Online (Sandbox Code Playgroud)\n

同样的事情 - 24 步等。

\n

我不知道这些功能的历史。总是nonzero返回常见二维数组的视图?最近的版本试图阻止我们np.where使用np.nonzero. 我们还没有np.arg_nonzero:)

\n

正如您所注意到的,nonzero元组作为索引效果很好。

\n
x[np.nonzero(x)]\n
Run Code Online (Sandbox Code Playgroud)\n

新手通常不理解这一点,而是认为他们需要一个元组列表。但要使用它们,他们必须迭代

\n
 for tup in [(0,0),(1,0),...]: print(x[tup])\n
Run Code Online (Sandbox Code Playgroud)\n

argwhere不太合适:

\n
In [480]: x[idx]                                                                                     \nOut[480]: array([ 0,  3,  6,  9, 12, 15, 18, 21])\nIn [481]: np.transpose(idx)                                                                          \nOut[481]: \narray([[0, 0, 0],\n       [0, 0, 3],\n        ....\n       [1, 2, 1]])\nIn [482]: [x[tuple(i)] for i in np.transpose(idx)]                                                   \nOut[482]: [0, 3, 6, 9, 12, 15, 18, 21]\n
Run Code Online (Sandbox Code Playgroud)\n

我不知道如果没有tuple早期版本中的 ,这是否有效;现在它会引发错误。

\n

所以,是的,通过直接访问基地可能会提高argwhere效率nonzero。但有什么好处呢?

\n
In [487]: idx[0].base                                                                                \nOut[487]: \narray([[0, 0, 0],\n       [0, 0, 3],\n         ...\n       [1, 2, 1]])\n\nIn [492]: timeit x[idx]                                                                              \n2.46 \xc2\xb5s \xc2\xb1 8.17 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 100000 loops each)\nIn [493]: timeit [x[tuple(i)] for i in idx[0].base]                                                  \n21.7 \xc2\xb5s \xc2\xb1 836 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 10000 loops each)\nIn [494]: timeit [x[tuple(i)] for i in np.transpose(idx)]                                            \n33.2 \xc2\xb5s \xc2\xb1 94.3 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 10000 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n

最终像why这样的问题只能通过代码中的评论或开发者论坛或 github issues 中的讨论来回答。

\n