news 2026/10/19 5:33:18

矩阵方法进阶:低秩近似、随机SVD与神经网络中的线性代数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
矩阵方法进阶:低秩近似、随机SVD与神经网络中的线性代数

最近终于把18.065的后半程啃完了,手上这份笔记正好覆盖第19到24讲。说实话,前半程学完SVD、最小二乘、四种基本子空间的时候,我觉得自己已经掌握了“矩阵方法”的全部家底,结果到了后半程才发现,那些分解和定理只是入场券。第19到24讲的核心不再是“怎么分解一个矩阵”,而是“怎么用这些分解去解决真实的数据问题”:低秩近似背后是图像压缩和推荐系统,随机化算法解决的是“矩阵太大算不动”的尴尬,梯度下降和反向传播则把线性代数的语言直接搬进了神经网络的训练流程。

这篇笔记我会把这六个讲座里我认为最值得反复咀嚼的内容重新整理一遍,包括低秩近似的原理与截断参数选择、随机SVD的完整操作步骤、优化问题里的矩阵条件数影响,以及神经网络前向和反向传播背后的矩阵链。同时会穿插一些我实际动手验证过的例子和踩过的坑,希望能给同样在啃这门课或者想补线性代数在数据科学中应用的同学一点参考。

1. 后半程课程在讲什么:从分解到决策

1.1 课程前半程留下的工具箱

18.065的前半部分其实已经建立了一套完整的“矩阵世界观”:任何矩阵都有列空间、行空间、零空间和左零空间;SVD把矩阵分解成旋转-拉伸-旋转三步;最小二乘的本质是往列空间上投影。这些东西单独拿出来都很漂亮,但如果你只停留在“会算”的层面,面对真实数据时还是会懵。真实世界里的矩阵很少是方方正正干干净净的,它们往往是高维、低秩、带噪声的观测数据。

到了第19讲往后,课程明显转向了“这些工具能帮我们做什么决策”。特征值分解和奇异值分解不再是计算题,而是用来判断系统稳定性、度量信号能量、寻找数据主方向的标尺。最小二乘也升级成了正则化的前奏,开始和偏差-方差的权衡挂钩。这种视角转换一开始会有点不适应,习惯之后会发现整个线性代数的价值一下就被放大了。

1.2 第19到24讲的整体脉络

我整理笔记的时候,把这六讲的内容归纳成了三条主线。第一条主线是“低秩”:为什么真实数据矩阵可以被低秩近似?截断SVD在里面扮演什么角色?第二条主线是“随机化”:当矩阵规模大到确定性算法扛不住的时候,怎么用采样和概率来换取计算效率。第三条主线是“迭代与优化”:从直接求解线性方程组过渡到梯度下降,再把矩阵乘法的链条延伸到神经网络。

这三条主线不是割裂的,它们被同一个底层需求串在一起:高维数据中真正有用的信息往往集中在少数几个方向上。低秩近似是从代数结构上抓住少数方向,随机化算法是从计算策略上快速逼近这些方向,而优化方法则是从目标函数的角度一步步朝这些方向逼近。把这层关系想明白之后,后面很多内容看起来就不再是一堆孤立公式了。

2. 重新审视三个基础概念,否则后面全崩

2.1 范数的选择决定了问题的度量方式

第19到24讲里频繁出现各种范数,特别是谱范数和Frobenius范数。很多人在学线性代数时对范数不敏感,觉得它就是“长度”的一种推广,但在数据科学里,范数的选择直接决定了你衡量误差的方式。

Frobenius范数本质上是把所有元素当作向量算欧几里得长度,所以在截断SVD的时候,最优低秩近似A_k = U_k Σ_k V_k^T会在Frobenius范数下达到全局最优,这是Eckart-Young定理告诉我们的,也是PCA和图像压缩的理论根基。而谱范数(也就是最大奇异值)度量的是矩阵作为线性变换时,能把单位向量拉长多少倍。它更关心“最坏情况下的放大倍数”,在控制论、误差分析、对抗样本讨论里都很重要。

我自己的一个深刻体验是:如果你只是做数据压缩,Frobenius范数就够了;但如果你在分析一个迭代算法是否收敛,或者评估一个扰动对解的影响,谱范数几乎是唯一正确的工具。把这两个范数搞混,会在后面看随机算法的误差界时觉得完全看不懂。

2.2 伪逆与最小范数解的分工

