Skip to content

数组运算 ​

NumPy 的核心优势之一是向量化运算——无需显式循环即可对整个数组进行数学运算。本章涵盖元素级运算、广播机制、规约运算和线性代数。

一、元素级运算 ​

NumPy 的算术运算默认是逐元素(element-wise)进行的。

1、算术运算符 ​

python
import numpy as np

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

print(a + b)      # [11 22 33 44]     加法
print(a - b)      # [-9 -18 -27 -36]  减法
print(a * b)      # [10 40 90 160]    乘法
print(a / b)      # [0.1 0.1 0.1 0.1] 除法
print(a // b)     # [0 0 0 0]         整除
print(a ** b)     # [1, 2**20, ...]   幂运算
print(a % b)      # [1 2 3 4]         取模

2、等价函数形式 ​

每个运算符都有对应的函数,支持更多参数(如 out 指定输出位置):

python
np.add(a, b)          # a + b
np.subtract(a, b)     # a - b
np.multiply(a, b)     # a * b
np.divide(a, b)       # a / b
np.floor_divide(a, b) # a // b
np.power(a, b)        # a ** b
np.mod(a, b)          # a % b
np.negative(a)        # -a
np.absolute(a)        # |a|

3、标量广播 ​

数组与标量运算,标量自动广播到每个元素:

python
a = np.array([1, 2, 3, 4])

print(a * 2)       # [2 4 6 8]
print(a + 100)     # [101 102 103 104]
print(a ** 0.5)    # [1.   1.41 1.73 2.  ]

二、广播机制(Broadcasting) ​

广播是 NumPy 处理不同形状数组运算的核心机制。它避免了显式复制数据,使运算高效且节省内存。

1、广播规则 ​

当两个数组形状不同时,NumPy 按以下规则自动扩展:

规则一:如果两个数组维度数不同,形状较短的数组在左侧补 1。

形状 (3, 4) + 形状 (4,) → (3, 4) + (1, 4)

规则二:如果某个维度大小为 1,则该维度"拉伸"到与另一个数组匹配。

(3, 4) + (1, 4) → (3, 4) + (3, 4) → 结果 (3, 4)

规则三:如果两个数组在某个维度上大小不同且都不为 1,则报错。

(3, 4) + (3, 5) → ValueError!

2、广播示例 ​

python
# 二维 + 一维
A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])         # shape (3, 3)

b = np.array([10, 20, 30])       # shape (3,) → 广播为 (1, 3) → (3, 3)

print(A + b)
# [[11 22 33]
#  [14 25 36]
#  [17 28 39]]

# 列向量 + 行向量
col = np.array([[1], [2], [3]])   # shape (3, 1)
row = np.array([10, 20, 30])     # shape (3,)  → (1, 3)

print(col + row)
# [[11 21 31]
#  [12 22 32]
#  [13 23 33]]                    — 结果 shape (3, 3)

3、广播的实际应用 ​

python
# 数据标准化:每列减去均值,除以标准差
data = np.random.randn(1000, 5)    # 1000 个样本,5 个特征
mean = data.mean(axis=0)           # shape (5,)
std  = data.std(axis=0)            # shape (5,)

normalized = (data - mean) / std   # 广播!shape (1000, 5)

# 距离矩阵计算
points = np.random.rand(100, 2)    # 100 个二维点
diff = points[:, np.newaxis, :] - points[np.newaxis, :, :]  # (100,100,2)
dist = np.sqrt((diff ** 2).sum(axis=2))  # (100, 100)

三、规约运算(Reduction) ​

规约运算沿指定轴(axis)聚合数组元素。

1、基本规约函数 ​

python
a = np.array([[1, 2, 3],
              [4, 5, 6]])

print(np.sum(a))          # 21       — 所有元素求和
print(np.sum(a, axis=0))  # [5 7 9]  — 沿行求和(列方向压缩)
print(np.sum(a, axis=1))  # [ 6 15]  — 沿列求和(行方向压缩)

axis 的直觉理解

  • axis=0:沿行方向操作,压缩行,结果形状去掉第 0 维 → 列方向聚合
  • axis=1:沿列方向操作,压缩列,结果形状去掉第 1 维 → 行方向聚合

口诀:指定哪个 axis,就"消灭"哪个 axis。

2、常用规约函数 ​

函数说明
np.sum(a, axis)求和
np.prod(a, axis)连乘
np.mean(a, axis)均值
np.std(a, axis)标准差
np.var(a, axis)方差
np.min(a, axis)最小值
np.max(a, axis)最大值
np.argmin(a, axis)最小值索引
np.argmax(a, axis)最大值索引
np.cumsum(a, axis)累加和
np.cumprod(a, axis)累乘积
np.any(a, axis)任一为 True
np.all(a, axis)全部为 True

3、keepdims 保持维度 ​

python
a = np.array([[1, 2, 3],
              [4, 5, 6]])

# 不保持维度
print(np.sum(a, axis=0).shape)       # (3,)

# 保持维度(方便后续广播)
print(np.sum(a, axis=0, keepdims=True).shape)  # (1, 3)

