
本文深入解析为何对近奇异矩阵使用 a.inv() 与 a.adjugate() / a.det() 会得到截然不同(且均不可靠)的结果,并阐明其本质是矩阵严重病态导致的数值灾难;同时提供基于 numpy 的稳健伪逆解决方案。
本文深入解析为何对近奇异矩阵使用 a.inv() 与 a.adjugate() / a.det() 会得到截然不同(且均不可靠)的结果,并阐明其本质是矩阵严重病态导致的数值灾难;同时提供基于 numpy 的稳健伪逆解决方案。
在使用 SymPy 进行符号化或高精度数值矩阵运算时,一个常见却极易被忽视的陷阱是:对病态(ill-conditioned)甚至实质奇异(essentially singular)的浮点矩阵强行调用 .inv() 或手动计算 adj(A)/det(A),不仅得不到有效逆矩阵,反而会因极小行列式引发灾难性数值放大,导致结果完全失真。
您提供的 A_MATRIX_M 是一个 6×6 数值矩阵,表面看各项元素差异微小但结构高度相关——这正是病态矩阵的典型特征。我们来验证其数学本质:
import sympy as sp
# 复现您的矩阵(注意:使用 float 字面量,非符号数)
A = sp.Matrix([
[0.174683794941032, 0.174688696013867, 0.174688696013957, 0.174683794941032, 0.174688696013867, 0.174688696013957],
[0.152985639387222, 0.153053070078189, 0.153053070078123, 0.152985677401143, 0.153053070078189, 0.153053070078123],
[0.122289719307085, 0.12255894161074, 0.12255894161075, 0.122289780080333, 0.12255894161074, 0.12255894161075 ],
[0.0888623464387251,0.0894599879866164,0.0894599879864983,0.0888623905998649,0.0894599879866165,0.0894599879864984],
[0.0584375462953993,0.0593271595778474,0.0593271595779078,0.0584375753365935,0.0593271595778473,0.0593271595779078],
[0.034711924284652, 0.0356325950288525,0.035632595028856, 0.0347119415351321,0.0356325950288525,0.035632595028856 ]
])
print("行列式 det(A):", A.det().evalf()) # 输出:-9.86673942982700e-57 → 实质为 0
print("秩 rank(A):", A.rank()) # 输出:6(SymPy 符号引擎未识别数值退化)
print("特征值 eigenvals():", A.eigenvals())运行结果揭示关键事实:
- det(A) ≈ −1×10⁻⁵⁶:远低于双精度浮点相对误差限(≈1.1×10⁻¹⁶),数学上已等价于零;
- 特征值中包含 −3.6×10⁻¹⁴, −2.95×10⁻¹⁵, −6.35×10⁻¹⁷, 4.07×10⁻⁹ —— 四个特征值模长接近机器零,表明矩阵列空间存在严重线性相关;
- 尽管 A.rank() 返回 6(SymPy 对浮点数的符号化处理掩盖了数值秩亏),但其条件数 κ(A) = |λ_max| / |λ_min| ≈ 0.63 / 1e-14 ≈ 6×10¹³,属于典型的病态矩阵(条件数 > 10⁸ 即视为严重病态)。
❗ 两种“逆”为何天差地别?
- .inv() 方法:SymPy 内部采用符号高斯消元或 LU 分解,但面对极小主元时,舍入误差被指数级放大,最终输出看似“整齐”的 ~1e-38 级近零矩阵——这是数值坍塌(numerical underflow)的假象,并非真实逆。
- adj(A)/det(A) 方法:伴随矩阵 adj(A) 元素本身是 (n−1)×(n−1) 子式行列式,对病态矩阵仍具合理量级(如 ~1e+30),而 det(A) ≈ 1e-56,二者相除产生 ~1e+86 量级的荒谬结果——这是除零灾难(division-by-near-zero blowup)的必然后果。
二者均失效,根本原因在于:该矩阵在数值意义上不可逆,强行求逆无数学意义,仅暴露浮点计算的脆弱性。
✅ 正确解法:使用 Moore–Penrose 伪逆(Pseudoinverse)
当矩阵奇异或病态时,应放弃“精确逆”,转而求解最小二乘意义下的最优广义逆——即 Moore–Penrose 伪逆 A⁺。它稳定、唯一,且可通过 SVD 鲁棒计算:
import numpy as np
# 转为 NumPy 数组(保持双精度)
A_np = np.array(A.tolist(), dtype=float)
# 方法1:直接调用 numpy.linalg.pinv(推荐,自动截断小奇异值)
A_pinv = np.linalg.pinv(A_np, rcond=1e-12) # rcond 控制截断阈值
# 方法2:手动 SVD 验证(更透明)
U, s, Vt = np.linalg.svd(A_np)
# 屏蔽过小的奇异值(避免 1/s 导致爆炸)
s_inv = np.where(s > 1e-12, 1/s, 0)
A_pinv_svd = Vt.T @ np.diag(s_inv) @ U.T
print("伪逆 A⁺ 形状:", A_pinv.shape)
print("验证 A @ A⁺ ≈ 投影矩阵:\n", np.round(A_np @ A_pinv, decimals=12))输出中 A_np @ A_pinv 将接近一个秩为 rank(A)≈3~4 的正交投影矩阵(非单位阵),这正是病态系统下最合理的“逆”行为。
⚠️ 关键注意事项
- 永不信任病态矩阵的 inv() 结果:尤其当 abs(det(A)) < 1e-15 或 np.linalg.cond(A) > 1e10 时,应立即切换至伪逆;
- SymPy 不适合纯浮点病态计算:其符号引擎对 float 输入的数值鲁棒性弱于 NumPy 的 SVD 实现;
- 伪逆不是万能解:它给出的是最小二乘解 x = A⁺b,若原方程 Ax = b 本就无解,结果仅为最优逼近;
- 预处理优先:对实测数据矩阵,先做中心化、标准化,或使用 PCA 降维,可显著改善条件数。
综上,您遇到的“两种逆不一致”现象,绝非 SymPy 的 bug,而是数值线性代数的基本警示:当矩阵徘徊在奇异边缘时,‘求逆’本身就是一个病态问题;此时,拥抱伪逆,才是工程实践的理性选择。

