伪逆是另一个容易被低估的概念。很多人记住了A^+ = V Σ^+ U^T这个公式,却不太清楚它在什么时候真正派上用场。当矩阵列满秩时,最小二乘解x = (A^T A)^{-1} A^T b没问题;但当矩阵缺秩或者病态时,正规方程里的A^T A可能不可逆或者条件数爆炸,这时候伪逆给出的最小范数最小二乘解就成了最稳妥的选择。

在后面的讲义里,伪逆还会和“欠定系统”联系起来。数据科学里最常见的情况就是样本数远小于特征数,比如基因表达数据里几万个基因、只有几百个样本,这时候A^T A根本不可能求逆。伪逆直接基于SVD,能把零空间方向彻底丢掉,所以得到的是所有可行解里范数最小的那个,这正好对应了正则化思想的一个雏形。

提示:如果你手头有现成的SVD实现,算伪逆就是三步:先做SVD,再把非零奇异值逐个取倒数,最后重组成矩阵。别在高维情况下直接硬算A^T A的逆,数值上极不稳定。

2.3 奇异值分解与特征值分解的适用边界

后半程还会反复出现一个问题:什么时候该用特征值分解,什么时候该用SVD。对对称矩阵,两者基本是一回事,特征值就是奇异值(最多差个符号)。但真实数据矩阵大都不对称、甚至根本不是方阵,这时候特征值分解根本没有定义,只能靠SVD。

更微妙的是协方差矩阵。A^T A的特征值分解和A的SVD有严格的对应关系:A的右奇异向量就是A^T A的特征向量,奇异值的平方就是特征值。所以在PCA里大家习惯把数据矩阵先中心化,再算协方差矩阵X^T X,然后做特征值分解。但直接对中心化后的数据矩阵X做SVD,计算上往往更稳定,得到的主方向完全一致。这个等价关系不搞清楚,会在后面学到“用SVD做PCA”时觉得前后矛盾。

3. 低秩近似:数据压缩与推荐系统的共同底色

3.1 为什么真实数据都是近似低秩的

一个满秩矩阵理论上需要保留所有奇异值才能精确重建,但真实数据不是这样的。一张256×256的灰度照片,做SVD之后你会发现奇异值衰减非常快,可能前50个奇异值就占了总能量的90%以上。一个用户-商品评分矩阵,本质上是“用户偏好”和“商品属性”的交互结果,而这两个隐藏因子空间的维度远小于用户数和商品数,所以评分矩阵天然就是低秩加噪声的结构。

课程在这里反复强调一个观点:噪声才是满秩的来源。信号的自由度远小于观测空间的维度。低秩近似就是在做“去噪”:把大的奇异值当作信号,把小的奇异值当作噪声丢掉。这个观点在图像压缩、推荐系统、文本主题模型里是通用的。理解了这一点,你就能明白为什么截断SVD和PCA本质上是一回事,也明白为什么推荐系统里的矩阵分解模型会如此流行。

3.2 截断SVD的参数怎么选

实际操作中,最直接的问题是:到底保留多少个奇异值?讲义给出了几种实用的判断依据,我整理成了一张速查表:

方法判断依据适用场景
能量阈值累计奇异值平方占总能量的比例达到0.9或0.95图像压缩、降噪
奇异值拐点奇异值按对数坐标绘图,寻找斜率突变处探索性数据分析
交叉验证在验证集上比较重建误差推荐系统、有监督任务
硬阈值奇异值超过某理论阈值的保留已知噪声方差时最优

以图像压缩为例,假设一张图像矩阵大小是256×256,原始存储需要65536个像素值。如果保留前50个奇异值,就只需要保存50个奇异值和50个左奇异向量、50个右奇异向量,总数据量是50×(1+256+256)=25650,压缩比大约2.5倍。虽然单个数字看起来不多,但奇异值衰减是指数级的,实际效果往往比这好得多。我在实验里对一张真实照片保留前30个奇异值重建,肉眼几乎看不出区别,而数据量只有原来的三成左右。

3.3 一个值得动手做一遍的小实验

如果你想验证低秩近似的威力,我推荐花半小时做一个实验:随便找一张灰度图,读进来转成矩阵,做SVD,然后分别保留前5、10、20、50个奇异值重建图像并保存。你会直观地看到图像从一团模糊逐渐变清晰的过程,而且会注意到“低频信息”(整体明暗和轮廓)很快就恢复了,“高频信息”(边缘和纹理)则要更多奇异值才能跟上。

这个实验还能帮你理解为什么要对数据做中心化。如果你忘了中心化就直接做SVD,第一主方向会被均值主导,压缩效果会明显变差。真实的PCA流程里,中心化是前置步骤,不是可选项。

