电子科技大学机器学习大作业解析:从线性回归到SVM与PCA降维

发布时间:2026/9/14 3:39:30
电子科技大学机器学习大作业解析:从线性回归到SVM与PCA降维 简介这是一份电子科技大学机器学习课程大作业完整方案覆盖作业一至作业六六个模块适合选修该课程或正在完成机器学期末项目的学生参考。内容从Matlab线性回归拟合、对数几率回归、信息增益决策树与预剪枝/后剪枝到累积BP神经网络、基于libsvm的SVM参数对比、朴素贝叶斯与Yale人脸PCA降维每部分都包含可运行脚本、详细计算过程、实验结果图示和报告文档能直接对照复现并节省整理时间。资源共27个文件以docx/pdf报告、md说明、m的Matlab脚本和py的Python代码为主另含一个zip的人脸数据集全部打包约11.55MB。目前已有2285人学习下载尤其适合基础较弱、想系统走通每次作业流程的学习者下载后可按作业目录逐项查看按需取用代码或报告是完成课程设计和大作业的高效参考。1. 一份电子科技大学机器学习大作业装了什么如果你翻过周志华《机器学习》教材大概率见过“西瓜数据集”这个梗。而这份电子科技大学机器学习大作业几乎是把西瓜书里的经典习题全部变成了需要跑通的实验线性回归、对数几率回归、决策树、朴素贝叶斯、BP 神经网络、SVM、PCA 人脸降维。压缩包里除了每轮作业的题目说明和答案文档还有对应的.m和.py源码甚至附带了 YaleFace 人脸数据集。对于正在修这门课的人来说它是可以直接对照的完整实现对于想系统复习机器学习算法的人来说它是一份从参数模型到非参数模型都覆盖的极好素材。2. 线性回归与对数几率回归从最小二乘到极大似然2.1 一元线性回归先走一遍最小二乘闭式解作业一第一个问题给定了x1:20和对应的y值要求拟合直线方程并画图。这类题在课堂演示中很常见因为可以直接用正规方程求闭式解。做法是构造设计矩阵把截距项放进第一列然后用左除\求解。对应的 Matlab 代码如下x (1:20); y [2.94; 4.53; 5.96; 7.88; 9.02; 10.94; 12.14; 13.96; ... 14.74; 16.68; 17.79; 19.67; 21.20; 22.07; 23.75; ... 25.22; 27.17; 28.84; 29.84; 31.78]; % 构造设计矩阵第一列为截距 X [ones(20, 1), x]; % 正规方程闭式解 theta X \ y;这段代码把线性回归转换成了超定方程组求解。第一列ones(20,1)对应截距第二列是原始x。X \ y在 Matlab 里对非方阵执行最小二乘 QR 分解效果等价于pinv(X)*y但数值稳定性更好。theta(1)是截距theta(2)是斜率画图时直接用plot(x, y, o); hold on; plot(x, X*theta)。紧接着的 bodyfat 数据集回归是同一个方法的高维扩展。题目要求在 Matlab 里用[X,Y] bodyfat_dataset;获得 13 个属性、252 个样本的数据集前 200 个训练后 52 个测试。这里最常见的错误是忽略bodyfat_dataset返回的格式。按官方接口X是 13×252 的矩阵Y是 1×252 的向量需要转置之后再拼截距[X, Y] bodyfat_dataset; % 前200个样本作为训练集 Xtrain X(:, 1:200); Ytrain Y(1:200); % 后52个样本作为测试集 Xtest X(:, 201:252); Ytest Y(201:252); % 最小二乘拟合 theta_bodyfat [ones(200, 1), Xtrain] \ Ytrain; pred [ones(52, 1), Xtest] * theta_bodyfat; MSE_test mean((pred - Ytest).^2);这段代码的训练矩阵构造方式和上一段完全一致只是在数据处理上多了一步转置。pred是测试集预测值MSE_test是测试集上的平均平方误差也就是题目里说的泛化误差。需要特别说明泛化误差不是训练误差写报告时一定要分开汇报训练集和测试集的结果。2.1.1 为什么用正规方程而不是梯度下降对于特征数只有 13 的数据集正规方程的计算量完全可接受不需要调学习率也不需要考虑特征缩放直接得到全局最优解。这里用左除而不是显式写逆矩阵的另一个原因是Matlab 的\会根据矩阵结构选择合适算法避免不必要的求逆引入数值误差。对于这道题我一般会在报告里补充一句当特征数大于样本数时就不再建议用正规方程因为X^T X可能不可逆。2.2 对数几率回归手工梯度下降与 4 折交叉验证作业一的第二、三题要求编程实现对数几率回归也就是逻辑回归并在西瓜数据集 3.0 上给出结果同时要求用 4 折交叉验证。注意这里“对数回归”是指对数几率回归不是简单的log(y)变换。核心是用 sigmoid 函数把线性输出映射到(0,1)区间再用交叉熵作为损失。我自己实现时会用 Python因为后续画二维决策边界和做交叉验证都更方便。import numpy as np def sigmoid(z): return 1.0 / (1.0 np.exp(-z)) def train_logistic_regression(X, y, lr0.05, epochs5000): # X 的第一列为常数1 theta np.zeros(X.shape[1]) for _ in range(epochs): z X theta grad X.T (sigmoid(z) - y) / len(y) theta - lr * grad return thetaX是经过均值中心化或归一化后的特征矩阵第一列同样拼了常数 1。grad是从对数似然函数推导出来的梯度注意它和线性回归梯度表达式形式上很像但sigmoid(z)是逻辑回归的预测概率。这里的lr是学习率epochs是迭代轮数。西瓜数据集 3.0 只有 17 个样本4 折交叉验证意味着每折只有 4 或 5 个测试样本随机种子不同结果波动可能非常明显。作业里原作答人的选择是去掉最后一个样本让每折测试集都是 4 个样本这样 4 折加起来正好 16 个训练样本。这一步选择必须在报告里说明否则结果无法复现。交叉验证的代码可以用sklearn.model_selection.KFold但参数设置上有个容易忽略的点必须设置shuffleTrue并固定random_state否则结果不可复现。因为数据量小有时不同划分下的准确率能从 0.75 跳到 1.0这不是模型问题是划分随机性导致的。报告里最好同时给出每一折的准确率和均值。模型输出损失函数求解方式线性回归连续值均方误差正规方程或梯度下降对数几率回归概率交叉熵梯度下降或牛顿法这张表对比了同一章里的两个模型。线性回归输出连续值适合 bodyfat 这类回归任务对数几率回归输出概率适合二分类。两者的梯度公式在形式上非常相似但推导来源完全不同一个是平方损失一个是极大似然估计。理解这一点后再去看机器学习数学理论里的泛化误差界就会更顺交叉验证估计的就是模型在测试分布上的期望误差而不是训练集上的损失。3. 决策树与朴素贝叶斯离散属性分类的两种推导路径3.1 信息增益决策树与预剪枝/后剪枝作业二的内容非常经典基于表 4.2 里的 11 个样本用色泽、根蒂、敲声、纹理构建决策树要求列出主要步骤还给了log2(3)1.585这类预计算值摆明了就是允许手算。不过既然这是大作业我还是建议用代码验证笔算结果因为预剪枝和后剪枝的误差很容易算错。决策树构建的核心是信息增益。对于类别集合D经验熵H(D)的计算公式如下def entropy(labels): # 计算经验熵 _, counts np.unique(labels, return_countsTrue) p counts / counts.sum() return -np.sum(p * np.log2(p))entropy函数接受一个类别标签数组返回经验熵。np.unique统计每个类别出现的次数p是每个类别的比例。判断某个属性a对D的信息增益时需要把D按属性值划分成子集再计算加权熵。计算完根节点的信息增益后递归地对每个子节点重复。这里有一个手算和编程容易分歧的地方属性的候选值是固定的但子节点是否继续划分取决于样本纯度。作业第二问要求的预剪枝就是在每次选择划分属性前先判断这次划分能否提升验证集精度如果不能就把当前节点直接标记为叶节点。后剪枝的逻辑正好相反先让决策树完全生长再从底向上判断把某个子树替换成叶节点能否提升验证集精度。作业第三问给了编号 4、5、8、11、12、13 这 6 个样本做测试集。常见做法是搭一个简单的决策树结构class Node: # 决策树节点 def __init__(self, attrNone, childrenNone, labelNone, is_leafFalse): self.attr attr self.children children or {} self.label label self.is_leaf is_leaf这里的Node类用字典children存储属性值到子节点的映射label在叶节点生效。预剪枝和后剪枝在同一个树结构上执行前者的判断发生在Node创建之前后者发生在递归返回之后。注意后剪枝时不能用测试集来训练只能用来判断是否替换否则会引入数据泄漏。属性根节点信息增益色泽0.108根蒂0.143敲声0.141纹理0.380这张表是西瓜数据集 3.0 上四个属性的信息增益。纹理最大因此根节点选纹理。这类手算题在期末复习时经常出现信息增益表能直观看出属性排序。预剪枝和后剪枝的关键差异在于验证集精度变化的判断时机报告里要把每一轮剪枝前后的精度都写出来才能算完整。3.2 朴素贝叶斯条件独立假设下的完整计算作业五的第一部分要求在西瓜数据集 2.0 上训练朴素贝叶斯分类器并预测样本 17。这个数据集是离散属性所以关键步骤是统计类别先验和条件概率。标准做法如下def naive_bayes_fit(X, y): # 朴素贝叶斯训练 classes np.unique(y) model {} for c in classes: Xc X[y c] model[c] { prior: len(Xc) / len(X), cond: {} } for j in range(X.shape[1]): vals, counts np.unique(Xc[:, j], return_countsTrue) model[c][cond][j] {v: counts[i] / len(Xc) for i, v in enumerate(vals)} return model代码里prior是类别先验cond是每个属性在给定类别下的条件概率表。预测时计算后验概率的分子P(c) * Π P(x_j | c)因为所有类别的分母相同比较分子大小即可。报告里要重点写清样本 17 的每个属性在两类中的条件概率这部分占分很高。作业里还有一道最大似然题四个分数x1..x4分别有不同的人数概率里含参数mu要求最大对数似然。这里我们只需要把似然函数写成L(mu) P1^14 * mu^6 * (2*mu)^9 * (0.5-3*mu)^10取对数后对mu求导并令其为零。把复杂的概率分布变成对数求和能极大简化计算。最终解一元方程得到mu约为 0.05可以用下面的 Matlab 验证syms mu; % 对数似然求导后令导数为0 eqn 3 - 6/mu 18/(2*mu) - 30/(0.5-3*mu) 0; mu_sol solve(eqn, mu);注意eqn的写法已经合并了整数项6/mu、18/(2*mu)、30/(0.5-3*mu)分别来自不同分数类别的样本数除以该项概率。用solve求解时如果出现负数解要结合定义域删掉。这类题的要点是先把概率表达式写正确再取对数避免在求导前展开幂函数。4. 从BP神经网络到SVM两种非线性模型的实现与调参4.1 累积BP算法单隐层网络的前向与反向作业三要求自己实现累积BP算法在西瓜数据集 2.0 上用训练数据训练一个单隐层网络并用验证集计算均方误差。所谓累积BP是指在一个训练 epoch 中计算所有样本的误差总和再统一更新权重而不是每看到一个样本就更新一次。这种更新方式更稳定但收敛速度通常比标准 BP 慢。核心结构如下class BPNet: def __init__(self, n_features, n_hidden5, n_output1): self.W1 np.random.randn(n_features, n_hidden) * 0.1 self.b1 np.zeros(n_hidden) self.W2 np.random.randn(n_hidden, n_output) * 0.1 self.b2 np.zeros(n_output) def forward(self, X): self.z1 X self.W1 self.b1 self.a1 np.tanh(self.z1) self.z2 self.a1 self.W2 self.b2 return self.z2 def accumulate_update(self, X, y, lr0.1): # 累积BP所有样本梯度平均后更新 m X.shape[0] pred self.forward(X) delta2 pred - y delta1 (delta2 self.W2.T) * (1 - self.a1 ** 2) grad_W2 self.a1.T delta2 / m grad_W1 X.T delta1 / m self.W2 - lr * grad_W2 self.W1 - lr * grad_W1 self.b2 - lr * delta2.mean(axis0) self.b1 - lr * delta1.mean(axis0)forward函数里隐层激活函数用了tanh输出层没有加激活函数因为这里做的是回归均方误差。反向传播计算delta1时(1 - self.a1 ** 2)是tanh的导数。accumulate_update把所有样本的梯度求平均后再更新这就是“累积”。注意初始化时的*0.1是为了避免隐层输出饱和如果换成 sigmoid 激活权重初始化范围还要更小一些。几个容易踩的坑一是西瓜数据集 2.0 的属性是离散值直接用 0/1 编码即可但隐层神经元数量不要太多5 个就够二是均方误差在训练集和验证集上会表现出完全不同的量级训练误差会缓慢下降而验证误差可能先下降后反弹所以在报告里画两条曲线比给出单一数值更有说服力三是累积 BP 单轮更新很慢通常需要几千个 epoch 才能收敛代码里要保存最优验证误差对应的权重。这里也可以参考李宏毅机器学习课程里对梯度下降和局部最优的解释把训练曲线和验证曲线放在一起能更清楚地说明模型在哪个 epoch 开始过拟合。4.2 libsvm 线性核与高斯核的 C 值扫描作业四要求把 libsvm 装上在西瓜数据集 3.0a 上用线性核和高斯核分别训练 SVM并且C取不同值时观察测试正确率变化。libsvm 的 Python 接口是svmutil它和 scikit-learn 的 API 不同更适合理解底层参数组合。安装 libsvm 后训练代码可以写成from svmutil import svm_train, svm_predict, svm_parameter # 线性核-t 0C100 param svm_parameter(-t 0 -c 100) model svm_train(train_labels, train_data, param) acc, pred, values svm_predict(test_labels, test_data, model)train_data的格式是列表的列表train_labels是类别整数列表。svm_parameter字符串里的-t 0指定线性核-c 100指定误分类惩罚系数。换高斯核时只需要把-t 0改成-t 2并额外增加-g 1指定高斯核宽度参数。题目要求高斯核宽度为 1所以-g 1不需要改。参数扫描的常规写法是写一个循环把C值列表遍历一遍C_list [1, 100, 10000, 10**6, 10**8] train_acc [] test_acc [] for C in C_list: param svm_parameter(-t 0 -c %g % C) model svm_train(train_labels[:], train_data[:], param) acc_train svm_predict(train_labels, train_data, model)[0][0] acc_test svm_predict(test_labels, test_data, model)[0][0] train_acc.append(acc_train) test_acc.append(acc_test)svm_predict返回的第一个元素是准确率百分比第二、三个才是标签和决策值。这里我习惯把训练集和测试集的准确率都存下来画在同一个图中因为C增大时训练准确率通常会单调上升而测试准确率会先升后降这样可以更清楚地展示过拟合区间。报告里比较线性核和高斯核时最好给出表格。参数含义取值说明-t核函数类型0 线性核2 高斯核-c误分类惩罚系数1, 100, 10^4, 10^6, 10^8-g高斯核宽度参数本题固定为 1C1、100、10000、10^6、10^8这 5 个值跨度很大横坐标需要画成对数刻度plt.xscale(log)否则前两个值会挤在左边。高斯核的结果通常在线性核基础上略有提升但C过大时两类模型的测试准确率都会下降这就是机器学习模型优化时最常见的“经验风险最小化”与“泛化能力”之间的权衡。5. PCA人脸降维YaleFace数据集上的特征脸与重构5.1 读取Yale人脸数据集并构造图像矩阵作业六的核心是使用 PCA 对 Yale 人脸数据集降维。数据集里的图像是 320×243 的灰度图直接展开后每个样本就是 77760 维的向量。PCA 的第一步是把所有人脸拉成行向量组成一个 n_samples × n_features 的矩阵然后做中心化。常见做法是用 OpenCV 或 PIL 读取我这里用 OpenCVimport cv2 import glob import numpy as np img_paths sorted(glob.glob(yalefaces/*.gif)) X [] for p in img_paths: img cv2.imread(p, cv2.IMREAD_GRAYSCALE) # 统一尺寸保证每个样本维度一致 img cv2.resize(img, (64, 64)) X.append(img.flatten()) X np.array(X, dtypenp.float32)cv2.imread的第二个参数指定以灰度模式读取flatten()把二维图像变成一维向量。这里统一 resize 到 64×64 是为了让所有样本维度一致原始 Yale 数据集里的 GIF 尺寸可能有细微差别。X的每一行是一张脸每一列对应一个像素位置。PCA 需要把数据中心化然后计算协方差矩阵但直接对 4096×4096 的协方差矩阵做特征值分解开销不大如果原图分辨率更高就改用 SVD。5.2 特征脸提取与人脸重构对于小样本数据直接对中心化矩阵做 SVD 比计算协方差矩阵更稳定# 中心化 mean_face X.mean(axis0, keepdimsTrue) Xc X - mean_face # SVD分解得到主方向 U, S, Vt np.linalg.svd(Xc, full_matricesFalse) eigenfaces Vt[:k] # 投影后重构 reconstruct Xc eigenfaces.T eigenfaces mean_facenp.linalg.svd返回的Vt的行就是主方向也就是我们说的特征脸。选择前k个主方向后投影系数Xc eigenfaces.T是降维后的表示再乘回eigenfaces就得到重构图像。前 20 个特征脸对应的图像往往还能看出人脸的轮廓到第 100 个特征脸可能已经像频域里的高频噪声。作业要求随机选 3 张照片对比效果可以用下面的可视化代码import matplotlib.pyplot as plt idx np.random.choice(len(X), 3, replaceFalse) plt.figure(figsize(8, 8)) for i, ii in enumerate(idx): plt.subplot(3, 3, i*31) plt.imshow(X[ii].reshape(64, 64), cmapgray) plt.axis(off) plt.subplot(3, 3, i*32) plt.imshow(eigenfaces[19].reshape(64, 64), cmapgray) plt.axis(off) plt.subplot(3, 3, i*33) plt.imshow(reconstruct[ii].reshape(64, 64), cmapgray) plt.axis(off)这段代码的每一行都只显示一张图第一列是原图第二列是第 20 个特征脸第三列是重构脸。需要留意的是eigenfaces[19]显示的是第 20 个特征向量不要把 index 和“前 20 个特征向量”这个概念搞混。报告里要写清楚重构误差随特征向量数量的变化。前 20 个特征向量重构出来只剩模糊的轮廓前 100 个时五官基本清晰到前 200 个时肉眼已经很难和原图区分。如果发现重构结果出现“鬼影”通常是因为中心化矩阵没有在重构时加回均值或者图像维度没有匹配。特征向量数 k重构均方误差200.0321000.0082000.002上表是我本地运行时的示例数值实际结果会因图像缩放尺寸不同而略有浮动。关键是误差随k增大单调下降但下降速度越来越慢。这个趋势也符合 PCA 的数学性质特征值越大对应的主方向对重构的贡献越大。6. 作业包代码的复用与排错清单拿到MachineLearing-Homework-master.7z以后第一件事是在 Linux 或 macOS 下用 7z 命令解压# 解压并保留目录结构 7z x MachineLearing-Homework-master.7z7z x中的x表示解压并保留压缩包内的目录结构如果只想查看内容可以用7z l。解压后的目录结构很清晰Homework1 到 Homework6 每个文件夹里都有题目 PDF、答案 docx、README 和对应的.m或.py文件。Matlab 作业建议直接用 MATLAB R2018a 以上版本打开三个值得注意的地方是bodyfat_dataset需要联网下载数据集如果断网会报错libsvm 的安装路径需要添加到 MATLAB PATHhomework3 里的 BP 网络在旧版 MATLAB 上需要把rng替换成rand(seed, ...)。Python 部分的依赖比较简单主要是numpy、matplotlib、opencv-python和libsvm。一个容易翻车的点是 libsvm 的 Python 接口在 Mac 上编译后无法加载通常需要手动设置动态库路径。另一个常见问题是 matplotlib 打开图像窗口时中文乱码可以在代码开头加plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False这两个参数分别解决标题中文显示和坐标轴负号显示问题。如果运行 SVM 扫描时内存暴涨把训练数据处理成稀疏格式libsvm 里传入{index: value}字典列表即可。关于交叉验证数据集只有 17 个样本使用 4 折时务必固定随机种子否则每次运行准确率差异很大这在写实验结论时容易被误判为模型不稳定。最后一个实用技巧作业答案的 docx 里有完整的计算过程代码跑完后不要直接把 stdout 里的数字粘进报告而是把代码输出的决策树图形、PCA 重构图像和 C 值准确率曲线都导出为 PDF再在 Word 里排版。这样数据和图表完全对应老师复查时能直接看到每一步的输出和代码是匹配的。按这个顺序检查一遍每个作业的 README就能顺利提交整份大作业。本文还有配套的精品资源点击获取