news 2026/9/8 10:31:41

西电机器学习实验实战:线性回归、决策树与K-means完整代码与报告思路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
西电机器学习实验实战:线性回归、决策树与K-means完整代码与报告思路

简介:面向西电机器学习课程学习者,这份资料完整收录三个课程实验的代码实现、训练模型与实验报告,适合初次接触监督学习、需要完成同类作业或想通过实操巩固理论的高校学生。包内共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, lr=0.01, epochs=1000): 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 真实值散点图,散点越贴近y=x对角线说明预测越准。我建议把正规方程和梯度下降的结果画在同一张散点图里对比,视觉冲击力很强,报告里也好看。

提示: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_counts=True) 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_depth=3, min_samples_split=2): 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, depth=0) 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(), key=lambda 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_depthmin_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, k=3, max_iter=100, tol=1e-4, random_seed=42): 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, size=self.k, replace=False) centers = X[init_idx].copy() for _ in range(self.max_iter): dist = np.linalg.norm(X[:, None, :] - centers[None, :, :], axis=2) labels = np.argmin(dist, axis=1) new_centers = np.array([X[labels == j].mean(axis=0) for j in range(self.k)]) if np.linalg.norm(new_centers - centers, ord='fro') < 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, :, :], axis=2) return np.argmin(dist, axis=1)

这段代码用了一个小技巧:把距离计算写成np.linalg.norm(X[:, None, :] - centers[None, :, :], axis=2),一次性算出所有样本到所有中心的距离矩阵。相比双层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(k=k, random_seed=42).fit(X_scaled) sse_list.append(km.sse) plt.plot(range(1, 11), sse_list, marker='o') plt.xlabel("K") plt.ylabel("SSE") plt.savefig("elbow.png", dpi=150)

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绘制散点图时,给不同聚类类别分配不同颜色,并设置alpha=0.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", dpi=150)保存图片,打开图片查看结果。

5.3 头歌平台提交的关键提醒

西电不少班级的实验是在头歌平台上提交代码,平台会自动做代码检查和结果校验。这类平台普遍有一个特点:只允许你补全指定的函数体,不能修改测试代码,所以你在本地写得再花哨,也要适配平台的函数签名才能通过。提交前第一件事是仔细读清楚每个实验任务点的输入和输出要求,尤其是返回值类型:有的要求返回数组,有的要求返回列表,一个类型对不上就会判错。

另一个平台常见问题是本地numpy版本和平台运行环境不一致,同一段代码本地运行结果正常,平台上一跑就报ValueError: setting an array element with a sequence。我的经验是尽量在代码里避免那种强依赖数据维度的写法,多使用显式的reshape,输入数据进来先打印shape调试,确认无误再传进模型。如果平台允许你查看测试用例的输入样例,一定要先利用这个信息把数据格式锁定。

还有一点,平台上如果报"输出格式不符合要求",大概率不是算法写错了,而是打印语句的问题。比如你为了让本地调试方便print了一些中间变量,忘了删,平台会把print结果当作输出的一部分。提交前全局搜索一下代码里的print,能删全删,或者放进一个if __name__ == "__main__":块里。

最后再分享一个小技巧:三个实验的代码框架其实是可以复用的。线性回归里做数据预处理的StandardScaler流程,直接搬到K-means实验里用;决策树实验里打印树结构的递归遍历函数,改造一下就能用来分析K-means的聚类中心。做实验不要每个实验都从零写一遍,先把通用的数据加载、画图、评估模块沉淀下来,后续实验能节省大量时间。我这几套代码在平台上全部一次提交通过,靠的就是提前把环境、数据格式和输出格式这三个不确定性最大的变量先解决掉。

本文还有配套的精品资源,点击获取

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

VLAN间通信与DHCP接口地址池配置:华为eNSP三层交换机实验详解

很多刚接触交换网络的读者&#xff0c;会遇到一个很有代表性的场景&#xff1a;VLAN 划分完成后&#xff0c;不同部门之间的 PC 突然互相 ping 不通了&#xff1b;接着想图省事让 PC 自动获取 IP&#xff0c;结果 DHCP 配置了半天就是不生效。问题通常不是 VLAN 本身&#xff0…

作者头像 李华
网站建设 2026/9/8 10:31:27

libcudart.so缺失怎么办?详解CUDA动态库加载原理与修复策略

上周有个朋友发我终端截图&#xff0c;红字一大片&#xff0c;最扎眼的是这一句&#xff1a;ImportError: libcudart.so.11.7: cannot open shared object file: No such file or directory。他当时只是import onnxruntime跑个推理脚本&#xff0c;结果环境直接崩了。这种报错在…

作者头像 李华
网站建设 2026/9/8 10:28:42

C盘清理自救指南:从系统工具到深度瘦身全攻略

先问一句&#xff1a;你的 C 盘是不是又红了&#xff1f; 这个场景我太熟悉了——某天准备部署一个项目&#xff0c;IDE 提示磁盘空间不足&#xff1b;想安装一个体积稍大的软件&#xff0c;安装包还没下载完就报错&#xff1b;更糟的是系统更新卡在 50%&#xff0c;C 盘剩余空…

作者头像 李华
网站建设 2026/9/8 10:27:04

基于BP神经网络的人流量检测系统:YOLO检测与流量预测实战

简介&#xff1a;基于BP神经网络的人流量检测系统毕业设计资料&#xff0c;面向计算机、人工智能、自动化等相关专业需要完成毕设或课设的开发者&#xff0c;覆盖数据采集、预处理、网络搭建、训练评估到结果输出的完整流程。系统通过摄像头或红外传感器采集人流量数据&#xf…

作者头像 李华
网站建设 2026/9/8 10:25:28

Python毕业设计实战:智慧地铁客流数据洞察平台从爬虫到预测

做Python方向的毕业设计&#xff0c;选“智慧地铁数据洞察平台”这类题目&#xff0c;算是踩中了近几年城市交通数字化的热点。你想想&#xff0c;地铁是城市通勤的大动脉&#xff0c;客流数据天然具有高密度、强周期性、实时性强的特点&#xff0c;拿来做数据采集、分析挖掘、…

作者头像 李华
网站建设 2026/9/8 10:24:06

三层交换机VLAN间通信:静态路由配置与排障指南

前几天在一个网络群里看到有人问&#xff1a;公司两台交换机&#xff0c;一个 VLAN 10 给办公区&#xff0c;一个 VLAN 20 给财务部&#xff0c;三层交换机也买了&#xff0c;静态路由也配了&#xff0c;但两边就是不通。下面跟了一堆回答&#xff0c;有人让查网关&#xff0c;…

作者头像 李华