介绍
广播 (Broadcasting) 是 NumPy 中的一个基本概念,它能够对不同形状的数组执行元素级算术运算。这一强大功能消除了对显式循环的需求,从而编写出更简洁、计算效率更高的代码。在本实验中,你将学习广播的规则,并通过编写和执行 Python 脚本将其应用于实际示例。
广播 (Broadcasting) 是 NumPy 中的一个基本概念,它能够对不同形状的数组执行元素级算术运算。这一强大功能消除了对显式循环的需求,从而编写出更简洁、计算效率更高的代码。在本实验中,你将学习广播的规则,并通过编写和执行 Python 脚本将其应用于实际示例。
最简单的广播形式发生在数组和单个数字(标量)之间执行运算时。NumPy 会自动将标量“拉伸”或“广播”以匹配数组的形状。
首先,在屏幕左侧文件浏览器中找到 broadcasting.py 文件。双击它在编辑器中打开。
现在,用以下代码替换 broadcasting.py 的内容。这段代码创建了一个一维 NumPy 数组,并将其乘以一个标量值。
import numpy as np
## 创建一个一维数组
a = np.array([1.0, 2.0, 3.0])
## 定义一个标量
b = 2.0
## 将数组乘以标量
## 标量 'b' 被广播到 'a' 的形状
result = a * b
print("Array 'a':", a)
print("Scalar 'b':", b)
print("Result of a * b:", result)
要查看结果,你需要运行脚本。通过点击屏幕底部终端面板中的 + 图标并选择 Terminal 来打开一个终端。然后,执行以下命令:
python broadcasting.py
你应该会看到以下输出,其中数组 a 的每个元素都乘以了 2.0。
Array 'a': [1. 2. 3.]
Scalar 'b': 2.0
Result of a * b: [2. 4. 6.]
如果两个数组的形状兼容,广播也适用于它们之间。兼容性的通用规则是:从右到左比较两个数组的维度时,每一对维度必须相等,或者其中一个维度必须是 1。
让我们看一个将一维数组添加到二维数组的例子。一维数组将会在二维数组的每一行上进行广播。
使用以下代码更新你的 broadcasting.py 文件:
import numpy as np
## 创建一个二维数组 (形状:2, 3)
a = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
## 创建一个一维数组 (形状:3,)
b = np.array([10.0, 20.0, 30.0])
## 添加两个数组
## 'b' 被广播到形状 (2, 3) 以匹配 'a'
## 在内部,它变成了 [[10. 20. 30.], [10. 20. 30.]]
result = a + b
print("Array 'a' (shape {}):\n{}".format(a.shape, a))
print("Array 'b' (shape {}): {}".format(b.shape, b))
print("Result of a + b:\n", result)
在终端中再次运行脚本:
python broadcasting.py
输出显示一维数组 b 被添加到了二维数组 a 的每一行。
Array 'a' (shape (2, 3)):
[[1. 2. 3.]
[4. 5. 6.]]
Array 'b' (shape (3,)): [10. 20. 30.]
Result of a + b:
[[11. 22. 33.]
[14. 25. 36.]]
如果数组的形状不符合规则,广播将会失败。这将导致一个 ValueError。理解何时会发生这种情况对于调试至关重要。
让我们尝试一个形状不兼容的操作。在这里,我们将尝试将一个形状为 (2, 3) 的数组与一个形状为 (2,) 的数组相加。NumPy 会比较末尾的维度(3 和 2),发现它们不相等,并且其中一个也不是 1。这将导致错误。
将你的 broadcasting.py 文件修改为包含以下代码:
import numpy as np
## 创建一个二维数组 (形状:2, 3)
a = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
## 创建一个不兼容的一维数组 (形状:2,)
b = np.array([1.0, 2.0])
print("Array 'a' (shape {}):\n{}".format(a.shape, a))
print("Array 'b' (shape {}): {}".format(b.shape, b))
## 这将引发一个 ValueError
try:
result = a + b
except ValueError as e:
print("\nError:", e)
从终端运行脚本:
python broadcasting.py
正如预期的那样,程序捕获了一个 ValueError 并打印了一条错误消息,解释了形状不匹配。对于不兼容的形状,这是正确且符合预期的行为。
Array 'a' (shape (2, 3)):
[[1. 2. 3.]
[4. 5. 6.]]
Array 'b' (shape (2,)): [1. 2.]
Error: operands could not be broadcast together with shapes (2,3) (2,)
让我们将广播应用于一个实际问题。向量量化 (Vector Quantization, VQ) 是一种用于数据压缩和分类的技术。一个关键步骤是找到一个“码本”(codebook) 中与给定“观测”(observation) 向量最接近的“码”(code) 向量。广播使得这个计算过程非常高效。
想象一下我们有一个观测值(例如,一个运动员的体重和身高)和一个包含不同运动员类型的码本。我们想找出我们的观测值最接近哪种类型。
将 broadcasting.py 中的代码替换为以下内容:
import numpy as np
## 一个观测向量 (例如,体重,身高)
observation = np.array([111.0, 188.0])
## 一个码本向量
codes = np.array([[102.0, 203.0],
[132.0, 193.0],
[45.0, 155.0],
[57.0, 173.0]])
## 使用广播一次性将观测值从所有码中减去
## observation (2,) 被广播到 (4, 2)
diff = codes - observation
## 计算欧几里得距离的平方
dist_sq = np.sum(diff**2, axis=-1)
## 找到最小距离的索引
closest_index = np.argmin(dist_sq)
## 从码本中获取最接近的码
closest_code = codes[closest_index]
print("Observation:", observation)
print("Codebook:\n", codes)
print("Distances squared:", dist_sq)
print("Closest code index:", closest_index)
print("Closest code:", closest_code)
在你的终端中运行脚本:
python broadcasting.py
输出将显示观测值到每个码的平方距离,并识别出最接近的匹配码向量。由于广播,整个计算过程都没有使用显式的 Python 循环。
Observation: [111. 188.]
Codebook:
[[102. 203.]
[132. 193.]
[ 45. 155.]
[ 57. 173.]]
Distances squared: [ 306. 466. 5445. 3141.]
Closest code index: 0
Closest code: [102. 203.]
在本实验中,你学习了 NumPy 广播的基础知识。你从将标量广播到数组的简单情况开始,然后过渡到两个兼容数组之间的广播,并学会了识别会导致错误的形状不兼容情况。最后,你将广播应用于实际的向量量化问题,展示了它在编写简洁、高效且无循环的数值计算代码方面的强大功能。掌握广播是编写有效且专业 NumPy 代码的关键一步。