如何用 Python 从零开始计算 SVD

矩阵分解,也称为矩阵因子分解,涉及使用其组成元素来描述给定的矩阵

奇异值分解(SVD)可能是最广为人知和广泛使用的矩阵分解方法。所有矩阵都具有SVD,这使其比其他方法(如特征分解)更稳定。因此,它常用于各种应用中,包括压缩、去噪和数据降维。

在本教程中,您将学习奇异值分解方法,用于将矩阵分解为其组成元素。

完成本教程后,您将了解:

  • 什么是奇异值分解以及它涉及哪些内容。
  • 如何计算SVD并从SVD元素重构矩形和方阵。
  • 如何使用SVD计算伪逆和执行降维。

使用我的新书机器学习线性代数,启动您的项目,包括分步教程和所有示例的Python源代码文件。

让我们开始吧。

  • 2018年3月更新:修正了重建中的拼写错误。为清晰起见,将代码中的 V 改为 VT。修正了伪逆方程中的拼写错误。
  • 2019年4月更新:修正了 SVD 重建示例解释中数组大小的小错误。
A Gentle Introduction to Singular-Value Decomposition

奇异值分解简介
图片由Chris Heald提供,保留部分权利。

教程概述

本教程分为5个部分,它们是:

  1. 奇异值分解
  2. 计算奇异值分解
  3. 从 SVD 重构矩阵
  4. SVD 用于伪逆
  5. SVD 用于降维

在机器学习线性代数方面需要帮助吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

奇异值分解

奇异值分解,简称 SVD,是一种矩阵分解方法,用于将矩阵分解为其组成部分,以简化后续的某些矩阵计算。

为简单起见,我们将重点关注实值矩阵的 SVD,而忽略复数的情况。

其中 A 是我们希望分解的实数 m x n 矩阵,U 是一个 m x m 矩阵,Sigma(通常用大写希腊字母 Sigma 表示)是一个 m x n 对角矩阵,V^T 是一个 n x n 矩阵的转置,其中 T 是上标。

奇异值分解是线性代数的一个亮点。

— 第 371 页,《线性代数导论》,第五版,2016 年。

Sigma 矩阵中的对角值称为原始矩阵 A 的奇异值。U 矩阵的列称为 A 的左奇异向量,V 的列称为 A 的右奇异向量。

SVD 是通过迭代数值方法计算的。我们不会深入探讨这些方法的细节。每个矩形矩阵都有奇异值分解,尽管生成的矩阵可能包含复数,并且浮点运算的限制可能导致某些矩阵无法整齐地分解。

奇异值分解(SVD)提供了另一种分解矩阵的方式,将其分解为奇异向量和奇异值。SVD 允许我们发现与特征分解相同的一些信息。但是,SVD 更普遍适用。

— 第 44-45 页,《深度学习》,2016 年。

SVD 广泛应用于其他矩阵运算(如矩阵求逆)的计算中,也用作机器学习中的数据降维方法。SVD 还可用于最小二乘线性回归、图像压缩和数据去噪。

奇异值分解 (SVD) 在统计学、机器学习和计算机科学中有着广泛的应用。对矩阵应用 SVD 就像用 X 射线透视它一样……

— 第 297 页,《线性代数无废话指南》,2017

计算奇异值分解

SVD 可以通过调用 svd() 函数来计算。

该函数接受一个矩阵并返回 U、Sigma 和 V^T 元素。Sigma 对角矩阵以奇异值向量的形式返回。V 矩阵以转置形式返回,例如 V.T。

下面的示例定义了一个 3×2 矩阵并计算其奇异值分解。

运行示例首先打印定义的 3×2 矩阵,然后是 3×3 U 矩阵、2 元素 Sigma 向量和 2×2 V^T 矩阵元素,这些都是从分解中计算出来的。

从 SVD 重构矩阵

原始矩阵可以从 U、Sigma 和 V^T 元素重构。

从 svd() 返回的 U、s 和 V 元素不能直接相乘。

s 向量必须使用 diag() 函数转换为对角矩阵。默认情况下,此函数将创建一个相对于我们原始矩阵的 n x n 方阵。这导致了一个问题,因为矩阵的大小不符合矩阵乘法的规则,即一个矩阵的列数必须与后续矩阵的行数匹配。

