
简介面向西电机器学习课程学习者这份资料完整收录三个课程实验的代码实现、训练模型与实验报告适合初次接触监督学习、需要完成同类作业或想通过实操巩固理论的高校学生。包内共27个文件以Python脚本、CSV数据集、joblib模型文件及实验报告PDF为主同时包含乳腺癌诊断、空气质量预测等任务所需的训练数据与特征集覆盖数据预处理、模型训练、验证评估和调参等关键环节。已有336人下载学习说明其在同类资源中具备一定参考价值。配合实验报告中的目的、方法、结果分析以及按实验划分的代码目录读者既能对照理解不同算法的优缺点也能直接迁移模型构建流程到自己的项目中。每个实验目录下都提供可直接运行的脚本和训练产出的模型文件便于复现和二次开发。 西电机器学习课程的三次实验每学期题目会有调整但主体思路基本固定入门阶段做线性回归进阶阶段做决策树最后做聚类分析。这三件事刚好把监督学习里的回归、分类和无监督学习的主线全部覆盖了一遍。我当年做这三套实验的时候最大的感受是代码不是跑通就完事模型选型、参数调整和实验报告的分析深度才是分数拉开差距的地方。这篇文章直接从代码出发把我用过的模型实现、实验报告结构和调试过程中遇到的各种问题都摆出来给正在做西电机器学习实验、或者想找一份能直接参考的实现的同学一些帮助。1. 实验一线性回归——从正规方程到梯度下降1.1 实验要求与原理选型西电的线性回归实验一般要求学生在不直接调用sklearn.linear_model.LinearRegression的前提下用numpy自己实现线性模型的训练与预测。数据集通常选sklearn自带的房价数据实验报告里需要说清楚两种训练方法最小二乘的解析解也就是正规方程以及梯度下降的迭代解。正规方程的核心就一行w (X^T X)^(-1) X^T y。它的优点是一次矩阵运算直接出结果不用调学习率也不用担心收敛问题。缺点也很明显当特征维度高的时候X^T X求逆的计算量很大而且当特征之间存在多重共线性时这个矩阵可能不可逆直接np.linalg.inv会报错。这个细节实验报告里提一句老师会觉得你是真懂而不是照抄代码。梯度下降则是从一组初始参数出发沿着损失函数梯度的反方向反复迭代更新。优点是好扩展样本量大、特征多的时候也能跑深度学习里的优化思路和它一脉相承。缺点是要调学习率学习率太大容易发散太小收敛很慢。我建议实验里两个都实现做一组对比实验在相同数据上比较两种方法得到的权重、MSE和运行时间报告的内容量一下子就充实了。1.2 完整代码实现numpy版下面是两个模型类的核心代码都在numpy基础上手写数据从fetch_california_housing加载后做标准化处理。import numpy as np from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler class LinearRegressionNormal: 正规方程求解 def __init__(self): self.w None self.bias None def fit(self, X, y): X np.c_[np.ones(X.shape[0]), X] self.w np.linalg.pinv(X.T X) X.T y self.bias self.w[0] self.w self.w[1:] def predict(self, X): return X self.w self.bias class LinearRegressionGD: 批量梯度下降求解 def __init__(self, lr0.01, epochs1000): self.lr lr self.epochs epochs self.w None self.bias None self.losses [] def fit(self, X, y): n, d X.shape self.w np.random.randn(d) * 0.01 self.bias 0.0 for _ in range(self.epochs): y_pred X self.w self.bias grad_w X.T (y_pred - y) / n grad_b np.sum(y_pred - y) / n self.w - self.lr * grad_w self.bias - self.lr * grad_b loss np.mean((y_pred - y) ** 2) self.losses.append(loss) def predict(self, X): return X self.w self.bias训练部分注意一点先切分训练集、测试集再用StandardScaler拟合训练集用同一个scaler转换测试集。这里有个新手很容易踩的坑就是先标准化整个数据集再切分虽然代码能跑但实际上造成了数据泄漏让测试集的信息提前进入了训练过程实验报告里如果被老师看出来会扣分。1.3 模型评估与可视化评估指标主要用均方误差MSE和决定系数R²。MSE反映了预测值和真实值的平均平方偏差数值越小越好R²衡量模型解释了目标变量多少比例的方差越接近1越好。from sklearn.metrics import mean_squared_error, r2_score y_pred model.predict(X_test) print(MSE:, mean_squared_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred))可视化部分至少画两张图一张是梯度下降过程的loss曲线用来证明收敛另一张是测试集上的预测值 vs 真实值散点图散点越贴近yx对角线说明预测越准。我建议把正规方程和梯度下降的结果画在同一张散点图里对比视觉冲击力很强报告里也好看。提示fetch_california_housing返回的是(data, target)形式的数据加载后先train_test_split再缩放顺序一定不要搞反。2. 实验二决策树——特征选择、递归构建与可视化2.1 特征选择准则怎么选决策树实验一般要求手写ID3或C4.5算法数据常用鸢尾花数据集或者西瓜数据集。核心问题只有一个每次分裂节点时按什么准则选择特征。ID3用信息增益值越大代表该特征带来的纯度提升越大C4.5用信息增益率解决信息增益偏向取值较多特征的问题CART用基尼指数是现在默认实现里最常用的。西电实验通常不会限死必须用哪个但报告里要把这些准则的公式写清楚。我的建议是实现信息增益作为默认选择因为代码最简洁、原理最好解释然后在报告里补一段文字说明为什么C4.5和CART做了改进。只理解公式不够还要能在代码里还原熵的计算过程这样万一验收时老师让现场改代码你也不至于懵。2.2 决策树完整代码采用嵌套字典表示树结构递归构建预测时逐层匹配。import numpy as np def entropy(y): _, counts np.unique(y, return_countsTrue) p counts / counts.sum() return -np.sum(p * np.log2(p)) def info_gain(X, y, feature): total_ent entropy(y) values np.unique(X[:, feature]) weighted_ent 0.0 for v in values: y_v y[X[:, feature] v] weighted_ent len(y_v) / len(y) * entropy(y_v) return total_ent - weighted_ent class DecisionTree: def __init__(self, max_depth3, min_samples_split2): self.max_depth max_depth self.min_samples_split min_samples_split self.tree None def fit(self, X, y): self.tree self._build(X, y, depth0) def _build(self, X, y, depth): if depth self.max_depth or len(np.unique(y)) 1 or len(y) self.min_samples_split: return int(np.bincount(y).argmax()) gains [info_gain(X, y, f) for f in range(X.shape[1])] best_f int(np.argmax(gains)) if gains[best_f] 0: return int(np.bincount(y).argmax()) node {best_f: {}} for v in np.unique(X[:, best_f]): mask X[:, best_f] v node[best_f][v] self._build(X[mask], y[mask], depth 1) return node def _predict_one(self, x, node): if not isinstance(node, dict): return node f next(iter(node)) value node[f].get(x[f]) if value is None: return max(node[f].values(), keylambda v: v if isinstance(v, int) else 0) return self._predict_one(x, value) def predict(self, X): return np.array([self._predict_one(x, self.tree) for x in X])这段代码里有两个地方要特别说明。第一_predict_one的递归出口有两类到达了最大深度或者当前节点样本已经纯了返回类别。第二测试时如果碰到了训练集里没出现过的特征取值我选择了返回该分支下出现次数最多的叶子节点这是一种很朴素的兜底策略实际工程里也经常这么干。你可以把这个逻辑写在报告的实验心得里属于一个加分细节。2.3 剪枝与可视化不剪枝的决策树很容易把训练集完全记住测试集上表现差。手写实验里最实用的两种方式预剪枝在建树时限制max_depth和min_samples_split后剪枝把训练集切一部分做验证集从下往上尝试剪掉某个子树如果剪完验证集精度不下降就保留剪枝结果。可视化不要用plt硬画树形图控制起来非常麻烦。建议直接用pydotplus配合sklearn.tree.export_graphviz或者干脆在报告里手画一棵简化版的小树。我当时是用matplotlib把树的决策区域当成二维散点图画出来选两个特征做坐标轴运行结果看着直观老师也更容易快速理解你的模型分类边界。提示手写决策树的输入特征必须是数值型如果数据集里是青绿乌黑浅白这种文字型特征需要先做标签编码用0、1、2替代。3. 实验三K-means聚类——肘部法则与初始化策略3.1 手写K-means会暴露哪些问题K-means表面看着简单四步流程谁都能背初始化中心点、分配样本到最近中心、重新计算中心、重复直到收敛。但手写一遍就会发现真正难的是三个细节K值怎么定、初始中心怎么选、数据要不要标准化。K值最常见的方法是肘部法则对不同的K计算SSE样本到各自中心点的距离平方和K增大时SSE会下降下降速度骤减的点就是肘部。初始中心如果随机选运气不好时容易陷入局部最优每次跑出来的聚类结果都不一样。数据标准化这一点往往被忽略如果某个特征的量纲特别大它会在距离计算中占据主导地位聚出来的类基本等于只看了这一个特征。3.2 K-means完整代码import numpy as np class KMeans: def __init__(self, k3, max_iter100, tol1e-4, random_seed42): self.k k self.max_iter max_iter self.tol tol self.random_state np.random.RandomState(random_seed) self.centers None self.labels None self.sse 0.0 def fit(self, X): n, d X.shape init_idx self.random_state.choice(n, sizeself.k, replaceFalse) centers X[init_idx].copy() for _ in range(self.max_iter): dist np.linalg.norm(X[:, None, :] - centers[None, :, :], axis2) labels np.argmin(dist, axis1) new_centers np.array([X[labels j].mean(axis0) for j in range(self.k)]) if np.linalg.norm(new_centers - centers, ordfro) self.tol: centers new_centers break centers new_centers self.centers centers self.labels labels self.sse np.sum((X - centers[labels]) ** 2) return self def predict(self, X): dist np.linalg.norm(X[:, None, :] - self.centers[None, :, :], axis2) return np.argmin(dist, axis1)这段代码用了一个小技巧把距离计算写成np.linalg.norm(X[:, None, :] - centers[None, :, :], axis2)一次性算出所有样本到所有中心的距离矩阵。相比双层for循环代码更简洁跑起来也快实验报告里写向量化加速能体现你代码功底。3.3 肘部法则与K-means选K的代码也很短对K从1到10分别训练一个模型记录SSE然后画折线图import matplotlib.pyplot as plt sse_list [] for k in range(1, 11): km KMeans(kk, random_seed42).fit(X_scaled) sse_list.append(km.sse) plt.plot(range(1, 11), sse_list, markero) plt.xlabel(K) plt.ylabel(SSE) plt.savefig(elbow.png, dpi150)K-means的改进思路其实就一句话初始中心点之间尽量分散。算法第一点随机选第二个中心点以正比于距离平方的概率选后面的点同样处理。这个概率选点在代码里实现时要小心常见做法是用np.cumsum配合np.searchsorted写起来很绕。我自己在实际操作中的体会是如果没有硬性要求随机初始化加多次尝试取SSE最小的那一次性价比更高代码也更不容易出bug报告里写清楚这个思路同样合理。4. 实验报告写作结构清晰比堆字数重要4.1 推荐报告结构实验报告不要直接粘贴代码再加一句运行结果如图。你要把老师当成一个对你这几次实验完全不清楚的读者他能通过报告复现你的全部工作。我建议用这样的结构实验目的、实验环境、实验原理、实验内容与核心代码、实验结果与分析、实验总结与心得。实验目的写两到三行就够不要抄课程大纲。实验环境要写操作系统的具体版本、Python版本、numpy版本、sklearn版本。西电的机器学习实验一般要求在平台上提交代码但报告里这一步不能省因为不同版本的程序行为真的有差异写清楚版本是学术规范。实验原理要把公式手敲进文档不要截图信息增益、正规方程、SSE这些关键公式必须自己会推导。4.2 实验内容和结果分析怎么写实验内容部分配合核心代码段代码不要整段贴只贴每个模块最核心的函数比如决策树的_build、K-means的fit。最关键的是实验结果与分析这部分字数要占到整篇报告的三分之一以上。每个实验结果附上一张图然后用至少两段话去解释从图里观察到了什么现象、为什么会出现这个现象、和理论预期是否一致。举个例子线性回归的对比实验里如果正规方程和梯度下降的MSE差别很大你要分析是学习率没调好还是迭代次数不够。决策树实验里把不同max_depth下训练集和测试集的准确率画在同一张图里就能很直观地看出过拟合的临界点。K-means实验里展示不同K值的聚类散点图说明为什么你选的K是合理的。这些分析老师一眼就能看出你是认真做的还是临时拼的。4.3 图表规范与排版细节图表的命名和排版也有讲究。每张图都要有图题图题放在图的下方格式是图1 线性回归预测结果。坐标轴必须有名称比如学习率预测房价单位写清楚。实验环境部分列一个表格把依赖库的名称和版本号排成一列两列阅读体验远好于一段文字。一个小技巧matplotlib绘制散点图时给不同聚类类别分配不同颜色并设置alpha0.7让重叠的点也能看清楚报告整体会显得专业不少。提示报告导出前把多次运行截图里的时间、路径等信息遮掉或重新运行一次保持格式干净态度分很值钱。5. 环境配置、版本兼容与平台提交的实战排查5.1 数据集和库版本带来的连锁问题做线性回归实验时最容易遇到的报错是ImportError: cannot import name load_boston from sklearn.datasets。原因是sklearn 1.2版本之后波士顿房价数据因为数据本身存在一些问题被移除了。解决方案有两个一是老实用fetch_california_housing替代在报告里说明数据集更换的原因二是如果你能找到原始的波士顿房价离线数据自己加载后封装成数组完全能继续用。我实测下来用加州房价数据集更方便样本量更大而且不需要额外处理缺失值。numpy版本也会带来一个隐藏的坑如果你用的np.linalg.inv遇到矩阵奇异会直接抛出LinAlgError。我在代码里用了np.linalg.pinv也就是伪逆来替换即使X^T X不可逆也能得到一个最小二乘意义下的解。这个细节建议你在报告里写上老师会觉得你有工程意识。5.2 数据预处理和中文显示问题K-means实验的常见问题是不做标准化直接聚类导致结果完全被数值大的特征主导。我当时第一次跑聚类没标准化得到的三类标签分布明显跟着某一个特征走标准化之后聚类效果立刻正常了。这个现象非常适合写进实验报告通过对比实验说明标准化对基于距离的聚类算法的影响。matplotlib画图时中文乱码是另一个高频问题。很多同学的系统和matplotlib默认字体里没有中文字体plt.title(聚类结果)输出一堆方块。简单解决办法是在画图前固定两行代码import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False如果系统里连SimHei都没有比如某些精简版Linux就得先安装中文字体或者在代码里指定fontproperties参数传入字体路径。另外在无图形界面的环境里运行plt.show()会卡住或者什么都不显示最好用plt.savefig(result.png, dpi150)保存图片打开图片查看结果。5.3 头歌平台提交的关键提醒西电不少班级的实验是在头歌平台上提交代码平台会自动做代码检查和结果校验。这类平台普遍有一个特点只允许你补全指定的函数体不能修改测试代码所以你在本地写得再花哨也要适配平台的函数签名才能通过。提交前第一件事是仔细读清楚每个实验任务点的输入和输出要求尤其是返回值类型有的要求返回数组有的要求返回列表一个类型对不上就会判错。另一个平台常见问题是本地numpy版本和平台运行环境不一致同一段代码本地运行结果正常平台上一跑就报ValueError: setting an array element with a sequence。我的经验是尽量在代码里避免那种强依赖数据维度的写法多使用显式的reshape输入数据进来先打印shape调试确认无误再传进模型。如果平台允许你查看测试用例的输入样例一定要先利用这个信息把数据格式锁定。还有一点平台上如果报输出格式不符合要求大概率不是算法写错了而是打印语句的问题。比如你为了让本地调试方便print了一些中间变量忘了删平台会把print结果当作输出的一部分。提交前全局搜索一下代码里的print能删全删或者放进一个if __name__ __main__:块里。最后再分享一个小技巧三个实验的代码框架其实是可以复用的。线性回归里做数据预处理的StandardScaler流程直接搬到K-means实验里用决策树实验里打印树结构的递归遍历函数改造一下就能用来分析K-means的聚类中心。做实验不要每个实验都从零写一遍先把通用的数据加载、画图、评估模块沉淀下来后续实验能节省大量时间。我这几套代码在平台上全部一次提交通过靠的就是提前把环境、数据格式和输出格式这三个不确定性最大的变量先解决掉。本文还有配套的精品资源点击获取