Skip to content

NumPy 广播

本章目标

  1. 掌握广播(broadcasting)的三条核心规则
  2. 掌握标量、行向量、列向量与矩阵的广播计算
  3. 学会用广播实现外积
  4. 掌握广播在数据标准化中的典型应用

重点方法与概念速览

名称类型作用
arr + scalar表达式标量与数组广播(逐元素运算)
arr2d + arr1d表达式一维向量沿行方向广播
arr2d + col表达式列向量 (n, 1) 沿列方向广播
a[:, np.newaxis] * b表达式用广播手动构造外积
np.outer(...)函数外积(一维向量专用 API)
arr.mean(axis=0)方法按列均值,配合广播用于标准化
arr.std(axis=0)方法按列标准差,配合广播用于标准化

1. 广播规则

三条核心规则

  1. 从后往前(末尾对齐)逐维比较两个数组的 shape
  2. 每一维满足相等其中一个为 1即可广播
  3. 缺失的维度1 处理,自动在前方补上

形状兼容速查

左形状右形状是否兼容广播后形状
(3, 4)(4,)(3, 4)
(3, 4)(3, 1)(3, 4)
(3, 1)(1, 4)(3, 4)
(2, 3, 4)(3, 4)(2, 3, 4)
(3, 4)(3,)
(3, 4)(2, 4)

示例代码

python
import numpy as np

A = np.ones((3, 4))
B1 = np.array([1, 2, 3, 4])         # (4,)
B2 = np.array([[1], [2], [3]])      # (3, 1)

print(f"(3,4) + (4,) = {(A + B1).shape}")
print(f"(3,4) + (3,1) = {(A + B2).shape}")
print(f"(3,1) + (1,4) = "
      f"{(np.ones((3, 1)) + np.ones((1, 4))).shape}")

输出

text
(3,4) + (4,) = (3, 4)
(3,4) + (3,1) = (3, 4)
(3,1) + (1,4) = (3, 4)

2. 标量与数组广播

作用

标量(0 维)与任意形状的数组均可广播,标量自动扩展为同形状后逐元素运算。

示例代码

python
import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"arr + 10:\n{arr + 10}")
print(f"arr * 2:\n{arr * 2}")

输出

text
arr + 10:
[[11 12 13]
 [14 15 16]
 [17 18 19]]
arr * 2:
[[ 2  4  6]
 [ 8 10 12]
 [14 16 18]]

3. 一维数组沿行广播

作用

arr2d (m, n)arr1d (n,) 相加时,一维数组被视作 (1, n) 后在行方向复制 m 份,相当于"每一行加同一个向量"。

示例代码

python
import numpy as np

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr1d = np.array([10, 20, 30])
print(f"arr2d + arr1d:\n{arr2d + arr1d}")

输出

text
arr2d + arr1d:
[[11 22 33]
 [14 25 36]
 [17 28 39]]

理解重点

  • (m, n)(n,) 对齐后,第 0 轴长度自动补为 1,再广播到 m
  • 若要"每一列加同一个向量",需要形状 (m, 1) 而不是 (m,)

4. 列向量沿列广播

作用

arr2d (m, n)col (m, 1) 相加时,列向量在列方向复制 n 份,相当于"每一列加同一个列向量"。

示例代码

python
import numpy as np

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
col = np.array([[100], [200], [300]])  # (3, 1)
print(f"arr2d + col:\n{arr2d + col}")

输出

text
arr2d + col:
[[101 102 103]
 [204 205 206]
 [307 308 309]]

理解重点

  • 想要"列向量"必须显式做成 (m, 1):可用 arr.reshape(-1, 1)arr[:, np.newaxis]np.expand_dims(arr, axis=1)
  • (m,)(m, 1) 在广播中方向不同:前者沿行方向广播,后者沿列方向广播

5. 广播应用:外积

外积 ab 的结果形状为 (len(a), len(b)),元素公式:

(ab)ij=aibj

用广播的视角:把 a 变成列向量 (m, 1)b 保持 (n,),相乘即得。

np.outer

作用

直接计算两个一维向量的外积,等价于 a[:, np.newaxis] * b

重点方法

python
np.outer(a, b, out=None)

参数

参数名类型说明示例取值
aarray_like第一个输入,会被展平为一维[1, 2, 3]
barray_like第二个输入,会被展平为一维[10, 20, 30, 40]
outndarrayNone写入结果的目标数组——

综合示例

示例代码

python
import numpy as np

a = np.array([1, 2, 3])
b = np.array([10, 20, 30, 40])

# 方式 1:广播
print(f"广播外积:\n{a[:, np.newaxis] * b}")

# 方式 2:np.outer
print(f"np.outer:\n{np.outer(a, b)}")

输出

text
广播外积:
[[ 10  20  30  40]
 [ 20  40  60  80]
 [ 30  60  90 120]]
np.outer:
[[ 10  20  30  40]
 [ 20  40  60  80]
 [ 30  60  90 120]]

6. 广播应用:特征标准化

Z-score 标准化公式:

z=xμσ

其中 μ(均值)和 σ(标准差)沿样本维(axis=0)计算。它们形状为 (n_features,),与数据 (n_samples, n_features) 相减/相除时自动沿行广播。

示例代码

python
import numpy as np

np.random.seed(42)
data = np.random.randint(0, 100, size=(5, 3))
print(f"原始数据:\n{data}")

mean = data.mean(axis=0)  # (3,)
std = data.std(axis=0)    # (3,)
normalized = (data - mean) / std

print(f"每列均值: {mean.round(2)}")
print(f"每列标准差: {std.round(2)}")
print(f"标准化后:\n{normalized.round(2)}")
print(f"标准化后均值: {normalized.mean(axis=0).round(10)}")
print(f"标准化后标准差: {normalized.std(axis=0).round(2)}")

输出

text
原始数据:
[[51 92 14]
 [71 60 20]
 [82 86 74]
 [74 87 99]
 [23  2 21]]
每列均值: [60.2 65.4 45.6]
每列标准差: [21.22 33.61 34.4 ]
标准化后:
[[-0.43  0.79 -0.92]
 [ 0.51 -0.16 -0.74]
 [ 1.03  0.61  0.83]
 [ 0.65  0.64  1.55]
 [-1.75 -1.89 -0.72]]
标准化后均值: [-0. -0. -0.]
标准化后标准差: [1. 1. 1.]

理解重点

  • mean / std 形状 (3,),与 data (5, 3) 运算时自动沿行广播到 (5, 3)
  • 标准化后每列均值为 0、标准差为 1——这正是 StandardScaler 的底层实现
  • keepdims=True(1, 3) 形状,语义更明确,对高维也通用

常见坑

  1. 维度不兼容时 NumPy 抛 ValueError: operands could not be broadcast together——先打印各自 shape 定位
  2. (n,)(n, 1)(1, n) 在广播中语义完全不同,不要凭直觉写
  3. 广播本身不复制数据(内部用 stride trick),但运算结果会分配新内存
  4. 标准化时分母 std 可能为 0(常数列),结果出现 inf / nan,生产代码应加 epsilon
  5. 高维张量建议始终用 keepdims=True 聚合,结果形状更可控

小结

  • 广播是 NumPy 高性能表达力的核心,消除了大量显式循环
  • 记住三条规则(后向对齐、为 1 可扩、缺失补 1),绝大多数场景都能自洽
  • 主动使用 np.newaxis / expand_dims 控制广播方向,比依赖隐式 (n,) 更清晰