AI 中的数值求解:使用 solve,避免显式求逆
使用 NumPy 求解 AI 参数恢复系统,再通过残差、条件数和奇异性检查判断答案是否可信。
AI 校准步骤可能需要从两个测量响应中恢复两个未知参数。这些方程小到可以写在白板上,但生产代码仍然要决定如何求解,以及如何检查答案。一个看起来合理的向量,并不能证明数据可靠地确定了它。
本课使用 NumPy 来建立这种判断。你将求解一个方阵线性系统,把 numpy.linalg.solve 与显式求逆路径进行比较,计算残差,测量条件数,并识别不存在唯一参数估计的奇异边界。
学习目标: 使用 numpy.linalg.solve,并解释为什么通常应避免显式求逆。
第 22 课:恒等变换与逆变换 展示了逆在数学上的含义。第 24 课:行阶梯形揭示结构 展示了主元和自由变量如何揭示系统能够确定什么。在这里,我们把那种精确代数连接到有限精度计算。官方的 Mathematics for Machine Learning companion 将这部分内容放在书籍 PDF的第 2.3 节;下面的校准场景、数字、代码、视觉组件和评估题均为原创内容。
线性系统是一份参数恢复契约
将未知参数向量写成
这个向量有两个坐标,因此系数矩阵 的每一行必须包含两个系数。如果两个观测产生右端向量 ,参数恢复系统的形式就是
对于一个很小的校准头, 可以是施加于某个信号的缩放值,而 可以是施加于另一个信号的、类似偏移的修正量。更一般地说,同一套记号可以描述一个小型线性层、传感器修正,或更大的 AI 流水线中的参数恢复子问题。 的各行是观测到的系数模式; 包含这些模式应当产生的响应。
如果 是方阵且可逆,前一课中的代数给出
这个恒等式对推理很有用,但并不意味着程序每次需要 时都应先构造 。
工作示例:恢复两个校准参数
考虑下面这个刻意缩小的系统
两个方程是
先用代入法求解,再让 NumPy 完成同样的工作。第一个方程给出 。将这个表达式代入第二个方程:
于是 ,因此
检查两个原始方程,而不只是检查变形后的方程:
为什么 solve 通常是正确的中间步骤
直接的数学表达式 隐藏了两个不同的计算任务:
| 路径 | 它计算什么 | 对单个右端项为何重要 |
|---|---|---|
np.linalg.solve(A, b) | 对 做分解,并将分解因子应用于 | 只计算所请求的向量。 |
np.linalg.inv(A) @ b | 构造 的每一列,然后与 相乘 | 计算的信息远多于这一次求解所需的信息。 |
对于单个右端项,solve 避免实体化整个逆矩阵。对于以 的列存储的多个右端项,使用 np.linalg.solve(A, B),这样可以让同一个系数矩阵只分解一次,同时计算所有请求的解。只有当应用确实需要逆矩阵本身时,才应保留逆矩阵;它不是求解的默认替代品。
当前的 NumPy solve 文档说明系数矩阵必须是方阵且满秩,并记录该例程使用 LAPACK 的 _gesv 实现。inv 文档也警告说,即使求逆没有抛出错误,病态矩阵仍可能产生不准确的结果。因此,应该在同一个思维模型中理解实现选择和诊断方法。
运行时间取决于硬件和库,不能作为可移植的基准。下面的示例只是一个说明:作者在主机上使用固定的随机 矩阵,进行五次计时重复,每次重复调用二十次:
from timeit import repeat
rng = np.random.default_rng(7)
A_timing = rng.normal(size=(64, 64)) + 64 * np.eye(64)
b_timing = rng.normal(size=64)
solve_time = min(
repeat(lambda: np.linalg.solve(A_timing, b_timing), repeat=5, number=20)
) / 20
inverse_time = min(
repeat(lambda: np.linalg.inv(A_timing) @ b_timing, repeat=5, number=20)
) / 20
print(solve_time * 1e6, inverse_time * 1e6, inverse_time / solve_time)
经过验证的运行大约打印出 15.2 µs、52.2 µs 和 3.43×,分别对应求解时间、逆矩阵路径时间和比值。将这些数字视为可复现的比较形态,而不是性能承诺:偏好 solve 的算法原因在于它避免计算未使用的矩阵,而不同的 BLAS 库或矩阵大小可能改变这个比值。
残差是拟合检查,不是可信度证明
对于工作示例,从直接路径计算残差:
浮点程序报告的范数接近 ,而不是恰好为零。这是返回向量拟合这个 的有力证据。不过,假设系数或响应来自经过四舍五入的传感器测量。求解器可以找到一个非常贴合这些舍入值的向量,但当测量值在其不确定度范围内变化时,这个向量仍可能发生显著移动。
条件数衡量敏感性
条件数比较矩阵放大相对输入变化的程度。对于可逆矩阵和 2-范数,
其中 和 是最大和最小奇异值。接近 的条件数表示各个方向较为均衡。较大的值意味着输入中的某些微小变化可能导致解出现大得多的相对变化。如果最小奇异值为零,矩阵就是奇异的,条件数为无穷大。
对于受扰动的右端项,一个有用的界可以写成
这适用于非奇异系统的通常假设。这个不等式是对可能放大的警告,而不是承诺每个扰动都会达到最坏情况。
现在使用几乎相关的行:
第二行几乎是第一行的副本。对于显示的精确 ,求解会恢复 。只将第二个响应改变 ,再求解一次。NumPy 报告:
condition number: 40002.000074915224
original solution: [ 2. -1.]
perturbed solution: [ 1.99 -0.99]
relative input change: 7.071421374297804e-07
relative solution change: 0.006324555320519327
perturbed residual norm: 0.0
输入相对变化约为 ,而参数向量相对变化约为 。这大致是四个数量级的条件数所体现出的敏感性。受扰动的解对于受扰动的数据仍有零残差:它正确地求解了新的方程,但这些数据并没有稳定地识别出参数向量。
静态稳定性图
很小的残差也可能隐藏敏感的解
残差检查已经求解的方程。条件数则提醒我们:输入数据变化时,解可能移动多少。
| 情形 | 条件数 | 相对输入变化 → 解变化 | 残差与解释 |
|---|---|---|---|
| 良态 | 2.62 | 1.2 × 10⁻⁷ → 2.0 × 10⁻⁷ | 残差约为 10⁻¹⁵;在这个良态例子中两条路径一致。 |
| 病态 | 4.0002 × 10⁴ | 7.1 × 10⁻⁷ → 6.3 × 10⁻³ | 扰动数据的残差为 0,但 7.1 × 10⁻⁷ 的输入变化使 x 变化 6.3 × 10⁻³。 |
| 奇异 | ∞ | 不适用 | κ₂(A) 为无穷大;solve 和 inverse 都报告奇异矩阵错误,而不是返回唯一答案。 |
一起阅读两个诊断:残差问“这个 x 是否拟合 b?”;条件数问“如果 b 或 A 稍有不同,x 会移动多少?”
这个视觉组件将两个问题分开。残差问的是候选值是否拟合当前内存中的方程。条件数问的是方程发生微小变化时,该候选值是否会移动。AI 校准系统需要这两个问题的答案。
边界情形:奇异意味着“没有唯一解”
考虑
第二行是第一行的两倍,因此 的秩为 ,而不是 。当 时,方程相容但有无穷多个解:所有满足 的 都可以。当 时,方程不相容,因为第二个方程要求左端是第一个方程左端的两倍,却给出了不同的右端。
对于 np.linalg.solve,这两种情况都没有可以返回的唯一向量。在捆绑的运行时中,np.linalg.solve(C, [3, 6]) 和 np.linalg.inv(C) 都会抛出 numpy.linalg.LinAlgError: Singular matrix。NumPy solve 文档说明了满秩要求,并在非方阵或秩亏问题适合最小二乘解时指向 numpy.linalg.lstsq。这是不同于求解可逆方阵的问题,因此不要默默地用后一种方法替换前一种。
面向 AI 系统的规范 NumPy 工作流
对于小型参数恢复子问题,一个有充分依据的工作流是:
- 检查契约。 确认 、 和目标参数向量的维度一致。
- 直接求解。 对方阵、满秩系统使用
np.linalg.solve(A, b);问题没有良好确定时,使用专为最小二乘设计的方法。 - 测量残差。 计算
np.linalg.norm(A @ x - b),并将其与 的尺度比较,而不是只与任意的绝对阈值比较。 - 检查条件数。 计算
np.linalg.cond(A),并将可能的敏感性与测量精度及下游计划使用的位数进行比较。 - 把失败当作信息。 奇异错误、极大的条件数,或相对于 较大的残差,都应触发对数据、特征、缩放或模型设计的调查。
在校准或参数恢复任务中,这套工作流会把一组权重变成可审计的结果。很小的残差可以支持这样的说法:实现求解了所提供的方程。适中的条件数和扰动检查则提供证据,说明这些方程对预期用途而言足够有力地约束了参数。任何一个诊断都不能证明更广泛的 AI 系统是准确的;它们回答的是可以测试的、更狭窄的实现问题。
下一课将暂时离开数值算法,询问在常见 AI 张量使用的数系中,哪些代数运算是有效的。现在先记住这个实用规则:求解你手上的系统,检查你得到的残差,并通过条件数询问一个相近的系统是否会讲述不同的参数故事。
检查你的理解
问题 1
对于 A ∈ R^(m×n)、x ∈ R^n 和 b ∈ R^m,Ax = b 要求程序找出什么?
显示分步解答
它要求找出一个有 个坐标的向量 ,使它与 矩阵 相乘后得到有 个坐标的向量 。首先要检查形状: 的每一行必须有 个元素,这样它与 的点积才有定义,并且 的每个元素都必须对应一个结果方程。
问题 2
手算 3x_1 + x_2 = 8 和 x_1 + 2x_2 = 1。
显示分步解答
由第一个方程可得 。代入第二个方程:,所以 ,且 。然后 。解是 ,代入两个原始方程即可检查:,而 。
问题 3
某个计算向量的残差为 r = [0.0001, -0.0002]。残差范数告诉你什么,又不能告诉你什么?
显示分步解答
残差范数是
它告诉我们候选值在多大程度上满足所提供的方程。它不能告诉我们真实参数是否接近、矩阵是否良态,或测量是否准确。这些问题需要条件数、数据尺度和建模检查。
问题 4
为什么 solve(A, b) 和 inv(A) @ b 在数学上可以一致,而 solve(A, b) 仍然是更推荐的代码路径?
显示分步解答
如果 可逆,恒等式 证明两条路径都指向同一个数学向量。实现路径却不同:solve 对 做分解,并将分解因子应用于所请求的右端项;inv 则先构造逆矩阵的每一列,再进行乘法。因此,对于单次求解,逆矩阵路径会进行额外工作并创建一个不需要的中间矩阵。
问题 5
某个系统的条件数约为 40,000。残差为零,是否足以让你放心报告 x 的许多位数字?
显示分步解答
不。零残差只说明在当前算术中,报告的 拟合所提供的 。接近 的条件数说明, 或 的小幅相对变化,可能在最坏方向上按大约这个尺度被放大。应报告与测量不确定度一致的精度,检查奇异值或条件数,并在认为许多位数字有意义之前测试扰动。
问题 6
有无穷多个解的奇异系统与无解的奇异系统有什么区别?
显示分步解答
两者都有相关行,因此系数矩阵无法确定唯一向量。如果右端项具有同样的相关性,例如 的 ,方程相容且有无穷多个解。如果右端项破坏了这种相关性,例如 ,行化简会产生类似 的矛盾,因此没有解。这两种情况都不是有效的、可以产生唯一 solve 结果的输入。
问题 7
在 AI 校准头中调用 np.linalg.solve(A, b) 后,你会记录哪些证据?
显示分步解答
记录矩阵和向量的形状、返回的参数向量、残差范数 ,以及 np.linalg.cond(A) 这样的条件数诊断。如果测量的单位、缩放或不确定度会影响解释,也要记录这些细节。残差支持关于拟合所提供方程的说法;条件数和扰动检查则支持另一种关于参数敏感性的说法。
问题 8
代码审查建议每次请求都使用 np.linalg.inv(A) @ b,包括可能秩亏的矩阵。你应如何修改?
显示分步解答
在检查问题确实应为方阵且满秩之后,将默认路径替换为 np.linalg.solve(A, b)。捕获或暴露 LinAlgError,而不是将其隐藏,并在求解后计算残差。当输入敏感性很重要时,加入 np.linalg.cond(A) 或等效的条件数检查。如果数据是矩形的或秩亏的,应选择并记录最小二乘或正则化形式,而不是假装逆矩阵存在。