一份实用 NumPy 代数指南,覆盖数组代数、广播、矩阵乘法、张量、多项式、最小二乘、特征值和数值精度。

  • 理解逐元素代数与矩阵代数的区别。
  • 有意识地使用 shape、axis、广播和 dtype。
  • 应用多项式、线性系统、投影和张量式操作。
  • 通过残差、恒等式和数值稳定性检查验证代数结果。

示例均为独立代码片段,可复制到 Python 3 环境中运行。

1. 把数组视为代数对象

NumPy 数组支持标量、向量、矩阵和高维张量代数。首先应明确语义:数组代表独立数值、坐标、观测行、特征列,还是线性算子?shape 和 dtype 只编码了部分含义,程序员还必须在整个计算过程中保持约定一致。

对象

典型形状

代数角色

标量

()

单个系数或数值

向量

(n,)

坐标或系数

列向量

(n, 1)

明确的矩阵兼容向量

矩阵

(m, n)

线性映射或系数系统

张量

(d₁, d₂, ..., dₖ)

批次或多轴数据

矩阵批次

(batch, m, n)

重复代数操作

import numpy as np

scalar = np.array(3.0)
vector = np.array([1.0, 2.0, 3.0])
column = vector[:, None]
matrix = np.arange(12.0).reshape(3, 4)
tensor = np.arange(24.0).reshape(2, 3, 4)

for name, value in [("scalar", scalar), ("vector", vector), ("column", column), ("matrix", matrix), ("tensor", tensor)]:
    print(name, value.shape, value.ndim, value.dtype)

Shape 是代数的一部分

  • 表达一维向量行为时使用 (n);需要明确列向量时使用 (n, 1)。
  • 每次矩阵乘法前检查内部维度是否匹配。
  • 在可复用代码中把轴含义视为 API 约定。
  • 选择能够表示中间结果的 dtype,而不只是能够表示最终结果的类型。

2. 逐元素运算、广播与归约

NumPy 的算术运算默认是逐元素的。广播允许尾部维度兼容的数组参与运算,而不需要手动复制数值。sum、mean、min 和 max 等归约会压缩一个或多个轴。这种组合支持简洁的向量化代数,但意外的 shape 也可能产生看似合理、实际含义错误的结果。

图 1:(3, 1) 和 (1, 4) 的数组广播后得到 (3, 4) 结果。

A = np.arange(12.0).reshape(3, 4)
row_offset = np.array([10.0, 20.0, 30.0])[:, None]
column_scale = np.array([1.0, 0.5, 2.0, 1.5])

shifted = A + row_offset
scaled = A * column_scale
centered = A - A.mean(axis=0, keepdims=True)
row_norms = np.linalg.norm(A, axis=1)
print(shifted.shape, scaled.shape, centered.mean(axis=0), row_norms)

操作

示例

常见用途

逐元素加法

A + b

偏移和校准

逐元素乘法

A * scale

特征或坐标缩放

幂运算

x ** 2

能量和平方误差

归约

A.sum(axis=0)

列总和

条件运算

np.where(mask, a, b)

分段代数

裁剪

np.clip(x, lo, hi)

限制数值范围

广播检查表

  • 从最后一个轴开始向前比较维度。
  • 两个维度相等,或其中一个为 1 时兼容。
  • 方向重要时使用 None 或 reshape 显式增加单例轴。
  • 在测试中打印中间 shape,并用 assert 固化约定。

3. 矩阵乘法与几何变换

@ 运算符执行矩阵乘法,与 * 不同。矩阵可以把向量映射到向量、与另一个矩阵组合并变换几何对象。转置、逆、行列式、秩和范数描述不同性质,不能把任何一个当作理解运算的通用替代品。

图 2:矩阵乘法改变单位圆的几何形状。

A = np.array([[2.0, 0.5], [0.0, 1.5]])
B = np.array([[1.0, -1.0], [2.0, 0.0]])
x = np.array([3.0, 4.0])