创建方阵 Sigma 对角矩阵后,矩阵的大小相对于我们要分解的原始 m x n 矩阵,如下所示:

而实际上,我们需要:

我们可以通过创建一个所有元素为零的 m x n (例如,行数更多) 的新 Sigma 矩阵来实现这一点,并用通过 diag() 计算出的方对角矩阵填充该矩阵的前 n x n 部分。

运行示例首先打印原始矩阵,然后打印从 SVD 元素重构的矩阵。

上述 Sigma 对角线的复杂性只存在于 m 和 n 不相等的情况。在重构方阵时,对角矩阵可以直接使用,如下所示。

运行示例打印原始的 3×3 矩阵,以及直接从 SVD 元素重构的版本。

SVD 用于伪逆

伪逆是方阵逆的推广,适用于行数和列数不等的矩形矩阵。

它也被称为 Moore-Penrose 逆,以该方法的两位独立发现者命名,或称为广义逆。

矩阵求逆对非方阵没有定义。[…] 当 A 的列数多于行数时,使用伪逆求解线性方程提供了一种可能的解。

— 第 46 页,《深度学习》,2016。

伪逆表示为 A^+,其中 A 是被求逆的矩阵,+ 是上标。

伪逆是使用 A 的奇异值分解计算的

或者,不使用点符号

其中 A^+ 是伪逆,D^+ 是对角矩阵 Sigma 的伪逆,U^T 是 U 的转置。

我们可以从 SVD 运算中得到 U 和 V。

D^+可以通过从Sigma创建一个对角矩阵,计算Sigma中每个非零元素的倒数,并在原始矩阵是矩形的情况下进行转置来计算。

伪逆提供了一种解决线性回归方程的方法,特别是在行数多于列数的情况下,这种情况经常发生。

NumPy 提供了 pinv() 函数用于计算矩形矩阵的伪逆。

下面的示例定义了一个 4×2 矩阵并计算其伪逆。

运行示例首先打印定义的矩阵,然后打印计算出的伪逆。

我们可以通过 SVD 手动计算伪逆,并将其结果与 pinv() 函数进行比较。

首先我们必须计算 SVD。接下来,我们必须计算 s 数组中每个值的倒数。然后,s 数组可以转换为对角矩阵,并添加一排零使其成为矩形。最后,我们可以从这些元素计算伪逆。

具体的实现是

完整的示例如下所示。

运行示例首先打印定义的矩形矩阵,然后是与上面 pinv() 函数结果匹配的伪逆。

SVD 用于降维

SVD 的一个流行应用是降维。

具有大量特征的数据,例如特征(列)多于观测值(行)的数据,可以减少到与预测问题最相关的一小部分特征。

结果是一个秩较低的矩阵,据称可以近似原始矩阵。

为此,我们可以对原始数据执行 SVD 操作,并选择 Sigma 中前 k 个最大的奇异值。这些列可以从 Sigma 中选择,行可以从 V^T 中选择。

然后可以重构原始向量 A 的近似值 B。

在自然语言处理中,这种方法可应用于文档中单词出现次数或单词频率的矩阵,称为潜在语义分析或潜在语义索引。

在实践中,我们可以保留并使用数据的一个描述性子集,称为 T。这是矩阵的密集摘要或投影。

此外,这种变换可以计算并应用于原始矩阵 A 以及其他相似的矩阵。

下面的示例演示了使用 SVD 进行数据降维。

首先定义一个 3×10 矩阵,其中列多于行。计算 SVD 并仅选择前两个特征。将元素重新组合以准确重现原始矩阵。最后以两种不同的方式计算变换。

运行示例首先打印定义的矩阵,然后是重建的近似值,接着是原始矩阵的两个等效变换。

scikit-learn 提供了一个 TruncatedSVD 类,可以直接实现此功能。

可以创建 TruncatedSVD 类,您必须在其中指定要选择的所需特征或组件的数量,例如 2。创建后,您可以通过调用 fit() 函数来拟合变换(例如计算 V^Tk),然后通过调用 transform() 函数将其应用于原始矩阵。结果是上面称为 T 的 A 的变换。