# 实用场景:每行归一化
row_sums = a.sum(axis=1, keepdims=True)  # shape (2, 1)
normalized = a / row_sums
print(normalized)
# [[0.167 0.333 0.5  ]
#  [0.267 0.333 0.4  ]]

四、比较运算 ​

1、元素级比较 ​

python
a = np.array([1, 2, 3, 4, 5])
b = np.array([5, 4, 3, 2, 1])

print(a == b)    # [False False  True False False]
print(a > b)     # [False False False  True  True]
print(a >= 3)    # [False False  True  True  True]

2、数组级比较 ​

python
a = np.array([1, 2, 3])
b = np.array([1, 2, 3])
c = np.array([1, 2, 4])

print(np.array_equal(a, b))   # True
print(np.array_equal(a, c))   # False

五、数学函数 ​

NumPy 提供了大量通用数学函数(ufunc),都是元素级操作:

python
a = np.array([0, np.pi/4, np.pi/2, np.pi])

# 三角函数
print(np.sin(a))     # [0.    0.707 1.    0.   ]
print(np.cos(a))     # [ 1.    0.707  0.   -1.  ]
print(np.tan(a))     # [ 0.    1.     inf  -0.  ]

# 反三角函数
print(np.arcsin([0, 0.5, 1]))  # [0.    0.524 1.571]

# 指数与对数
print(np.exp([1, 2, 3]))        # [ 2.718  7.389 20.086]
print(np.log([1, np.e, np.e**2]))  # [0. 1. 2.]
print(np.log10([1, 10, 100]))    # [0. 1. 2.]
print(np.log2([1, 2, 4, 8]))    # [0. 1. 2. 3.]

# 取整
print(np.round(3.14159, 2))     # 3.14
print(np.floor([1.7, 2.3]))     # [1. 2.]
print(np.ceil([1.1, 2.8]))      # [2. 3.]
print(np.trunc([-1.7, 1.7]))    # [-1.  1.]

# 其他
print(np.abs([-1, -2, 3]))      # [1 2 3]
print(np.sqrt([4, 9, 16]))      # [2. 3. 4.]
print(np.clip(a=[1,5,10], a_min=2, a_max=8))  # [2 5 8]

六、线性代数 ​

1、矩阵运算 ​

python
A = np.array([[1, 2],
              [3, 4]])

B = np.array([[5, 6],
              [7, 8]])

# 矩阵乘法(三种等价写法)
print(A @ B)              # 推荐写法
print(np.dot(A, B))
print(np.matmul(A, B))
# [[19 22]
#  [43 50]]

# 逐元素乘法(不是矩阵乘法!)
print(A * B)
# [[ 5 12]
#  [21 32]]

# 转置
print(A.T)
# [[1 3]
#  [2 4]]

@ 与 * 的区别

A @ B 是矩阵乘法(线性代数),A * B 是逐元素乘法(Hadamard 积)。两者完全不同!

2、np.linalg 线性代数模块 ​

python
A = np.array([[1, 2],
              [3, 4]])

# 行列式
print(np.linalg.det(A))        # -2.0

# 逆矩阵
print(np.linalg.inv(A))
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 验证:A @ A⁻¹ = I
print(np.allclose(A @ np.linalg.inv(A), np.eye(2)))  # True

# 特征值与特征向量
eigenvalues, eigenvectors = np.linalg.eig(A)
print(eigenvalues)    # [-0.372  5.372]

# 解线性方程组 Ax = b
b = np.array([5, 7])
x = np.linalg.solve(A, b)
print(x)              # [-3.  4.]

# 奇异值分解(SVD)
U, S, Vt = np.linalg.svd(A)

# QR 分解
Q, R = np.linalg.qr(A)

# Cholesky 分解(矩阵必须正定)
P = np.array([[4, 2], [2, 3]])
L = np.linalg.cholesky(P)

# 范数
print(np.linalg.norm(A))          # Frobenius 范数
print(np.linalg.norm(A, ord=1))   # 1-范数
print(np.linalg.norm(A, ord=np.inf))  # 无穷范数

# 矩阵秩
print(np.linalg.matrix_rank(A))   # 2

# 伪逆(Moore-Penrose)
print(np.linalg.pinv(A))

3、矩阵运算速查 ​

操作代码
矩阵乘法A @ B
逐元素乘法A * B
转置A.T
共轭转置A.conj().T
行列式np.linalg.det(A)
逆矩阵np.linalg.inv(A)
特征值分解np.linalg.eig(A)
SVDnp.linalg.svd(A)
解 Ax=bnp.linalg.solve(A, b)
最小二乘np.linalg.lstsq(A, b)
范数np.linalg.norm(A)
秩np.linalg.matrix_rank(A)
迹np.trace(A)

4、批量线性代数运算 ​

python
# 对一批矩阵同时运算(向量化)
batch = np.random.randn(100, 3, 3)    # 100 个 3×3 矩阵

dets = np.linalg.det(batch)            # 100 个行列式
invs = np.linalg.inv(batch)            # 100 个逆矩阵
eigenvalues, _ = np.linalg.eig(batch)  # 100 组特征值

📖 官方文档