西瓜书第三章线性模型:习题推导与代码验证全攻略
1. 为什么第三章让这么多人栽跟头——先聊聊这章的“含金量”如果你正抱着《机器学习》周志华著俗称“西瓜书”啃到第三章我太懂你现在的心情了。教材正文写得克制又精炼线性模型三兄弟——线性回归、对数几率回归、线性判别分析——加起来不过二十来页看起来很好懂。可一翻到章末习题你会发现自己连第一题的“设”都没看明白。别慌这不是你智商的问题是这章的教学设计本来就是这个套路正文教你“认识”模型习题要你“亲手造”模型。这中间的断层就是大多数人栽跟头的地方。我自己当初学这章前后做了三遍。第一遍对着答案抄抄完脑袋空空第二遍硬着头皮推公式推到凌晨两点头发掉了一把第三遍才真正摸到门道意识到这些习题根本不是在考计算而是在逼你回答三个问题这个模型为什么这样做目标函数怎么来梯度怎么求这篇文章就是我把三遍经验浓缩成的一套“解题地图”不是为了给你现成答案这么简单而是告诉你看到一道习题时应该沿着哪条思路往下走走到最后怎么验证自己走对了。1.1 教材正文与习题之间的“断层”西瓜书第三章的正文结构很清晰先讲线性回归的基本形式再引入对数几率回归解决分类问题接着讲线性判别分析从几何角度找投影方向最后补充多分类和学习规则。每一部分都自成体系公式推导也完整但习题完全不按这个顺序来。它经常把几个模型糅在一起考你比如让你比较对数几率回归与线性判别分析在什么条件下等价或者让你从概率角度重新解释线性回归的损失函数。这种交叉式提问对初学者来说就像刚学会走路的婴儿直接被扔进四百米障碍赛道。我见过不少读者卡在第三章开头的同一个地方所有公式都认识但不知道哪个公式该用在哪道题里。原因在于正文是用“模型”组织内容的习题却是用“问题”组织内容的。你得学会建立一张双向索引表一列是“给定额外信息比如噪声分布”另一列是“该用什么模型、什么损失函数”。我后面会专门讲怎么建这张表这里先举个最典型的例子——教材正文只给出了线性回归的最小二乘解法但习题会问“如果误差不满足高斯分布怎么办”这就逼你跳出固定套路回到“极大似然估计”这个更底层的框架去重新设计损失函数。1.2 这一章帮你建立的两个核心能力第一从目标函数到更新规则的“肌肉记忆”。线性模型是我见过的所有机器学习模型里最适合练这门手艺的结构简单、可解释性强但推导链路完整。你一步步从“模型表达式”写到“损失函数”再求偏导得到“梯度”最后写出“参数更新公式”时你建立的不只是一种技巧而是一个可迁移到神经网络、SVM等复杂模型的思考框架。第三章习题就是专门用来训练这条链路的。做完这些题之后再看反向传播算法你会觉得它不过是这个思路的机械化版本。第二用数学和代码互相验证的习惯。大部分同学做习题只停留在“纸上推导出闭式解”或“手算几步梯度”然后草草收场。但其实第三章几乎每道题都藏着一个“可编程”的扩展给你一个小数据集你可以用手推的参数跑一遍预测再用numpy实现一遍梯度下降看看结果能不能对上。这个过程我把它叫作“双重验证”。它能直接暴露出你对矩阵求导、维度变化、梯度符号这些细节的模糊理解。我在第5节会给出具体的验证工作流这里先记住结论习题答案如果只落了纸面那等于没做。2. 拆解第三章的“家底”每个模型都对应哪些习题考点第三章经常被概括为“线性模型全家桶”但很多读者对“全家桶”里到底有哪些成员每个成员擅长什么心里并不清楚。我建议你先做一件事把这一章所有模型列成一个清单每个模型后面写上它对应的数学形式和适用场景。这看起来简单却是拆解题目的关键一步——因为超过一半的习题第一步考的就是“你会不会选模型”。2.1 线性回归从最小二乘到解析解线性回归是这一切的起点。它的模型形式是 (f(x)w^T x b)损失函数通常是均方误差 (E \frac{1}{2}\sum_{i1}^m (y_i - \hat{y}_i)^2)。习题里经常出现的几个考点是为什么要用均方误差而不是绝对误差在什么条件下最小二乘与极大似然估计等价闭式解 (w (X^T X)^{-1} X^T y) 又是怎么推出来的针对第一个问题你要知道均方误差的数学性质好处处可导梯度是线性函数优化容易。绝对误差在零点不可导这对凸优化不友好。第二个问题更重要它把线性回归从“一个公式”升级成“一条概率假设链”当误差项服从均值为零的高斯分布时最小化均方误差恰好等价于最大化对数似然。这个等价关系是许多习题的出题核心也是理解后续逻辑回归的敲门砖。第三个问题就是矩阵求导的实操训练我会在第3节专门演示。2.2 对数几率回归为什么用Sigmoid、如何推梯度分类问题接踵而至。线性回归用于回归很自然但拿来分类会出问题——预测值可能超出 ([0,1]) 区间没有概率意义。于是教科书引入了对数几率回归把一个线性函数 (w^T x b) 放进Sigmoid函数的咽喉里(y \frac{1}{1 e^{-(w^T x b)}})。习题的重点不在“怎么算预测值”而在“为什么这样组合”以及“损失函数怎么定义”。为什么用Sigmoid因为它能把 ((-\infty, \infty)) 映射到 ((0,1))且导数 (f(z)f(z)(1-f(z))) 非常漂亮可以简化梯度。损失函数不是用均方误差而是用交叉熵负对数似然核心思想是预测分布和真实分布的“距离”最小。习题经常让你推导这个损失函数对 (w) 的梯度最后会得到一个特别干净的形式梯度等于预测误差 ((y - \hat{y}) x)其中 (y) 是真实标签(\hat{y}) 是预测概率。这个形式你会反复用到一定要亲手推一遍。2.3 线性判别分析类内散度矩阵的来龙去脉线性判别分析LDA与前两者思路不同它不预设“预测值直接代表后验概率”而是从“投影”的角度找一条直线让同类样本投影后尽可能聚集异类样本投影后尽可能分散。习题里出现的核心概念是类内散度矩阵 (S_w) 和类间散度矩阵 (S_b)目标函数通常是最大化瑞利商 (\frac{w^T S_b w}{w^T S_w w})。很多读者在这节开始懵因为矩阵代数的味道突然加重了。但你只需要记住一条线投影后均值差要大方差和要小于是引入这两个散度矩阵再通过拉格朗日乘子法求极值最后得出 (w S_w^{-1} (\mu_1 - \mu_2)) 的闭式解。习题里常见的陷阱是让你手工计算二维数据上的 (S_w) 和 (S_b)这时候最容易错的地方是样本均值要按类别分别算散度矩阵要用样本向量外积而不是内积。2.4 多分类问题一对多与多对多的选择第三章最后常涉及多分类典型做法是一对一OvO、一对多OvR和多对多MvM。习题可能会问你哪种策略更适合某一数据集或者让你计算某种划分下的整体预测次数。不要死记谁好谁坏而要理解“一对多”只用K个分类器但每个分类器训练时要面对样本不平衡一对一需要K(K-1)/2个分类器但每个分类器任务更简单。多对多则再引入编码矩阵像纠错输出码那样提高容错能力。这节的题相对概念把三种策略的优劣比较写清楚基本就能拿住分。3. 典型习题的推导示范手把手把你的思路理清楚这一节是整篇文章的主菜。我不会把某一本习题集里的答案逐字抄出来那样对你没有价值。我会挑三类高频题型把推导的逻辑链一步步摆出来。你看完之后应该能感受到习题答案不是背出来的而是“顺着数理逻辑自己长出来”的。3.1 线性回归目标函数的矩阵求导先从一个几乎必考的题开始给定设计矩阵 (X \in \mathbb{R}^{m \times d})目标向量 (y \in \mathbb{R}^{m \times 1})模型输出为 (\hat{y} X w)这里暂时忽略偏置项偏置项可以并到 (X) 里加一列全1损失函数 (L(w) |y - Xw|_2^2)。习题通常会问(w) 的最优解是什么推导链是这样展开的把损失函数展开(L (y - Xw)^T (y - Xw) y^T y - 2w^T X^T y w^T X^T X w)。对 (w) 求梯度(\frac{\partial L}{\partial w} -2X^T y 2X^T X w)。令梯度为零得到正规方程(X^T X w X^T y)。如果 (X^T X) 可逆特征数小于样本数且无多重共线性则 (w (X^T X)^{-1} X^T y)。这里最容易被忽略的步骤是第二步的矩阵求导规则。我的经验是不要去记高阶张量直接用“标量对向量求导再用维度检查法”来验证(w) 是 (d) 维梯度也应该输出 (d) 维(X^T X w) 是 (d) 维(X^T y) 是 (d) 维两者相减合理。如果你推完梯度发现维度对不上十有八九是矩阵转置错了。3.2 对数几率回归的梯度下降推导再来一个从头推到梯度下降的经典题。对数几率回归的概率模型是 (P(y1|x) \frac{1}{1e^{-w^T x}})样本的输出 (y) 取0或1。损失函数采用负对数似然(L -\sum_{i1}^m [y_i \log \hat{y_i} (1-y_i)\log(1-\hat{y_i})])。推导梯度时你只需抓住一个关键Sigmoid函数满足 (\frac{d\sigma(z)}{dz} \sigma(z)(1-\sigma(z)))。令 (z_i w^T x_i)(\hat{y_i} \sigma(z_i))。对单个样本求偏导对 (y_i1) 的部分求导(-\frac{\partial}{\partial w} \log \hat{y_i} -\frac{1}{\hat{y_i}} \cdot \frac{\partial \hat{y_i}}{\partial w})其中 (\frac{\partial \hat{y_i}}{\partial w} \hat{y_i}(1-\hat{y_i}) x_i)所以这个贡献是 (-(1-\hat{y_i}) x_i)。另一部分类似最终合并得到单个样本的梯度((\hat{y_i} - y_i) x_i)。整个损失函数的梯度是 (\sum_i (\hat{y_i} - y_i) x_i)。这个结果干净得让人心情舒畅也暗示了参数更新的方向当预测值高于真实标签时梯度为正参数向减少预测值的方向调整。习题里常让你推导这个结果你可别看答案简单就跳过一定要亲手动笔把符号的来龙去脉走到位。3.3 一个容易被忽略的对率回归的“对数似然”与“极大似然”关系还有一类习题会考察你对“概率假设”的整体理解。比如问为什么线性回归用均方误差而不直接用极大似然为什么对数几率回归却真的是在最大化似然我的理解方法是画一张“假设-损失”对应表线性回归假设 (y|x \sim \mathcal{N}(w^T x, \sigma^2))极大似然等价于最小化均方误差。对数几率回归直接对 (P(y|x)) 建模使用伯努利分布极大似然就是分类交叉熵。LDA则是先假设类条件概率为高斯分布且协方差矩阵相同再利用贝叶斯定理推导线性判别函数。习题如果你能把这几个假设清晰写出来等于拿到了70%的分数剩下的计算都是体力活。4. 做题时那些“导演没告诉你的坑”做完推导你以为万事大吉错了。我在实操中遇到过一大堆习题答案看起来合理、一跑代码就翻车的例子。这些坑教材不会写在正文里但它们才是真正拉开“会做”和“真正理解”距离的地方。4.1 为什么我一开始的梯度不收敛用梯度下降解对数几率回归时我第一次跑实验迭代两百次损失还在乱跳。后来发现问题出在初始化与数据缩放上。如果你直接把原始特征喂进去而特征的单位相差几个量级比如一列是年龄0到80另一列是收入10000到50000梯度方向会被大头特征主导模型振荡非常常见。所以做题时如果涉及用代码实现请先做特征归一化或标准化。习题本意是让你理解数学但编程验证时这个工程细节会立刻暴露出来。另外学习率选择也有讲究。对数几率回归的损失函数是凸函数理论上固定学习率也能收敛但如果学习率太大会跑过头。我的习惯是先用对数坐标试一组学习率比如 (10^{-3}, 10^{-2}, 10^{-1})观察损失曲线下降情况再挑选看起来最平稳的那个。4.2 练习题里的“陷阱”——数据偏置与噪声线性回归的标准推导默认数据是线性可回归的并且误差均值为零。但习题有时会故意给你一个带偏置项的数据集比如所有点都整体向上平移了一截。这时候如果你忘记保留公式里的偏置项 (b)而只用一个无截距模型闭式解算出来永远差一截。解决办法很常见在特征矩阵里加一列全1这样偏置就被吸收进权重向量了。这句话听起来老生常谈但每次做矩阵推导时我都会提醒自己一遍。数据噪声也是重点。习题喜欢问当存在异常值时最小二乘结果会怎样答案是它会被异常值强烈拉动。这时你应该联想到绝对误差损失L1对异常值更鲁棒但不可导。这类题目考的不是计算而是在考察你对“损失函数形状”和“解的性质”之间关系的深一层理解。4.3 如何判断自己的推导是否正确推导完公式心里没底是常态。我的经验有三步自我验证法维度校验检查每一层数学量标量、向量、矩阵的维度是否匹配。特殊值校验令某些权值或特征为特殊值如全零、全一看结果是否符合直觉。比如线性回归中 (w0) 时损失应等于 (y^T y)。数值梯度校验用有限差分法逼近解析梯度看两者相对误差是否小于 (10^{-5}) 左右。这套方法我在工程里验证过很多模型非常好用习题练手时也可以直接用来检查你的手推梯度有没有符号错误。5. 用代码验证你的答案我的工作流和工具链纸上推导完了一定要用代码“接地”。第三章习题几乎都可以编程实现而我们正是要靠这种实现来捕捉那些手算时容易忽略的问题。下面是我在验证自己答案时常用的一套工作流你可以直接抄。5.1 手写线性回归从零开始我推荐用Python加上NumPy不先调第三方机器学习库这样你能掌握每一步的数学运算。伪代码片段如下import numpy as np # 构造数据 X np.array([[1, 1], [1, 2], [2, 2], [2, 3]]) y np.array([6, 8, 9, 11]) # 加一列全1作为偏置 X_b np.c_[np.ones((X.shape[0], 1)), X] # 闭式解 w np.linalg.inv(X_b.T X_b) X_b.T y print(w) # 也可以用np.linalg.lstsq w2, _, _, _ np.linalg.lstsq(X_b, y, rcondNone) print(w2)运行这段代码你会看到两组权重基本一致。如果你的手推答案和这个结果对不上恭喜你提早发现了错误。接着你可以加上梯度下降版本验证闭式解与迭代解是否收敛到同一位置。代码实现不是一个额外的负担而是对你手推公式最直接的“照妖镜”。5.2 对数几率回归的验证脚本对数几率回归没有闭式解只能迭代优化。你可以这样写一个简单的梯度下降循环import numpy as np def sigmoid(z): return 1 / (1 np.exp(-z)) def loss(y, p): # 加极小值防止log(0) return -np.mean(y * np.log(p 1e-12) (1 - y) * np.log(1 - p 1e-12)) # 数据与初始化 X np.random.randn(100, 2) w np.array([0.0, 0.0]) y (X[:, 0] * 1.5 X[:, 1] * 0.5 0).astype(float) lr 0.1 for i in range(1000): z X w p sigmoid(z) grad X.T (p - y) / len(y) w - lr * grad if i % 200 0: print(loss(y, p), w)运行后你会发现损失逐渐下降权重收敛到某个方向。把这个结果和你的手推梯度进行比较如果发生发散请回看第4.1节的收敛技巧。5.3 可视化交叉验证画出决策边界最后验证分类模型还可以画一条决策边界。线性模型的决策边界就是 (w^T x 0) 这条直线对二分类而言。你可以用matplotlib把样本点和这条线画出来直观看到模型是否把两类队列分开。这不算一个严肃的评估但当你的答案和“边界位置”不一致时说明你手推的某个符号有误。我的习惯是写完任何分类模型先随手画边界视觉反馈永远比只看数字更灵敏。6. 把“答案”变成“能力”的几个习惯最后一节我想分享一些更高层面的习惯毕竟习题答案只是手段你对模型的理解才是目的。这些习惯让我在学完第三章后后面几章的学习轻松了不少。6.1 错题本的正确打开方式很多人的错题本就是把正确答案抄一遍然后“下次我会了”。我的建议不同错题本里只记录“当初卡住的位置”和“突破的线索”。比如某道题我卡在“不知道为什么要用共轭梯度”而不是手算逆矩阵我就会写卡点在于矩阵可逆性假设突破是意识到数据维度高时直接求逆不现实所以才构造迭代算法。这样的笔记会让你看到自己在数学直觉上的成长轨迹而不是又一本参考答案汇编。6.2 与同学/同事讨论的边界找一个人互相讲题是效率最高的学习方式。但边界很明确讨论的是“思路”不是“答案”。两个人各推各的公式然后共同核对最后一步任何一步对不上就从头过一遍。这样可以避开“一个人推错了但自己看不出来”的盲区。我自己当初就是靠和一位同学对完第3.2节的梯度才发现把负号写丢了。这种错一个人关着门埋头写可能永远发现不了。6.3 我复盘后的三点心得第一线性模型的习题本质是“概率假设 优化策略”的组合题。你越早把每个模型的概率假设写成一句话后面推导越顺。第二不要把求导当作负担而是当作“理解模型的一种语法”。每次求导时问问自己这个梯度有意思在哪为什么刚好是预测误差乘以特征答案通常藏着与模型最深的直觉。第三做题和写代码要交替进行手推完立刻验证代码运行完代码马上回到公式这种来回拉扯会把你对知识的记忆锚定得非常牢固。我到现在做新模型实验时还会沿用第三章养成的这套“假设-推导-编码-验证-复盘”的回路。它并不神奇就是每个认真学过线性模型的人都会沉淀下来的工作方式。你只要耐心把这一章啃完后面再见到什么复杂模型底气都会不一样。