Skip to content

第 7 章 广播

学习目标

  • 理解广播(broadcasting)的定义
  • 掌握广播的三条规则
  • 能判断两个数组能否广播
  • 会利用广播简化代码:z-score 标准化、行/列运算

7.1 什么是广播

广播(broadcasting)是 NumPy 在形状不同的数组之间做逐元素运算时,自动「拉伸」较小数组以匹配较大数组的机制。

你已经见过最简单的广播:数组与标量相加。

python
import numpy as np

a = np.array([1, 2, 3])
print(a + 10)     # 10 被广播到每个元素

输出:

[11 12 13]

标量是 0 维的「数组」。NumPy 把 10 拉伸成与 a 相同形状,逐元素相加。

7.2 广播的三种常见形态

形态一:标量与数组

标量 + 数组,标量广播到整个数组(上面的例子)。

形态二:行向量与二维数组

(m, n) 数组 + (n,) 数组,一维数组沿行方向广播到每一行:

python
import numpy as np

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])
row = np.array([10, 20, 30])
print(m + row)

输出:

[[11 22 33]
 [14 25 36]
 [17 28 39]]

row 长度 3 与 m 的列数 3 匹配,广播成 3×3 后相加。这是「给每一行加上同一个向量」。

形态三:列向量与二维数组

(m, n) 数组 + (m, 1) 数组,列向量沿列方向广播到每一列:

python
import numpy as np

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])
col = np.array([[1], [2], [3]])     # (3, 1)
print(m + col)

输出:

[[ 2  3  4]
 [ 6  7  8]
 [10 11 12]]

col 是 3 行 1 列,先水平拉伸成 3×3,再与 m 相加:第 i 行整体加上 col[i, 0]

7.3 广播规则

判断两个形状能否广播,规则只有两条:

  1. 从右往左对齐维度,逐个比较。
  2. 每一对维度,要么相等,要么其中一个是 1(会被拉伸到另一个),要么缺失(视为 1)。

例子:形状 (3,)(3, 1) 相加:

(3,)   → 对齐为 (1, 3)
(3, 1) →        (3, 1)
逐位比较:1 vs 3 → 拉伸;3 vs 1 → 拉伸
结果 (3, 3)
python
import numpy as np

v = np.array([1, 2, 3])
print(v + v[:, np.newaxis])    # (3,) + (3, 1) → (3, 3)

输出:

[[2 3 4]
 [3 4 5]
 [4 5 6]]

不满足规则时,报 ValueError。两个典型的失败例子:

>>> np.array([1, 2, 3]) + np.array([1, 2])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (3,) (2,)

