如何在Python中利用NumPy实现矩阵的奇异值分解(SVD)?

2026-06-03 132370 日常编程

numpy.linalg.svd 返回U、s、Vh三元组,s为降序一维数组,Vh是V的共轭转置;full_matrices=False更高效;重建需构造匹配形状的对角矩阵;处理小奇异值需设阈值防除零;结果符号不唯一。

NumPy 的 numpy.linalg.svd 是最直接、最可靠的 SVD 实现方式,它返回标准的 UsVh 三元组,无需额外封装或手动拼接。

如何调用 numpy.linalg.svd 并理解返回值

该函数默认返回完整分解(full_matrices=True),但多数场景下你只需要“精简”形式。注意 s 是一维数组,不是对角矩阵;VhV 的共轭转置(即 Hermitian),不是 V 本身。

  • U 是左奇异向量矩阵,形状为 (m, m)(full)或 (m, k)(reduced),列正交
  • s 是奇异值数组,长度为 min(m, n),按降序排列
  • Vh 是右奇异向量的共轭转置,形状为 (n, n)(full)或 (k, n)(reduced)
  • 若只需降维或重建近似矩阵,务必设 full_matrices=False,否则内存和计算开销陡增
import numpy as np
A = np.array([[1, 2, 3], [4, 5, 6]])
U, s, Vh = np.linalg.svd(A, full_matrices=False)  # 推荐:节省空间
# 此时 U.shape == (2, 2), s.shape == (2,), Vh.shape == (2, 3)

重建原矩阵时为什么 U @ np.diag(s) @ Vh 不总是成立?

因为 np.diag(s) 生成的是方阵,而 UVhfull_matrices=False 下维度不匹配。必须手动扩展 s 成合适形状的中间矩阵,或用广播技巧。

Python 3.14.3

微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。

  • 错误写法:U @ np.diag(s) @ Vh → 形状不兼容(如 (2,2) @ (2,2) @ (2,3) 中间结果是 (2,2) @ (2,3) 才对)
  • 正确做法:构造 (U.shape[1], Vh.shape[0]) 大小的零矩阵,再把 s 填入对角线
  • 更简洁方式:用 np.zeros((U.shape[1], Vh.shape[0])) + np.fill_diagonal,或直接用 np.einsum
# 安全重建(适用于 full_matrices=False)
s_mat = np.zeros((U.shape[1], Vh.shape[0]))
np.fill_diagonal(s_mat, s)
A_recon = U @ s_mat @ Vh

处理非满秩或含零奇异值的矩阵时要注意什么?

numpy.linalg.svd 本身能稳定处理秩亏矩阵,但后续使用 s 时容易因数值误差触发除零或精度问题。

  • 奇异值接近零(如 1e-16)不代表严格为零,取决于矩阵条件数和浮点精度
  • 做伪逆或降维时,别直接用 1/s,应先设阈值截断小奇异值:s_inv = np.where(s > 1e-10, 1/s, 0)
  • np.linalg.matrix_rank(A, tol=1e-10) 辅助判断有效秩,比数 s > 0 更鲁棒
  • 如果输入含 naninfsvd 会报 LinAlgError: SVD did not converge,需提前清洗

真正容易被忽略的是:SVD 结果不唯一——UVh 的符号可能翻转(只要保持 U @ diag(s) @ Vh 不变),不同 NumPy 版本或硬件上可能给出不同符号组合,这在跨环境比对结果时会引发误判。

如何在Python中使用asyncio.shield防止关键任务被取消?

asyncio.shield必须用于关键清理操作(如日志落盘、事务提交、连接关闭),防止被取消中断导致数据不一致;应只包裹协程本身(shield(coro)),而非Task,且内部子await需单独shield。 asyncio.shield什么时候必须用? 当一个asyncio.Task正在执行关键清理、日志落盘、数据库事务提交或网络连接优雅关闭时,如果外部调用task.cancel()或超时触...

如何在Python 3中正确判断一个对象是否为某个类的实例?

应优先使用isinstance()而非type()做类型判断,因isinstance()遵循MRO、支持继承与ABC,体现鸭子类型哲学;type()仅判别直接类型,适用极少数需严格身份校验的场景。 用isinstance()而不是type() 直接比较type(obj)==SomeClass在绝大多数场景下是错的——它无法识别子类实例。比如isinstance(42,int)返回True,但typ...

如何在Python中自定义NumPy的打印格式以方便调试?

np.set_printoptions()可直接控制NumPy数组显示格式;默认格式因小数位不一致、科学计数法混用及自动截断而影响调试效率;关键参数需协同设置,如precision控制有效位数、suppress禁用科学计数法、threshold避免省略、linewidth防换行混乱、floatmode='fixed'统一小数位;调试时应按目标切换组合,并注意全局生效与及时还原。 直接用np.set...

如何在 Python 模块中安全使用主程序的循环变量

python模块具有独立作用域,无法直接访问导入它的主程序中的变量(如循环计数器i);正确做法是通过函数参数显式传递值,而非重复import。 python模块具有独立作用域,无法直接访问导入它的主程序中的变量(如循环计数器i);正确做法是通过函数参数显式传递值,而非重复import。 在Python中,模块(.py文件)被设计为独立的命名空间单元,其作用域与导入它的脚本完全隔离。这意味着:主程序...

如何利用Python Django的Include标签拆分大型模板文件?

{%include%}能拆分模板但需注意变量作用域、路径规则(仅从templates根目录解析)和循环中重复渲染的性能问题,非万能解法。 直接说结论:用{%include%}能拆分模板,但必须注意变量作用域、路径解析规则和循环内重复渲染的性能陷阱——它不是“无脑切文件”的万能解药。 include标签怎么写才不报TemplateDoesNotExist Django的{%include%}默认只在...

如何利用Python NumPy计算两个向量之间的余弦相似度?

最可靠的方法是手动用numpy.dot和numpy.linalg.norm计算余弦相似度:cosθ=dot(a,b)/(norm(a)*norm(b)),需确保向量为1D、非零且已归一化,避免隐式行为和形状错误。 用numpy.dot和numpy.linalg.norm手动计算最可靠 直接调用现成的余弦相似度函数容易踩坑——比如sklearn.metrics.pairwise.cosine_sim...

如何在Python中通过NumPy实现一维信号的平滑处理?

用np.convolve做移动平均需归一化kernel并截取中间部分;推荐镜像填充防边界畸变,小核用cumsum加速;savgol_filter参数需满足polyorder<window_length且window_length为奇数。 用np.convolve做简单移动平均平滑,但要注意边界处理 直接调用np.convolve是最常用的一维信号平滑方式,但它默认做的是“全卷积”,输出比原信...

如何在Python中将同步生成器重构为异步生成器yield?

异步生成器需用asyncdef+asyncyield,且内部只能混用await和yield,不能含同步阻塞调用;返回async_generator,须用asyncfor遍历,I/O操作需替换为异步版本(如aiofiles),CPU密集型任务应交由run_in_executor处理。 asyncdef里不能直接用yield Python的异步生成器不是给普通yield加个async就能跑通的。如果你...