趋近智
NumPy 可以直接在相同大小和形状的数组之间执行操作,例如将两个 矩阵相加。但是,当需要对不同形状的数组执行操作(例如加法)时,情况会怎样呢?例如,将一个单独的数字(一个标量)加到数组的每个元素上,或者将一个一维数组(一个向量 (vector))加到二维数组(一个矩阵)的每一行上。
手动操作时,你可能会想到编写循环来执行这些操作。然而,NumPy 提供了一种更高效、更简洁的机制,称为广播(broadcasting)。广播描述了 NumPy 用来处理不同形状数组之间算术运算的一套规则,它有效地“拉伸”或复制较小的数组,使其形状与较大的数组匹配,而无需实际使用额外的内存。这使得向量化 (quantization)操作成为可能,它们比显式的 Python 循环快得多。
如果数组满足特定的兼容性条件,广播机制就使得在不同形状数组之间进行操作成为可能。NumPy 从末尾(最右边)的维度开始,逐元素比较数组的形状。两个维度兼容的条件是:
如果这些条件对于任何维度对都不满足,就会引发 ValueError: operands could not be broadcast together 错误。
让我们分解一下 NumPy 如何应用这些规则:
一旦形状兼容并完成了广播,NumPy 就会执行逐元素操作。
我们来看一下广播的实际作用。
最简单的例子是数组与标量值之间的操作。标量被视为零维数组。
import numpy as np
arr = np.array([1, 2, 3])
scalar = 5
result = arr + scalar
print(f"数组:\n{arr}")
print(f"形状:{arr.shape}\n")
print(f"标量:{scalar}\n")
print(f"结果 (arr + scalar):\n{result}")
print(f"形状:{result.shape}")
# 输出:
# 数组:
# [1 2 3]
# 形状:(3,)
#
# 标量:5
#
# 结果 (arr + scalar):
# [6 7 8]
# 形状:(3,)
在这里,标量 5 被有效地广播到数组 arr 的所有元素上。遵循规则:
arr 的形状是 (3,),scalar 实际的形状是 ()。NumPy 为标量添加维度以匹配 arr 的维度数量:形状变为 (1,)。等等,标量是 0 维的。数组是 1 维的(形状 (3,))。标量的形状被视为通过重复其值来匹配任何数组的形状。该操作将标量视为一个与 arr 形状相同的数组 [5, 5, 5]。我们考虑将一个一维数组加到二维数组的每一行上。
matrix = np.arange(6).reshape((2, 3)) # 形状 (2, 3)
row_vector = np.array([10, 20, 30]) # 形状 (3,)
result = matrix + row_vector
print(f"矩阵 (形状 {matrix.shape}):\n{matrix}\n")
print(f"行向量 (形状 {row_vector.shape}):\n{row_vector}\n")
print(f"结果 (matrix + row_vector) (形状 {result.shape}):\n{result}")
# 输出:
# 矩阵 (形状 (2, 3)):
# [[0 1 2]
# [3 4 5]]
#
# 行向量 (形状 (3,)):
# [10 20 30]
#
# 结果 (matrix + row_vector) (形状 (2, 3)):
# [[10 21 32]
# [13 24 35]]
我们来追踪一下广播规则:
matrix 形状:(2, 3)。row_vector 形状:(3,)。row_vector 的形状前添加 1。它变为 (1, 3)。row_vector 实际上变为 [[10, 20, 30], [10, 20, 30]]。以下图表说明了此过程:
一维数组
[10, 20, 30](形状 (3,))首先被视为形状 (1, 3),然后沿第一个轴广播(拉伸),以匹配矩阵的形状 (2, 3) 进行逐元素加法。灰色行表示数据的复制。
广播还可以组合数组以生成更高维度的结果。我们来将一个列向量加到一个行向量上。
col_vector = np.array([[0], [10], [20]]) # 形状 (3, 1)
row_vector = np.array([1, 2, 3]) # 形状 (3,)
result = col_vector + row_vector
print(f"列向量 (形状 {col_vector.shape}):\n{col_vector}\n")
print(f"行向量 (形状 {row_vector.shape}):\n{row_vector}\n")
print(f"结果 (col + row) (形状 {result.shape}):\n{result}")
# 输出:
# 列向量 (形状 (3, 1)):
# [[ 0]
# [10]
# [20]]
#
# 行向量 (形状 (3,)):
# [1 2 3]
#
# 结果 (col + row) (形状 (3, 3)):
# [[ 1 2 3]
# [11 12 13]
# [21 22 23]]
我们来追踪一下这个过程:
col_vector 形状:(3, 1)。row_vector 形状:(3,)。row_vector 视为形状 (1, 3)。col_vector 大小为 1 的维度被拉伸到 3。它变为形状 (3, 3)。row_vector 大小为 1 的维度被拉伸到 3。它也变为形状 (3, 3)。广播只有在形状根据规则兼容的情况下才有效。如果在任何时候维度大小不同且都不是 1,NumPy 将无法解决这种不明确性,并会引发错误。
arr1 = np.arange(6).reshape((2, 3)) # 形状 (2, 3)
arr2 = np.array([1, 2]) # 形状 (2,)
try:
result = arr1 + arr2
except ValueError as e:
print(f"错误:{e}")
# 输出:
# 错误:操作数无法一起广播,形状为 (2,3) (2,)
这里,arr1 是 (2, 3),arr2 是 (2,)。NumPy 将 arr2 视为 (1, 2)。比较 (2, 3) 与 (1, 2):
为了使其正常工作,arr2 需要具有形状 (3,) 或 (2, 1) 或 (1, 3),具体取决于预期的操作。
广播是 NumPy 中一个基本机制,它通过避免显式的 Python 循环,让你能编写更清晰、更简洁、明显更快的代码,用于对兼容但不同形状的数组执行操作。了解其规则对于使用 NumPy 进行高效的数值编程非常重要。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造