numpy ndarray意外形状广播错误

时间:2019-04-14 19:07:38

标签: python numpy numpy-ndarray

我有一个形状为(3,)的numpy ndarray。我有另一个形状为(3,100,100)的ndarray。以下作品:

a = np.array([1,1,1]) # Shape is (3,)
b = np.zeros((3,100,100)) # Shape is (3,100,100)
c = np.array([b[0], b[1], 0]) # Shape (3,)
c - a # works fine and as expected 

但是有以下中断:

c_wrong = np.array([b[0], b[1], b[2]]) # now c_wrong is (3,100,100) too

c_wrong - a # ValueError: operands could not be broadcast together with shapes (3,100,100) (3,)

是否有办法将(3,100,100)重塑为(3,)?

我发现一个丑陋的循环只是添加一个虚拟的额外组件:

>>> c_wrong = np.array([b[0],b[1],b[2],0])
>>> a = np.array([1,1,1,1])
>>> d = c_wrong - a
>>> d[0:3]

虽然这很丑陋,但我希望它有助于理解问题和所需的行为。

1 个答案:

答案 0 :(得分:2)

不只是看形状!

In [82]: a = np.array([1,1,1]) # Shape is (3,) 
    ...: b = np.zeros((3,10,10)) # Shape is (3,10,10) 
    ...: c = np.array([b[0], b[1], 0]) # Shape (3,)                             
In [83]:                                                                        
In [83]: c                                                                      
Out[83]: 
array([array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]),
       array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]),
       0], dtype=object)
In [84]: c.shape                                                                
Out[84]: (3,)

是的,c只有3个元素,但每个元素都是数组或标量(最后0个)。

In [85]: c-a                                                                    
Out[85]: 
array([array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]]),
       array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]]),
       -1], dtype=object)

因此您设法从每个元素中减去1!

c_wrong是一个非常不同的数组-它是带有数字dtype的3d。将0替换为d[3]会带来很大的不同。

In [88]: c_wrong.shape                                                          
Out[88]: (3, 10, 10)
In [89]: c_wrong.dtype                                                          
Out[89]: dtype('float64')

要从(3,N,N)中减去(3,),必须将a的尺寸调整为(3,1,1)。然后它可以进行适当的广播。

In [91]: c_wrong -  a[:,None,None]                                              
Out[91]: 
array([[[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
        [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
        ....
        [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]]])

我认为您的c-a工作只是偶然。通过使用c元素定义0,您创建了object dtype数组。带有对象dtype数组的数学运算是偶然的。这种减法恰好是这些命中之一。但是不要指望它。在很多情况下,使用这种数组的数学无法正常工作-而且速度总是较慢。


c_wrongb基本相同。


numpy的核心是多维数字数组。默认情况下,np.array尝试构建尽可能高的维数。在您的c_wrong情况下,它可以制作3d; c中的in不能是因为标量为0。因此,它只能使用1d对象数组。

使对象阵列具有所需形状的最可靠方法是初始化一个“空白”对象并填充它。但是即使那样填充也很棘手。在这里,我设法做到了:

In [92]: c3 = np.empty(3, object)                                               
In [93]: c3                                                                     
Out[93]: array([None, None, None], dtype=object)
In [94]: c3[:] = list(b)                                                        
In [95]: c3                                                                     
Out[95]: 
array([array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       ....
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])], dtype=object)
In [96]: c3-a                                                                   
Out[96]: 
array([array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
....
       [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]])], dtype=object)

无效的填充:

In [97]: c3[:] = b                                                              
------------------------------------------------------------------------ 
...
ValueError: could not broadcast input array from shape (3,10,10) into shape (3)

a[:,None,None]在您熟悉广播时看起来并不难看。

比较时间:

In [98]: timeit c_wrong-a[:,None,None]                                          
5.22 µs ± 6.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [99]: timeit c3-a                                                            
9.53 µs ± 20.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [100]: timeit c-a                                                            
7.66 µs ± 10.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

或与dot

In [103]: timeit np.dot(a, b.reshape(3,-1)).shape                              
2.44 µs ± 9.63 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [104]: timeit np.dot(a,c).shape                                              
10.9 µs ± 16.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [105]: timeit np.dot(a,c3).shape                                             
11.6 µs ± 30.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

dot具有非常特定的规则-a的最后一个轴必须与b的第二个到最后一个轴匹配。这就是为什么我使用reshape。并将任务传递给快速的“ blas”例程。

使用(3,)对象数组,它会做1d dot乘积-但要反复进行。

@matmul与重新设计的b一起使用,但不适用于cc3。与einsum相同:np.einsum('i,ijk->jk',a,b).shape有效,但使用c无效。