注意:截断SVD在多数数值计算库里都是通过“部分SVD”算法实现的,不会真的先做完整SVD再丢掉后面的项。以Python的scipy.sparse.linalg.svds为例,你直接传k=50,它会用迭代法只算出前50个奇异值和对应向量,计算量远远小于完整SVD。但用numpy.linalg.svd时没有这个选项,它会老老实实做完整分解。

4. 随机化线性代数:矩阵太大时的破局手段

4.1 确定性算法的瓶颈在哪里

到第20讲左右,我开始意识到:前面学的所有方法都默认矩阵能完整放进内存,并且做一次完整的SVD或特征分解是可接受的。但现实数据往往不是这样。一个用户行为日志矩阵可能是十万行乘十万列,存储本身就是问题,完整SVD的时间复杂度是O(mn·min(m,n)),在千万级元素上根本无法接受。

确定性算法的瓶颈在于它总是“贪婪地”处理整个矩阵。随机化思想的核心转变是:我们并不需要精确的SVD,只需要一个足够好的低秩近似。那能不能先用随机投影把矩阵的列空间“压扁”到一个低维子空间,再在子空间里做精确分解?这就是随机化SVD的基本思路。

4.2 随机SVD的完整操作步骤

随机化SVD的实现非常简洁,我在自己的机器上用Python从头实现了一遍,核心流程是这样的:

import numpy as np from numpy.linalg import svd, qr def randomized_svd(A, k, oversampling=10, n_power_iter=1): m, n = A.shape # 1. 生成随机高斯矩阵 Omega = np.random.randn(n, k + oversampling) # 2. 幂迭代:增强小奇异值的贡献 Y = A @ Omega for _ in range(n_power_iter): Y = A @ (A.T @ Y) # 3. 对采样后的列空间做QR分解,得到标准正交基 Q, _ = qr(Y) # 4. 把小矩阵投影到低维空间 B = Q.T @ A # 5. 对小矩阵做精确SVD U_tilde, Sigma, Vt = svd(B, full_matrices=False) U = Q @ U_tilde return U[:, :k], Sigma[:k], Vt[:k, :]

这段代码的核心思想只有一句话:先用随机投影把矩阵的列空间“捞”出来,再在缩放后的小矩阵上做精确分解。oversampling参数多取几个随机方向,是为了降低遗漏重要方向的风险;n_power_iter参数控制幂迭代次数,每多做一次,小奇异值方向的贡献就会被放大一次,误差会显著下降。

我实际测试过,对一张1024×1024的图像,用k=50、oversampling=10、n_power_iter=2,随机SVD得到的重建图像和精确截断SVD几乎一模一样,但耗时只有后者的几十分之一。这在实际项目中意味着,你可能可以把原本需要一台高性能服务器才能跑的任务搬到笔记本上完成。

4.3 误差到底有多大

随机化方法最难回答的问题是“结果可不可靠”。好在这些算法都有理论保证,而且误差界通常长这样:

E[||A - Q Q^T A||_F] ≤ (1 + O(sqrt(k/(p+1)))) * ||A - A_k||_F

其中A_k是最优低秩近似,p是额外采样的列数,也就是上面代码里的oversampling。这个式子说明随机SVD的误差不会比最优误差高出多少,采样越多就越接近最优。而且注意期望符号,意味着多次运行结果会在最优附近波动,所以如果你需要非常稳定的输出,可以把随机种子固定下来,或者多做几次取平均。

实操心得:把oversampling取得太小是新手最容易犯的错误。我一开始图快,把oversampling设成2,结果误差比理论预期高了一大截。后来想明白,额外的采样列相当于“冗余保险”,少了它,随机投影漏掉重要方向的概率就会上升。一般建议至少oversampling=10,数据本身噪声大时甚至可以取到20。

5. 优化问题里的矩阵视角:梯度下降真正依赖什么

5.1 从解方程到迭代优化,为什么需要换思路

前面所有方法都建立在“能够直接分解矩阵”的假设上。但到了第22讲左右,课程开始处理另一种场景:我们要优化的目标函数根本没有解析解,或者有但计算代价太大。比如最小二乘问题可以通过正规方程一步解出,但当A^T A太大时,一步到位的求逆操作本身就是灾难。

梯度下降的想法非常朴素:从一个初始点出发,沿着梯度反方向走一小步,反复迭代直到收敛。从线性代数的角度看,它其实是在用“局部线性近似”不断逼近全局最优。有趣的是,课程把梯度下降和前面学过的条件数联系在了一起,这个联系以前完全没意识到。

5.2 条件数如何决定收敛速度