>>> np.ones((3, 2)) + np.ones((2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (3,2) (2,3)

第一个:3 与 2 既不相等、也没有 1,无法拉伸。第二个:对齐后 (3, 2)(2, 3),第 0 位 3 vs 2、第 1 位 2 vs 3,都不匹配。报错信息里的 shapes (...) 直接给出了两个形状,对照规则即可判断。

规则总结表:

形状 A形状 B结果说明
(n,)() 标量(n,)标量广播
(m, n)(n,)(m, n)行向量广播到每行
(m, n)(m, 1)(m, n)列向量广播到每列
(n,)(n, 1)(n, n)互相拉伸成方阵
(3,)(2,)报错3 vs 2,不匹配
(3, 2)(2, 3)报错两个维度都不匹配

7.4 应用:z-score 标准化

z-score 标准化把每列数据减去列均值、再除以列标准差,使每列均值为 0、标准差为 1。用广播一行搞定:

python
import numpy as np

data = np.array([[170, 65],   # 身高,体重
                 [175, 70],
                 [168, 55],
                 [182, 80]])
mean = data.mean(axis=0)       # 每列均值,形状 (2,)
std = data.std(axis=0)         # 每列标准差,形状 (2,)
z = (data - mean) / std        # 广播:每列减去自己的均值、除以自己的标准差
print(z)

输出:

[[-0.69411722 -0.2773501 ]
 [ 0.23137241  0.2773501 ]
 [-1.06431307 -1.38675049]
 [ 1.52705788  1.38675049]]

解读:data(4, 2),mean(2,)。减法时 mean 沿行广播到每一行,第 0 列减身高均值、第 1 列减体重均值——列与列互不干扰。没有广播的话,你要写两层循环。

7.5 利用列向量广播

把形状不对齐的数据先变成列向量,再广播。例如给每行加一个「行号偏移」:

python
import numpy as np

m = np.ones((2, 3))
offset = np.array([[1], [2]])      # (2, 1)
print(m + offset)

输出:

[[2. 2. 2.]
 [3. 3. 3.]]

动手实践

  1. 手动判断以下组合能否广播,能的话写出结果形状: (4, 3)(3,);(4, 3)(4, 1);(4, 3)(3, 4);(5, 1)(1, 4)。用代码验证。
  2. 创建 4×4 数组,给每一行加上 [10, 20, 30, 40];再给每一列加上 [[1], [2], [3], [4]]
  3. 用广播对成绩表 np.array([[85, 90, 78], [92, 88, 95], [76, 84, 80]]) 做 z-score 标准化,验证每列均值约为 0。
  4. 构造一个「不能广播」的组合,运行并读出报错里的两个形状。

常见错误

错误写法现象原因
(3,)(2,) 相加ValueError: operands could not be broadcast together with shapes (3,) (2,)对应维度 3 vs 2,都不为 1,无法广播
(3, 2)(2, 3) 相加同上两个维度都不匹配
忘了给列向量加 newaxis形状报错或结果不对需要列广播时先 v[:, np.newaxis] 变成 (n, 1)
以为广播会「补零」报错而不是自动补零广播只拉伸长度为 1 的维度,不补数据
广播结果出乎意料结果数组形状是两者形状的最大值先按规则推结果形状,再验证

章末练习

基础

  1. 判断并写出结果形状:(5,) + (5, 1)(2, 3) + (3,)(4, 2) + (4, 1)
  2. 用广播给 3×3 数组 np.arange(9).reshape(3, 3) 的每一行加上 [1, 2, 3]
  3. 给同一数组的每一列加上 [[10], [20], [30]],打印结果。

提高

  1. 生成 5 名学生的 3 门课成绩(自行设计),计算 z-score 标准化矩阵,并验证每列 mean 约为 0、std 约为 1。
  2. 用广播把摄氏温度数组 np.array([0, 10, 20, 30, 40]) 转成华氏,再与 np.array([[0], [10], [20]]) 组合演示一个二维广播例子。

挑战

  1. 解释为什么 np.arange(3) + np.arange(3)[:, np.newaxis] 得到 3×3 的加法表,写出结果。
  2. 用广播写一个函数 normalize_rows(a),把每行归一化到和为 1(提示:每行除以行和,行和用 sum(axis=1, keepdims=True))。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. 广播的定义是?
    • A. 把数组变成列表
    • B. 自动拉伸较小数组以匹配较大数组做逐元素运算
    • C. 复制数组
    • D. 打印数组
  2. np.array([1, 2, 3]) + 5 依赖的机制是?
    • A. 类型转换
    • B. 广播
    • C. 拼接
    • D. 循环
  3. 形状 (3,)(3, 1) 广播的结果形状是?
    • A. (3,)
    • B. (3, 1)
    • C. (3, 3)
    • D. 报错
  4. 形状 (4, 3)(3,) 广播的结果形状是?
    • A. (4,)
    • B. (4, 3)
    • C. (3, 4)
    • D. 报错
  5. 形状 (3,)(2,) 相加会?
    • A. 结果 (5,)
    • B. 结果 (3, 2)
    • C. 报 ValueError
    • D. 结果 (2,)
  6. 需要列向量广播时,应把 v 变成?
    • A. v.T
    • B. v[:, np.newaxis]
    • C. v.reshape(-1)
    • D. v.flatten()
  7. z-score 标准化的公式是?
    • A. (x - min) / (max - min)
    • B. (x - mean) / std
    • C. x / sum
    • D. (x - mean) / n
  8. 广播规则中,一对维度能拉伸的条件是?
    • A. 两者相等或其中一个为 1
    • B. 两者都是偶数
    • C. 两者都大于 1
    • D. 任意都能拉伸
  9. 下列哪组形状不能广播?
    • A. (2, 3)(3,)
    • B. (2, 3)(2, 1)
    • C. (2, 3)(3, 2)
    • D. (3,)(1, 3)
  10. np.arange(3) + np.arange(3)[:, np.newaxis] 的结果形状是?
    • A. (3,)
    • B. (3, 3)
    • C. (1, 3)
    • D. 报错