机器学习大作业高分指南:分类回归聚类全流程工程实践
简介面向机器学习课程学生与算法实战入门者这是一套覆盖分类、回归、聚类三大方向的完整大作业源码合集共八次高分项目可直接用于期末大作业或课程设计。压缩包共48个文件以28个ipynb实验脚本为核心另配text/txt数据文件、py辅助脚本及一个rar示例数据代码与数据分离便于直接运行包体仅1.42MB。已有1292人学习/下载。八次作业按主题递进涵盖一维/二维拟合、逻辑回归二分类与三分类、线性判别分析、支持向量机、决策树、MLPClassifier与Perceptron线性分类器、AdaBoostClassifier分类以及KMeans/KMedoids与GMM聚类等算法每次作业均有对应数据文本与Notebook并按“第X次作业”目录清晰组织便于逐个复现与巩固。整套项目经老师指导并获高分模块划分明确既可对照学习算法原理也可作为课程设计的代码与报告底板。1. 机器学习大作业的高分不在算法多而在“一次能跑通”一学期八次机器学习大作业内容覆盖分类、回归、聚类算法三类任务最后打包成一个项目源码 zip 交上去。大多数人的翻车点不是模型选错而是交付物不完整有代码但没有数据加载逻辑有训练结果但没有评估指标有可视化但没固定随机种子老师一复现就得到另一组数字。这篇内容按“分类 → 回归 → 聚类”的主线把每一次作业从数据载入、建模到实验记录需要补齐的工程细节拆开讲。适合已经跑过 sklearn 示例、但还不清楚怎么把八次作业稳定整理成高分项目的开发者。逻辑回归、岭回归、K-Means 聚类算法是作业里点名率最高的三类模型我会把它们的参数范围、验证方法和报告写法一次说全。2. 分类作业先从逻辑回归分类器做基线再用分类评估指标拿分2.1 为什么基线一定选逻辑回归分类器老师没有限定只能用 SVM 或神经网络时分类作业的最佳起点永远是逻辑回归分类器。原因不是它效果最强而是最容易解释每个特征的系数可以直接写成“该特征每增加一个单位对数几率变化多少”这句话放进报告就是算法原理部分。逻辑回归在小样本数据上训练只要几十毫秒配合交叉验证跑十轮也不会让打开你 notebook 的助教干等。另一个理由是逻辑回归对“特征标准化”非常敏感这一步能自然引出数据预处理的讨论。作业里常见的问题是直接把load_iris()或read_csv()的结果丢给模型训练和画图全挤在一个 cell 里。我一般会先写load_data()负责读文件、统计缺失值和类别分布再写train_baseline()返回训练好的模型与预测概率。评分人看到的是“加载 → 预处理 → 建模 → 评估”四个明确步骤这种结构本身就能拿到过程分。import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, StratifiedKFold from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score X, y load_data() # 假设已经完成缺失值和类别分布检查 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) lr LogisticRegression(max_iter1000) lr.fit(X_train_s, y_train) y_pred lr.predict(X_test_s) y_prob lr.predict_proba(X_test_s)代码逻辑分三层stratifyy让分类比例在训练集和测试集里保持一致防止数据不平衡时测试集里只剩一个类别StandardScaler只在训练集上fit测试集只做transform这一点是作业报告的常见扣分点很多同学在全部数据上 fit 一遍再划分形成轻微数据泄漏指标虚高但经不起追问最后保留predict_proba的结果后面画 ROC 曲线和调阈值都要用只输出predict的作业完成度会被认定为“刚及格”。2.2 分类评估混淆矩阵、F1、ROC-AUC 一起给别只写 accuracy一份 95% 负样本、5% 正样本的数据全部预测成负类也有 95% 准确率。如果作业报告里只有accuracy_score评分人会默认你没理解类别不均衡问题。标准做法是打印分类报告和混淆矩阵再单独给 ROC-AUCprint(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) print(ROC-AUC {:.4f}.format(roc_auc_score(y_test, y_prob)))参数说明classification_report默认输出每个类别的精确率、召回率、F1 和样本数。多分类作业建议以 macro-F1 作为最终指标因为 macro 对每个类等权能体现小类表现二分类则直接看正类的 F1。ROC-AUC 传入的是y_prob而不是y_predAUC 本质上是根据风险分数排序算出来的喂进去硬分类标签会得到一个没有区分意义的值这个细节在作业互评中能拉开明显差距。如果课程要求做 5 折交叉验证每个折内都要重新做标准化再训练不能先标准化整个训练集再切折否则每一折都在使用未来信息。2.3 分类提分的三个动作特征变换、阈值移动、误分类回看第一次提分靠特征变换而不是换模型。逻辑回归是线性分类器数值特征严重偏斜时系数估计不稳先对长尾特征做np.log1p往往比换 SVM 更有效。第二次提分在概率阈值y_pred_new (y_prob[:, 1] 0.40).astype(int)阈值取多少由验证集上的 precision-recall 曲线决定报告里写“为保证召回率不下降阈值从 0.5 下调到 0.4”这比空谈调参更有说服力。第三次是误分类样本回看把验证集预测错的样本逐条打印出来找出是哪几个特征导致的这一条能让作业从 85 分档进入 90 分档。3. 回归作业从线性回归到岭回归、随机森林回归算法、XGBoost 回归模型3.1 第一步是画残差分布不是直接看 R²回归作业最常见的时间浪费是上来就LinearRegression().fit()把 R² 贴进报告就结束。R² 高不代表假设成立数据里有离群点、存在异方差时单看 R² 甚至会给出相反结论。我一般先做三张图预测值对真实值的散点图、残差对预测值的散点图、残差直方图。第一张看整体贴合度第二张看残差是否存在喇叭形发散有的话要做对数变换或加权回归第三张看残差是否近似正态。这一步的现实意义是评分人一天要读十几份回归作业主动画残差并写出解读的已经超过大半。大部分示例代码只输出均方误差和 R²你的报告里多一张残差图下面写一句“残差无明显趋势模型基本假设成立”这就是“真正理解回归”和“只会调包”的分界线。import numpy as np from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) ols LinearRegression() ols.fit(X_train, y_train) y_pred ols.predict(X_test) residual y_test - y_pred print(R2:, r2_score(y_test, y_pred)) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse)) print(residual mean: {:.4f}, std: {:.4f}.format(residual.mean(), residual.std()))这里squaredFalse返回 RMSE单位与目标变量一致报告里更容易解释。残差均值接近 0 是线性回归的必然结果不能当作模型好的证据必须配合残差图的实际形状判断。另一个细节LinearRegression不要求特征同量纲但特征单位差异极大时回归系数的大小不能直接当重要性排序这一点在“系数解释”部分容易翻车。3.2 岭回归、Lasso 与 alpha 的选择RidgeCV 和 LassoCV 比手动试参可靠特征数量接近样本量或者特征之间存在较强相关性时普通最小二乘的系数方差会很大训练集 R² 极高测试集掉分。此时先换岭回归加 L2 惩罚让系数收缩但保留全部特征Lasso 加 L1 惩罚会把部分系数压到精确 0做特征选择更直观。作业中统一用交叉验证选超参数这个流程本身就是评分点。from sklearn.linear_model import RidgeCV, LassoCV ridge RidgeCV(alphasnp.logspace(-3, 3, 50)) ridge.fit(X_train, y_train) print(Ridge best alpha:, ridge.alpha_) lasso LassoCV(alphasnp.logspace(-4, 1, 50), max_iter100000) lasso.fit(X_train, y_train) print(Lasso nonzero features:, np.sum(lasso.coef_ ! 0))参数说明RidgeCV默认使用留一交叉验证几百个样本的作业数据集跑起来没有问题alphas用对数空间取 50 个值覆盖三个数量级既能看到几乎无正则的一端也能看到严重压缩的一端。LassoCV的max_iter必须调大默认值在小规模数据上可能不收敛。Lasso 在高度相关特征之间会随机选一个进入模型换随机种子得到不同的系数集合所以报告里建议同时放岭回归做对照写“Lasso 保留了 5 个特征岭回归表明另外 2 个也有贡献”这就是统计思想的体现。模型惩罚项适用场景报告写作建议线性回归无特征独立、样本充足作为基线模型岭回归L2特征相关性强系数整体收缩保留所有特征LassoL1高维特征选择报告保留特征个数ElasticNetL1L2特征存在分组相关性同时约束稀疏和系数幅度3.3 从回归树到随机森林回归算法再到 XGBoost 回归模型当特征与目标变量之间呈现明显的非线性关系线性模型继续调参也提不动分数。此时引入树模型。树模型不受特征尺度影响不需要做标准化这是它相比线性模型在预处理环节上的简化。作业里比较随机森林回归算法和 XGBoost 回归模型时固定同一个测试集逐项对比 RMSE 和训练时间即可from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor from sklearn.metrics import mean_squared_error rf RandomForestRegressor( n_estimators200, max_depth6, random_state42 ) rf.fit(X_train, y_train) xgb XGBRegressor( n_estimators200, max_depth4, learning_rate0.05, random_state42 ) xgb.fit(X_train, y_train) for name, model in [(RF, rf), (XGB, xgb)]: pred model.predict(X_test) rmse mean_squared_error(y_test, pred, squaredFalse) print(name, RMSE:, rmse)随机森林属于 Bagging对异常值的鲁棒性中上max_depth控制单棵树复杂度几百个样本的数据集深度在 4 到 8 之间通常足够。XGBoost 带 L1/L2 正则和收缩学习率更容易拟合训练集但也更容易在测试集上过拟合如果发现训练集 RMSE 远低于测试集先调小max_depth或把learning_rate下调并同步提高n_estimators。报告里不要把两个模型只写成一个跑分表要分别给出最优参数例如“随机森林 best_depth6XGBoost best_depth4”这两行字能证明你做了多轮实验而不是参数一把梭。4. 聚类作业K-Means 聚类算法、GMM 与无监督评估的组合拳4.1 肘部法则确定 K轮廓系数做二次验证聚类作业中被问得最多的问题是“你为什么选 K3”。如果回答“因为 KMeans 的默认值是 8”基本上告别高分。常规做法是画两组曲线不同 K 下的簇内平方和inertia以及轮廓系数。inertia 的肘部对应一个相对合理的 K 值轮廓系数衡量每个样本与自身簇内点的相似度减去与最近簇的相似度。import numpy as np from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score from sklearn.preprocessing import StandardScaler X_scaled StandardScaler().fit_transform(X) K_range range(2, 11) inertia_list [] sil_list [] for k in K_range: model KMeans(n_clustersk, n_init10, random_state42) labels model.fit_predict(X_scaled) inertia_list.append(model.inertia_) sil_list.append(silhouette_score(X_scaled, labels)) for k, s in zip(K_range, sil_list): print(k{}, silhouette{:.4f}.format(k, s)) import matplotlib.pyplot as plt plt.plot(K_range, inertia_list, markero) plt.title(Elbow Curve) plt.show()参数说明n_init10表示每个 K 值用 10 组不同初始中心分别训练保留最优结果避免随机初始化落入次优局部解。K-Means 基于欧氏距离特征量纲不一致时必须先标准化否则值域大的特征会主导簇分配轮廓系数也会失真。如果数据本身簇结构不明显SSE 曲线可能平滑递减没有明显肘部这时更要依赖轮廓系数并在报告里坦率写一句“该数据簇结构不强”这比硬编一个 K 值得到的认可更多。注意不同版本 scikit-learn 的 KMeans 默认参数有差异现代版本中n_init默认取auto即认为 10但为了报告可复现建议显式写出数值。4.2 K-Means 的初始化陷阱与特征缩放稳定结果的前提聚类作业复现性差十有八九出在 KMeans 的两端初始中心没有固定或者特征根本没有缩放。KMeans 的目标函数是固定 K 下的 SSE 最小化一个典型的非凸问题不同初始化会收敛到不同局部最优。作业里第二次运行得到不同标签不必怀疑数据先查random_state有没有写死。n_init越大越能缓解局部最优问题但训练时间线性上升作业数据量小取 10 到 20 都在合理范围。另一个高频问题来自类别特征。K-Means 按欧氏距离计算把类别特征简单编码成 0/1 后距离语义依然是错的红、绿、蓝三个取值被映射到 0/1/2距离 1 和距离 2 被强行定义了大小关系。此时要么改用 GMM 这类软聚类模型要么为类别特征单独设计距离不要指望 KMeans 自动处理。聚类作业的“功能完整”不能靠单个模型K-Means 为主、GMM 做结构验证、可视化辅助判断是我建议的固定套路。4.3 GMM 聚类当簇形状不是圆形用协方差结构回答K-Means 假设每个簇是球形且同方差遇到长条状或椭圆形的簇时会把点沿边缘硬切开。高斯混合模型用多个高斯分布的加权和建模数据每个簇有自己的均值和协方差矩阵椭圆形状也能覆盖。作业里用GaussianMixture与 K-Means 对照分别计算轮廓系数和 BIC比只跑一个模型更有说服力。from sklearn.mixture import GaussianMixture for k in range(2, 8): gm GaussianMixture( n_componentsk, covariance_typefull, random_state42 ) labels gm.fit_predict(X_scaled) bic_val gm.bic(X_scaled) sil_val silhouette_score(X_scaled, labels) print(k{}, BIC{:.2f}, silhouette{:.4f}.format(k, bic_val, sil_val))参数说明covariance_type的主要选项包括full、tied、diag、spherical。full允许每个簇拥有各自的椭圆协方差tied让所有簇共用同一个协方差矩阵diag只建模对角方差计算更快但要求簇的椭圆方向与坐标轴平行spherical退化为球形假设。作业可以跑full和spherical两组对比如果聚类结果差异明显说明数据本身不是球形簇报告结论会更有层次。另一个写作要点是点明“K-Means 输出硬标签GMM 输出软标签”软标签指的是每个样本属于各簇的后验概率fit_predict等同于取后验概率最大的簇这一句话就能体现出对无监督算法的理解。5. 八次大作业项目源码的结构按实验流程组织而不是按算法堆文件5.1 src/models/notebooks 三层结构让助教三分钟看懂提交八次作业被打包成一个项目源码 zip 后评分人打开压缩包第一眼看文件结构。很多同学的压缩包里有七八个叫final、final2、new_final的文件夹谁都不想评。更可靠的方案是八次作业共享同一个项目骨架每次只替换数据和模型注册project/ ├── README.md # 运行入口、依赖、目录说明 ├── requirements.txt ├── data/ │ ├── raw/ # 原始数据只读不改 │ └── processed/ # 清洗和特征构造的输出 ├── src/ │ ├── data_loader.py # 数据加载返回 X, y 和字段名 │ ├── preprocessing.py # 标准化、编码、缺失值处理 │ ├── models.py # 统一训练与评估入口 │ └── evaluate.py # 分类/回归/聚类指标汇总 └── notebooks/ ├── 01_eda.ipynb └── 02_modeling.ipynb这个结构最核心的约束是data/raw只放原始数据任何清洗结果写入processednotebook 只负责调用src下的函数不承载核心训练逻辑。这样助教从 README 能知道每个实验入口你换作业时也只需要替换data_loader和修改models里的配置。八次作业不是八套不同结构而是一套骨架复用八次这种复用本身就是大作业评分标准里的“工程能力”。5.2 统一训练接口分类、回归、聚类共用同一个 Runner一个简单的ExperimentRunner能让三类任务共用同一个训练与评估模式。每次换算法时只改模型对象不改调用逻辑。from sklearn.model_selection import StratifiedKFold, KFold, cross_val_score class ExperimentRunner: def __init__(self, model, X, y, taskclassification, cv_folds5, seed42): self.model model self.X X self.y y self.task task self.seed seed if task classification: self.cv StratifiedKFold(n_splitscv_folds, shuffleTrue, random_stateseed) elif task regression: self.cv KFold(n_splitscv_folds, shuffleTrue, random_stateseed) else: self.cv None def run_cv(self): scores cross_val_score(self.model, self.X, self.y, cvself.cv) return scores.mean(), scores.std()关键在 task 参数分类用StratifiedKFold保持每折的类别比例回归用普通KFold聚类不传入 y交叉验证没有意义直接返回训练完成后的簇标签和指标字典。很多同学把分类的交叉验证直接复制到回归任务里StratifiedKFold 对连续目标会报错或产生意外的分箱效果这个分支处理能避免评分人复现时出现异常。5.3 实验结果表与可视化报告素材从 notebook 里直接长出来课程作业里最容易被低估的是参数实验。先跑基线改一个参数再跑一次把结果整理成表格评分人会认定你做过系统实验。常用模板是在 README 里维护一份实验记录实验编号模型核心参数验证指标测试指标备注1逻辑回归max_iter1000macro-F10.820.80基线2逻辑回归StandardScalermacro-F10.850.83加预处理3逻辑回归阈值 0.40macro-F10.860.84阈值下移每一行对应一次有效改动加了预处理、特征变换、调阈值各算一组这份表就是报告“实验部分”的骨架。可视化至少需要三张训练验证曲线、混淆矩阵热力图、特征重要性图。树模型直接用内置特征重要性逻辑回归用系数绝对值排序。表格和图片全部来自 notebook报告不必事后补图这也是源码类作业能拉开平均分的关键。6. 提交前的一小时固定随机种子、缓存清理、从头把 notebook 跑一遍6.1 三行代码固定随机源否则所有实验白做作业里导致“重跑结果不一样”的第一个坑是随机性。KMeans 的初始化、XGBoost 的列采样、训练测试集划分这三处只要有一个没固定重跑一次就是另一套指标。我习惯在每个项目的入口模块最上方调用统一函数import random import numpy as np def fix_seed(seed42): random.seed(seed) np.random.seed(seed)fix_seed管住 Python 内置随机和 NumPy 全局随机train_test_split、KMeans、XGBoost这类自带随机数参数的模型仍然建议显式传random_state单纯依赖全局种子在 sklearn 的某些并行并行场景不生效这是一个容易被忽略的细节。6.2 十分钟自检命令验证一个“能从干净状态复现”的项目提交前做三件事语法编译、测试 src 下的公共函数、用无缓存参数跑通全流程。python -m compileall src/ python -m pytest src/ -q python src/run_all.py --freshcompileall检查语法错误pytest验证数据加载函数和预处理函数的基本行为--fresh等价于删除 processed 目录和所有缓存文件强制从原始数据开始重新执行。如果这套流程能在十分钟内跑完并且输出指标与 README 实验表一致这份作业在可复现性上已经超过多数提交。最后不要改完代码只跑一次 notebook 就交必须使用Restart Run All从空状态重跑隐蔽的 cell 执行顺序依赖在助教电脑上触发时轻则缺变量重则整份结果显示不一致。把这一小时花在可复现性上比临阵换一个模型多出来的那零点几个百分点的分数要稳得多。本文还有配套的精品资源点击获取