考虑最经典的线性最小二乘问题min ||Ax - b||^2,它的梯度是2A^T(Ax - b)。收敛速度取决于矩阵A^T A的条件数:条件数越大,等值线越扁长,梯度下降就会来回震荡,收敛越慢。条件数接近1时,等值线接近圆形,一步就能走向中心。

我做了个简单实验:构造一个条件数为100的正定矩阵,用固定步长的梯度下降求解最小二乘,发现需要几百次迭代才能达到满意的精度;换一个条件数为10的矩阵,几十次就收敛了。这个差异完全由矩阵性质决定,和初始点的选择无关。

这就解释了为什么预处理(preconditioning)在实际工程里如此重要。你不需要改变问题的答案,只需要通过一个可逆变换把矩阵条件数变小,收敛速度就能大幅提升。课程里提到的对角缩放、随机预处理,本质都是在做这件事。

5.3 学习率的选择与步长谱

梯度下降里最头疼的参数是学习率η。从矩阵特征值的角度理解会清晰很多:对二次型目标函数,梯度下降的收敛性要求η < 2/λ_max(A^T A),否则迭代会发散。而最优学习率则和最大最小特征值有关,典型取法是让η接近2/(λ_max + λ_min)。

实际调参的时候不会有人先算特征值再定学习率,但理解这个边界很有用。你可以先用一个小实验估算A^T A的谱半径,然后设置一个安全的学习率。我记得第23讲里提到一个实用策略:先从一个很小的学习率开始,逐步增大直到损失曲线由稳定变震荡,然后退回上一个稳定值。这个方法背后就是利用了上述特征值边界。

注意:梯度下降和随机梯度下降最大的差别在于,全批量梯度下降的每一步都精确使用全部数据,收敛曲线平滑;而随机梯度下降每一步只用一个样本,梯度估计有噪声,收敛曲线剧烈震荡,但整体期望方向仍然是向下的。课程里反复强调,理解随机梯度的关键是“它引入了噪声,但噪声在期望意义上不改变最优解”。

6. 神经网络里的矩阵乘法链:把线性代数串起来

6.1 前向传播就是一系列矩阵乘法

第21讲左右的内容把前面的所有线性代数串了起来。一个全连接神经网络的前向传播,本质上就是一组矩阵乘法和非线性激活函数交替:h_1 = σ(W_1 x + b_1),h_2 = σ(W_2 h_1 + b_2)……每一层都在对输入做仿射变换,然后再引入非线性。

用矩阵语言看,这个结构的计算复杂度完全由权重矩阵的形状决定:输入维度是d_0,第一层权重形状是d_1×d_0,第二层是d_2×d_1,以此类推。前向传播的总计算量是所有矩阵乘法的工作量之和,这个视角可以帮助你理解为什么模型越来越大时,计算量和内存需求会呈爆炸式增长。

后面讲义里还提到一个有趣的观察:训练完成后,很多权重矩阵的奇异值分布会呈现明显的衰减,也就是说权重矩阵本身是近似低秩的。这解释了为什么模型压缩(比如低秩分解、剪枝)在实践里有效:你剪掉的是那些几乎不携带信息的冗余方向。

6.2 反向传播的矩阵解释

反向传播经常被讲成“链式法则”,但用矩阵方法看会更优雅。损失函数对某一层输入的导数,等于该层权重矩阵的转置乘以后一层的梯度。如果用δ_l表示第l层的梯度,那么反向传播的核心公式就是δ_l = W_{l+1}^T δ_{l+1} ⊙ σ'(z_l)。

注意这里出现了W^T,这正是线性代数课上前半程在四种基本子空间里讲的转置映射。前向传播是在输入空间到输出空间进行线性映射,反向传播则是沿着转置方向把梯度“传回去”。如果你理解零空间和行空间的关系,就会明白为什么转置在反向传播里如此自然:梯度携带的是“输出空间里的误差”,要变成“输入空间里的更新方向”,必须经过转置映射。

6.3 权重初始化为什么和矩阵谱有关

第24讲给了我一个特别大的启发:权重初始化不是拍脑袋定的,它直接关系到前向传播的尺度稳定性。如果每层权重矩阵的谱半径大于1,信号在深层网络中会被指数放大;如果小于1,信号会被指数压缩到零。这两种情况都会让深层网络很难训练。

常用的Xavier初始化和He初始化,本质上就是想让每层输出的方差在传播过程中保持稳定。用矩阵语言的表述就是:希望权重矩阵的奇异值尺度大致保持在1附近。理解了这一点,再看初始化公式就不是背参数了,而是知道它在控制什么。