下面的示例演示了 TruncatedSVD 类。

运行示例首先打印定义的矩阵,然后打印矩阵的转换版本。

我们可以看到这些值与上面手动计算的值相匹配,除了某些值的符号。考虑到所涉及计算的性质以及所用底层库和方法之间的差异,我们可以预期符号会存在一些不稳定性。只要转换经过训练可重复使用,这种符号的不稳定性在实践中不应该成为问题。

扩展

本节列出了一些您可能希望探索的扩展本教程的想法。

  • 尝试在您自己的数据上使用 SVD 方法。
  • 研究并列出 SVD 在机器学习中的 10 种应用。
  • 将 SVD 应用于表格数据集作为数据降维技术。

如果您探索了这些扩展中的任何一个,我很想知道。

进一步阅读

如果您想深入了解,本节提供了更多关于该主题的资源。

书籍

API

文章

总结

在本教程中,您学习了奇异值分解方法,用于将矩阵分解为其组成元素。

具体来说,你学到了:

  • 什么是奇异值分解以及它涉及哪些内容。
  • 如何计算SVD并从SVD元素重构矩形和方阵。
  • 如何使用SVD计算伪逆和执行降维。

你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。

掌握机器学习线性代数!

Linear Algebra for Machine Learning

建立对线性代数的工作理解

...通过在 python 中编写代码

在我的新电子书中探索如何实现
机器学习线性代数

它提供关于以下主题的自学教程
向量范数、矩阵乘法、张量、特征分解、SVD、PCA 等等...

最终理解数据的数学

跳过学术理论。只看结果。

查看内容

