6.10. 广播¶
当一个二元运算符接收到两个形状不完全匹配的数组时,numpy 不会抛出错误——它会进行广播。广播是一小组规则,用来判断两个形状是否兼容,如果兼容,则决定如何将较小的那个虚拟地拉伸以匹配较大的那个。
6.10.1. 规则¶
当两个操作数的形状分别为 A 和 B 时,numpy 会分两步处理它们。
对齐秩。 如果一个操作数的轴数比另一个少,
numpy会在其形状的前端虚拟地补上大小为 1 的轴,直到两个形状的轴数相同。一个形状为(3,)的一维操作数与一个形状为(2, 3)的二维操作数配对时,会变成(1, 3)对(2, 3)。逐轴检查。 逐轴遍历现在长度相等的两个形状,每一对大小都必须满足以下两个条件之一:大小相等,或其中一个为 1。大小为 1 的轴会被虚拟地拉伸到另一侧的大小以进行运算。
(1, 3)对(2, 3)是兼容的,因为第一个轴有一个 1(拉伸为 2),第二个轴匹配(3 == 3);结果的形状为(2, 3)。
如果任何一对轴都不满足上述两个条件,则形状不兼容,运算符会抛出 ValueError。
6.10.2. 示例¶
标量与任意数组。 标量的行为就像形状 (1,),可以拉伸成任何形状:
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10 # (2, 3) + scalar -> (2, 3)
一维向量跨越二维矩阵。 规则 1 在前端补上一个大小为 1 的轴,将 (3,) 变为 (1, 3);规则 2 随后将那一行沿 a 的每一列向下拉伸:
row = np.array([100, 200, 300], dtype=np.float)
a + row # (2, 3) + (3,) -> (2, 3)
两个长度相等的一维数组逐元素相加——无需广播:
np.arange(4) + np.arange(4)
列向量与行向量 产生一个二维的“外积”形状:(4, 1) 与 (3,) 配对,经过秩前置后变为 (4, 1) 对 (1, 3),规则 2 随后将每个操作数沿其大小为 1 的轴拉伸:
x = np.array([1, 2, 3, 4]).reshape((4, 1)) # column
y = np.array([10, 20, 30]) # row
x + y # (4, 3) matrix
相同的形状规则适用于任何双参数 ufunc,包括 arctan2():
np.arctan2(y, 1.0)
np.arctan2(y, x)
6.10.3. 广播不会分配什么¶
拉伸是虚拟的。numpy 会同时遍历两个操作数,沿广播轴重新读取较小的那个,而不是复制它。较短数组的数据绝不会在内存中被复制。
对内存而言,重要的是输出数组的大小。a + row 会分配一个形状与 a 相同的输出,而不是 a 的形状加上 row 的形状。冗长的广播链仍然可能产生庞大的中间结果。
6.10.4. 广播出错时¶
典型的失败情形是两个形状都没有大小为 1 的轴可供拉伸,且大小不一致——例如 (3, 4) 对 (4, 3)。规则 2 无法让 3 匹配 4,因此 numpy 会抛出 ValueError。
一个更微妙的问题是广播成功了,但并非应用程序所期望的方式。(5,) 对 (5, 1) 是典型案例:秩前置将 (5,) 变为 (1, 5),它与 (5, 1) 广播后产生一个 (5, 5) 矩阵——即两个向量的外积组合,而非应用程序很可能想要的长度为 5 的逐元素结果。如有疑问,请打印两侧的 shape,并在动用 reshape() 或 transpose() 之前逐条对照规则。