使用广播对数组进行计算
向量化运算的另一种方法是使用 NumPy 的广播功能:为对不同大小的数组应用二元 ufuncs(如加法、减法或乘法)创建规则。
以前,当我们对相同大小的数组执行二元运算时,将按元素逐个执行这些操作。
first_array = np.array([3, 6, 8, 1])
second_array = np.array([4, 5, 7, 2])
first_array + second_array
输出为:
array([ 7, 11, 15, 3])
通过广播,可以对不同大小的数组执行此类二元运算。 因此,你可轻松地将标量(这只是一个零维数组)添加到数组中:
first_array + 5
输出为:
array([ 8, 11, 13, 6])
同样,可以将一维数组添加到二维数组中:
one_dim_array = np.ones((1))
one_dim_array
输出为:
array([1.])
对于此运算:
two_dim_array = np.ones((2, 2))
two_dim_array
输出为:
array([[1., 1.],
[1., 1.]])
接下来,请尝试:
one_dim_array + two_dim_array
输出为:
array([[2., 2.],
[2., 2.]])
到目前为止,一切顺利。 但你可通过更复杂的方法在数组上使用广播。 请考虑此示例:
horizontal_array = np.arange(3)
vertical_array = np.arange(3)[:, np.newaxis]
print(horizontal_array)
print(vertical_array)
输出为:
[0 1 2]
[[0]
[1]
[2]]
现在运行:
horizontal_array + vertical_array
输出为:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
广播规则
广播遵循一组规则,以确定两个数组之间的交互:
- 规则 1:如果这两个数组的维数不同,则维度较少的数组的形状将在其前(左)端以 1 填充。
- 规则 2:如果两个数组的形状在任何维度中都不匹配,该维度中形状等于 1 的数组将拉伸以匹配另一个形状。
- 规则 3:如果在任何维度中,大小不一致且两者都不等于 1,则 NumPy 将引发错误。
接下来看一下这些规则的实际应用,以便更好地了解它们。
广播示例 1
了解如何将一维数组添加到二维数组中:
two_dim_array = np.ones((2, 3))
one_dim_array = np.arange(3)
请考虑对这两个数组进行运算。 数组的形状是:
two_dim_array.shape = (2, 3)one_dim_array.shape = (3,)
数组 one_dim_array 的维度较少。 根据规则 1,我们在其左侧以 1 填充:
two_dim_array.shape -> (2, 3)one_dim_array.shape -> (1, 3)
我们现在看到第一个维度不一致。 根据规则 2,我们将延伸此维度以进行匹配:
two_dim_array.shape -> (2, 3)one_dim_array.shape -> (2, 3)
形状匹配,显示最终形状将为 (2, 3):
two_dim_array + one_dim_array
输出为:
array([[1., 2., 3.],
[1., 2., 3.]])
亲自试一试
翻转此运算。 试着将它们添加到 two_dim_array = np.ones((3, 2)) 和 one_dim_array = np.arange(3)[:, np.newaxis]。
结果如何?
提示 (展开以显示)
two_dim_array = np.ones((3, 2))
two_dim_array
输出为:
array([[1., 1.],
[1., 1.],
[1., 1.]])
并使用:
one_dim_array = np.arange(3)[:, np.newaxis]
one_dim_array
输出为:
array([[0],
[1],
[2]])
广播示例 2
接下来看看当这两个数组需要广播时,会发生什么情况:
vertical_array = np.arange(3).reshape((3, 1))
horizontal_array = np.arange(3)
同样,我们首先编写数组的形状:
vertical_array.shape = (3, 1)horizontal_array.shape = (3,)
规则 1 指出,需要使用 1 填充 horizontal_array 的形状:
vertical_array.shape -> (3, 1)horizontal_array.shape -> (1, 3)
而规则 2 指出,我们需要升级其中的每个 1,以匹配其他数组的相应大小:
vertical_array.shape -> (3, 3)horizontal_array.shape -> (3, 3)
由于结果匹配,这些形状是兼容的。 可在此处查看:
vertical_array + horizontal_array
输出为:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
广播示例 3
下面是不兼容数组的情况:
M = np.ones((3, 2))
i = np.arange(3)
这种情况与第一个示例中的情况略有不同:矩阵 M 已转置。 这一变化对计算有何影响? 数组的形状是:
M.shape = (3, 2)i.shape = (3,)
同样,规则 1 指出,我们需要使用 1 填充 i 的形状:
M.shape -> (3, 2)i.shape -> (1, 3)
根据规则 2,延伸 i 的第一个维度,使其与 M 的维度相匹配:
M.shape -> (3, 2)i.shape -> (3, 3)
现在来到规则 3:最终形状不匹配,而且这两个数组不兼容:
M + i
输出为:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-140-90923315444d> in <module>
----> 1 M + i
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
广播实践
Ufuncs 使你可以避免使用慢速 Python 循环。 广播就建立在这个基础上。
常见的数据实践是集中数据数组。 例如,假设我们有一个包含 10 个观察值的数组,其中每个观察值都由三个值(在此上下文中称为特征)组成。 我们可能需要集中该数据,以便发现与平均值(而非原始数据本身)的差。 这样做有助于更好地比较不同的值。
我们会将此数据存储在 10 x 3 数组中:
T = np.random.random((10, 3))
T
输出为:
array([[0.90137772, 0.75292208, 0.26398243],
[0.46383154, 0.38378661, 0.93354504],
[0.17893062, 0.2792731 , 0.24203594],
[0.32155001, 0.11237084, 0.9133384 ],
[0.59296043, 0.45756963, 0.25448096],
[0.91645837, 0.04154293, 0.02954946],
[0.23938136, 0.13087661, 0.37177127],
[0.34654638, 0.0745468 , 0.78676817],
[0.33536819, 0.49004234, 0.90644125],
[0.03382156, 0.3552458 , 0.3588655 ]])
现在,在第一个维度中使用 mean 聚合来计算每个特征的平均值:
Tmean = T.mean(0)
Tmean
输出为:
array([0.43302262, 0.30781767, 0.50607784])
最后减去平均值来集中 T。 (此运算为广播运算。)
T_centered = T - Tmean
T_centered
输出为:
array([[ 0.4683551 , 0.4451044 , -0.24209541],
[ 0.03080892, 0.07596894, 0.4274672 ],
[-0.254092 , -0.02854458, -0.2640419 ],
[-0.11147261, -0.19544684, 0.40726056],
[ 0.15993781, 0.14975196, -0.25159688],
[ 0.48343576, -0.26627475, -0.47652838],
[-0.19364126, -0.17694106, -0.13430658],
[-0.08647624, -0.23327088, 0.28069033],
[-0.09765443, 0.18222467, 0.40036341],
[-0.39920105, 0.04742813, -0.14721234]])
这种方法不仅速度更快,而且比编写循环更简单。
要点
你将在数据科学中处理的数据总是有不同的形状和大小(至少在处理该数据的数组方面是如此)。 通过 NumPy 中的广播功能,能够以可预测的方式对不规则拟合数据使用二元函数。