如何从零开始用 Python 计算 SVD 的 59 条回复

  1. 约瑟夫·利维博士 2018 年 2 月 26 日上午 5:49 #

    有一件重要的事情需要澄清:SVD 仅适用于实数,因此不应应用于序数或分类变量。

  2. MF 2018 年 2 月 26 日下午 12:33 #

    谢谢你的文章!一些校对修正:

    “其中 A 是我们希望分解的实数 n x m 矩阵……”

    A 是 m x n。

    “运行示例首先打印定义的 3×2 矩阵,然后是 3×3 U 矩阵、2 元素 Sigma 向量和 2×3 V^T 矩阵元素,这些都是从分解中计算出来的。”

    V^T 是 2×2。

    “其中 A^+ 是伪逆,D^+ 是对角矩阵 Sigma 的伪逆,V^T 是 V^T 的转置。”

    最后一部分应该是关于 U^T 是 U 的转置。

  3. john 2018 年 3 月 3 日上午 9:32 #

    您好。很棒的教程。一个问题。如果 A 矩阵的行数多于列数,会发生什么?我倾向于将 A 定义为 [特征,样本]。

    • Jason Brownlee 2018 年 3 月 4 日上午 5:58 #

      好问题,我暂时不确定。

      如果您将其用于降维,不妨尝试一下,看看投影如何影响模型技能。

  4. Junpeng Zhang 2018 年 3 月 3 日下午 5:46 #

    谢谢。您的课程很棒!

    再次感谢您。

  5. Satrio Adi Prabowo 2018 年 4 月 8 日下午 8:35 #

    谢谢 Jason 先生!我正在处理一个 M×N 矩阵,其中 M > N。如果我想对它实现 SVD 降维怎么办?我在 Sigma[:A.shape[0], :A.shape[0]] = diag(s) 处得到了矩阵大小错误,如果我将其更改为 Sigma[:diag(s).shape[0], :diag(s).shape] = diag(s) 怎么办?顺便说一下,它运行得非常好。

  6. SzatanSerduszko 2018 年 5 月 15 日下午 11:18 #

    你说 U.dot(Sigma) 和 A.dot(VT.T) 是原始矩阵的两个等效变换。

    那么为什么 U.dot(Sigma) == A.dot(VT.T) 返回的大多是 False 呢?

    其次,你能给我们展示一下如何使用 randomized_svd 函数对随机 SVD 转换数据进行操作吗?

    • Jason Brownlee 2018 年 5 月 16 日上午 6:03 #

      您确定吗?我到底在哪里说过?

      感谢您的建议。

  7. Geoffrey Anderson 2018 年 7 月 26 日上午 8:41 #

    像 FunkSVD 这样的迭代 SVD 能够增量更新,但如果用于推荐系统,标准 SVD 需要完全重新计算以合并评分矩阵中的新行或列。这种快速更新功能对于实用的推荐系统至关重要。FunkSVD 不是精确的 SVD,但足够接近,并且对于添加用户或项目非常高效,这在亚马逊或 Netflix 等大型网站上经常发生。对于大型推荐系统来说,完全重新计算的成本太高,而且在 24 小时流量的全球网站上何时执行它——你无法做到。

    但是,对于研究论文或数据是静态的地方,只要数据集足够小,普通 SVD 可能就非常适用。

    SVD 也不适用于高度稀疏的评分矩阵,因为 SVD 完全无法合并任何缺失数据。您需要提供值,以便没有数据缺失。您可以随意将 0 或用户或项目的平均值或全局平均值分配给缺失值。但这样您就告诉 SVD 一些不真实的事情,SVD 就会像对待实际存在的诚实数据一样接受您的谎言。SVD 只能将您提供的值视为所有实际真实值。因此,分解同样会受到您在分解之前选择的无意义数据值的强烈影响。尽管如此,SVD 和 FunkSVD 等迭代 SVD 近似值都已用于具有原始评分矩阵中缺失数据的系统,并取得了一些成功,表明尽管“真实”数据存在问题,SVD 仍能产生一些信号。

    当存在高度稀疏性时,情况更糟。我最近看到的一个 subreddit 推荐器中存在 99.99% 的稀疏性,其中用户对 subreddit 的每个评论为 1,没有评论则为缺失数据。大多数 Reddit 用户只在少数 subreddit 中评论。在一个为期 5 天的 Reddit 评论流量样本中,有 200 万独立用户和 5 万个 subreddit 提交评论。

    有些推荐器中缺失数据确实为 0,例如隐式推荐器,其中 1 表示用户点击,缺失数据表示 0 次点击。将 0 放在那里是处理缺失数据的正确做法,因为它实际上是 0。那么 SVD 就完全适用,它可以被提供 100% 真实数据进行分解,因此它产生的输出也可能很好,而不是建立在垃圾输入之上。

    您可以使用专门将缺失评分从损失函数中排除的算法,来正确地使用梯度下降算法(如Adam)在TensorFlow中训练矩阵分解模型。这种模型设计和代码设计完全不受预先插补的缺失数据的影响。这很好地解决了某些推荐系统中的缺失值问题。

    进行一项实验可能很有趣,以经验性地看看如果正确处理缺失值并在模型训练期间不使用它们,与在数据缺失的地方填入虚假数据值并将其纳入模型训练相比,预测效果会好多少。

  8. Shirin 2018年8月29日 晚上11:23 #

    我认为“从SVD重建矩阵”部分下第一个方程中指定的矩阵大小不正确。根据上面的讨论,diag(S)应该生成一个n x n大小的矩阵。

    • Jason Brownlee 2018年8月30日 上午6:29 #

      你为什么这么说?

    • Thierry 2018年12月5日 下午3:25 #

      确实如此。

      根据文档,svd()返回“s:ndarray [...] 形状为(K,),其中K = min(M, N)。”
      (https://docs.scipy.org.cn/doc/scipy/reference/generated/scipy.linalg.svd.html)
      这意味着S是一个向量,其长度是原始矩阵的最小尺寸。

      因此,diag(S)将创建一个大小为(min(M, N), min(M, N))的对角矩阵。

      然后,如果m > n(如示例A的形状是(3, 2)),则公式
      U (m x m) . Sigma (m x m) . V^T (n x n)
      将变成
      U (m x m) . Sigma (n x n) . V^T (n x n)

  9. Vinu Talagune 2018年11月12日 晚上10:45 #

    我们可以用SVD进行聚类吗?

    • Jason Brownlee 2018年11月13日 上午5:46 #

      我没有关于聚类的材料,所以我无法给你很好的即时建议。

  10. Ravikiran 2019年1月5日 晚上7:53 #

    谢谢你所做的一切,Jason。

  11. bbrighttaer 2019年1月21日 晚上10:18 #

    非常感谢这个教程。
    你能解释一下你所说的“这种符号的不稳定性在实践中不应该是一个问题,只要转换经过训练以便重用”是什么意思吗?
    “这种符号的不稳定性在实践中不应该是一个问题,只要转换经过训练以便重用。”

    • Jason Brownlee 2019年1月22日 上午6:22 #

      这意味着符号(+或-)可能会根据找到的不同解而改变,对此不必担心。

      • Raj 2020年9月6日 晚上10:13 #

        很棒的文章!!!!

        您能否进一步解释符号(+或-)的意义,以便我们得出一个关于为何不必担心它的结论性理解?我的意思是,这个符号意味着什么?

        • Jason Brownlee 2020年9月7日 上午8:31 #

          分析上,正解或负解是两个可能的解,实际上它们是同一回事。

  12. Uma Shankar 2019年1月29日 晚上8:34 #

    你好,如果我们使用TruncatedSVD,我们如何知道捕获的方差量,或者换句话说,如何决定n_components的数量?对于PCA,我们通过“pca.explained_variance_ratio_”方法获得解释的方差。SVD有类似的选项吗?

  13. Anushri 2019年3月7日 下午1:43 #

    如上所述,SVD执行特征降维任务。特征降维具体是指特征提取还是特征选择(因为两者都是特征降维技术)?我猜SVD在做的是特征提取。我说的对吗?如果是这样,它能用于特征选择吗?

    • Jason Brownlee 2019年3月7日 下午2:34 #

      是的,它是一种投影或特征提取。

      它是特征选择的替代方案。

  14. Goutam Das 2019年5月2日 下午4:04 #

    你好,我想从一个矩阵中找到第一个奇异向量……如果我使用svd,我该如何从中找到奇异向量。请帮助我……

  15. Jeanne S. 2019年6月6日 下午3:45 #

    嗨,Jason——感谢您的精彩教程。它们对我的研究非常有帮助。我意识到这可能有点偏题,但我似乎找不到一个对我来说有意义的答案。它与sklearn的FactorAnalysis有关——与本帖的主题不同,但相关。基本上,在传统的探索性因子分析中,我认为变量多于观测值会阻止模型收敛。当我在R中尝试运行这样的模型时,出现了错误。然而,我使用sklearn的FactorAnalysis运行相同的数据时,得到了可解释的结果。我很想得到一个简短的解释,为什么机器学习版本的EFA可以收敛,而我的传统EFA却没有。(注意:我没有在R中尝试使用多个包,所以我可能错了。)提前感谢!

  16. Jun Wang 2019年9月11日 晚上5:52 #

    eigh?不需要通过减去均值和除以(n-1)来预处理数据吗?
    如果我有一个m x n的数据矩阵X(m=20000特征-行,n=19样本-列),我仍然可以使用
    u, s, v = np.linalg.svd(X)
    返回的u是(20000,20000)的特征向量吗?
    返回的s**2是(19,)的特征值吗?

  17. Venkata 2019年10月30日 晚上10:54 #

    我有一个关于以下内容的问题
    A = array([
    [1,2,3,4,5,6,7,8,9,10],
    [11,12,13,14,15,16,17,18,19,20],
    [21,22,23,24,25,26,27,28,29,30]])
    print(A)
    # 奇异值分解
    U, s, VT = svd(A)

    对于上面的“s”,它的形状应该是(10,),因为我们有10个特征,但显示的是(3,)。我很困惑。我们再考虑一个例子

    A = array([[1, 2], [3, 4], [5, 6]])
    print(A.shape)
    # 奇异值分解
    U, s, VT = svd(A)
    这里“s”的形状显示为(2,)

    我不明白为什么形状会有差异。请解释一下。

    • Jason Brownlee 2019年10月31日 上午5:30 #

      “s”是Sigma。来自帖子

      Sigma对角矩阵作为奇异值向量返回。

  18. Mukund 2020年1月26日 晚上8:36 #

    感谢Jason的精彩教程。
    我被鼓励使用SVD进行降维,但我无法理解在上述教程中,维度到底在哪个点被降低了?我可以看到当选择顶部SVD元素时,Sigma和VT将具有较低的维度。对吗?
    我可以将这两者视为降维,还是必须考虑U,当U、S、Vt从SVD创建时,维度会增加?
    请指教。

    • Jason Brownlee 2020年1月27日 上午7:04 #

      请参阅上面标题为“SVD for Dimensionality Reduction”的部分。

  19. Rakhesh Kumbi 2020年1月30日 下午5:54 #

    嗨,Jason,

    我正在尝试从T计算U,公式为

    U = T.dot(inv(Sigma))

    这里Sigma应该是一个方阵才能计算逆矩阵。

    有没有办法仅从T和VT重构原始矩阵?

  20. Rakhesh Kumbi 2020年2月13日 下午3:51 #

    嗨,Jason,

    我们可以使用U、s和V^T的最小维度来构建原始矩阵,如下所示

    np.dot(U[:, :n_components], np.dot(VT[:n_components, :], s[:n_components]))

    因此,这里计算原始数据所需的维度更少。所以压缩更多。

  21. JustGlowing 2020年3月24日 上午5:28 #

    一如既往的精彩!

    几年前我做过这个,它也揭示了伪逆的使用背后的一些秘密

    https://glowingpython.blogspot.com/2011/06/svd-decomposition-with-numpy.html

    希望它能帮助这里的读者。

  22. Arsène 2020年4月21日 晚上10:33 #

    感谢这个精彩的教程!
    我认为当您用特征值填充S矩阵时,您应该使用A的秩而不是n,例如

    r = npl.matrix_rank(A)

    # 创建 m x n 的 Sigma 矩阵

    Sigma = np.zeros((A.shape[0], A.shape[1]))

    # 用 n x n 对角矩阵填充 Sigma

    Sigma[:r, :r] = np.diag(s)

    • Arsène 2020年4月21日 晚上10:41 #

      应该是

      Sigma[:r, :r] = np.diag(s)[:r,:r]

    • Jason Brownlee 2020年4月22日 上午5:56 #

      感谢您的建议。

  23. joxemi 2020年9月3日 晚上9:42 #

    在“从SVD重建矩形矩阵”代码部分,第17行您写道
    B = U.dot(Sigma.dot(VT))

    应该是B=U.dot(Sigma.dot(VT.T)),其中VT.T是VT的转置矩阵。

    • Jason Brownlee 2020年9月4日 上午6:31 #

      你为什么这么说?

      • joxemi 2020年9月4日 下午5:06 #

        我认为这可能是一个混淆。重构是B= U (m x m) . Sigma (m x n) . V^T (n x n) ,而您没有使用V的转置,用您的符号表示将是VT.T。
        但是Scipy直接且隐式地提供了V的转置,所以您不需要转置V矩阵。

  24. Muaadh Abdo 2020年11月23日 上午7:45 #

    你太棒了,哇哦哦哦哦哦哦哦,解释得很好,很清楚,非常感谢你。

  25. Uche Jerry 2021年8月5日 上午3:11 #

    谢谢你,Jason。
    我现在理解了SVD的基本概念,但是SVD如何用于图像隐写术中的封面图像选择呢?

    我很乐意获得一个想法或Python源代码。提前感谢

  26. yasirroni 2021年9月10日 上午1:56 #

    scipy的代码在我的情况下不起作用。我认为,它应该被修复为


    # SVD
    U, s, VT = svd(A)

    # 创建 m x n 的 Sigma 矩阵
    Sigma = np.zeros((A.shape))

    # populate Sigma with n x n diagonal matrix
    Sigma[:, :min(A.shape)] = np.diag(s)
    print(U)
    print(Sigma)
    print(VT)

    • Adrian Tam
      Adrian Tam 2021年9月11日 上午6:22 #

      谢谢。但是这里的代码似乎在最新版本的scipy中仍然运行良好。

      • FEBBY 2022年11月23日 上午1:47 #

        是的,真的很好

发表评论

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。