1. 数组相加
一个 2*5 维的数组对象和一个 1 维的数组对象进行相加,结果会怎样?
In [1]: import numpy as np
In [2]: a = np.arange(10).reshape(2,5)
In [3]: a
Out[3]:
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
In [4]: b = np.array([2])
In [5]: a + b
Out[5]:
array([[ 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11]])
In [6]:
可以看到在维度不同时是将小维度的值全部匹配到大维度的值上,即把 a 中的每个元素都加上 b 的元素。但是看看下面的计算。
In [12]: c = np.array([1,2])
In [13]: c
Out[13]: array([1, 2])
In [14]: a + c
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-14-e81e582b6fa9> in <module>
----> 1 a + c
ValueError: operands could not be broadcast together with shapes (2,5) (2,)
In [15]: c.shape
Out[15]: (2,)
In [16]:
为什么 2*5 维的数组和 2 维的数组相加会报错呢? 这是因为 Numpy 的广播机制。因为 a、c 按照广播的规则,无法达成一致的 shape,所以抛出异常。
因为广播机制的存在,b 数组会适配 a 数组,按照第 0、1 维度,分别发生一次广播,广播后的 b 变为:
In [21]: b
Out[21]: array([2])
In [22]: b = np.tile(b,(2,5))
In [23]: b
Out[23]:
array([[2, 2, 2, 2, 2],
[2, 2, 2, 2, 2]])
In [24]:
然后,执行再执行加法操作时,因为 a、b 的 shape 变得完全一致,所以就能实现相加操作了。
2. 广播规则
从上面我们可以看到,不是任意 shape 的多个数组,操作时都能广播到一起,必须满足一定的约束条件。
NumPy
首先会比较最靠右的维度,如果最靠右的维度相等或其中一个为 1,则认为此维度相等;- 那么,再继续向左比较,如果一直满足,则认为两者兼容;
- 最后,分别在对应维度上发生广播,以此补齐直到维度一致;
如下,两个数组 a、b,shape 分别为 (2,1,3)、(4,3), 它们能否广播兼容?我们来分析下。
a = np.arange(6).reshape(2,1,3) # shape: (2,1,3)
b = np.arange(12).reshape(4,3) # shape: (4,3)
- 按照规则,从最右侧维度开始比较,数组 a, b 在此维度上的长度都为 3,相等;
- 继续向左比较,a 在此维度上长度为 1,b 长度为 4,根据规则,也认为此维度是兼容的;
- 继续比较,但是数组 b 已到维度终点,停止比较。
结论,数组 a 和 b 兼容,通过广播能实现 shape
一致。
3. 广播步骤
下面看看,数组 a 和 b 广播操作实施的具体步骤。
In [1]: import numpy as np
In [2]: a = np.arange(6).reshape(2,1,3)
In [3]: b = np.arange(12).reshape(4, 3)
In [4]: a
Out[4]:
array([[[0, 1, 2]],
[[3, 4, 5]]])
In [5]: b
Out[5]:
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
In [6]:
维度编号从 0 开始,数组 a 在维度 1 上发生广播,复制 4 次:
In [6]: a = np.repeat(a, 4, axis=1)
In [7]: a
Out[7]:
array([[[0, 1, 2],
[0, 1, 2],
[0, 1, 2],
[0, 1, 2]],
[[3, 4, 5],
[3, 4, 5],
[3, 4, 5],
[3, 4, 5]]])
In [8]:
此时,数组 a 和 b 在后两个维度一致,但是数组 b 维度缺少一维,所以 b 也会广播一次:
In [8]: b = b[np.newaxis,:,:] # 首先增加一个维度
In [9]: b
Out[9]:
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]]])
In [10]: b = np.repeat(b,2,axis=0) # 在维度 0 上复制 2 次
In [11]: b
Out[11]:
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]],
[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]]])
In [12]:
经过以上操作,数组 a 和 b 维度都变为 (2,4,3),至此广播完成,做个加法操作:
In [11]: b
Out[11]:
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]],
[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]]])
In [12]: a
Out[12]:
array([[[0, 1, 2],
[0, 1, 2],
[0, 1, 2],
[0, 1, 2]],
[[3, 4, 5],
[3, 4, 5],
[3, 4, 5],
[3, 4, 5]]])
In [13]: a + b
Out[13]:
array([[[ 0, 2, 4],
[ 3, 5, 7],
[ 6, 8, 10],
[ 9, 11, 13]],
[[ 3, 5, 7],
[ 6, 8, 10],
[ 9, 11, 13],
[12, 14, 16]]])
In [14]:
验证我们自己实现的广播操作,是否与 NumPy
中的广播操作一致,直接使用原始的 a 和 b 数组相加,看到与上面得到的结果一致。
In [14]: a = np.arange(6).reshape(2,1,3)
In [15]: b = np.arange(12).reshape(4,3)
In [16]: a + b
Out[16]:
array([[[ 0, 2, 4],
[ 3, 5, 7],
[ 6, 8, 10],
[ 9, 11, 13]],
[[ 3, 5, 7],
[ 6, 8, 10],
[ 9, 11, 13],
[12, 14, 16]]])
In [17]:
至此,我们已经了解了广播的规则,所以再遇到如下的广播错误时,我们就可以很清楚的知道该如何解决这样的错误。
ValueError: operands could not be broadcast together with shapes (2,5) (2,)