广播仅仅是一组用于在不同大小的数组上应用二元ufuncs(加法、减法、乘法等)的规则。
对于相同大小的数组,二元操作按元素逐元素执行。
In[1]: import numpy as np
In[2]: a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
a + b
Out[2]: array([5, 6, 7])
广播允许在不同大小的数组上执行这些类型的二元操作。
在NumPy中,广播遵循一组严格的规则来确定两个数组之间的操作:
规则1:如果两个数组在维度的数量上有差异,那么维度较少的数组的形状就会被用1填充在它的前导(左)边。
规则2:如果两个数组的形状在任何维度上都不匹配,但等于1,那么在这个维度中,形状为1的数组将被拉伸以匹配另一个形状。
规则3:如果在任何维度上,大小都不一致,且两者都不等于1,就会出现错误。
下面看例子:
In[1]: import numpy as np
In[2]: M = np.ones((2, 3))
a = np.arange(3)
In [3]: M.shape
Out[3]: (2, 3)
In [4]: a.shape
Out[4]: (3,)
根据规则1,数组a的维数更少,所以我们用1填充在它的左边。
M.shape -> (2, 3)
a.shape -> (1, 3)
根据规则2,我们现在看到第一个维度不匹配,所以我们扩展这个维度来匹配。
M.shape -> (2, 3)
a.shape -> (2, 3)
现在就可以相加了,而且最终的shape是(2, 3)。我们验证一下:
In [5]: (M + a).shape
Out[5]: (2, 3)
再看一个例子:
In[6]: a = np.arange(3).reshape((3, 1))
b = np.arange(3)
In [7]: a.shape
Out[7]: (3, 1)
In [8]: b.shape
Out[8]: (3,)
根据规则1,数组b的维数更少,所以我们用1填充在它的左边。
a.shape -> (3, 1)
b.shape -> (1, 3)
规则2告诉我们,我们将每一个是1的都升级,以匹配另一个数组的相应大小。
a.shape -> (3, 3)
b.shape -> (3, 3)
现在可以相加了,验证下:
In [9]: (a + b).shape
Out[9]: (3, 3)
下面看一个满足规则3的例子:
In [15]: M = np.ones((3, 2))
In [16]: a = np.arange(3)
In [16]: M.shape
Out[16]: (3, 2)
In [17]: a.shape
Out[17]: (3,)
根据规则1,
M.shape -> (3, 2)
a.shape -> (1, 3)
根据规则2,a的第一个维度被拉伸到与M匹配:
M.shape -> (3, 2)
a.shape -> (3, 3)
现在根据规则3,如果我们进行加的操作,会出现错误,验证下:
In [18]: M + a
Traceback (most recent call last):
File "<ipython-input-18-8cac1d547906>", line 1, in <module>
M + a
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
注意这里可能出现的混淆:你可以想象出a和M的兼容性,比方说,用1在右边而不是左边填充a。但这不是广播规则的工作方式!还要注意的是,虽然我们一直在关注+运算符,但是这些广播规则适用于任何二元ufunc。
参考:
《Python Data Science Handbook》
https://docs.scipy.org/doc/numpy-dev/user/basics.broadcasting.html