Numpy的frompyfunc通过形状数组(2,4)和(2,1)执行8(2*4)次,而不是2次

sgtfey8w  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(105)

我有一个函数x,如下所示,它接受两个numpy数组作为输入,我想在一些计算后返回一个布尔值。

import numpy as np

def x(a,b):
    print(a)
    print(b)
    # Some computation...
    return boolean_value

wrappedFunc = np.frompyfunc(x,nin=2,nout=1)

arg_a = np.arange(8).reshape(2,4)
# arg_b is a numpy array having shape (2,1)
arg_b = np.array((np.array([[0, 1, 0],
                            [0, 0, 0],
                            [1, 0, 0],
                            [1, 1, 0]]),
                  np.array([[0., 1., 0.],
                            [0., 0., 0.],
                            [1., 0., 0.],
                            [1., 1., 0.],
                            [0.5, 0.5, 0.]])), dtype=object).reshape(2, 1)

字符串
执行上面的代码会得到以下输出。

# Output of a is:
0
1
2
3
4
5
6
7
# output of b is:
[[0 1 0]
 [0 0 0]
 [1 0 0]
 [1 1 0]]

[[0 1 0]
 [0 0 0]
 [1 0 0]
 [1 1 0]]

[[0 1 0]
 [0 0 0]
 [1 0 0]
 [1 1 0]]

[[0 1 0]
 [0 0 0]
 [1 0 0]
 [1 1 0]]

[[0.  1.  0. ]
 [0.  0.  0. ]
 [1.  0.  0. ]
 [1.  1.  0. ]
 [0.5 0.5 0. ]]

[[0.  1.  0. ]
 [0.  0.  0. ]
 [1.  0.  0. ]
 [1.  1.  0. ]
 [0.5 0.5 0. ]]

[[0.  1.  0. ]
 [0.  0.  0. ]
 [1.  0.  0. ]
 [1.  1.  0. ]
 [0.5 0.5 0. ]]

[[0.  1.  0. ]
 [0.  0.  0. ]
 [1.  0.  0. ]
 [1.  1.  0. ]
 [0.5 0.5 0. ]]


正如你所看到的,变量ab分别被打印了8次,这不是预期的行为,因为我期望分别看到ab的print语句的输出两次。print(a)print(b)语句的预期输出如下所示:

On first call:
a needs to be:[0,1,2,3]
b needs to be:[[0 1 0]
              [0 0 0]
              [1 0 0]
              [1 1 0]]
On second call:
a needs to be:[4,5,6,7]
b needs to be:[[0.  1.  0. ]
              [0.  0.  0. ]
              [1.  0.  0. ]
              [1.  1.  0. ]
              [0.5 0.5 0. ]]


我做错什么了?

ac1kyiln

ac1kyiln1#

让我们看看frompyfunc与一个更简单的b,并将其与简单的numpy加法进行比较。

In [267]: a=np.arange(1,9).reshape(2,4); a
Out[267]: 
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])

In [268]: b = np.array([[10],[20]]); b
Out[268]: 
array([[10],
       [20]])

字符串
a(2,4)与a(2,1)相加得到a(2,4)。根据broadcasting的规则,大小为1的维度被“复制”以匹配a的4:

In [269]: a+b
Out[269]: 
array([[11, 12, 13, 14],
       [25, 26, 27, 28]])


定义一个简单的函数来添加两个“标量”。就像写的那样,它可以处理数组,包括ab,但是想象一下,有一些if行只能处理标量。

In [270]: def x(i,j):
     ...:     print(i,j)
     ...:     return i+j
     ...:


使用frompyfunc创建一个ufunc,它可以将broadcast作为其参数,将标量值传递给x

In [271]: np.frompyfunc(x,2,1)(a,b)
1 10
2 10
3 10
4 10
5 20
6 20
7 20
8 20
Out[271]: 
array([[11, 12, 13, 14],
       [25, 26, 27, 28]], dtype=object)


你似乎想要的是数组在第一维上的zip

In [272]: [x(i,j) for i,j in zip(a,b)]
[1 2 3 4] [10]
[5 6 7 8] [20]
Out[272]: [array([11, 12, 13, 14]), array([25, 26, 27, 28])]


请注意,x在这里得到一个(4,)和(1,)形状的数组,再次通过broadcasting得到一个(4,)结果。
这两个输出数组可以连接起来,得到与前面相同的(4,2):

In [273]: np.array(_)
Out[273]: 
array([[11, 12, 13, 14],
       [25, 26, 27, 28]])


一个相关的函数vectorize接受一个signature,它允许我们在第一个轴上指定迭代。要正确地做到这一点需要一些练习(尽管我第一次尝试就做对了!):

In [274]: np.vectorize(x, otypes=[int], 
    signature='(n,m),(n,1)->(n,m)')(a,b)
[[1 2 3 4]
 [5 6 7 8]] [[10]
 [20]]
Out[274]: 
array([[11, 12, 13, 14],
       [25, 26, 27, 28]])


vectorize有一个性能免责声明,这对signature版本来说是双重的。frompyfunc通常表现更好(当它做我们想要的事情时)。
对于小数组,列表解析通常做得更好,但是对于大数组,vectorize似乎扩展得更好,并且最终具有适度的速度优势。但是为了获得最佳的numpy性能,最好使用整个数组(真正的向量化),而没有任何“迭代”。

相关问题