print("element-wise:", A * B)
print("matrix product:
", A @ B)
print("transformed vector:", A @ x)
print("determinant:", np.linalg.det(A))
print("rank:", np.linalg.matrix_rank(A))

性质

NumPy 表达式

含义

转置

A.T

交换矩阵轴

矩阵乘法

A @ B

组合线性映射

行列式

np.linalg.det(A)

方阵的有符号体积尺度

秩

np.linalg.matrix_rank(A)

独立方向数量

逆

np.linalg.inv(A)

可逆时映射回去;谨慎使用

条件数

np.linalg.cond(A)

对输入扰动的敏感性

不要混淆代数含义

A.T 不是矩阵逆。A * B 不是 A @ B。方阵行列式非零表示可逆,但不保证数值上适合求逆。求解 Ax=b 时应优先使用 solve,而不是显式构造逆矩阵。

4. 多项式代数、根与近似

NumPy 的 polyval 和 roots 使用降幂系数顺序表示多项式。多项式操作包括求值、求导、积分、拟合和求根。现代代码也可以使用 polynomial 命名空间,其系数顺序更容易推理;不同约定混用前必须确认。

图 3:NumPy 可以计算二次多项式并寻找其根。

# 降幂系数:x^2 - 3x + 2
coefficients = np.array([1.0, -3.0, 2.0])
x = np.linspace(-1, 4, 100)
y = np.polyval(coefficients, x)
roots = np.roots(coefficients)
derivative = np.polyder(coefficients)
antiderivative = np.polyint(coefficients)
print(roots)
print(np.polyval(derivative, 1.0))

操作

函数

结果

求值

np.polyval(c, x)

多项式值

求根

np.roots(c)

可能为复数的根

求导

np.polyder(c)

导数系数数组

积分

np.polyint(c)

原函数系数

拟合

np.polyfit(x, y, degree)

最小二乘多项式系数

卷积

np.polynomial.polynomial.polymul

按指定系数约定相乘

多项式稳定性

高阶多项式的求值和求根可能是病态的。适当缩放或中心化 x,检查 p(root) 的残差,不要解释超过数据和数值方法支持精度的根。

5. 线性系统、最小二乘与投影

线性代数是 NumPy 代数的重要部分。solve 处理方阵系统,lstsq 处理超定系统,并返回秩和奇异值信息。最小二乘解是目标向量在设计矩阵列空间上的投影。

图 4:最小二乘直线使观测值与预测值之间的平方残差最小。

rng = np.random.default_rng(2026)
x = np.linspace(0, 10, 50)
y = 1.5 * x + 2 + rng.normal(0, 1.5, size=x.size)
A = np.column_stack([x, np.ones_like(x)])

coefficients, residuals, rank, singular_values = np.linalg.lstsq(A, y, rcond=None)
prediction = A @ coefficients
relative_residual = np.linalg.norm(prediction - y) / np.linalg.norm(y)
print(coefficients, rank, singular_values, relative_residual)

任务

方法

检查

求解 Ax=b

np.linalg.solve(A, b)

相对残差

拟合线性模型

np.linalg.lstsq(A, y)

残差、秩和奇异值

最小范数解

np.linalg.pinv(A) @ b

敏感性和截断

投影

A @ np.linalg.lstsq(A, y)[0]

残差正交性

多个目标

solve(A, B) 或 lstsq(A, B)

检查每个目标列

残差与正交性检查

  • 计算绝对和相对残差;相对于很小的目标值,绝对残差也可能很大。
  • 最小二乘中,残差应近似与 A 的各列正交。
  • 特征冗余或近似共线时检查数值秩。
  • 使用已知合成系数向量,区分求解器误差和观测噪声。

6. 高维数组、张量运算与 einsum

高维数组通过增加批次或特征轴推广矩阵代数。reshape 改变兼容数据的形状;transpose 或 moveaxis 改变轴顺序;einsum 使用索引收缩表达式,可以在明确记录索引含义后替代嵌套循环。

图 5:三维数组的不同切片拥有不同 shape 和含义。

tensor = np.arange(24.0).reshape(2, 3, 4)

first_batch = tensor[0]
last_feature = tensor[:, :, -1]
transposed = tensor.transpose(2, 0, 1)

# 对每个批次进行矩阵式加权
weights = np.arange(4.0)
weighted = np.einsum("bif,f->bi", tensor, weights)
print(first_batch.shape, last_feature.shape, transposed.shape, weighted.shape)

操作

模式

用途

重塑

x.reshape(...)

改变形状但不改变元素总数

转置

x.transpose(...)

重新排列轴

广播

x + bias

应用低维量

堆叠

np.stack([...], axis=...)

创建新轴

拼接

np.concatenate([...], axis=...)

沿已有轴连接

Einstein 求和

np.einsum('bif,f->bi', ...)

显式索引收缩

可读的张量代数

使用能表达含义的变量名和注释,说明每个轴代表什么。复杂 einsum 表达式优化前,应先写一个参考实现,或与 matmul 和归约结果进行测试。

7. 数值精度、特征值与验证

浮点代数是近似的。由于消去、溢出、下溢和运算顺序,代数等价的公式可能有不同舍入行为。NumPy 提供 allclose 和 isclose 进行带容差比较。特征值和特征向量应通过残差恒等式检查,而不是要求小数完全相等。

图 6:代数等价表达式在小参数下可能产生不同数值结果。

图 7:特征向量表示对称矩阵在缩放下保持的方向。

A = np.array([[3.0, 1.0], [1.0, 2.0]])
values, vectors = np.linalg.eigh(A)
for i, value in enumerate(values):
    vector = vectors[:, i]
    assert np.allclose(A @ vector, value * vector)

x = np.array([1.0, 1.0 + 1e-12])
y = np.array([1.0, 1.0])
print(np.array_equal(x, y))
print(np.allclose(x, y, rtol=1e-10, atol=1e-12))

验证

问题

实践

形状断言

维度是否有意设计?

assert x.shape == expected

有限性检查

数值是否有效?

np.isfinite(x).all()

恒等式检查

已知恒等式是否成立?

np.allclose(left, right)

残差检查

解是否满足方程?

||Ax−b|| / ||b||

秩检查

方向是否独立?

matrix_rank 和奇异值

容差记录

何时算相等?

记录 rtol 和 atol

NumPy 代数最终检查表

  • 区分逐元素运算、广播和矩阵乘法。
  • 明确轴含义、shape、dtype 和单位。
  • 保持多项式系数约定一致,检查根和残差。
  • 方程组使用 solve 或 lstsq,检查秩、条件性和残差。
  • 使用容差和数学恒等式验证浮点结果。

更多推荐