简介:《Linear Algebra and Learning from Data》是 Gilbert Strang 所著的英文原版电子书,旨在打通线性代数与机器学习、深度学习之间的数学桥梁,适合具备一定数学基础的高年级本科生、研究生及数据科学相关研究人员阅读。全书按七大板块展开,系统讲解矩阵运算、特征值、奇异值分解等核心概念,同时覆盖大规模随机化矩阵计算、低秩与压缩感知、特殊矩阵、概率统计与优化方法,并深入介绍深度神经网络的构建、卷积神经网络和反向传播原理,配套实例与练习题便于读者巩固理解。资源为单份 PDF 文件,压缩包大小约 26.38MB,内容完整、排版清晰,可直接作为系统学习或查阅参考。已有 232 人学习下载,对于希望从数学底层理解机器学习算法、掌握从数据中学习模型的读者,是一份颇具价值的经典教材。
1. 机器学习数学底子薄?Gilbert Strang 这本教材帮你补上最关键的线性代数
做机器学习久了你会发现一个尴尬现象:调包调参能跑通模型,但一旦涉及损失函数收敛、特征降维、权重更新这些核心环节,数学底子不够就处处碰壁。很多人回来补课首选 MIT 18.065,而 Gilbert Strang 老爷子这本《Linear Algebra and Learning from Data》就是配套教材,2019 年由 Wellesley-Cambridge Press 出版。它不是一本纯线性代数书,也不是纯机器学习书,而是把线性代数、概率统计、优化方法拧成一股绳,讲清楚深度学习中每个关键步骤背后的数学动机——矩阵分解怎么帮数据降维、随机梯度下降为什么有效、ReLU 为什么取代 sigmoid 成为默认激活函数。适合手里有本科数学基础、想系统打通机器学习数学关节的从业者和研究生,靠短视频和博客补数学是零碎的,这本书能帮你把散点连成体系。
2. 奇异值分解:贯穿全书的“王牌工具”与四个落地点
2.1 为什么机器学习里 SVD 比特征值分解更常用
如果你只学过传统《线性代数》教材,大概率对特征值分解更熟,但数据科学里真正的主角是奇异值分解(SVD)。Strang 在全书中反复强调一个观点:对称矩阵可以用特征值分解,但现实中的样本数据矩阵几乎不对称,行数和列数、行空间和列空间的维度都可能不一致,强行套用特征值分解会碰壁。SVD 则对任意矩阵成立,A = UΣVᵀ,左奇异向量 U 张成列空间,右奇异向量 V 张成行空间,奇异值 σ 按从大到小排列。
Strang 特别强调从对称矩阵 S = QΛQᵀ 到一般矩阵 A = UΣVᵀ 的推广是“我们这代人最重要的数学一步”。对称矩阵用一组正交特征向量就能完全描述,但对任意矩阵你需要两套基,一套管行空间、一套管列空间。书中用样本协方差矩阵做例子说明:对协方差矩阵做特征值分解,特征值对应各主成分方向的方差,而特征向量就是主成分方向。但如果你面对的是原始数据矩阵,直接做 SVD 更自然,因为数据矩阵的奇异值平方恰好等于协方差矩阵的特征值,两者在数学上等价,计算上 SVD 更稳定。
2.2 核心定理 Eckart-Young:截断 SVD 为什么是最优低秩近似
这一节要敲黑板。Strang 在前言里特别点出“Eckart-Young Theorem”是全书最重要的定理之一:如果想把矩阵 A 近似成一个秩为 k 的矩阵,那么保留前 k 个最大奇异值对应的部分 Σᵢ₌₁ᵏ σᵢuᵢvᵢᵀ,是在 Frobenius 范数意义下的最优近似。换句话说,你想丢掉一部分信息来压缩数据,丢掉哪些?丢掉最小的奇异值对应的那些分量,损失最小。
这个定理在机器学习里到处都在用。PCA 降维的数学依据就是它——把原始数据矩阵截断到前 k 个主成分方向,保留最大方差;图像压缩也一样,把一张图片的像素矩阵做 SVD,只保留前几十个奇异值,就能用远小于原始存储空间的参数重建出视觉上几乎无差别的图像。Strang 在书中用“秩一矩阵求和”的方式重新表述 SVD:A = σ₁u₁v₁ᵀ + σ₂u₂v₂ᵀ + …,每一项的“重要程度”由奇异值大小决定,这样理解低秩近似特别直观。
2.3 用 NumPy 亲手跑一遍截断 SVD 与 PCA 降维
光看不练容易飘。我一般建议拿到这本书后,第一件事就是在本地用 NumPy 把 PCA 从零实现一遍。这里给你一套可以直接跑的代码,数据用 sklearn 自带的鸢尾花数据集,目标是降到 2 维可视化:
import numpy as np from sklearn.datasets import load_iris # 加载数据,X 是 150 行 4 列的样本矩阵 iris = load_iris() X = iris.data y = iris.target # 第一步:中心化,让每个特征均值变为 0 # 这是 PCA 的前提,否则第一个主成分会被均值带偏 mean = np.mean(X, axis=0) X_centered = X - mean # 第二步:对中心化后的矩阵做 SVD,而不是直接对协方差矩阵做特征值分解 # full_matrices=False 避免生成冗余的零空间向量,减小计算量 U, S, Vt = np.linalg.svd(X_centered, full_matrices=False) # 第三步:取前 2 个右奇异向量作为主成分方向 # Vt 的行是主成分方向,前两行就是最重要的两个方向 W = Vt[:2].T # 形状 (4, 2) # 第四步:把原始数据投影到主成分空间 X_pca = X_centered @ W # 形状 (150, 2) # 第五步:验证解释方差比例 # 奇异值平方除以总平方和 = 该主成分解释的方差比例 explained_var_ratio = S**2 / np.sum(S**2) print("各主成分解释方差比例:", explained_var_ratio) print("前两主成分累计:", np.sum(explained_var_ratio[:2]))有人习惯先算协方差矩阵 np.cov(X.T) 再对它做特征值分解,结果在数值上通常一致,但直接对数据矩阵做 SVD 更稳。原因在于计算协方差矩阵涉及矩阵乘法,会放大数值误差;尤其在特征维度高、条件数大的时候,直接做 SVD 的数值稳定性明显更好。这一点 Strang 在书中也反复暗示:能直接对数据矩阵操作,就不要绕一道弯构造协方差矩阵。参数上需要注意的只有一点——full_matrices=False必须带上,否则对于非方阵,会生成一块形状很大的全零块,浪费内存和计算。
2.4 SVD 和 PCA 的边界与容易用错的地方
PCA 是个好东西,但它有几条使用边界,书里虽没有专门列出来,但字里行间有提示。第一,PCA 是线性方法,它只能捕捉数据中的线性结构,对非线性流形无能为力。你拿它处理瑞士卷数据,投影结果一塌糊涂,这时候该想的是 t-SNE 或 UMAP,而不是换一个 PCA 变体。第二,PCA 之前记得做标准化。如果特征的量纲差异大,比如一列是年龄(几十)、一列是收入(几万),直接做 SVD,结果会被量纲大的特征主导,主成分方向基本就是收入方向,年龄信息全被淹没了。Strang 讨论协方差矩阵时隐含假设了量纲一致,实际数据加工时你得自己做这一步。
第三个容易忽略的是中心化。很多初学者直接对原始数据矩阵做 SVD,得到的第一主成分不是最大方差方向,而是数据中心的方向,相当于把均值项当成了信号。必须先减均值再做分解,得到的奇异向量才有几何意义。我在带新人复现 PCA 时,踩得最多的就是这个坑,代码写出来跟教科书一模一样,但图不对,最后发现是忘了中心化。
3. 随机梯度下降与优化:Strang 给出的两条核心更新规则
3.1 为什么大规模训练不能走全量梯度
读完前两章你会觉得矩阵分解好看,但真正让深度学习跑起来的是优化。Strang 在第六章点破了关键矛盾:损失函数对所有权重求梯度,理论上“导数等于零”是最优条件,但实际计算中,全量梯度的成本高到你无法接受——每更新一次权重都要把全部训练样本过一遍,几十万样本的神经网络根本算不动。更麻烦的是,Newton 法需要海森矩阵,对神经网络来说海森矩阵的规模是权重数量的平方,完全不可行。
于是随机梯度下降(SGD)登场。它的核心思想是:不用全部样本算梯度,只随机抽一小批,用这个“有噪声的梯度”近似全量梯度,照样能收敛。Strang 特别强调“stochastic”这个词的含义——随机性意味着结果由概率支配而非确定性保证。抽到一批恰好不具代表性的样本时,更新方向会偏离正确方向,但长期来看噪声会互相抵消,整体方向还是对的。这不是玄学,是大数定律在优化算法上的延伸。
3.2 minibatch 大小与学习率的经验法则
SGD 有两个最关键的参数:学习率和 minibatch 大小。Strang 在书中没有给出万能公式(也不存在),但从原理上给出了思考框架。学习率决定了每一步走多远,太大了会在最优点附近震荡甚至发散,太小了收敛慢到怀疑人生。一个常见做法是用学习率衰减策略:训练前期步子大一点,快速接近最优区域,后期步子小一点,精细收敛。具体数值上,从 0.1 到 0.001 都有人用,要看损失函数的尺度,我自己的习惯是先拿 0.01 试跑几百步,如果 loss 完全不动就把学习率调大,如果 loss 剧烈震荡就调小。
minibatch 大小的影响比较隐蔽。批量太大,梯度估计很准但计算成本高,且容易陷入尖锐极小值,泛化性能反而差;批量太小,梯度噪声大,收敛不稳定,但有时这种噪声反而帮模型跳出不好的局部极值。实践中 32 或 64 是默认起点,不是因为它最优,而是多年经验证明它在多数任务上够用。说白了,这两个参数的组合没有理论最优解,多半靠实验和感觉,这就是我在团队里常说的“调参玄学环节”。
3.3 从损失函数到反向传播的数学链条
反向传播不复杂,本质就是链式法则,但把它写成矩阵形式需要这本教材的铺垫。设网络输出为 F(v),损失函数记为 L,反向传播要算的是 ∂L/∂Aₖ 和 ∂L/∂bₖ,也就是损失相对于每一层权重矩阵和偏置向量的梯度。链式法则一层层往回传:从最后一层的损失梯度开始,乘以当前层激活函数的导数,再乘以上一层的输出,就得到这一层权重的梯度。
Strang 在书中把这件事讲得特别清楚:每个隐藏层的输出是 zₖ = Aₖvₖ₋₁ + bₖ,紧接着的非线性激活是 vₖ = R(zₖ),所以反向传播的每一环就是两个操作——一个线性变换的转置乘法,一个逐元素激活函数导数。为什么矩阵乘法中要用转置?因为正向传播是从前到后,反向传播的梯度是逆向流动的,维度必须通过转置来匹配。用代码写一个简单的两层网络反向传播,比看十页公式更直接:
import numpy as np def relu(x): return np.maximum(0, x) def relu_derivative(x): return (x > 0).astype(float) # 输入 4 个特征,二分类任务 X = np.random.randn(64, 4) y_true = np.random.randint(0, 2, (64, 1)) # 初始化权重:第 1 层 4->8,第 2 层 8->1 # 权重初始化的尺度很重要,太大容易梯度爆炸,太小容易梯度消失 A1 = np.random.randn(4, 8) * 0.1 b1 = np.zeros((1, 8)) A2 = np.random.randn(8, 1) * 0.1 b2 = np.zeros((1, 1)) learning_rate = 0.01 # 正向传播:记录中间结果,反向传播要用 z1 = X @ A1 + b1 # 第一层线性变换 v1 = relu(z1) # 第一层激活 z2 = v1 @ A2 + b2 # 第二层线性变换 y_pred = 1 / (1 + np.exp(-z2)) # sigmoid 得到概率 # 损失:二分类交叉熵 loss = -np.mean(y_true * np.log(y_pred + 1e-8) + (1 - y_true) * np.log(1 - y_pred + 1e-8)) # 反向传播:从最后一层梯度开始往回推 dz2 = y_pred - y_true # 交叉熵+sigmoid 的复合梯度,形式很简洁 dA2 = v1.T @ dz2 # 第二层权重梯度 db2 = np.sum(dz2, axis=0, keepdims=True) dv1 = dz2 @ A2.T # 梯度穿过线性层 dz1 = dv1 * relu_derivative(z1) # 梯度穿过 ReLU dA1 = X.T @ dz1 # 第一层权重梯度 db1 = np.sum(dz1, axis=0, keepdims=True) # 更新权重,走一步梯度下降 A1 -= learning_rate * dA1 b1 -= learning_rate * db1 A2 -= learning_rate * dA2 b2 -= learning_rate * db2关键点在第一行的 sigmoid 复合梯度:交叉熵损失和 sigmoid 激活组合后,梯度恰好等于预测值减真实值,这是数学上精心设计的结果,不是巧合。如果你把损失换成均方误差,梯度公式就不是这么简洁了。另外注意 ReLU 的导数——输入大于 0 时为 1,否则为 0,所以它天然带来稀疏性:一部分神经元的梯度为 0,这些神经元在本次更新中不参与学习,这既降低了计算量,也在一定程度上减少了过拟合。
3.4 学习率太大或太小:失败时看什么信号
训练刚开始时,loss 曲线是最好的诊断工具。学习率太大时,loss 可能出现 NaN(数值溢出),或者上下激烈跳动根本不收敛;学习率太小时,loss 非常平稳地缓慢下降,可能看起来“很正常”但几百轮只降了一点点——这种最容易被忽视,因为它不报错,还会让你误以为训练在正常进行。我一般同时跑三个学习率做对比,0.001、0.01、0.1,看谁先用同样步数把验证集精度拉到最高,然后再在这个值附近微调,比盲猜省时间得多。
4. 神经网络架构:从 ReLU 到卷积层的数学语言
4.1 前馈网络就是一层层仿射变换加激活函数
Strang 对神经网络的定义很简洁:深度学习的函数形式是 F(v) = L(R(L(R(...(Lv))))),每个 L 是仿射变换 Lv = Av + b,每个 R 是逐元素非线性激活函数。矩阵 A 和偏置向量 b 就是需要学习的所有权重。这个视角对工程师特别有用:你不需要把神经网络理解成一个神秘的“黑匣子”,它就是数学上定义清晰的复合函数。层数越多,函数的表达能力越强,能拟合更复杂的模式,但代价是参数增多、优化难度加大、过拟合风险上升。
全连接层的参数量怎么算?输入维度 m 到输出维度 n,权重矩阵是 m×n,加上 n 个偏置。这个数字涨得极快:输入一张 256×256 的灰度图,光第一层全连接接 1000 个神经元,参数量就是 6500 万。Strang 在第七章里花了篇幅解释为什么卷积网络对图像任务更合适——因为自然图像有个特性:局部相关性。像素与相邻像素的关系最紧密,距离远的像素几乎无关。全连接层把每个像素跟所有神经元都连起来,大量权重是在学习“远处的像素之间的相关性”,这些参数既浪费计算也容易过拟合。
4.2 ReLU 为什么成了默认激活函数
先看激活函数的演进历史。S 形的 sigmoid 在早期很流行,但有两个硬伤:一是输出均值不为 0,导致下一层收到的输入总是正的或总是负的,信号在深层网络中传递时效率低;二是两边饱和,大值和小值区域的梯度几乎为 0,反向传播时梯度连乘会指数级缩小,这就是“梯度消失”。Strang 在书中指出,后来人们发现,把光滑但计算复杂的 sigmoid 换成极其简单的 max(0, x) 阶梯函数,网络表现反而更好。ReLU 的优势是:计算零成本、正区间梯度恒为 1 不存在梯度消失、天然产生稀疏激活。
ReLU 有一个隐性缺点需要你在实战中提防——神经元死亡。如果某条路径的输入长期为负,梯度恒为 0,这个神经元就再也无法从训练中学到东西。数据里出现大面积负值、或者学习率太大时,一整个层的神经元都可能“集体阵亡”。常见补救措施有 Leaky ReLU,在负数区间给一个很小的斜率(如 0.01),保证梯度不至于完全归零。Strang 在书中给出了 ReLU 的数学定义但在工程细节上没有展开,这个坑要靠实战踩过才能体会。
4.3 卷积层的矩阵本质:共享权重的大小革命
卷积层从数学上看就是在重复使用同一组权重。Strang 给出过一个精妙的视角:卷积层的权重矩阵 A 是对角常数矩阵——对角线上的元素反复出现,同一个卷积核滑动到图像的不同位置,用的都是同一套权重值。对比全连接层动辄百万/千万的参数量,卷积层参数量往往只有几千。AlexNet 在 2012 年 ImageNet 上夺冠,靠的就是这个思路,把深度网络训练成了一件可行的事。
对于图像数据,卷积核的大小(常见 3×3 或 5×5)、步长(stride)、填充(padding)这三个参数决定了输出特征图的尺寸。公式很简单:输出尺寸 = (输入尺寸 - 卷积核尺寸 + 2×padding) / stride + 1。这些在书中第七章有讨论,虽然不会手把手教你 TensorFlow API,但理解了“同一组权重在平移过程中反复作用”这个矩阵视角,再去调 CNN 结构会清楚得多——为什么 1×1 卷积可以改变通道数、为什么 stride=2 可以替代池化层做下采样,这些都能从矩阵形状变化的角度推出来。
4.4 过拟合的数学解释与正则化手段
这本书前言特意警告过拟合是深度学习最大的危险之一,还举了一针见血的例子:用 99 次多项式去拟合 100 个点,可以做到训练误差几乎为 0,但测试集一测就崩。神经网络参数数量动辄比训练样本数量还多,理论上完全可以“背下”所有训练样本,但没有学到可泛化的模式,这正是过拟合的数学本质。
常用的正则化手段书里都有讨论。L2 正则化在损失函数上加上权重平方和乘以 λ,迫使权重保持小值,等价于对权重施加“不要太大”的先验约束。更值得实操的是 Dropout——训练时随机忽略一部分神经元,Strang 在第七章把它描述为一种“每次只训练一个更小的网络”的手段,多个瘦网络的集成效果优于一个胖网络。Batch Normalization 则是另一种思路,它把每层输入重新调整为均值 0 方差 1,保证所有层的输入尺度一致,训练会稳很多。经验上,我先用 Dropout 解决过拟合,再用 Batch Normalization 加速收敛,这两步加法比盲目堆数据更有效。
5. 避坑与排查:读这本书和搭模型时的七个常见问题
5.1 现象:SVD 和特征分解得到的向量对不上
原因:这两个分解对矩阵的要求不同。特征分解只对可对角化的方阵有效,而 SVD 对任意矩阵都成立。当你对一个非对称矩阵做特征分解,得到的特征向量并不具备正交性,跟 SVD 的奇异向量不是一回事。
解决:处理数据矩阵时统一用 SVD,不要绕道特征分解。只有面对协方差矩阵这种对称矩阵时两者才等价,此时也要留意数值误差可能导致微小的不一致。
5.2 现象:做了 PCA 之后数据解释性变差,每个主成分看不出业务含义
原因:PCA 本质上是在做坐标旋转,主成分方向是原始特征的线性组合,组合系数通常有正有负,业务解释性天然就差。这是方法特性,不是你代码写错了。
解决:如果业务上需要可解释的降维,用稀疏 PCA 或因子分析;如果只是为模型提速、去噪声,PCA 仍然合适,别指望每个主成分都有业务名称。
5.3 现象:SGD 训练时 loss 曲线震荡剧烈,甚至出现 NaN
原因:学习率太大,梯度更新步长跨越了损失函数的“悬崖”,到达了数值不稳定的区域;或者 minibatch 太小,个别批次产生了异常大的梯度。
解决:把学习率降到当前值的 1/10 试跑 100 步,观察 loss 是否稳住。同时加梯度裁剪——把梯度向量的模长限制在一个阈值(如 5)内,防止异常批次把权重一步推到火山口。
5.4 现象:训练集误差很低但测试集误差很高
原因:过拟合,模型记住了训练数据的噪音和细节,没有学到可泛化的结构。神经网络参数量大于训练样本数时尤其容易出现。
解决:先加 Dropout(从 0.2 开始试),再加权重衰减(L2 正则化系数从 0.0001 开始试),同时看看训练样本量是否太小——数据增强也是一个思路,但图像以外领域这样做要谨慎。
5.5 现象:模型训练速度极慢,loss 下降像蜗牛
原因:可能是输入特征量纲差异大导致梯度更新方向被大尺度特征控制,也可能是网络没有做归一化导致每层输入分布不断漂移。
解决:先对输入做标准化,然后检查 Batch Normalization 是否加在了卷积/全连接层之后。这两个操作有时能把训练速度翻倍。
5.6 现象:ReLU 网络中大量神经元输出恒为 0,权重更新不动
原因:学习率过大或者权重初始化不当,导致某些神经元的输入长期为负,ReLU 输出恒为 0,梯度也为 0,神经元永远不会再更新。
解决:把学习率调小,换用 He 初始化替代标准正态初始化;如果仍然大量死亡,换 Leaky ReLU 或 ELU。
5.7 现象:照着书里的公式实现了算法,但数值总是跟结果对不上
原因:书上往往展示的是理想化数学公式,工程实现时还差几步:数值稳定性处理(比如 log 里加极小正数防止除零)、矩阵形状的广播规则、浮点误差累积。这些细节书里不会逐一写。
解决:先用小规模随机数据跑通流程,打印每一步中间变量的 shape,对照公式的维度要求。shape 全对、数值不对的时候,再看有没有忘掉中心化、标准化这类前置步骤。我用这个流程排查过的 bug,十有八九都出在自己对公式理解不够透彻。
6. 把这本书用起来:阅读顺序、配套资源与一个有效的验证习惯
6.1 精读顺序建议
这本书结构跟传统教材不同,不建议从第 1 章线性不动点开始死磕。我自己带人的经验是分三档:基础扎实的读者可以从第 I 章奇异值分解读起,在最短时间拿下最核心的数学工具;基础一般的读者先读第 VII 章神经网络架构,从反向传播的复合函数结构倒推出需要哪些数学背景,再回去补前面的 SVD、概率统计和优化;想解决特定问题的工程师可以直接跳到第 VI 章优化方法,把随机梯度下降学透,再回头补矩阵理论。书中各部分之间有强烈联系,但每章也相对独立,跳读完全可行。
6.2 配套课程材料与动手路径
教材的价值有一半在配套资源。MIT 的 18.065 课程视频可以在 MIT OpenCourseWare 找到,Strang 讲的风格非常清晰——板书推导每一个关键定理,而且对新手的困惑点预判极准。B 站上有带中文字幕的完整版视频,链接在本书摘要里可以找到,搜索“18.065”也能定位到。视频配合书的进度看,先看视频再精读对应章节,效果比单独啃书好得多。动手方面,动手时选一个简单数据集把 PCA、SGD、两层网络各实现一遍,相当于做一次全线巡检。参考 keras 做情感分类、PyTorch 做图像分类的示例项目也值得花半天过一遍,不是为了学框架,是看别人怎么把数学转化成工程代码。
6.3 一个“从公式到代码”的双向验证习惯
强烈建议你养成一个习惯:每学完书里的一个算法(PCA、SVD 截断、SGD、反向传播),在纸上写完数学推导后,立刻用 NumPy 把他实现一遍,然后在同一份数据集答案做好实验。实现三层网络的推导过程验证是做一次“数学反向传播”和“数值有限差分梯度”的比较:
def gradient_check(model, X, y, eps=1e-6): # 对模型每个参数,用有限差分近似梯度,与反向传播计算的真梯度对比 # 相对误差小于 1e-5 就说明反向传播实现正确 for name, param in model.params.items(): grad_analytic = param.grad grad_numeric = np.zeros_like(param.value) it = np.nditer(param.value, flags=['multi_index']) while not it.finished: idx = it.multi_index old_value = param.value[idx] param.value[idx] = old_value + eps loss_plus = compute_loss(model, X, y) param.value[idx] = old_value - eps loss_minus = compute_loss(model, X, y) grad_numeric[idx] = (loss_plus - loss_minus) / (2 * eps) param.value[idx] = old_value it.iternext() relative_error = np.linalg.norm(grad_analytic - grad_numeric) / \ (np.linalg.norm(grad_analytic) + np.linalg.norm(grad_numeric)) assert relative_error < 1e-5, f"{name} 梯度检查未通过: {relative_error}"这条代码做了什么事?数值梯度是用定义去逼近:把某个参数动一个微小量,看损失变化了多少,相除得到梯度的近似值;解析梯度是反向传播算出来的精确值。如果两者相对误差小于 1e-5,说明梯度计算正确,反向传播链路上没有 bug。这个习惯能一次性验证多个环节:前向传播对不对、损失函数对不对、反向传播的矩阵维度乘对没有。从那以后,我每次实现新的网络结构前,都强制走一遍梯度检查,不通过坚决不开始训练。希望这个习惯能帮到你。
本文还有配套的精品资源,点击获取