我自己曾经在训练一个20层网络时,因为初始化尺度过大,前几层的输出全部变成了NaN。当时完全摸不着头脑,后来用谱范数的视角去检查权重矩阵,发现第一层权重的最大奇异值已经是几十的量级,信号经过前向传播早就爆炸了。重新初始化之后,这个问题立刻消失。

7. 学习后半程的几个实用建议

7.1 动手实验比看公式重要十倍

第19到24讲的公式密度不小,但真正理解它们全靠动手。我自己的实践顺序是:先拿小矩阵验证随机SVD的误差界,再拿图片做截断SVD压缩,最后把梯度下降套在一个人造的最小二乘问题上观察条件数的影响。整个过程大概花了一个周末,收获比单纯看讲义大得多。

我强烈建议你用Python把文中的随机SVD代码跑一遍,然后改改参数看看误差变化。特别是把n_power_iter从0改成1、2,你会明显看到误差缩小,这比背十遍理论公式都管用。

7.2 建立“矩阵语言”的思维习惯

学完这六讲后我最大的变化,是不管遇到什么问题都会先问一句:这个问题能不能写成矩阵形式?推荐系统写成评分矩阵分解,图像处理写成矩阵低秩近似,优化问题写成梯度迭代,神经网络写成矩阵乘法链。一旦完成了这个转换,后面所有的工具都能复用。

我记得课程后半段反复出现的一个句子就是:把问题用矩阵表示,你就成功了一半。这句话听上去像口号,真正实践后才发现它确实是理解这门课的关键。

如果你现在正在啃这门课,或者刚学完前半程,希望这份笔记能帮你把后面的路看得更清楚。至少对我而言,第19到24讲才是这门课真正的精华所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 5:33:04

Spark SQL快速入门:调优、常用函数与UDF实践指南

最近我在重啃Spark SQL的快速入门&#xff0c;前面阶段还好&#xff0c;但到了第10到15节那部分&#xff0c;画风明显不一样了。这部分内容实际上是在讲三件事&#xff1a;调优、常用SQL函数、UDF开发。很多学习者也是从这一块开始产生明显的感受差异&#xff0c;SQL写得出来&a…

作者头像 李华
网站建设 2026/10/19 5:33:04

Spark SQL调优与UDF开发实战:从执行计划到数据倾斜

最近在补Spark SQL快速入门系列课程&#xff0c;正好刷到第10到15节&#xff0c;这几节把调优手段、常用SQL函数和UDF的开发串在一起看。以前用Spark SQL基本靠经验堆参数&#xff0c;数据量一上来就卡死&#xff0c;也不知道从哪看执行计划。学完这几节之后&#xff0c;我对“…

作者头像 李华
网站建设 2026/10/19 5:31:42

LFW人脸识别数据集全流程解析:预处理、训练与验证指南

简介&#xff1a;LFW&#xff08;Labeled Faces in the Wild&#xff09;人脸识别基准数据集资源包&#xff0c;面向人脸验证与人脸识别方向的算法研究者、深度学习者及安全风控相关从业者&#xff0c;可用于模型训练、效果评估与学术基准对比&#xff0c;也是人脸识别课程设计…

作者头像 李华
网站建设 2026/10/19 5:31:34

微信聊天记录清理后消失?三天找回实战与避坑指南

有一阵子&#xff0c;我特别怕打开微信的存储空间管理页面&#xff0c;因为那上面明晃晃写着一个数字&#xff1a;聊天记录占了多少GB。直到某个深夜&#xff0c;我做了所有人都可能做的一件事——清理。结果却是&#xff0c;和那个人从相识到分开的三年聊天记录&#xff0c;一…

作者头像 李华
网站建设 2026/10/19 5:30:59

Spark Worker启动流程与心跳机制源码解析:从注册到超时摘除

1. 从一次集群扩容说起&#xff1a;为什么Worker启动流程值得单独拎出来讲很多人啃Spark源码&#xff0c;习惯从SparkContext或者DAGScheduler入手&#xff0c;觉得那才是"核心逻辑"。但我在实际运维和调优过程中发现&#xff0c;集群层面最容易被忽视、出问题后最难…

作者头像 李华
网站建设 2026/10/19 5:30:48

C语言进阶:堆内存、结构体与联合体全面解析

写C语言教程写到“高级语法篇”这个节点&#xff0c;其实挺感慨的。很多人在指针和数组那儿就已经掉队了&#xff0c;能一路坚持到堆内存、结构体和联合体&#xff0c;说明你已经开始从“写作业”转向“写程序”了。这三样东西&#xff0c;恰恰是C语言从“玩具语言”走向“系统…

作者头像 李华