数组运算
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) |
| SVD | np.linalg.svd(A) |
| 解 Ax=b | np.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 组特征值📖 官方文档